bytecraft 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bytecraft-0.1.0/PKG-INFO +7 -0
- bytecraft-0.1.0/bytecraft/__init__.py +1 -0
- bytecraft-0.1.0/bytecraft/__main__.py +20 -0
- bytecraft-0.1.0/bytecraft/interpreter.py +157 -0
- bytecraft-0.1.0/bytecraft.egg-info/PKG-INFO +7 -0
- bytecraft-0.1.0/bytecraft.egg-info/SOURCES.txt +9 -0
- bytecraft-0.1.0/bytecraft.egg-info/dependency_links.txt +1 -0
- bytecraft-0.1.0/bytecraft.egg-info/entry_points.txt +2 -0
- bytecraft-0.1.0/bytecraft.egg-info/top_level.txt +1 -0
- bytecraft-0.1.0/setup.cfg +4 -0
- bytecraft-0.1.0/setup.py +14 -0
bytecraft-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Bytecraft DSL v0.1
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entry point for: py -m bytecraft file.bc
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from .interpreter import run
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
if len(sys.argv) < 2:
|
|
11
|
+
print("Usage: py -m bytecraft <script.bc>")
|
|
12
|
+
print(" bytecraft <script.bc>")
|
|
13
|
+
sys.exit(1)
|
|
14
|
+
|
|
15
|
+
script = sys.argv[1]
|
|
16
|
+
run(script)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
main()
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bytecraft DSL Interpreter - v0.1
|
|
3
|
+
A lightweight DSL for creating files and folders.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ─────────────────────────────────────────────
|
|
12
|
+
# Helpers
|
|
13
|
+
# ─────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
def _log(msg: str) -> None:
|
|
16
|
+
print(f"[Bytecraft] {msg}")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _warn(line_num: int, line: str, reason: str) -> None:
|
|
20
|
+
print(f"[Bytecraft] WARNING (line {line_num}): {reason} → skipping: {line!r}", file=sys.stderr)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _extract_strings(text: str) -> list[str]:
|
|
24
|
+
"""
|
|
25
|
+
Extract quoted strings from text.
|
|
26
|
+
Falls back to splitting on whitespace if no quotes are found,
|
|
27
|
+
implementing the 'forgiving parser' behaviour from the spec.
|
|
28
|
+
"""
|
|
29
|
+
quoted = re.findall(r'"([^"]*)"', text)
|
|
30
|
+
if quoted:
|
|
31
|
+
return quoted
|
|
32
|
+
|
|
33
|
+
# Auto-recovery: treat unquoted tokens as strings
|
|
34
|
+
tokens = text.strip().split()
|
|
35
|
+
return tokens
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ─────────────────────────────────────────────
|
|
39
|
+
# Command handlers
|
|
40
|
+
# ─────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
def _handle_set_working_folder(args: str, state: dict, line_num: int) -> None:
|
|
43
|
+
parts = _extract_strings(args)
|
|
44
|
+
if not parts:
|
|
45
|
+
_warn(line_num, args, "missing path for set-working-folder")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
path = parts[0]
|
|
49
|
+
os.makedirs(path, exist_ok=True)
|
|
50
|
+
state["working_folder"] = path
|
|
51
|
+
_log(f"Working folder set: {path}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _handle_make_folder(args: str, state: dict, line_num: int) -> None:
|
|
55
|
+
parts = _extract_strings(args)
|
|
56
|
+
if not parts:
|
|
57
|
+
_warn(line_num, args, "missing path for make-folder")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
folder = parts[0]
|
|
61
|
+
full_path = _resolve(folder, state)
|
|
62
|
+
os.makedirs(full_path, exist_ok=True)
|
|
63
|
+
_log(f"Created folder: {full_path}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _handle_make_file(args: str, state: dict, line_num: int) -> None:
|
|
67
|
+
# Split on 'with' keyword (case-sensitive per spec)
|
|
68
|
+
with_split = re.split(r'\bwith\b', args, maxsplit=1)
|
|
69
|
+
|
|
70
|
+
path_part = with_split[0].strip()
|
|
71
|
+
content_part = with_split[1].strip() if len(with_split) > 1 else None
|
|
72
|
+
|
|
73
|
+
path_strings = _extract_strings(path_part)
|
|
74
|
+
if not path_strings:
|
|
75
|
+
_warn(line_num, args, "missing path for make-file")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
file_path = path_strings[0]
|
|
79
|
+
full_path = _resolve(file_path, state)
|
|
80
|
+
|
|
81
|
+
# Ensure parent directories exist
|
|
82
|
+
parent = os.path.dirname(full_path)
|
|
83
|
+
if parent:
|
|
84
|
+
os.makedirs(parent, exist_ok=True)
|
|
85
|
+
|
|
86
|
+
# Resolve content
|
|
87
|
+
content = ""
|
|
88
|
+
if content_part is not None:
|
|
89
|
+
content_strings = _extract_strings(content_part)
|
|
90
|
+
content = content_strings[0] if content_strings else ""
|
|
91
|
+
|
|
92
|
+
with open(full_path, "w", encoding="utf-8") as f:
|
|
93
|
+
f.write(content)
|
|
94
|
+
|
|
95
|
+
_log(f"Created file: {full_path}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ─────────────────────────────────────────────
|
|
99
|
+
# Path resolution
|
|
100
|
+
# ─────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
def _resolve(path: str, state: dict) -> str:
|
|
103
|
+
"""Resolve a path against the current working folder."""
|
|
104
|
+
working = state.get("working_folder")
|
|
105
|
+
if working and not os.path.isabs(path):
|
|
106
|
+
return os.path.join(working, path)
|
|
107
|
+
return path
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ─────────────────────────────────────────────
|
|
111
|
+
# Line dispatcher
|
|
112
|
+
# ─────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
COMMANDS = {
|
|
115
|
+
"set-working-folder": _handle_set_working_folder,
|
|
116
|
+
"make-folder": _handle_make_folder,
|
|
117
|
+
"make-file": _handle_make_file,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _dispatch(line: str, state: dict, line_num: int) -> None:
|
|
122
|
+
stripped = line.strip()
|
|
123
|
+
|
|
124
|
+
# Empty line or comment
|
|
125
|
+
if not stripped or stripped.startswith("#"):
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# Match the longest command name first (greedy)
|
|
129
|
+
for cmd, handler in COMMANDS.items():
|
|
130
|
+
if stripped.lower().startswith(cmd):
|
|
131
|
+
args = stripped[len(cmd):].strip()
|
|
132
|
+
handler(args, state, line_num)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
_warn(line_num, stripped, "unknown command")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ─────────────────────────────────────────────
|
|
139
|
+
# Public entry point
|
|
140
|
+
# ─────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
def run(script_path: str) -> None:
|
|
143
|
+
"""Parse and execute a .bc script file."""
|
|
144
|
+
try:
|
|
145
|
+
with open(script_path, "r", encoding="utf-8") as f:
|
|
146
|
+
lines = f.readlines()
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
print(f"[Bytecraft] ERROR: File not found: {script_path!r}", file=sys.stderr)
|
|
149
|
+
sys.exit(1)
|
|
150
|
+
except OSError as e:
|
|
151
|
+
print(f"[Bytecraft] ERROR: Could not read file: {e}", file=sys.stderr)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
state: dict = {"working_folder": None}
|
|
155
|
+
|
|
156
|
+
for line_num, line in enumerate(lines, start=1):
|
|
157
|
+
_dispatch(line, state, line_num)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bytecraft
|
bytecraft-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="bytecraft",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="A lightweight DSL for scaffolding files and folders",
|
|
7
|
+
packages=find_packages(),
|
|
8
|
+
python_requires=">=3.10",
|
|
9
|
+
entry_points={
|
|
10
|
+
"console_scripts": [
|
|
11
|
+
"bytecraft=bytecraft.__main__:main",
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
)
|