Skip to content

Commit

Permalink
feat: refactor hangman plugin to support run commands syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
vberlier committed Mar 29, 2021
1 parent 09eb6ab commit 849697b
Show file tree
Hide file tree
Showing 20 changed files with 247 additions and 35 deletions.
131 changes: 96 additions & 35 deletions beet/contrib/hangman.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,124 @@
"""Plugin that allows hanging indents to spread commands on multiple lines."""
"""Plugin that provides indentation-based syntactic extensions for functions.
With this plugin, commands can spread over multiple lines by using
hanging indents. Interspersed and trailing comments are hoisted above
the current command. The plugin tries its best to keep blank lines
and retain the original formatting of the function.
The plugin also supports the "run commands" syntax suggested here:
https://feedback.minecraft.net/hc/en-us/community/posts/360077450811-In-line-functions-in-mcfunction-files
"""


__all__ = [
"fold_hanging_commands",
"parse_lines",
"parse_trailing_comment",
"fold_hanging_commands",
]


import re
from typing import List, Optional, Tuple
from typing import Iterable, List, Literal, Optional, Tuple, Union

from beet import Context
from beet import Context, Function

REGEX_COMMENT = re.compile(r"(\s+#(?:\s+.*)?$)")
REGEX_QUOTE = re.compile(r"(\"(?:.*?[^\\])?\"|'(?:.*?[^\\])?')")
REGEX_RUN_COMMANDS = re.compile(r"(\s*execute\b(?:.*)\b)run\s+commands(\s*)")


TokenType = Union[
Literal["TEXT"],
Literal["BLANK"],
Literal["COMMENT"],
Literal["INDENT"],
Literal["DEDENT"],
]
Token = Tuple[TokenType, str]


def beet_default(ctx: Context):
for function in ctx.data.functions.values():
function.lines = fold_hanging_commands(function.lines)
for function in list(ctx.data.functions.values()):
function.lines = list(fold_hanging_commands(ctx, parse_lines(function.lines)))


def fold_hanging_commands(lines: List[str]) -> List[str]:
def fold_hanging_commands(ctx: Context, tokens: Iterable[Token]) -> Iterable[str]:
"""Fold hanging commands on a single line."""
result = []
current, *lines = lines
indentation = 0
hanging_blank_lines = 0
tokens = iter(tokens)

current = ""
indent_level = 0

for token_type, value in tokens:
if token_type == "DEDENT":
indent_level -= 1
if indent_level < 0:
break

elif token_type == "INDENT":
if REGEX_RUN_COMMANDS.match(current):
key = ctx.generate(Function(list(fold_hanging_commands(ctx, tokens))))
current = REGEX_RUN_COMMANDS.sub(fr"\1run function {key}\2", current)
else:
indent_level += 1

else:
if indent_level:
if token_type == "TEXT":
current += " " + value
elif token_type != "BLANK":
yield value
else:
if current:
yield current
if token_type == "TEXT":
current = value
else:
current = ""
yield value

if current:
yield current


def parse_lines(lines: Iterable[str]) -> Iterable[Token]:
"""Split the input lines into tokens."""
indentation = [0]
blanks: List[Token] = []

for line in lines:
stripped = line.lstrip()

if stripped:
indentation = len(line) - len(stripped)

if indentation > 0:
if stripped.startswith("#"):
result.append(stripped)
hanging_blank_lines = 0
elif stripped:
stripped, comment = parse_trailing_comment(stripped)
if comment:
result.append(comment)
current += " " + stripped
hanging_blank_lines = 0
else:
hanging_blank_lines += 1
if not stripped:
blanks.append(("BLANK", stripped))
continue

indent = len(line[: -len(stripped)].expandtabs())

while indent < indentation[-1]:
yield "DEDENT", ""
indentation.pop()

if indent > indentation[-1]:
yield "INDENT", ""
indentation.append(indent)

yield from blanks
blanks = []

