{"id":13524831,"url":"https://github.com/dansanderson/picotool","last_synced_at":"2025-04-01T03:32:53.140Z","repository":{"id":41453117,"uuid":"43388836","full_name":"dansanderson/picotool","owner":"dansanderson","description":"Tools and Python libraries for manipulating Pico-8 game files. http://www.lexaloffle.com/pico-8.php","archived":false,"fork":false,"pushed_at":"2024-02-03T01:04:26.000Z","size":600,"stargazers_count":367,"open_issues_count":51,"forks_count":46,"subscribers_count":16,"default_branch":"main","last_synced_at":"2024-08-02T06:17:09.639Z","etag":null,"topics":["lua","pico-8"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dansanderson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-29T19:14:13.000Z","updated_at":"2024-07-07T21:39:57.000Z","dependencies_parsed_at":"2022-08-10T02:27:10.947Z","dependency_job_id":null,"html_url":"https://github.com/dansanderson/picotool","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dansanderson%2Fpicotool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dansanderson%2Fpicotool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dansanderson%2Fpicotool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dansanderson%2Fpicotool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dansanderson","download_url":"https://codeload.github.com/dansanderson/picotool/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222698187,"owners_count":17024877,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lua","pico-8"],"created_at":"2024-08-01T06:01:13.821Z","updated_at":"2024-11-02T09:30:31.363Z","avatar_url":"https://github.com/dansanderson.png","language":"Python","funding_links":[],"categories":["Uncategorized","Python"],"sub_categories":["Uncategorized"],"readme":"# picotool: Tools and Python libraries for manipulating PICO-8 game files\n\n[PICO-8](http://www.lexaloffle.com/pico-8.php) is a _fantasy game console_ by [Lexaloffle Games](http://www.lexaloffle.com/). The PICO-8 runtime environment runs _cartridges_ (or _carts_): game files containing code, graphics, sound, and music data. The console includes a built-in editor for writing games. Game cartridge files can be played in a browser, and can be posted to the Lexaloffle bulletin board or exported to any website.\n\n`picotool` is a suite of tools and libraries for building and manipulating PICO-8 game cartridge files. The suite is implemented in, and requires, [Python 3](https://www.python.org/). The tools can examine and transform cartridges in various ways, and you can implement your own tools to access and modify cartridge data with the Python libraries.\n\nUseful tools include:\n\n- `p8tool build`: assembles cartridges from multiple sources, as part of a game development workflow\n- `p8tool stats`: reports statistics on one or many cartridge files\n- `p8tool listlua`: prints the Lua code of a cartridge\n- `p8tool luafind`: searches the Lua code of a collection of cartridges\n- `p8tool luafmt`: formats the Lua code of a cartridge to make it easier to read\n\nThere are additional tools that are mostly useful for demonstrating and troubleshooting the library: `writep8`, `listtokens`, `printast`, `luamin`. A separate demo, `p8upsidedown`, uses picotool to transform the code and data of a game to turn it upsidedown.\n\n`picotool` supports reading and writing both of PICO-8's cartridge file formats: the text-based format `.p8`, and the PNG-based binary format `.p8.png`.\n\n## How do most people use picotool?\n\nMost people use `picotool` for the `luamin` command, which condenses the Lua code of a cart to use as few characters as possible. This is useful if you have a large game whose code is above PICO-8's character limit but below the token limit. In this rare case, `luamin` helps you keep your developer version easy to read and modify while you polish the game for publication. In most cases, your cart will exceed the token limit first, and minification doesn't help with unusual cases such as long string data.\n\nI originally created `picotool` for general purpose build workflows, especially the `build` tool. Since then, PICO-8 has added features such as `#include` support, making some of the `build` features unnecessary. (`#include` doesn't quite work like `require()` but it's good enough for most things!) I'm still interested in the potential for token optimization features such as dead code elimination, but these are not yet implemented.\n\nI hope `picotool` can be the basis for your custom workflows and cart manipulation experiments, so you don't have to write your own cart read/write routines. If there's a feature you'd like to see in the libraries or the tools, please let me know!\n\n## Installing picotool\n\nTo install the `picotool` tools and libraries:\n\n1. Install [Python 3](https://www.python.org/) version 3.4 or later, if necessary. (picotool has not been tested with Python 2.)\n1. Download and unpack [the zip archive](https://github.com/dansanderson/picotool/archive/master.zip), or use Git to clone [the Github repository](https://github.com/dansanderson/picotool).\n   - Unpacking the zip archive creates a root directory named `picotool-master`.\n   - When cloning the repo, this is just `picotool`, or whatever you named it when you cloned it.\n1. Change to the unpacked archive or clone directory from the above step.\n1. Install the software. The dot (`.`) tells `pip install` to use the current working directory, which should be `picotool-master` (from the .zip file) or `picotool` (from git clone).\n\n   ```shell\n   pip install .\n   ```\n\nYou should now have a `p8tool` command in your path.\n\n## Using picotool\n\nTo use a tool, you run the `p8tool` command with the appropriate arguments. Without arguments, it prints a help message. The first argument is the name of the tool to run (such as `stats`), followed by the arguments expected by that tool.\n\nFor example, to print statistics about a cart named `helloworld.p8.png`:\n\n```shell\np8tool stats helloworld.p8.png\n```\n\n### p8tool build\n\nThe `build` tool creates or updates a cartridge file using other files as sources. It is intended as a part of a game development workflow, producing the final output cartridge.\n\nThe tool takes the filename of the output cartridge, with additional arguments describing the build. If the output cartridge does not exist, the build starts with an empty cartridge. Otherwise, it uses the existing cartridge as the default, and overwrites sections of it based on the arguments.\n\nFor example, you can create a cartridge in PICO-8, use PICO-8's built-in graphics and sound editors, then use `p8tool build` to replace the Lua code with the contents of a `.lua` file:\n\n```shell\np8tool build mygame.p8.png --lua mygame.lua\n```\n\nAs another example, to create a new cartridge using the spritesheet (`gfx`) from one cartridge file, music (`sfx`, `music`) from another, and Lua code from a `.lua` file:\n\n```shell\np8tool build mygame.p8.png --gfx mygamegfx.p8 --sfx radsnds.p8.png --music radsnds.p8.png --lua mygame.lua\n```\n\nYou can also erase a section of an existing cart with an argument such as `--empty-map`.\n\nThe available arguments are as follows:\n\n- `--lua LUA`: use Lua code from the given cart or `.lua` file\n- `--gfx GFX`: use spritesheet from the given cart\n- `--gff GFF`: use sprite flags from the given cart\n- `--map MAP`: use map from the given cart\n- `--sfx SFX`: use sound effects from the given cart\n- `--music MUSIC`: use music patterns from the given cart\n- `--empty-lua`: use an empty Lua code section\n- `--empty-gfx`: use an empty spritesheet\n- `--empty-gff`: use empty sprite flags\n- `--empty-map`: use an empty map\n- `--empty-sfx`: use empty sound effects\n- `--empty-music`: use empty music patterns\n\nIf the output cart filename ends with `.p8.png`, the result is a cartridge with a label image. If the file already exists, the cartridge label is reused. If the file does not exist, an empty cartridge label is used. To use a non-empty label, you must open the cart in PICO-8, take a screenshot (press F6 while running), set the title and byline in the first two lines of code (as Lua comments), then save the `.p8.png` file from PICO-8. Future runs of `p8tool build` will reuse the label.\n\n#### Packages and the require() function\n\np8tool build supports a special feature for organizing your Lua code, called packages. When loading Lua code from a file with the `--lua mygame.lua` argument, your program can call a function named `require()` to load Lua code from another file. This is similar to the `require()` function available in some other Lua environments, with some subtle differences due to how picotool does this at build time instead of at run time.\n\nConsider the following simple example. Say you have a function you like to use in several games in a file called `mylib.lua`:\n\n```lua\nfunction handyfunc(x, y)\n  return x + y\nend\n\nhandynumber = 3.14\n```\n\nYour main game code is in a file named `mygame.lua`. To use the `handyfunc()` function within `mygame.lua`, call `require()` to load it:\n\n```lua\nrequire(\"mylib\")\n\nresult = handyfunc(2, 3)\nprint(result)\n\nr = 5\narea = handynumber * r * r\n```\n\nAll globals defined in the required file are set as globals in your program when `require()` is called. While this is easy enough to understand, this has the disadvantage of polluting the main program's global namespace.\n\nA more typical way to write a Lua package is to put everything intended to be used by other programs in a table:\n\n```lua\nHandyPackage = {\n  handyfunc = function(x, y)\n    return x + y\n  end,\n  handynumber = 3.14,\n}\n```\n\nThen in `mygame.lua`:\n\n```lua\nrequire(\"mylib\")\n\nresult = HandyPackage.handyfunc(2, 3)\n```\n\nThis is cleaner, but still has the disadvantage that the package must be known by the global name `HandyPackage` wihtin `mygame.lua`. To fix this, Lua packages can return a value with the `return` statement. This becomes the return value for the `require()` call. Furthermore, Lua packages can declare `local` variables that are not accessible to the main program. You can use these features to hide explicit names and return the table for the package:\n\n```lua\nlocal HandyPackage = {\n  handyfunc = function(x, y)\n    return x + y\n  end,\n  handynumber = 3.14,\n}\n\nreturn HandyPackage\n```\n\nThe main program uses the return value of `require()` to access the package:\n\n```lua\nHandyPackage = require(\"mylib\")\n\nresult = HandyPackage.handyfunc(2, 3)\n```\n\nThe `require()` function only evaluates the package's code once. Subsequent calls to `require()` with the same string name do not reevaluate the code. They just return the package's return value. Packages can safely require other packages, and only the first encountered `require()` call evaluates the package's code.\n\n#### Where packages are located\n\nThe first argument to `require()` is a string name. picotool finds the file that goes with the string name using a library lookup path. This is a semicolon-delimited (`;`) list of filesystem path patterns, where each pattern uses a question mark (`?`) where the string name would go.\n\nThe default lookup path is `?;?.lua`. With this path, `require(\"mylib\")` would check for a file named `mylib`, then for a file named `mylib.lua`, each in the same directory as the file containing the `require()` call. The lookup path can also use absolute filesystem paths (such as `/usr/share/pico8/lib/?.lua`). You can customize the lookup path either by passing the `--lua-path=...` argument on the command line, or by setting the PICO8_LUA_PATH environment variable.\n\nFor example, with this environment variable set:\n\n```shell\nPICO8_LUA_PATH=?;?.lua;/home/dan/p8libs/?/?.p8\n```\n\nThe require(\"3dlib\") statement would look for these files, in this order, with paths relative to the file containing the require() statement:\n\n```text\n3dlib\n3dlib.lua\n/home/dan/p8libs/3dlib/3dlib.p8\n```\n\nTo prevent malicious code from accessing arbitrary files on your hard drive (unlikely but it's nice to prevent it), the `require()` string cannot refer to files in parent directories with `../`. It can refer to child directories, such as `require(\"math/linear\")`.\n\nAs with Lua, packages are remembered by the string name used with `require()`. This means it is possible to have two copies of the same package, each known by a different name, if it can be reached two ways with the lookup path. For example, if the file is named `foo.lua` and the lookup path is `?;?.lua`, `require(\"foo\")` and `require(\"foo.lua\")` treat the same file as two different packages.\n\n#### Packages and game loop functions\n\nWhen you write a library of routines for PICO-8, you probably want to write test code for those routines. picotool assumes that this test code would be executed in a PICO-8 game loop, such that the library can be in its own test cart. For this purpose, you can write your library file with `_init()`, `_update()` or `_update60()`, and `_draw()` functions that test the library. By default, `require()` will strip the game loop functions from the library when including it in your game code so they don't cause conflicts or consume tokens.\n\nFor example:\n\n```lua\nlocal HandyPackage = {\n  handyfunc = function(x, y)\n    return x + y\n  end,\n  handynumber = 3.14,\n}\n\nfunction _update()\n  test1 = HandyPackage.handyfunc(2, 3)\nend\nfunction _draw()\n  cls()\n  print('test1 = '..test1)\nend\n\nreturn HandyPackage\n```\n\nIf you want to keep the game loop functions present in a package, you can request them with a second argument to `require()`, like so:\n\n```lua\nrequire(\"mylib\", {use_game_loop=true})\n```\n\n#### How require() actually works\n\nOf course, PICO-8 does not actually load packages from disk when it runs the cartridge. Instead, picotool inserts each package into the cartridge code in a special way that replicates the behavior of the Lua `require()` feature.\n\nWhen you run `p8tool build` with the `--lua=...` argument, picotool scans the code for calls to the `require()` function. If it sees any, it loads and parses the file associated with the string name, and does so again if the required file also has `require()` calls.\n\nEach required library is stored once as a function object in a table inserted at the top of the final cartridge's code. A definition of the `require()` function is also inserted that finds and evaluates the package code in the table as needed.\n\nTo match Lua's behavior, `require()` maintains a table named `package.loaded` that maps string names to return values. As with Lua, you can reset this value to `nil` to force a `require()` to reevaluate a package.\n\nThis feature incurs a small amount of overhead in terms of tokens. Each library uses tokens for its own code, plus a few additional tokens for storing it in the table. The definition for `require()` is another 40 tokens or so. Naturally, the inserted code also consumes characters.\n\n#### Formatting or minifying Lua in a built cart\n\nYou can tell `p8tool build` to format or minify the code in the built output using the `--lua-format` or `--lua-minify` command line arguments, respectively.\n\n```shell\np8tool build mycart.p8.png --lua=mygame.lua --lua-format\n```\n\nThis is equivalent to building the cart then running `p8tool luafmt` or `p8tool luamin` on the result.\n\nThe `build` command supports the options `--keep-names-from-file=\u003cfilename\u003e` and `--keep-all-names`. See `luamin`, below, for more information.\n\n### p8tool stats\n\nThe `stats` tool prints statistics about one or more carts. Given one or more cart filenames, it analyzes each cart, then prints information about it.\n\n```text\n% p8tool stats helloworld.p8.png\nhello world (helloworld.p8.png)\nby zep\nversion: 0  lines: 48  chars: 419  tokens: 134\n```\n\nThis command accepts an optional `--csv` argument. If provided, the command prints the statistics in a CSV format suitable for importing into a spreadsheet. This is useful when tallying statistics about multiple carts for comparative analysis.\n\n```shell\np8tool --csv stats mycarts/*.p8* \u003ecartstats.csv\n```\n\n### p8tool listlua\n\nThe `listlua` tool extracts the Lua code from a cart, then prints it exactly as it appears in the cart.\n\n```text\n% p8tool listlua helloworld.p8.png\n-- hello world\n-- by zep\n\nt = 0\n\nmusic(0)\n\nfunction _update()\n t += 1\nend\n\nfunction _draw()\n cls()\n\n...\n```\n\n### p8tool luafmt\n\nThe `luafmt` tool rewrites the Lua region of a cart to make it easier to read, using regular indentation and spacing. This does not change the token count, but it may increase the character count, depending on the initial state of the code.\n\nThe command takes one or more cart filenames as arguments. For each cart with a name like `xxx.p8.png`, it writes a new cart with a name like `xxx_fmt.p8`.\n\n```text\n% p8tool luafmt helloworld.p8.png\n% cat helloworld_fmt.p8\npico-8 cartridge // http://www.pico-8.com\nversion 5\n__lua__\n-- hello world\n-- by zep\n\nt = 0\n\nmusic(0)\n\nfunction _update()\n  t += 1\nend\n\nfunction _draw()\n  cls()\n\n  for i=1,11 do\n    for j0=0,7 do\n      j = 7-j0\n      col = 7+j\n...\n```\n\nBy default, the indentation width is 2 spaces. You can change the desired indentation width by specifying the `--indentwidth=...` argument:\n\n```text\n% p8tool luafmt --indentwidth=4 helloworld.p8.png\n% cat helloworld_fmt.p8\n...\nfunction _update()\n    t += 1\nend\n\nfunction _draw()\n    cls()\n\n    for i=1,11 do\n        for j0=0,7 do\n            j = 7-j0\n            col = 7+j\n...\n```\n\nThe current version of `luafmt` is simple and mostly just adjusts indentation. It does not adjust spaces between tokens on a line, align elements to brackets, or wrap long lines.\n\n### p8tool luafind\n\nThe `luafind` tool searches for a string or pattern in the code of one or more carts. The pattern can be a simple string or a regular expression that matches a single line of code.\n\nUnlike common tools like `grep`, `luafind` can search code in .p8.png carts as well as .p8 carts. This tool is otherwise not particularly smart: it's slow (it runs every file through the parser), and doesn't support fancier `grep`-like features.\n\n```text\n% p8tool luafind 'boards\\[.*\\]' *.p8*\ntest_gol.p8.png:11:  boards[1][y] = {}\ntest_gol.p8.png:12:  boards[2][y] = {}\ntest_gol.p8.png:14:    boards[1][y][x] = 0\ntest_gol.p8.png:15:    boards[2][y][x] = 0\ntest_gol.p8.png:20:boards[1][60][64] = 1\ntest_gol.p8.png:21:boards[1][60][65] = 1\ntest_gol.p8.png:22:boards[1][61][63] = 1\ntest_gol.p8.png:23:boards[1][61][64] = 1\ntest_gol.p8.png:24:boards[1][62][64] = 1\ntest_gol.p8.png:30:  return boards[bi][y][x]\ntest_gol.p8.png:36:      pset(x-1,y-1,boards[board_i][y][x] * alive_color)\ntest_gol.p8.png:54:          ((boards[board_i][y][x] == 1) and neighbors == 2)) then\ntest_gol.p8.png:55:        boards[other_i][y][x] = 1\ntest_gol.p8.png:57:        boards[other_i][y][x] = 0\n```\n\nYou can tell `luafind` to just list the names of files containing the pattern without printing the lines using the `--listfiles` argument. Here's an example that looks for carts that contain examples of Lua OO programming:\n\n```text\n% p8tool luafind --listfiles 'self,' *.p8*\n11243.p8.png\n12029.p8.png\n12997.p8.png\n13350.p8.png\n13375.p8.png\n13739.p8.png\n15216.p8.png\n...\n```\n\n### p8tool writep8\n\nThe `writep8` tool writes a game's data to a `.p8` file. This is mostly useful for converting a `.p8.png` file to a `.p8` file. If the input is a `.p8` already, then this just makes a copy of the file. (This can be used to validate that the picotool library can output its input.)\n\nThe command takes one or more cart filenames as arguments. For each cart with a name like `xxx.p8.png`, it writes a new cart with a name like `xxx_fmt.p8`.\n\n```text\n% p8tool writep8 helloworld.p8.png\n% cat helloworld_fmt.p8\npico-8 cartridge // http://www.pico-8.com\nversion 5\n__lua__\n-- hello world\n-- by zep\n\nt = 0\n\nmusic(0)\n\nfunction _update()\n t += 1\nend\n\nfunction _draw()\n cls()\n\n...\n```\n\n### p8tool luamin\n\nThe `luamin` tool rewrites the Lua region of a cart to use as few characters as possible. It does this by discarding comments and extraneous space characters, and renaming variables and functions. This does not change the token count.\n\nThe command takes one or more cart filenames as arguments. For each cart with a name like `xxx.p8.png`, it writes a new cart with a name like `xxx_fmt.p8`.\n\nBy default, `luamin` renames variables, attributes, and labels to use as few characters as possible. You can disable this behavior with a command line flag: `--keep-all-names`\n\nYou can disable renaming just for specific names by providing a text file listing names, with this command line option: `--keep-names-from-file=\u003cfilename\u003e` This file supports the following features:\n\n- Each name appears on its own line.\n- Blank lines are ignored.\n- Lines that begin with a `#` are ignored, so you can have comments in this file.\n\nDisabling renaming is useful in cases where code refers to object attributes using both dot syntax (`foo.bar`) and indexing syntax (`foo[\"bar\"]`). In this case, `luamin` renames the dot syntax but can't rename the indexing syntax (because `\"bar\"` can be any expression). You can add `bar` to the list of names to keep to prevent this, while still allowing renaming for other symbols (including `foo` in this example).\n\n`luamin` always preserves the first two comments that appear before any other code. PICO-8 uses these comments as the title and author metadata for the label when exporting a .p8.png.\n\nStatistically, you will run out of tokens before you run out of characters, and minifying is unlikely to affect the compressed character count. Carts are more useful to the PICO-8 community if the code in a published cart is readable and well-commented. That said, some authors have found luamin useful in the late stages of writing a large cart that includes blob data strings, where you might hit the character limit first and you need to squeeze in a few more characters to finish the game.\n\n```text\n% p8tool luamin helloworld.p8.png\n% cat helloworld_fmt.p8\npico-8 cartridge // http://www.pico-8.com\nversion 5\n__lua__\na = 0\nmusic(0)\nfunction _update()\na += 1\nend\nfunction _draw()\ncls()\n...\n```\n\n### p8tool listtokens\n\nThe `listtokens` tool is similar to `listlua`, but it identifies which characters picotool recognizes as a single token.\n\n```text\n% p8tool listtokens ./picotool-master/tests/testdata/helloworld.p8.png\n\u003c-- hello world\u003e\n\u003c-- by zep\u003e\n\n\u003c0:t\u003e\u003c \u003e\u003c1:=\u003e\u003c \u003e\u003c2:0\u003e\n\n\u003c3:music\u003e\u003c4:(\u003e\u003c5:0\u003e\u003c6:)\u003e\n\n\u003c7:function\u003e\u003c \u003e\u003c8:_update\u003e\u003c9:(\u003e\u003c10:)\u003e\n\u003c \u003e\u003c11:t\u003e\u003c \u003e\u003c12:+=\u003e\u003c \u003e\u003c13:1\u003e\n\u003c14:end\u003e\n\n\u003c15:function\u003e\u003c \u003e\u003c16:_draw\u003e\u003c17:(\u003e\u003c18:)\u003e\n\u003c \u003e\u003c19:cls\u003e\u003c20:(\u003e\u003c21:)\u003e\n\n...\n```\n\nWhen picotool parses Lua code, it separates out comments, newlines, and spaces, as well as proper Lua tokens. The Lua tokens appear with an ascending number, illustrating how picotool counts the tokens. Non-token elements appear with similar angle brackets but no number. Newlines are rendered as is, without brackets, to make them easy to read.\n\n**Note:** picotool does not currently count tokens the same way PICO-8 does. One purpose of `listtokens` is to help troubleshoot and fix this discrepancy. See \"Known issues.\"\n\n### p8tool printast\n\nThe `printast` tool prints a visualization of the abstract syntax tree (AST) determined by the parser. This is a representation of the structure of the Lua code. This is useful for understanding the AST structure when writing a new tool based on the picotool library.\n\n```text\n% p8tool printast ./picotool-master/tests/testdata/helloworld.p8.png\n\nChunk\n  * stats: [list:]\n    - StatAssignment\n      * varlist: VarList\n        * vars: [list:]\n          - VarName\n            * name: TokName\u003c't', line 3 char 0\u003e\n      * explist: ExpList\n        * exps: [list:]\n          - ExpValue\n            * value: 0\n    - StatFunctionCall\n      * functioncall: FunctionCall\n        * exp_prefix: VarName\n          * name: TokName\u003c'music', line 5 char 0\u003e\n        * args: FunctionArgs\n          * explist: ExpList\n            * exps: [list:]\n              - ExpValue\n                * value: 0\n    - StatFunction\n      * funcname: FunctionName\n        * namepath: [list:]\n          - TokName\u003c'_update', line 7 char 9\u003e\n        * methodname: None\n      * funcbody: FunctionBody\n        * parlist: None\n        * dots: None\n        * block: Chunk\n\n...\n```\n\n## Building new tools\n\npicotool provides a general purpose library for accessing and manipulating PICO-8 cart data. You can add the `picotool` directory to your `PYTHONPATH` environment variable (or append `sys.path` in code), or just copy the `pico8` module to the directory that contains your code.\n\nThe easiest way to load a cart from a file is with the `from_file()` function, in the `pico8.game.file` module:\n\n```python\n#!/usr/bin/env python3\n\nfrom pico8.game import file\n\ng = file.from_file('mycart.p8.png')\nprint('Tokens: {}'.format(g.lua.get_token_count()))\n```\n\nAspects of the game are accessible as attributes of the `Game` object:\n\n- `lua`\n- `gfx`\n- `gff`\n- `map`\n- `sfx`\n- `music`\n\nLua code is treated as bytestrings throughout the API. This is because PICO-8 uses a custom text encoding equivalent to lower ASCII plus arbitrary high characters for the glyph set. Take care to use b'bytestring literals' when creating or comparing values.\n\n### API under construction\n\nWhile the library in its current state is featureful enough for building simple tools, it is not yet ready to promise backwards compatibility in future releases. Feel free to mess with it, but please be patient if I change things.\n\n**Important:** The parser in particular is kinda jank and I may completely redo the API for the AST before v1.0.\n\n## Developing picotool\n\nTo start developing `picotool`, clone the repo, set up a Python virtual\nenvironment, and install `p8tool` in editable mode:\n\n```shell\ngit clone git@github.com:dansanderson/picotool.git\ncd picotool\npython3 -m venv pyenv\nsource pyenv/bin/activate\n\n# Install picotool in the virtual environment, with symlinks to source:\npip install -e .\n\n# Install development tools:\npip install --upgrade pip\npip install -r pydevtools.txt\n```\n\nWith everything installed and the virtual environment active, you can run some\nuseful commands:\n\n```shell\n# To run the development version of p8tool:\np8tool\n\n# To open an ipython REPL with picotool on the load path:\nipython\n# To enable module auto-reloading:\n#   %autoreload 2\n# To import a pico8 module:\n#   from pico8.lua import lua\n# Autocompletion works with the tab key:\n#   lua.unicode\u003cTAB\u003e\n\n# To run all tests:\npytest\n\n# To run specific tests:\npytest tests/pico8/lua/lua_tests.py\n\n# To run the tests and calculate a coverage report:\ncoverage run pytest\n\n# To display the coverage report on the console:\ncoverage report -m\n\n# To produce an interactive HTML document based on the coverage report, as ./htmlcov/index.html:\ncoverage html\n```\n\nSee [pytest documentation](https://docs.pytest.org/en/6.2.x/index.html),\n[coverage documentation](https://coverage.readthedocs.io/en/coverage-5.5/).\n\n### Visual Studio Code set-up\n\nI use [Visual Studio Code](https://code.visualstudio.com/) for editing with its\npowerful [Python\nextension](https://marketplace.visualstudio.com/items?itemName=ms-python.python).\nWhen you open the `picotool` root folder in VSCode, it finds the active Python\nvirtual environment and use `./pyenv/bin/python` as its Python interpreter. If\nit doesn't, click \"Python\" in the left of the bottom bar, then select this path\nfor the interpreter.\n\nWhen you open a new Terminal within VSCode (press Ctrl + backtick), it\nautomatically activates the Python environment. If it doesn't, or if you prefer\na standalone terminal app, remember to activate the environment at the\nbeginning of each session:\n\n```shell\nsource picotool/pyenv/bin/activate\n```\n\n### Building Sphinx documentation\n\nThe picotool documentation lives in `docs/` as a\n[Sphinx](https://www.sphinx-doc.org/) project. To build the documentation as\nweb pages:\n\n```shell\ncd docs\nmake html\n```\n\nThe generated HTML pages are in `docs/_build/html/`. Open the `index.html` file\nin your browser to use them.\n\nYou are welcome and encouraged to contribute pull requests to the Sphinx\ndocumentation. Once accepted, I'll take care of publishing the changes to [the\ndocumentation site](http://dansanderson.github.io/picotool/).\n\n(For my own reference: The documentation site uses the\n[gh-pages](https://github.com/dansanderson/picotool/tree/gh-pages) branch of\nthe Github repo as its source. There is a crude `make publish` target that\nbuilds the HTML and pushes the built files to this branch.)\n\n## Known issues\n\nFor a complete list of known issues, see the issues list in the Github project:\n\n[https://github.com/dansanderson/picotool/issues]\n\npicotool is a hobby project and I have to ration the time I spend on issues. I\nwelcome pull requests, and preemptively apologize if I don't review them in a\ntimely manner.\n\nI try to triage issues such that picotool's main features work with all\ncartridges made with the '''latest knwon version''' of PICO-8. During the\nPICO-8 beta, picotool has lagged behind on new Lua syntax shortcuts and\nbuilt-in keywords, though this should settle down as PICO-8 reaches version\n1.0. So far, PICO-8 has been largely backwards compatible, but I cannot promise\ncontinued support for carts made with old beta versions (0.x) of PICO-8.\n\npicotool and PICO-8 count tokens in slightly different ways, resulting in\ndifferent counts. More refinement is needed so that picotool matches PICO-8. As\nfar as I can tell, with picotool making some concessions to match PICO-8 in\nknown cases, PICO-8's counts are consistently higher. In most cases, the\ndifference is only by a few tokens, even for large carts. If you find specific\ncases of token count mismatches, please file them as issues with reproducable\nexamples.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdansanderson%2Fpicotool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdansanderson%2Fpicotool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdansanderson%2Fpicotool/lists"}