Metadata-Version: 2.4
Name: packscript
Version: 0.2.7
Summary: A datapack compiler for Minecraft
Author: Slackow
License: MIT License
        
        Copyright (c) 2024 Slackow
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
Project-URL: Repository, https://github.com/Slackow/PackScript.git
Project-URL: Issues, https://github.com/Slackow/PackScript/issues
Project-URL: Download, https://github.com/Slackow/PackScript/releases
Project-URL: Changelog, https://github.com/Slackow/PackScript/releases
Keywords: packscript,mcfunction,datapack,pack.mcmeta,nbt,templating,language
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Dynamic: license-file

# What is PackScript?
PackScript, provided as a CLI (Command Line Interface), is designed to generate files for Minecraft datapacks.
Datapacks can often have repetitive files or files that really 
should be together, that are not. There are many tools that already that solve these problems but 
this tool has some advantages:
## Direct use of Commands
This tool has no knowledge of the syntax of any individual Minecraft command, this makes it version agnostic
and means there is no need to learn a new way to write commands.
## Extension of Python
If you know Python you can leverage that knowledge to use this tool, since
by default, PackScript is Python.
## Small Footprint
The entirety of PackScript is defined in the `packscript.py` file. This makes it
easy to add to your path or relocate.
## Simplicity
The entire language adds very few constructs (see below) that are relatively simple to
understand. These constructs give you the power to add commands to functions,
and generate any other kind of file (especially JSON) easily.

# Syntax Highlighting
There is an associated syntax highlighter for VSCode with this project.
Get it [here](https://marketplace.visualstudio.com/items?itemName=Slackow.ps-support).

![packscript_ex](https://github.com/Slackow/PackScript/raw/main/.github/photos/packscript_ex.png)

# Constructs

## Command Lines
This is the main feature behind PackScript:

Command lines start with a `/` and are treated as commands instead of Python.

When not inside a function, command lines do nothing.

Command lines may also contain interpolation and function definitions, which will be explained below.

Command lines can also be extended to further lines by ending them with `\ ` the subsequent line will have its leading
whitespace removed

```
/execute as @e \
    at @s run ...
# is the same as
/execute as @e at @s run ...
```

## Interpolation
On command lines, the user may use `${python_expression}` or `$python_var` to refer to previously defined things in the
file. A `$` that is not followed by an identifier or `{` is interpreted as a literal dollar sign.
To always introduce a literal dollar sign, use `${'$'}` This is ugly, but rarely needed.

## Function Definitions
If a command line ends with a `:` and contains the word `function`, it is interpreted as a function definition.
This is the feature in PackScript that allows you to combine multiple files into one and have arbitrary levels of scope within your functions.

There are three main components of function definitions, they may all be omitted.

`/function main:func [tag1, tag2] with storage args:`
1. Name: `main:func`
2. Tags: `tag1, tag2`
3. Extra: `with storage args`

- **Name**: Placed right after `function`. Specifies the function's location within the datapack.
Defaults to the namespace of the source file if omitted.

- **Tags**: Placed within square brackets. Automatically generates function tags. 
Tags default to the `minecraft` namespace and are
commonly used to mark functions as `tick` or `load`.
They can also be empty.

- **Extra**: Placed after square brackets. Reserved for additional command text. To use "extra" without tags,
specify as `/function [] with storage args:`. Note the significant leading space in `" with storage args."`.

Any text before the function definition will appear in the final line but is not considered part of the definition itself.

### More Valid Examples
`/function a:`

`/schedule function [] 5t:`

`/function send_message [] {message:"Vital Message"}:`

`/function minecraft:func []:`

`/execute as @e at @s run function:`

You may use interpolation within the function definition, this is useful for creating functions in loops or shortening the "extra" part to a constant
(like in the example in the syntax highlighting section)

If the name of a function is omitted, (`/function:`) then the name of the function is set to `anon/function` 
(within the source namespace)
multiple `anon/function`'s take up names like `anon/function_1`, `anon/function_2` etc. 

ex:
```
=== data/main/source/main.dps ===
/function tick [tick]:
    /data modify storage args msg set value "example"
    /execute as @a run function main:func [] with storage args:
        /$say $(msg)
```
outputs
```
=== data/main/function/tick.mcfunction ===
# Generated by PackScript 0.1.4
data modify storage args msg set value "example"
execute as @a run function main:func with storage args

=== data/main/function/func.mcfunction ===
# Generated by PackScript 0.1.4
$say $(msg)

=== data/minecraft/tags/function/tick.json ===
{
    "values": [
        "main:tick"
    ]
}
```

You can also create a line like:

`/#function example:`

in order to define a function within another function without having an actual line.
It's unlikely for this to be useful though, as such a function could be pulled to the top level.

## The `dp` Object

The `dp` object provides a flexible way to create, manipulate, and delete datapack files directly from your code.

### Basic Usage

You can use `dp` with either dot notation or dictionary-style access:

```python
# Dot notation
dp.tags.block.chests = {
    "values": [
        "minecraft:chest",
        "minecraft:trapped_chest",
        "minecraft:ender_chest"
    ]
}

# Dictionary-style access
dp["tags/block", "example:metals"] = {
    "values": ["iron_block", "gold_block"]
}
```

### Modifying and Accessing Files

Files created with `dp` can be accessed and modified later:

```python
# Add a value to existing tag
dp.tags.block.chests["values"].append("minecraft:barrel")

# Delete a file or property
del dp.tags.block.outdated_tag
```

### Dynamic Paths

The `dp` object fully supports dynamic paths for programmatic file generation:

```python
# Generate multiple related files
for material in ["iron", "gold", "diamond"]:
    dp.recipe[f"{material}_upgrade"] = {
        "type": "minecraft:crafting_shapeless",
        "ingredients": [{"item": f"minecraft:{material}_ingot"}],
        "result": {"item": f"minecraft:{material}_block"}
    }
```

The `dp` object works with all datapack file types and automatically handles namespacing based on the source file's namespace, making it a powerful tool for organizing and manipulating your datapack structure.
## `capture_lines()` Function

The capture_lines() function diverts command lines to a list of strings instead of directly writing to a function file.
This is niche, but useful for constructing 'Only One Command's.

```
=== data/ooc/source/main.dps ===
def only_one_command(definer):
    """ Generates "Only One Command"s given a function that defines commands """
    with capture_lines() as lines:
        /gamerule commandBlockOutput false
        definer()
        /setblock ~ ~1 ~ command_block{auto:1b,Command:"fill ~ ~ ~ ~ ~-3 ~ air"}
        /kill @e[type=command_block_minecart,distance=..1]
    def escape(ln):
        return ln.replace("\\", "\\\\").replace("'", "\\'")
    main = ','.join(f"{{id:command_block_minecart,Command:'{escape(ln)}'}}" for ln in lines)
    /summon falling_block ~ ~1 ~ {BlockState:{Name:redstone_block},Passengers:[{id:falling_block,BlockState:{Name:activator_rail}},$main]}

/function ooc:
    def say(): # Commands For Only One Command!
        /say as second lame command
        /say third lamer command lol
        /tellraw @a "look ma I'm using quotes \\/!"
        /tellraw @a[name=!"Slackow"] "say \"lol\""
    only_one_command(say)
```
outputs
```
=== data/ooc/function/ooc.mcfunction ===
# Generated by PackScript 0.1.4
summon falling_block ~ ~1 ~ {BlockState:{Name:redstone_block},Passengers:[{id:falling_block,BlockState:{Name:activator_rail}},{id:command_block_minecart,Command:'gamerule commandBlockOutput false'},{id:command_block_minecart,Command:'say as second lame command'},{id:command_block_minecart,Command:'say third lamer command lol'},{id:command_block_minecart,Command:'tellraw @a "look ma I\'m using quotes \\\\/!"'},{id:command_block_minecart,Command:'tellraw @a[name=!"Slackow"] "say \\"lol\\""'},{id:command_block_minecart,Command:'setblock ~ ~1 ~ command_block{auto:1b,Command:"fill ~ ~ ~ ~ ~-3 ~ air"}'},{id:command_block_minecart,Command:'kill @e[type=command_block_minecart,distance=..1]'}]}
```
## `ns` String
The `ns` global variable lets you access the namespace of the dps file you are in as a string. note that `ns` is the namespace of the *source* file, not the function you are in.
```
=== data/example_pack/source/example.dps ===
/function say_something:
   /say something!
/function example:
   # Regular functions don't default to the namespace, so this is needed
   /function $ns:say_something
```
outputs
```
=== data/example_pack/function/say_something.mcfunction ===
# Generated by PackScript 0.1.4
say something!
=== data/example_pack/function/example.mcfunction ===
# Generated by PackScript 0.1.4
function example_pack:say_something
```

# Example Usage + PackScript Reloader

![packscript_ex1](https://github.com/Slackow/PackScript/raw/main/.github/photos/packscript_ex1.png)

![minecraft_output](https://github.com/Slackow/PackScript/raw/main/.github/photos/in_game_img.png)

This was used with the associated [packscript_reloader](https://github.com/Slackow/packscript_reloader) fabric mod.
This mod works on snapshots, and will automatically compile packs under the `dev` directory in the world into the 
`datapacks` folder of that world.

# How does it work internally?

Well it's basically just doing a bunch of very fancy find and
replaces to turn your invalid Python code
into valid Python code, and then executing it.
You can see this when you compile in verbose mode:

![packscript_ex2](https://github.com/Slackow/PackScript/raw/main/.github/photos/packscript_ex2.png)

turns into

```python
__f, __extra = __function_name__(f"tick [tick]")
__line__(rf""" function {__f}{__extra} """[1:-1])
with __function__(__f):
       __f, __extra = __function_name__(f"")
    __line__(rf""" execute as @a at @s run function {__f}{__extra} """[1:-1])
    with __function__(__f):
           __line__(rf""" title @s actionbar "Hey" """[1:-1])
        # remove short grass near the player
        __line__(rf""" fill ~10 ~10 ~10 ~-10 ~-10 ~-10 air replace short_grass """[1:-1])

__f, __extra = __function_name__(f"say_stuff")
__line__(rf""" function {__f}{__extra} """[1:-1])
with __function__(__f):
        __line__(rf""" $say packscript> $(message) < actual working macro parameter """[1:-1])
    __line__(rf""" say packscript> $(message) < that is just text """[1:-1])
    __line__(rf""" say packscript> 2 + 2 = {2 + 2} < this works because it is compile time """[1:-1])

dp.tags.block.chest = {
   'values': [
      'chest',
      'trapped_chest'
   ]
}

dp.tags.block.chest['values'].append('ender_chest')

del dp.tags.block.chest

dp['tags/block', 'chest'] = {
   'values': ['chest', 'trapped_chest', 'ender_chest']
}
```

All the functions being called here are provided by PackScript and are just routes through which the program can
add lines or create new files.

# FunctionPackScript
Most files shown so far have been `.dps` files, standing for DataPackScript,
but there's also FunctionPackScript with `.fps` files. These are contained in the root of the input directory instead
of under a proper datapack with a namespace underneath `source`[<sup>[1]</sup>](#f1), these are meant for generating independent
function files easily, usually those with repetitive lines. In these files you cannot use create statements, but you can generate
additional functions. All the generated function files
will have their namespace ignored and be generated in the same directory
as the main generated function.

<span id="f1">[1]</span>: In older versions of minecraft (pre 1.21) this folder is `sources` instead. PackScript will automatically figure out which folder name to use based on your pack_format value in your `pack.mcmeta`. 

# The CLI
This tool has two main actions it can perform: compiling packs and initializing templates.
- `packscript c` (you can also use compile or comp)
- `packscript init`

More actions:
- `packscript --help` list general help
- `packscript --version` print the version of packscript
- `packscript c --help` print the help for compiling
- `packscript init --help` print the help for initializing a datapack
- `packscript update` update the tool to the latest version (if installed via pip, use that instead)
- `packscript pf` edit and view the pack_format(s) supported by your datapack, you can use recognized version numbers directly
## Compile Options
- `-i/--input <dir>` specify the directory of the pack you are compiling defaults to current dir.
- `-o/--output <dir/zip>` specify the output of the pack (can output zip too) defaults to `output`
- `-s/--source` output the source files into the resulting pack, by default they get deleted
- `-v/--verbose` print out all the generated Python code with line numbers. Very good for debugging.

## Init Options
When init is called missing any options, it will prompt you to interactively fill them, this is the recommended way of
using this action.

You can also specify options using flags

ex: `packscript init --output "Datapack" --name "Datapack" --namespace "main" --description "Datapack for version 1.20.4" --pack-format 26`

it's preferable to do just call the following instead:

`packscript init`

There is also the `packscript init --modded` command, it lets you add config files for compiling to fabric, forge, and neoforge.

It is recommended that you take a look at the files it generates to add any additional metadata you may want.

## Compile Action
When a PackScript file is being compiled it will first print its location to the console, this is so if an error is encountered
you can easily tell which file was last being run, and ensure that your `.dps` files are being executed.

The input directory should be a directory that contains a `data` and `pack.mcmeta` and optional `pack.png` and `overlays` or any directory that contains `.fps` files

The output will either be a directory, a zip file, or a jar file (jar file will make a mod that applies your pack)

When compiling to a jar, an `assets` folder can also be included to include a resourcepack.

ex: `packscript c -o output`, `packscript c -o datapack.zip`, `packscript c -o mod.jar`

## Debugging
When there is an error in your PackScript file,
it will print out the Python version of your PackScript code, this lets you pinpoint exactly where your error is.
You should see a line that looks like: 
```
 File "<string>", line 41, in <module>
```
This is what an error in the generated Python code looks like.
This line number will not line up with your `.dps` file, but it will line up with the generated Python code.

The line with the error will get printed like this:
```
38:         'ender_chest'
39:     ]
40: }
41: a/say some text
```
In this case it's because there's a letter before the command line, making it get interpreted as regular Python.
# Get Started
1. **Install Python.** Get [Python3](https://www.python.org/downloads/) and
   make sure it's in your path. (You can check by running `python3 -V` in your terminal)
2. **Download/Install PackScript.** run `pip3 install packscript`, or directly use the `packscript.py` file in releases, and use `python3 packscript.py` instead of `packscript`
3. **Create a Datapack.** Run `packscript init` in order to create a datapack with
   PackScript. You'll be prompted for information about the datapack.
   You should have a new datapack, you can put files in there as usual for them to
   be outputted, files in `<pack>/data/*/source/*.dps` and `<pack>/*.fps` will be interpreted as PackScript
   on compilation. By default, you will find a main.dps file there.
4. **Compile Datapack.** To compile, run `packscript compile -i <datapack directory> -o <output>`
