assayfluxos 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.
- assayfluxos-0.1.0/LICENSE +17 -0
- assayfluxos-0.1.0/PKG-INFO +195 -0
- assayfluxos-0.1.0/README.md +170 -0
- assayfluxos-0.1.0/pyproject.toml +39 -0
- assayfluxos-0.1.0/setup.cfg +4 -0
- assayfluxos-0.1.0/src/assayfluxos/__init__.py +2 -0
- assayfluxos-0.1.0/src/assayfluxos/cli.py +13 -0
- assayfluxos-0.1.0/src/assayfluxos/config.py +24 -0
- assayfluxos-0.1.0/src/assayfluxos/filesystem.py +131 -0
- assayfluxos-0.1.0/src/assayfluxos/language.py +179 -0
- assayfluxos-0.1.0/src/assayfluxos/plugins.py +42 -0
- assayfluxos-0.1.0/src/assayfluxos/shell.py +310 -0
- assayfluxos-0.1.0/src/assayfluxos.egg-info/PKG-INFO +195 -0
- assayfluxos-0.1.0/src/assayfluxos.egg-info/SOURCES.txt +18 -0
- assayfluxos-0.1.0/src/assayfluxos.egg-info/dependency_links.txt +1 -0
- assayfluxos-0.1.0/src/assayfluxos.egg-info/entry_points.txt +3 -0
- assayfluxos-0.1.0/src/assayfluxos.egg-info/top_level.txt +1 -0
- assayfluxos-0.1.0/tests/test_filesystem.py +18 -0
- assayfluxos-0.1.0/tests/test_language.py +19 -0
- assayfluxos-0.1.0/tests/test_shell.py +21 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AssayFluxOS contributors
|
|
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.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: assayfluxos
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A thoughtfully designed Python shell and FluxScript workspace for Termux, Android-oriented workflows, and desktop Python.
|
|
5
|
+
Author: AssayFluxOS contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: assayfluxos,shell,terminal,termux,android,fluxscript,interpreter,workspace,developer-tools,python
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
19
|
+
Classifier: Topic :: System :: Shells
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# AssayFluxOS 0.1.0
|
|
27
|
+
|
|
28
|
+
**A calm, capable command-line workspace for Python, Termux, and the emerging FluxScript language.**
|
|
29
|
+
|
|
30
|
+
AssayFluxOS brings everyday file work, a persistent project workspace, familiar full-word commands, and a small expressive scripting language into one approachable environment. It is designed for people who want to organise projects and experiment with a custom scripting workflow from a Python-capable terminal—including Termux on Android—without pretending that an early CLI is already a complete mobile operating system.
|
|
31
|
+
|
|
32
|
+
> **Release status:** 0.1.0 is an alpha foundation. It provides a Python command-line application and an early FluxScript interpreter. A native Android APK/AAB, graphical terminal/editor, and some advanced language features are planned separately and are **not included** in this Python package.
|
|
33
|
+
|
|
34
|
+
## Highlights
|
|
35
|
+
|
|
36
|
+
- **Readable commands:** prefer `copy`, `move`, `rename`, `delete`, `directory`, and `change directory` instead of requiring cryptic abbreviations.
|
|
37
|
+
- **A home for your work:** creates a structured `AssayFluxOS Files` workspace with project, document, data, script, module, plugin, download, log, backup, and configuration folders.
|
|
38
|
+
- **FluxScript starter language:** variables, output and input, conditions, loops, functions, return/stop, collections, indexing, arithmetic, comparisons, and boolean expressions.
|
|
39
|
+
- **Persistent preferences:** settings, aliases, and command history are stored in the workspace.
|
|
40
|
+
- **Useful terminal workflows:** basic pipelines, output redirection, text search, file properties, and workspace tree inspection.
|
|
41
|
+
- **Extension point:** trusted Python plugins can register additional commands.
|
|
42
|
+
- **Backup tools:** create and restore workspace archives with path checks and a confirmation step.
|
|
43
|
+
- **Python integration:** run trusted Python scripts from the CLI when appropriate.
|
|
44
|
+
- **No mandatory runtime dependencies:** the core package uses the Python standard library.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
Python 3.10 or newer is required.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python -m pip install assayfluxos
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Start the shell with either command:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
assayfluxos
|
|
58
|
+
# or the short alias
|
|
59
|
+
af
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
See command-line options:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
assayfluxos --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Install from a downloaded source project
|
|
69
|
+
|
|
70
|
+
From the project directory:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m pip install .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Android / Termux workspace
|
|
77
|
+
|
|
78
|
+
In Termux, enable Android shared-storage access when prompted:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
termux-setup-storage
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
AssayFluxOS attempts to use `/storage/emulated/0/AssayFluxOS Files` when the shared-storage parent is writable. Otherwise it falls back to `~/AssayFluxOS Files`. You can explicitly choose a workspace where supported with `--workspace PATH`.
|
|
85
|
+
|
|
86
|
+
The workspace initialisation creates these folders when missing and does not intentionally erase existing content:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
AssayFluxOS Files/
|
|
90
|
+
├── Projects/
|
|
91
|
+
│ ├── Python/
|
|
92
|
+
│ ├── FluxScript/
|
|
93
|
+
│ ├── Web/
|
|
94
|
+
│ └── Experiments/
|
|
95
|
+
├── Documents/
|
|
96
|
+
├── Data/
|
|
97
|
+
├── Scripts/
|
|
98
|
+
├── Modules/
|
|
99
|
+
├── Downloads/
|
|
100
|
+
├── Logs/
|
|
101
|
+
├── Backups/
|
|
102
|
+
├── Config/
|
|
103
|
+
└── Plugins/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For a future native Android app, the recommended design is to use Android's Storage Access Framework for user-selected directories rather than assuming unrestricted filesystem access.
|
|
107
|
+
|
|
108
|
+
## Everyday commands
|
|
109
|
+
|
|
110
|
+
Examples (the exact command grammar is documented in the CLI help and source):
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
directory
|
|
114
|
+
change directory to Projects
|
|
115
|
+
create file "hello world.flux"
|
|
116
|
+
write file "hello world.flux"
|
|
117
|
+
read file "hello world.flux"
|
|
118
|
+
copy file "hello world.flux" to "hello backup.flux"
|
|
119
|
+
move "hello backup.flux" to Backups
|
|
120
|
+
rename file old.txt new.txt
|
|
121
|
+
search hello
|
|
122
|
+
findtext "Welcome" Projects
|
|
123
|
+
properties "hello world.flux"
|
|
124
|
+
tree
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Longer, natural command forms are supported where implemented. Full-word forms are preferred for readability.
|
|
128
|
+
|
|
129
|
+
## FluxScript quick start
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
let items = ["alpha", "beta"]
|
|
133
|
+
|
|
134
|
+
function greet(name)
|
|
135
|
+
say "Hello, " + name
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
call greet("Researcher")
|
|
139
|
+
|
|
140
|
+
loop 2
|
|
141
|
+
say items[0]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if 2 > 1
|
|
145
|
+
say "The expression is true"
|
|
146
|
+
else
|
|
147
|
+
say "The expression is false"
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Current language scope
|
|
152
|
+
|
|
153
|
+
The implemented subset includes `say`, `ask`, `let`, `if/else/end`, `loop/end`, `while/end`, `function/end`, `call`, `return`, and `stop`, along with common literals, lists, tuples, dictionaries, indexing, arithmetic, comparisons, and boolean operations. Expression handling uses a restricted Python AST evaluator; it does not call Python `eval()` or `exec()` for FluxScript expressions.
|
|
154
|
+
|
|
155
|
+
User-defined classes, asynchronous tasks, module imports (`use`), exception blocks, and general operating-system APIs are not implemented in this release. Execution limits help guard against accidental runaway scripts, but **FluxScript is not a security sandbox for hostile code**.
|
|
156
|
+
|
|
157
|
+
## Aliases, settings, history, and plugins
|
|
158
|
+
|
|
159
|
+
- `set NAME = VALUE` stores supported JSON-compatible settings in `Config/settings.json`.
|
|
160
|
+
- `alias ll = directory` creates a persistent command alias; `unalias ll` removes one.
|
|
161
|
+
- Command history is written to `Logs/history.txt`.
|
|
162
|
+
- Trusted Python plugins in `Plugins/` define `register(registry)` and may register commands using `registry.add(name, handler, help_text)`.
|
|
163
|
+
|
|
164
|
+
**Security note:** Python scripts and plugins execute with the permissions of the current user. Only run code you trust. Keep backups of important data. Restore operations can overwrite existing files even though archive paths are validated and links/devices are rejected.
|
|
165
|
+
|
|
166
|
+
## Test and develop
|
|
167
|
+
|
|
168
|
+
From the project root:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
python -m pip install -e .
|
|
172
|
+
python -m unittest discover -s tests -v
|
|
173
|
+
python -m compileall -q src tests tools
|
|
174
|
+
python tools/assayflux_preflight.py
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The preflight helper checks for an installed `assayflux` command and uses its available version/help/doctor interfaces. It does not assume the external CLI has an Android packaging feature.
|
|
178
|
+
|
|
179
|
+
## Roadmap
|
|
180
|
+
|
|
181
|
+
1. Stabilise the CLI and expand automated tests.
|
|
182
|
+
2. Extend FluxScript with carefully designed modules, classes, and async tasks.
|
|
183
|
+
3. Build a native Android application shell with a terminal, file browser, and graphical editor.
|
|
184
|
+
4. Integrate Android storage selection and manage runtime lifecycle correctly.
|
|
185
|
+
5. Build and test APK/AAB artifacts on real devices before calling them production-ready.
|
|
186
|
+
|
|
187
|
+
See `ANDROID_ROADMAP.md`, `android/README.md`, and `docs/FLUXSCRIPT_REFERENCE.md` for more details.
|
|
188
|
+
|
|
189
|
+
## Publishing
|
|
190
|
+
|
|
191
|
+
A Google Colab notebook is provided separately to run tests, build source and wheel distributions, validate metadata, and upload to PyPI using a token entered securely at runtime. Never commit or share a PyPI token. PyPI does not allow replacing an already-published file for the same project version; if `assayfluxos==0.1.0` already exists on PyPI, increment the version before publishing.
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# AssayFluxOS 0.1.0
|
|
2
|
+
|
|
3
|
+
**A calm, capable command-line workspace for Python, Termux, and the emerging FluxScript language.**
|
|
4
|
+
|
|
5
|
+
AssayFluxOS brings everyday file work, a persistent project workspace, familiar full-word commands, and a small expressive scripting language into one approachable environment. It is designed for people who want to organise projects and experiment with a custom scripting workflow from a Python-capable terminal—including Termux on Android—without pretending that an early CLI is already a complete mobile operating system.
|
|
6
|
+
|
|
7
|
+
> **Release status:** 0.1.0 is an alpha foundation. It provides a Python command-line application and an early FluxScript interpreter. A native Android APK/AAB, graphical terminal/editor, and some advanced language features are planned separately and are **not included** in this Python package.
|
|
8
|
+
|
|
9
|
+
## Highlights
|
|
10
|
+
|
|
11
|
+
- **Readable commands:** prefer `copy`, `move`, `rename`, `delete`, `directory`, and `change directory` instead of requiring cryptic abbreviations.
|
|
12
|
+
- **A home for your work:** creates a structured `AssayFluxOS Files` workspace with project, document, data, script, module, plugin, download, log, backup, and configuration folders.
|
|
13
|
+
- **FluxScript starter language:** variables, output and input, conditions, loops, functions, return/stop, collections, indexing, arithmetic, comparisons, and boolean expressions.
|
|
14
|
+
- **Persistent preferences:** settings, aliases, and command history are stored in the workspace.
|
|
15
|
+
- **Useful terminal workflows:** basic pipelines, output redirection, text search, file properties, and workspace tree inspection.
|
|
16
|
+
- **Extension point:** trusted Python plugins can register additional commands.
|
|
17
|
+
- **Backup tools:** create and restore workspace archives with path checks and a confirmation step.
|
|
18
|
+
- **Python integration:** run trusted Python scripts from the CLI when appropriate.
|
|
19
|
+
- **No mandatory runtime dependencies:** the core package uses the Python standard library.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Python 3.10 or newer is required.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m pip install assayfluxos
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Start the shell with either command:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
assayfluxos
|
|
33
|
+
# or the short alias
|
|
34
|
+
af
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
See command-line options:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
assayfluxos --help
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Install from a downloaded source project
|
|
44
|
+
|
|
45
|
+
From the project directory:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python -m pip install .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Android / Termux workspace
|
|
52
|
+
|
|
53
|
+
In Termux, enable Android shared-storage access when prompted:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
termux-setup-storage
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
AssayFluxOS attempts to use `/storage/emulated/0/AssayFluxOS Files` when the shared-storage parent is writable. Otherwise it falls back to `~/AssayFluxOS Files`. You can explicitly choose a workspace where supported with `--workspace PATH`.
|
|
60
|
+
|
|
61
|
+
The workspace initialisation creates these folders when missing and does not intentionally erase existing content:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
AssayFluxOS Files/
|
|
65
|
+
├── Projects/
|
|
66
|
+
│ ├── Python/
|
|
67
|
+
│ ├── FluxScript/
|
|
68
|
+
│ ├── Web/
|
|
69
|
+
│ └── Experiments/
|
|
70
|
+
├── Documents/
|
|
71
|
+
├── Data/
|
|
72
|
+
├── Scripts/
|
|
73
|
+
├── Modules/
|
|
74
|
+
├── Downloads/
|
|
75
|
+
├── Logs/
|
|
76
|
+
├── Backups/
|
|
77
|
+
├── Config/
|
|
78
|
+
└── Plugins/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For a future native Android app, the recommended design is to use Android's Storage Access Framework for user-selected directories rather than assuming unrestricted filesystem access.
|
|
82
|
+
|
|
83
|
+
## Everyday commands
|
|
84
|
+
|
|
85
|
+
Examples (the exact command grammar is documented in the CLI help and source):
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
directory
|
|
89
|
+
change directory to Projects
|
|
90
|
+
create file "hello world.flux"
|
|
91
|
+
write file "hello world.flux"
|
|
92
|
+
read file "hello world.flux"
|
|
93
|
+
copy file "hello world.flux" to "hello backup.flux"
|
|
94
|
+
move "hello backup.flux" to Backups
|
|
95
|
+
rename file old.txt new.txt
|
|
96
|
+
search hello
|
|
97
|
+
findtext "Welcome" Projects
|
|
98
|
+
properties "hello world.flux"
|
|
99
|
+
tree
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Longer, natural command forms are supported where implemented. Full-word forms are preferred for readability.
|
|
103
|
+
|
|
104
|
+
## FluxScript quick start
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
let items = ["alpha", "beta"]
|
|
108
|
+
|
|
109
|
+
function greet(name)
|
|
110
|
+
say "Hello, " + name
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
call greet("Researcher")
|
|
114
|
+
|
|
115
|
+
loop 2
|
|
116
|
+
say items[0]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if 2 > 1
|
|
120
|
+
say "The expression is true"
|
|
121
|
+
else
|
|
122
|
+
say "The expression is false"
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Current language scope
|
|
127
|
+
|
|
128
|
+
The implemented subset includes `say`, `ask`, `let`, `if/else/end`, `loop/end`, `while/end`, `function/end`, `call`, `return`, and `stop`, along with common literals, lists, tuples, dictionaries, indexing, arithmetic, comparisons, and boolean operations. Expression handling uses a restricted Python AST evaluator; it does not call Python `eval()` or `exec()` for FluxScript expressions.
|
|
129
|
+
|
|
130
|
+
User-defined classes, asynchronous tasks, module imports (`use`), exception blocks, and general operating-system APIs are not implemented in this release. Execution limits help guard against accidental runaway scripts, but **FluxScript is not a security sandbox for hostile code**.
|
|
131
|
+
|
|
132
|
+
## Aliases, settings, history, and plugins
|
|
133
|
+
|
|
134
|
+
- `set NAME = VALUE` stores supported JSON-compatible settings in `Config/settings.json`.
|
|
135
|
+
- `alias ll = directory` creates a persistent command alias; `unalias ll` removes one.
|
|
136
|
+
- Command history is written to `Logs/history.txt`.
|
|
137
|
+
- Trusted Python plugins in `Plugins/` define `register(registry)` and may register commands using `registry.add(name, handler, help_text)`.
|
|
138
|
+
|
|
139
|
+
**Security note:** Python scripts and plugins execute with the permissions of the current user. Only run code you trust. Keep backups of important data. Restore operations can overwrite existing files even though archive paths are validated and links/devices are rejected.
|
|
140
|
+
|
|
141
|
+
## Test and develop
|
|
142
|
+
|
|
143
|
+
From the project root:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python -m pip install -e .
|
|
147
|
+
python -m unittest discover -s tests -v
|
|
148
|
+
python -m compileall -q src tests tools
|
|
149
|
+
python tools/assayflux_preflight.py
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The preflight helper checks for an installed `assayflux` command and uses its available version/help/doctor interfaces. It does not assume the external CLI has an Android packaging feature.
|
|
153
|
+
|
|
154
|
+
## Roadmap
|
|
155
|
+
|
|
156
|
+
1. Stabilise the CLI and expand automated tests.
|
|
157
|
+
2. Extend FluxScript with carefully designed modules, classes, and async tasks.
|
|
158
|
+
3. Build a native Android application shell with a terminal, file browser, and graphical editor.
|
|
159
|
+
4. Integrate Android storage selection and manage runtime lifecycle correctly.
|
|
160
|
+
5. Build and test APK/AAB artifacts on real devices before calling them production-ready.
|
|
161
|
+
|
|
162
|
+
See `ANDROID_ROADMAP.md`, `android/README.md`, and `docs/FLUXSCRIPT_REFERENCE.md` for more details.
|
|
163
|
+
|
|
164
|
+
## Publishing
|
|
165
|
+
|
|
166
|
+
A Google Colab notebook is provided separately to run tests, build source and wheel distributions, validate metadata, and upload to PyPI using a token entered securely at runtime. Never commit or share a PyPI token. PyPI does not allow replacing an already-published file for the same project version; if `assayfluxos==0.1.0` already exists on PyPI, increment the version before publishing.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "assayfluxos"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A thoughtfully designed Python shell and FluxScript workspace for Termux, Android-oriented workflows, and desktop Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "AssayFluxOS contributors" }]
|
|
13
|
+
keywords = ["assayfluxos", "shell", "terminal", "termux", "android", "fluxscript", "interpreter", "workspace", "developer-tools", "python"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Interpreters",
|
|
26
|
+
"Topic :: System :: Shells",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
assayfluxos = "assayfluxos.cli:main"
|
|
33
|
+
af = "assayfluxos.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import argparse
|
|
3
|
+
from .shell import AssayFluxShell
|
|
4
|
+
|
|
5
|
+
def main() -> None:
|
|
6
|
+
parser = argparse.ArgumentParser(prog="assayfluxos", description="AssayFluxOS custom Python shell")
|
|
7
|
+
parser.add_argument("--workspace", help="Use a specific workspace directory")
|
|
8
|
+
args = parser.parse_args()
|
|
9
|
+
shell = AssayFluxShell(workspace=args.workspace)
|
|
10
|
+
shell.loop()
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
class Settings:
|
|
6
|
+
"""Small JSON settings store. Invalid settings are preserved and safely ignored."""
|
|
7
|
+
def __init__(self, workspace: Path):
|
|
8
|
+
self.path = workspace / "Config" / "settings.json"
|
|
9
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
10
|
+
self.data = {"theme": "dark", "aliases": {}, "variables": {}, "plugins_enabled": True}
|
|
11
|
+
try:
|
|
12
|
+
loaded = json.loads(self.path.read_text(encoding="utf-8"))
|
|
13
|
+
if isinstance(loaded, dict):
|
|
14
|
+
self.data.update(loaded)
|
|
15
|
+
except (OSError, ValueError):
|
|
16
|
+
pass
|
|
17
|
+
if not isinstance(self.data.get("aliases"), dict): self.data["aliases"] = {}
|
|
18
|
+
if not isinstance(self.data.get("variables"), dict): self.data["variables"] = {}
|
|
19
|
+
|
|
20
|
+
def save(self):
|
|
21
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
tmp = self.path.with_suffix(".json.tmp")
|
|
23
|
+
tmp.write_text(json.dumps(self.data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
24
|
+
tmp.replace(self.path)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
MASTER_FOLDER = "AssayFluxOS Files"
|
|
7
|
+
DEFAULT_FOLDERS = ("Projects/Python", "Projects/FluxScript", "Projects/Web", "Projects/Experiments", "Documents", "Data", "Scripts", "Modules", "Downloads", "Logs", "Backups", "Config", "Plugins")
|
|
8
|
+
|
|
9
|
+
class Workspace:
|
|
10
|
+
"""Filesystem facade confined to a workspace root, including symlink resolution."""
|
|
11
|
+
def __init__(self, root: str | Path | None = None):
|
|
12
|
+
if root is None:
|
|
13
|
+
shared = Path("/storage/emulated/0") / MASTER_FOLDER
|
|
14
|
+
home = Path.home() / MASTER_FOLDER
|
|
15
|
+
root_path = shared if shared.parent.exists() and os.access(shared.parent, os.W_OK) else home
|
|
16
|
+
else:
|
|
17
|
+
root_path = Path(root).expanduser()
|
|
18
|
+
root_path.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
self.root = root_path.resolve()
|
|
20
|
+
self.cwd = self.root
|
|
21
|
+
for item in DEFAULT_FOLDERS:
|
|
22
|
+
(self.root / item).mkdir(parents=True, exist_ok=True)
|
|
23
|
+
|
|
24
|
+
def resolve(self, raw: str | Path = ".") -> Path:
|
|
25
|
+
p = Path(raw).expanduser()
|
|
26
|
+
if not p.is_absolute(): p = self.cwd / p
|
|
27
|
+
resolved = p.resolve(strict=False)
|
|
28
|
+
if resolved != self.root and self.root not in resolved.parents:
|
|
29
|
+
raise PermissionError("Path is outside the AssayFluxOS workspace.")
|
|
30
|
+
return resolved
|
|
31
|
+
|
|
32
|
+
def change(self, raw: str) -> Path:
|
|
33
|
+
target = self.resolve(raw)
|
|
34
|
+
if not target.is_dir(): raise NotADirectoryError(str(target))
|
|
35
|
+
self.cwd = target
|
|
36
|
+
return target
|
|
37
|
+
|
|
38
|
+
def list(self, raw: str = ".") -> list[Path]:
|
|
39
|
+
target = self.resolve(raw)
|
|
40
|
+
if not target.is_dir(): raise NotADirectoryError(str(target))
|
|
41
|
+
return sorted(target.iterdir(), key=lambda p: (not p.is_dir(), p.name.casefold()))
|
|
42
|
+
|
|
43
|
+
def create_file(self, raw: str) -> Path:
|
|
44
|
+
target = self.resolve(raw)
|
|
45
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
target.touch(exist_ok=False)
|
|
47
|
+
return target
|
|
48
|
+
|
|
49
|
+
def make_dir(self, raw: str) -> Path:
|
|
50
|
+
target = self.resolve(raw)
|
|
51
|
+
target.mkdir(parents=True, exist_ok=False)
|
|
52
|
+
return target
|
|
53
|
+
|
|
54
|
+
def read_text(self, raw: str, encoding: str = "utf-8") -> str:
|
|
55
|
+
target = self.resolve(raw)
|
|
56
|
+
if not target.is_file(): raise IsADirectoryError(str(target))
|
|
57
|
+
if target.stat().st_size > 20_000_000: raise ValueError("File exceeds 20 MB text-read limit.")
|
|
58
|
+
return target.read_text(encoding=encoding)
|
|
59
|
+
|
|
60
|
+
def write_text(self, raw: str, content: str, encoding: str = "utf-8") -> Path:
|
|
61
|
+
target = self.resolve(raw)
|
|
62
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
target.write_text(content, encoding=encoding)
|
|
64
|
+
return target
|
|
65
|
+
|
|
66
|
+
def copy(self, source: str, destination: str) -> Path:
|
|
67
|
+
src, dst = self.resolve(source), self.resolve(destination)
|
|
68
|
+
if not src.exists(): raise FileNotFoundError(str(src))
|
|
69
|
+
if src == self.root: raise PermissionError("Copying the workspace root is not supported.")
|
|
70
|
+
if src.is_dir() and (dst == src or src in dst.parents):
|
|
71
|
+
raise ValueError("Cannot copy a directory into itself or one of its descendants.")
|
|
72
|
+
if dst.exists() and dst.is_dir(): dst = dst / src.name
|
|
73
|
+
if dst.exists(): raise FileExistsError(str(dst))
|
|
74
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
if src.is_dir(): shutil.copytree(src, dst)
|
|
76
|
+
else: shutil.copy2(src, dst)
|
|
77
|
+
return dst
|
|
78
|
+
|
|
79
|
+
def move(self, source: str, destination: str) -> Path:
|
|
80
|
+
src, dst = self.resolve(source), self.resolve(destination)
|
|
81
|
+
if not src.exists(): raise FileNotFoundError(str(src))
|
|
82
|
+
if src == self.root: raise PermissionError("The workspace root cannot be moved.")
|
|
83
|
+
if src.is_dir() and (dst == src or src in dst.parents):
|
|
84
|
+
raise ValueError("Cannot move a directory into itself or one of its descendants.")
|
|
85
|
+
if dst.exists() and dst.is_dir(): dst = dst / src.name
|
|
86
|
+
if dst.exists(): raise FileExistsError(str(dst))
|
|
87
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
result = Path(shutil.move(str(src), str(dst)))
|
|
89
|
+
if self.cwd == src or src in self.cwd.parents: self.cwd = self.root
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
def rename(self, source: str, destination: str) -> Path:
|
|
93
|
+
src, dst = self.resolve(source), self.resolve(destination)
|
|
94
|
+
if src == self.root: raise PermissionError("The workspace root cannot be renamed.")
|
|
95
|
+
if not src.exists(): raise FileNotFoundError(str(src))
|
|
96
|
+
if dst.exists(): raise FileExistsError(str(dst))
|
|
97
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
return src.rename(dst)
|
|
99
|
+
|
|
100
|
+
def delete(self, raw: str) -> Path:
|
|
101
|
+
target = self.resolve(raw)
|
|
102
|
+
if target == self.root: raise PermissionError("The workspace root cannot be deleted.")
|
|
103
|
+
if not target.exists(): raise FileNotFoundError(str(target))
|
|
104
|
+
if target.is_dir(): shutil.rmtree(target)
|
|
105
|
+
else: target.unlink()
|
|
106
|
+
if self.cwd == target or target in self.cwd.parents: self.cwd = self.root
|
|
107
|
+
return target
|
|
108
|
+
|
|
109
|
+
def search_names(self, needle: str) -> list[Path]:
|
|
110
|
+
key = needle.casefold()
|
|
111
|
+
found = []
|
|
112
|
+
for p in self.cwd.rglob("*"):
|
|
113
|
+
try:
|
|
114
|
+
resolved = p.resolve()
|
|
115
|
+
if (resolved == self.root or self.root in resolved.parents) and key in p.name.casefold(): found.append(p)
|
|
116
|
+
except OSError: continue
|
|
117
|
+
return found
|
|
118
|
+
|
|
119
|
+
def find_text(self, needle: str, raw: str = ".") -> list[tuple[Path, int, str]]:
|
|
120
|
+
target = self.resolve(raw)
|
|
121
|
+
candidates = [target] if target.is_file() else target.rglob("*")
|
|
122
|
+
results = []
|
|
123
|
+
for p in candidates:
|
|
124
|
+
try:
|
|
125
|
+
if not p.is_file() or p.stat().st_size > 2_000_000: continue
|
|
126
|
+
resolved = p.resolve()
|
|
127
|
+
if resolved != self.root and self.root not in resolved.parents: continue
|
|
128
|
+
for line_no, line in enumerate(p.read_text(encoding="utf-8").splitlines(), 1):
|
|
129
|
+
if needle.casefold() in line.casefold(): results.append((p, line_no, line.strip()))
|
|
130
|
+
except (UnicodeDecodeError, OSError, PermissionError): continue
|
|
131
|
+
return results
|