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.
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: bytecraft
3
+ Version: 0.1.0
4
+ Summary: A lightweight DSL for scaffolding files and folders
5
+ Requires-Python: >=3.10
6
+ Dynamic: requires-python
7
+ Dynamic: summary
@@ -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,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: bytecraft
3
+ Version: 0.1.0
4
+ Summary: A lightweight DSL for scaffolding files and folders
5
+ Requires-Python: >=3.10
6
+ Dynamic: requires-python
7
+ Dynamic: summary
@@ -0,0 +1,9 @@
1
+ setup.py
2
+ bytecraft/__init__.py
3
+ bytecraft/__main__.py
4
+ bytecraft/interpreter.py
5
+ bytecraft.egg-info/PKG-INFO
6
+ bytecraft.egg-info/SOURCES.txt
7
+ bytecraft.egg-info/dependency_links.txt
8
+ bytecraft.egg-info/entry_points.txt
9
+ bytecraft.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bytecraft = bytecraft.__main__:main
@@ -0,0 +1 @@
1
+ bytecraft
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )