FluxScript 0.1.1__py3-none-any.whl

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.
fluxscript/parser.py ADDED
@@ -0,0 +1,82 @@
1
+ """Safe parser for FluxScript's declarative Python subset. Source is never executed."""
2
+ import ast
3
+ from pathlib import Path
4
+ from .dsl import App, Page, Text, Heading, Button, Input
5
+ from .models import AppSpec
6
+
7
+ CONSTRUCTORS = {"App": App, "Page": Page, "Text": Text, "Heading": Heading, "Button": Button, "Input": Input}
8
+ MAX_SOURCE_BYTES = 1_000_000
9
+ class SourceError(ValueError): pass
10
+
11
+ def _literal(node):
12
+ try: value = ast.literal_eval(node)
13
+ except (ValueError, TypeError, MemoryError, RecursionError) as exc:
14
+ raise SourceError(f"Only literal arguments are allowed (line {getattr(node, 'lineno', '?')}).") from exc
15
+ if isinstance(value, (str, bytes)) and len(value) > 10_000:
16
+ raise SourceError(f"String/bytes literal is too large (line {getattr(node, 'lineno', '?')}).")
17
+ return value
18
+
19
+ def _construct(call: ast.Call, line: int):
20
+ if not isinstance(call.func, ast.Name) or call.func.id not in CONSTRUCTORS:
21
+ raise SourceError(f"Only approved constructors are allowed (line {line}).")
22
+ if any(k.arg is None for k in call.keywords): raise SourceError("**kwargs expansion is not supported.")
23
+ ctor = CONSTRUCTORS[call.func.id]
24
+ try:
25
+ return ctor(*[_literal(a) for a in call.args], **{k.arg: _literal(k.value) for k in call.keywords})
26
+ except (TypeError, ValueError, OverflowError) as exc:
27
+ raise SourceError(f"Invalid {call.func.id} at line {line}: {exc}") from exc
28
+
29
+ def parse_source(source: str) -> AppSpec:
30
+ if not isinstance(source, str): raise TypeError("source must be a string")
31
+ if len(source.encode("utf-8")) > MAX_SOURCE_BYTES: raise SourceError("Source exceeds 1 MB limit.")
32
+ try: tree = ast.parse(source)
33
+ except (SyntaxError, ValueError, RecursionError) as exc:
34
+ if isinstance(exc, SyntaxError): raise SourceError(f"Python syntax error at line {exc.lineno}: {exc.msg}") from exc
35
+ raise SourceError("Source could not be parsed safely.") from exc
36
+ env: dict[str, object] = {}; app: App | None = None
37
+ for node in tree.body:
38
+ if isinstance(node, ast.ImportFrom):
39
+ if node.level != 0 or node.module != "fluxscript" or any(a.name not in CONSTRUCTORS or a.asname for a in node.names):
40
+ raise SourceError(f"Unsupported import at line {node.lineno}; import only approved names directly from fluxscript.")
41
+ continue
42
+ if isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str): continue
43
+ if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
44
+ if node.targets[0].id.startswith("__"): raise SourceError(f"Private-looking names are not allowed (line {node.lineno}).")
45
+ if not isinstance(node.value, ast.Call): raise SourceError(f"Only approved constructor assignments are allowed (line {node.lineno}).")
46
+ obj = _construct(node.value, node.lineno)
47
+ if not isinstance(obj, (App, Page, Text, Heading, Button, Input)): raise SourceError("Unsupported constructor.")
48
+ env[node.targets[0].id] = obj
49
+ if isinstance(obj, App):
50
+ if app is not None: raise SourceError("Define exactly one App instance.")
51
+ app = obj
52
+ continue
53
+ if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call):
54
+ call = node.value
55
+ if not isinstance(call.func, ast.Attribute) or not isinstance(call.func.value, ast.Name):
56
+ raise SourceError(f"Unsupported expression at line {node.lineno}.")
57
+ receiver = env.get(call.func.value.id)
58
+ if receiver is None: raise SourceError(f"Unknown declarative object {call.func.value.id!r} at line {node.lineno}.")
59
+ if len(call.args) != 1 or call.keywords: raise SourceError(f"Expected exactly one positional argument (line {node.lineno}).")
60
+ arg = call.args[0]
61
+ if isinstance(arg, ast.Name):
62
+ if arg.id not in env: raise SourceError(f"Unknown declarative object {arg.id!r} at line {node.lineno}.")
63
+ argument = env[arg.id]
64
+ elif isinstance(arg, ast.Call): argument = _construct(arg, node.lineno)
65
+ else: raise SourceError(f"Method arguments must be a declared object or approved constructor (line {node.lineno}).")
66
+ if isinstance(receiver, Page) and call.func.attr == "add" and isinstance(argument, (Text, Heading, Button, Input)): receiver.add(argument)
67
+ elif isinstance(receiver, App) and call.func.attr == "add_page" and isinstance(argument, Page): receiver.add_page(argument)
68
+ else: raise SourceError(f"Unsupported method call at line {node.lineno}.")
69
+ continue
70
+ raise SourceError(f"Unsupported statement at line {getattr(node, 'lineno', '?')}; only declarative app definitions are accepted.")
71
+ if app is None: raise SourceError("No App(...) declaration found.")
72
+ issues = app.spec.validate()
73
+ if issues: raise SourceError("Application validation failed:\n- " + "\n- ".join(issues))
74
+ return app.spec
75
+
76
+ def parse_file(path) -> AppSpec:
77
+ p = Path(path)
78
+ if not p.is_file(): raise SourceError(f"Source file not found: {p}")
79
+ try:
80
+ if p.stat().st_size > MAX_SOURCE_BYTES: raise SourceError("Source exceeds 1 MB limit.")
81
+ return parse_source(p.read_text(encoding="utf-8"))
82
+ except UnicodeDecodeError as exc: raise SourceError("Source must be UTF-8 encoded.") from exc
fluxscript/project.py ADDED
@@ -0,0 +1,74 @@
1
+ """Project export and storage helpers."""
2
+ from __future__ import annotations
3
+ import json
4
+ import os
5
+ import re
6
+ import shutil
7
+ import zipfile
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from .backends import BACKENDS
11
+ from .parser import parse_file, SourceError
12
+
13
+
14
+ def safe_slug(value: str) -> str:
15
+ slug = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip()).strip(".-_").lower()
16
+ return slug[:64] or "fluxscript-project"
17
+
18
+
19
+ def default_storage() -> Path:
20
+ """Choose shared Android storage when mounted, otherwise a user-home directory."""
21
+ android_storage = Path("/storage/emulated/0")
22
+ if android_storage.is_dir() and os.access(android_storage, os.W_OK):
23
+ return android_storage / "FluxScriptProjects"
24
+ return Path.home() / "FluxScriptProjects"
25
+
26
+
27
+ def generate_project(source: Path, target: str, storage: Path, *, install_dependencies: bool = True) -> tuple[Path, Path, list[Path]]:
28
+ """Generate a self-contained project directory and ZIP archive under storage."""
29
+ if target not in (*BACKENDS.keys(), "all"):
30
+ raise ValueError(f"Unsupported target {target!r}; choose {', '.join((*BACKENDS.keys(), 'all'))}.")
31
+ spec = parse_file(source)
32
+ stamp = datetime.now().strftime("%Y%m%d-%H%M%S-%f")
33
+ name = safe_slug(spec.name)
34
+ root = storage.expanduser().resolve()
35
+ root.mkdir(parents=True, exist_ok=True)
36
+ project_dir = root / f"{name}-{target}-{stamp}"
37
+ project_dir.mkdir(parents=False, exist_ok=False)
38
+ generated: list[Path] = []
39
+ selected = list(BACKENDS) if target == "all" else [target]
40
+ try:
41
+ for backend_name in selected:
42
+ out = project_dir / backend_name if target == "all" else project_dir
43
+ generated.extend(BACKENDS[backend_name](spec, out))
44
+ if backend_name == "web":
45
+ from bs4 import BeautifulSoup
46
+ soup = BeautifulSoup((out / "index.html").read_text(encoding="utf-8"), "html.parser")
47
+ if soup.title is None or soup.main is None or not soup.find_all("section", attrs={"data-route": True}):
48
+ raise RuntimeError("Generated website failed structural HTML validation.")
49
+ elif backend_name == "android":
50
+ from defusedxml import ElementTree
51
+ ElementTree.parse(str(out / "app/src/main/AndroidManifest.xml"))
52
+ elif backend_name == "ios":
53
+ swift = (out / "GeneratedApp.swift").read_text(encoding="utf-8")
54
+ if "@main" not in swift or "struct ContentView" not in swift:
55
+ raise RuntimeError("Generated iOS source failed entry-point validation.")
56
+ elif backend_name == "cli":
57
+ import ast
58
+ ast.parse((out / "app_cli.py").read_text(encoding="utf-8"), filename="app_cli.py")
59
+ manifest = {
60
+ "name": spec.name, "version": "0.1.0", "source_file": Path(source).name,
61
+ "targets": selected, "generated_at": datetime.now().astimezone().isoformat(),
62
+ "files": sorted(str(p.relative_to(project_dir)) for p in generated),
63
+ }
64
+ manifest_path = project_dir / "fluxscript-project.json"
65
+ manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
66
+ generated.append(manifest_path)
67
+ archive = root / f"{project_dir.name}.zip"
68
+ with zipfile.ZipFile(archive, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
69
+ for file in sorted(project_dir.rglob("*")):
70
+ if file.is_file(): zf.write(file, file.relative_to(project_dir.parent))
71
+ return project_dir, archive, generated
72
+ except Exception:
73
+ shutil.rmtree(project_dir, ignore_errors=True)
74
+ raise
fluxscript/version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: FluxScript
3
+ Version: 0.1.1
4
+ Summary: FluxScript Python-first multi-target application generator and developer CLI
5
+ Author: Md Safarujjaman
6
+ License-Expression: MIT
7
+ Keywords: fluxscript,compiler,code-generation,python,web,android,ios
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Topic :: Software Development :: Code Generators
12
+ Classifier: Topic :: Software Development :: Compilers
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Provides-Extra: test
17
+ Requires-Dist: pytest>=8.0; extra == "test"
18
+ Requires-Dist: build>=1.2; extra == "test"
19
+ Requires-Dist: twine>=5.0; extra == "test"
20
+ Dynamic: license-file
21
+
22
+ # FluxScript 0.1.0
23
+
24
+ FluxScript is a Python-first project generator and developer command-line toolkit. It provides a restricted declarative Python source format, shared application model, web source generation, native-platform source starters, project export, and an optional remote execution-service client.
25
+
26
+ > **Release status:** 0.1.0 is an alpha. It is suitable for experimentation and generated-source workflows, not a claim that arbitrary Python compiles to every platform. Inspect generated source and validate native projects with their real toolchains before production use.
27
+
28
+ ## Install
29
+
30
+ After the package is published under the PyPI project name `FluxScript`, install it with `python -m pip install FluxScript`. Package names are case-insensitive on PyPI. Until publication, install from the source checkout using the command below.
31
+
32
+
33
+ From the extracted source directory:
34
+
35
+ ```bash
36
+ python -m pip install .
37
+ fluxscript --version
38
+ fluxscript guide
39
+ ```
40
+
41
+ After FluxScript has been published to PyPI, users can install or update it with `python -m pip install --upgrade FluxScript`. It has **not** been published from this archive. FluxScript's core has no mandatory third-party runtime dependencies. When a builder is invoked, it automatically upgrades the Python helper packages required for that target's validation. Use `--no-install-deps` to disable this network/install step when the helper is already installed. This does not install operating-system toolchains.
42
+
43
+ ## First project
44
+
45
+ ```bash
46
+ fluxscript new my_app
47
+ cd my_app
48
+ fluxscript check app.py
49
+ fluxscript website app.py
50
+ ```
51
+
52
+ Preview the generated site:
53
+
54
+ ```bash
55
+ cd build/web
56
+ python -m http.server 8000
57
+ ```
58
+
59
+ Open `http://127.0.0.1:8000` in a browser.
60
+
61
+ ## Generate a project ZIP into storage
62
+
63
+ ```bash
64
+ fluxscript generateproject --source app.py --target web
65
+ fluxscript generateproject --source app.py --target android
66
+ fluxscript generateproject --source app.py --target ios
67
+ fluxscript generateproject --source app.py --target all
68
+ ```
69
+
70
+ This creates **both a project directory and a ZIP archive**. By default, FluxScript uses `/storage/emulated/0/FluxScriptProjects` when that shared Android storage location exists and is writable; otherwise it uses `~/FluxScriptProjects`. Specify `--storage /path/to/folder` to choose a different location.
71
+
72
+ ## Builders
73
+
74
+ ```bash
75
+ fluxscript website app.py --out build/web
76
+ fluxscript android app.py --out build/android
77
+ fluxscript ios app.py --out build/ios
78
+ fluxscript cli app.py --out build/cli
79
+ fluxscript build app.py --target all --out build/all
80
+ ```
81
+
82
+ - **Website:** HTML, responsive CSS, JavaScript, TypeScript, and structural HTML validation. Buttons emit action events; real business logic, APIs, authentication and persistence must be connected by the developer.
83
+ - **Android:** Kotlin/Gradle source project with a manifest validated by a safe XML parser. Building an APK requires a JDK, Android SDK, and compatible Gradle/Android Gradle Plugin toolchain.
84
+ - **iOS:** SwiftUI source starter. Building/signing an app requires macOS and Xcode. The builder does not produce an IPA.
85
+ - **CLI:** standalone Python `argparse` command interface for declared button actions; generated commands emit JSON action requests for application-specific handlers to consume.
86
+
87
+ The web and Android builder commands automatically upgrade their respective Python validation helper package to the latest available release. The iOS source generator has no Python helper dependency to install. Native build tools are not pip packages and are not silently downloaded.
88
+
89
+ ## Remote execution service
90
+
91
+ Configure an execution API that implements the documented runtime-listing and code-execution request shape. FluxScript never starts a remote service implicitly.
92
+
93
+ ```bash
94
+ fluxscript service status
95
+ export FLUXSCRIPT_SERVICE_URL='http://127.0.0.1:2000/api/v2'
96
+ export FLUXSCRIPT_SERVICE_TOKEN='your-token-if-required'
97
+ fluxscript service runtimes
98
+ fluxscript service run python 3.12 main.py
99
+ fluxscript service run javascript 20.11.1 app.js --stdin-file input.txt
100
+ ```
101
+
102
+ Remote non-local endpoints must use HTTPS. Never send secrets or untrusted code to an execution service unless you trust its operator and isolation controls. Client-side limits do not make a server secure.
103
+
104
+ ## Commands
105
+
106
+ Run `fluxscript guide` for the complete built-in command reference. Other useful commands:
107
+
108
+ ```bash
109
+ fluxscript --help
110
+ fluxscript new [directory]
111
+ fluxscript check [app.py]
112
+ fluxscript targets
113
+ fluxscript doctor
114
+ fluxscript integrations
115
+ fluxscript analyze ./source --out report.json
116
+ fluxscript review ./source
117
+ ```
118
+
119
+ ## Source format and security boundary
120
+
121
+ The `app.py` source is parsed using Python's AST and is **not executed**. Version 0.1.0 accepts only an explicit subset of declarative constructors, assignments, and page-registration calls. It is not a general Python interpreter or arbitrary Python-to-native compiler. Unsupported statements are rejected.
122
+
123
+ ## Development and release checks
124
+
125
+ ```bash
126
+ python -m compileall -q fluxscript tests
127
+ python -m unittest discover -s tests -v
128
+ python -m pip install build twine
129
+ python -m build
130
+ python -m twine check dist/*
131
+ ```
132
+
133
+ See `docs/ARCHITECTURE.md`, `docs/COMMANDS.md`, `docs/INTEGRATION_NOTES.md`, `docs/ROADMAP.md`, and `docs/PYPI_RELEASE.md`.
134
+
135
+ ## Clean command names
136
+
137
+ The CLI accepts easy-to-type commands without hyphens or underscores: `fluxscript website app.py`, `fluxscript android app.py`, `fluxscript ios app.py`, `fluxscript cli app.py`, `fluxscript generateproject --source app.py --target all`, and `fluxscript device`. Existing hyphenated commands remain supported for compatibility.
138
+
139
+ ## Author
140
+
141
+ **Md Safarujjaman** is the author listed in the package metadata. Run `fluxscript developer` to display the project author and version.
@@ -0,0 +1,24 @@
1
+ fluxscript/__init__.py,sha256=RQmJ1Dt94ntXmgaI6GEtYnRRXDzI4rFG3rg4FZW3L0o,238
2
+ fluxscript/__main__.py,sha256=ee5vE0xcUZM3fPmxicvcw-IJXkm6nOwxARREHBWpF8Q,47
3
+ fluxscript/analyzer.py,sha256=5fJyKye_DMgrAbXVKA7x5bxNvUtidgB9mchZrev5wnA,4717
4
+ fluxscript/cli.py,sha256=VCh-LPNM1kt9w-tlUuN5D_CF2ibHz6sMkdUWuB0SmVA,19759
5
+ fluxscript/dependencies.py,sha256=D-_G5zL99a03RrgTwAEBjqf19WaZLOkrlYBfrfunDuA,3705
6
+ fluxscript/dsl.py,sha256=Y9TrGX1DHIQ2VatpWigycB9ElcXSOV0pNbfdueNubjY,1463
7
+ fluxscript/models.py,sha256=9wqvAsaZlaMhxKIwOoCdJUekQbkmP0OOM0GzCLLUX8Q,2842
8
+ fluxscript/parser.py,sha256=t8nIL_bPlGIIwyXNNj71gelOhMhmjhjA49oUDFSwKqE,5465
9
+ fluxscript/project.py,sha256=AXijhLypXEFKUA_5qlMbbAGq9DTk0oHWKm0PHpk903k,3688
10
+ fluxscript/version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
11
+ fluxscript/backends/__init__.py,sha256=kWF5kgFZ8z-kvvNiWwRh0LafJdhzQlkFXX7B7NT6ZHM,233
12
+ fluxscript/backends/android.py,sha256=XqyP2SLZWXi3DTjsth_bT_KOBE3kyhEWLUebtzYdJ4k,5846
13
+ fluxscript/backends/cli.py,sha256=rDr5YQglFjAoqVNSSy_pjX4klW0E_AH0xXTk1GAToYg,2635
14
+ fluxscript/backends/ios.py,sha256=KNs49ObPimcUL2auluUawWk3TnTQBbEB2YYhV1Oz4fQ,3429
15
+ fluxscript/backends/web.py,sha256=x5xsr62IicsCfsobdGQIkF9JGezFyHWK3-xAMea_gXA,9580
16
+ fluxscript/integrations/__init__.py,sha256=m4xjX458bYTox0La3UYkyONA3m-3lmuaM2pSgu3pjWw,1725
17
+ fluxscript/integrations/commands.py,sha256=4U5gJZkl_LXRCuMdJLiFeEojF29pbxT-963bZ514Bn8,1676
18
+ fluxscript/integrations/execution_service.py,sha256=MnCpSJVFl4zx-_CYnNczhJPcuITZ7kX8FcPE4SMRt-g,4268
19
+ fluxscript-0.1.1.dist-info/licenses/LICENSE,sha256=-UpPSGmvlj2HTDHPvzgLixIDXVWX_CyD0S-YgzpqhA4,1072
20
+ fluxscript-0.1.1.dist-info/METADATA,sha256=y6F25yfYsyEzwgjgLV2SjZ1QDBLrNgbg4oUSDifWbso,6518
21
+ fluxscript-0.1.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
22
+ fluxscript-0.1.1.dist-info/entry_points.txt,sha256=8SxxPOwXjr7XEjZU7kh7NAFa8YNUr02XRlLGhiZDcb8,78
23
+ fluxscript-0.1.1.dist-info/top_level.txt,sha256=j-le8HTwMG5o_327BMwvi3dhalEt2wAIuFJVtxVQM-w,11
24
+ fluxscript-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ flux = fluxscript.cli:main
3
+ fluxscript = fluxscript.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Md Safarujjaman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ fluxscript