if stripped.startswith("#"):
yield "COMMENT", stripped
else:
result.append(current)
result.extend([""] * hanging_blank_lines)
hanging_blank_lines = 0
stripped, comment = parse_trailing_comment(stripped)
if comment:
result.append(comment)
current = stripped
yield "COMMENT", comment
yield "TEXT", stripped

result.append(current)
while len(indentation) > 1:
yield "DEDENT", ""
indentation.pop()

return result
yield from blanks


def parse_trailing_comment(line: str) -> Tuple[str, Optional[str]]:
Expand All @@ -66,8 +127,8 @@ def parse_trailing_comment(line: str) -> Tuple[str, Optional[str]]:
result = ""

while chunks:
notcomment, *comment = REGEX_COMMENT.split(chunks.pop(0))
result += notcomment
text, *comment = REGEX_COMMENT.split(chunks.pop(0))
result += text
if comment:
return result, comment[0].lstrip() + "".join(chunks)
if chunks:
Expand Down
6 changes: 6 additions & 0 deletions examples/load_hangman_commands/beet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"data_pack": {
"load": ["src"]
},
"pipeline": ["beet.contrib.hangman"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This will be left as-is
execute as @p run say run commands

# This one works
execute as @a run commands
# Not this one
execute
as @p run say
run commands
thing

# But this one yes
execute
run
commands
say hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
execute run commands
say Hello world!
give @s minecraft:diamond 64 # One stack should be enough

execute
# First we select all players
as @a at @s
if block ~ ~-1 ~ minecraft:air
run commands # Blah blah this is a trailing comment
say Hello world!
# This give command is pretty long
give @s
minecraft:stone{
display: {
Name: '[{
"text": "Hello",
"bold": true # Bold makes it easier to read
}]',
Lore: [
# Pretty sure we don't need this
'[{ "text": "Something else here" }]'
]
}
}
1

# Now we add some space

say Lorem ipsum dolor sit amet, consectetur adipiscing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore
eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum

execute as @e[type=minecraft:cow] at @s run commands
data modify entity @s Motion[1] set value 5.0f
particle minecraft:cloud ~ ~ ~ 0 0 0 0.1 10 normal

execute as @a run commands
execute
facing entity @e[tag=target,limit=1,sort=nearest] eyes
run commands
say Boom!
say and we're done

execute run commands
execute run commands
execute run commands
execute run commands
execute run commands
say 1
say 2
say 3
say 4
say 5


say something



this isn't the end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This will be left as-is
execute as @p run say run commands

# This one works
execute as @a run function load_hangman_commands:generated_1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
execute run function load_hangman_commands:generated_2

# First we select all players
# Blah blah this is a trailing comment
execute as @a at @s if block ~ ~-1 ~ minecraft:air run function load_hangman_commands:generated_3

execute as @e[type=minecraft:cow] at @s run function load_hangman_commands:generated_4

execute as @a run function load_hangman_commands:generated_6

execute run function load_hangman_commands:generated_11


say something this isn't the end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
say hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Not this one
execute as @p run say run commands thing

# But this one yes
execute run function load_hangman_commands:generated_0
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
execute run function load_hangman_commands:generated_9
say 4
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
execute run function load_hangman_commands:generated_10
say 5
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
say Hello world!
# One stack should be enough
give @s minecraft:diamond 64
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
say Hello world!
# This give command is pretty long
# Bold makes it easier to read
# Pretty sure we don't need this
give @s minecraft:stone{ display: { Name: '[{ "text": "Hello", "bold": true }]', Lore: [ '[{ "text": "Something else here" }]' ] } } 1

# Now we add some space

say Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
data modify entity @s Motion[1] set value 5.0f
particle minecraft:cloud ~ ~ ~ 0 0 0 0.1 10 normal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
say Boom!
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
execute facing entity @e[tag=target,limit=1,sort=nearest] eyes run function load_hangman_commands:generated_5
say and we're done
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
say 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
execute run function load_hangman_commands:generated_7
say 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
execute run function load_hangman_commands:generated_8
say 3
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 6,
"description": ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 6,
"description": ""
}
}

0 comments on commit 849697b

Please sign in to comment.