artinforge 0.1.0__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.
- artinforge/__init__.py +4 -0
- artinforge/assets/artinforge-banner.png +0 -0
- artinforge/cli.py +24 -0
- artinforge/files.py +22 -0
- artinforge/jsonstore.py +23 -0
- artinforge/net.py +37 -0
- artinforge/server.py +27 -0
- artinforge/system.py +12 -0
- artinforge/text.py +23 -0
- artinforge-0.1.0.dist-info/METADATA +96 -0
- artinforge-0.1.0.dist-info/RECORD +15 -0
- artinforge-0.1.0.dist-info/WHEEL +5 -0
- artinforge-0.1.0.dist-info/entry_points.txt +2 -0
- artinforge-0.1.0.dist-info/licenses/LICENSE +17 -0
- artinforge-0.1.0.dist-info/top_level.txt +1 -0
artinforge/__init__.py
ADDED
|
Binary file
|
artinforge/cli.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""ArtinForge command line interface."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
from .server import LocalServer
|
|
5
|
+
from .system import info
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
parser = argparse.ArgumentParser(prog="artinforge", description="ArtinForge developer toolkit")
|
|
9
|
+
sub = parser.add_subparsers(dest="command")
|
|
10
|
+
sub.add_parser("info", help="show Python/platform information")
|
|
11
|
+
serve = sub.add_parser("serve", help="serve a directory for local development")
|
|
12
|
+
serve.add_argument("--host", default="127.0.0.1")
|
|
13
|
+
serve.add_argument("--port", type=int, default=8765)
|
|
14
|
+
serve.add_argument("--directory", default=".")
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
if args.command == "info":
|
|
17
|
+
print(json.dumps(info(), indent=2, ensure_ascii=False))
|
|
18
|
+
elif args.command == "serve":
|
|
19
|
+
LocalServer(args.host, args.port, args.directory).serve()
|
|
20
|
+
else:
|
|
21
|
+
parser.print_help()
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
main()
|
artinforge/files.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Filesystem helpers."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def ensure_dir(path) -> Path:
|
|
5
|
+
target = Path(path)
|
|
6
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
7
|
+
return target
|
|
8
|
+
|
|
9
|
+
def read_text(path, encoding="utf-8") -> str:
|
|
10
|
+
return Path(path).read_text(encoding=encoding)
|
|
11
|
+
|
|
12
|
+
def write_text(path, content, encoding="utf-8") -> Path:
|
|
13
|
+
target = Path(path)
|
|
14
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
target.write_text(str(content), encoding=encoding)
|
|
16
|
+
return target
|
|
17
|
+
|
|
18
|
+
def file_size(path) -> int:
|
|
19
|
+
return Path(path).stat().st_size
|
|
20
|
+
|
|
21
|
+
def list_files(path=".", pattern="*") -> list:
|
|
22
|
+
return sorted(p for p in Path(path).glob(pattern) if p.is_file())
|
artinforge/jsonstore.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""JSON read/write helpers."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def load(path, default=None, encoding="utf-8"):
|
|
6
|
+
target = Path(path)
|
|
7
|
+
if not target.exists():
|
|
8
|
+
return default
|
|
9
|
+
try:
|
|
10
|
+
with target.open("r", encoding=encoding) as stream:
|
|
11
|
+
return json.load(stream)
|
|
12
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
13
|
+
raise ValueError(f"Could not load JSON from {target}: {exc}") from exc
|
|
14
|
+
|
|
15
|
+
def save(path, data, *, indent=2, ensure_ascii=False, encoding="utf-8"):
|
|
16
|
+
target = Path(path)
|
|
17
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
temp = target.with_suffix(target.suffix + ".tmp")
|
|
19
|
+
with temp.open("w", encoding=encoding) as stream:
|
|
20
|
+
json.dump(data, stream, indent=indent, ensure_ascii=ensure_ascii)
|
|
21
|
+
stream.write("\n")
|
|
22
|
+
temp.replace(target)
|
|
23
|
+
return target
|
artinforge/net.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Small HTTP helpers using Python's standard library."""
|
|
2
|
+
import json
|
|
3
|
+
from urllib.error import HTTPError, URLError
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
from urllib.request import Request, urlopen
|
|
6
|
+
|
|
7
|
+
def validate_url(url):
|
|
8
|
+
parsed = urlparse(url)
|
|
9
|
+
if parsed.scheme not in ("http", "https") or not parsed.netloc:
|
|
10
|
+
raise ValueError("Expected an absolute http:// or https:// URL")
|
|
11
|
+
return url
|
|
12
|
+
|
|
13
|
+
def get(url, *, timeout=10, headers=None):
|
|
14
|
+
request = Request(validate_url(url), headers=headers or {}, method="GET")
|
|
15
|
+
try:
|
|
16
|
+
with urlopen(request, timeout=timeout) as response:
|
|
17
|
+
return response.read()
|
|
18
|
+
except (HTTPError, URLError, TimeoutError) as exc:
|
|
19
|
+
raise RuntimeError(f"GET request failed: {exc}") from exc
|
|
20
|
+
|
|
21
|
+
def get_text(url, *, timeout=10, encoding="utf-8", headers=None):
|
|
22
|
+
return get(url, timeout=timeout, headers=headers).decode(encoding, errors="replace")
|
|
23
|
+
|
|
24
|
+
def get_json(url, *, timeout=10, headers=None):
|
|
25
|
+
return json.loads(get_text(url, timeout=timeout, headers=headers))
|
|
26
|
+
|
|
27
|
+
def post_json(url, data, *, timeout=10, headers=None):
|
|
28
|
+
request_headers = {"Content-Type": "application/json"}
|
|
29
|
+
request_headers.update(headers or {})
|
|
30
|
+
request = Request(validate_url(url), data=json.dumps(data).encode("utf-8"),
|
|
31
|
+
headers=request_headers, method="POST")
|
|
32
|
+
try:
|
|
33
|
+
with urlopen(request, timeout=timeout) as response:
|
|
34
|
+
body = response.read().decode("utf-8", errors="replace")
|
|
35
|
+
return json.loads(body) if body else None
|
|
36
|
+
except (HTTPError, URLError, TimeoutError) as exc:
|
|
37
|
+
raise RuntimeError(f"POST request failed: {exc}") from exc
|
artinforge/server.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Local static-file server for development."""
|
|
2
|
+
from functools import partial
|
|
3
|
+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
class LocalServer:
|
|
7
|
+
def __init__(self, host="127.0.0.1", port=8765, directory="."):
|
|
8
|
+
if not 0 <= int(port) <= 65535:
|
|
9
|
+
raise ValueError("port must be between 0 and 65535")
|
|
10
|
+
self.host, self.port = host, int(port)
|
|
11
|
+
self.directory = str(Path(directory).resolve())
|
|
12
|
+
self._server = ThreadingHTTPServer(
|
|
13
|
+
(self.host, self.port),
|
|
14
|
+
partial(SimpleHTTPRequestHandler, directory=self.directory)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def serve(self):
|
|
18
|
+
print("ARTINFORGE LOCAL SERVER")
|
|
19
|
+
print(f"Serving: {self.directory}")
|
|
20
|
+
print(f"Open: http://{self.host}:{self._server.server_address[1]}")
|
|
21
|
+
print("Press Ctrl+C to stop.")
|
|
22
|
+
try:
|
|
23
|
+
self._server.serve_forever()
|
|
24
|
+
except KeyboardInterrupt:
|
|
25
|
+
print("\nStopping server...")
|
|
26
|
+
finally:
|
|
27
|
+
self._server.server_close()
|
artinforge/system.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Basic runtime information."""
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
def info() -> dict:
|
|
7
|
+
return {"python": sys.version.split()[0],
|
|
8
|
+
"implementation": platform.python_implementation(),
|
|
9
|
+
"platform": platform.platform(),
|
|
10
|
+
"executable": sys.executable,
|
|
11
|
+
"current_directory": os.getcwd(),
|
|
12
|
+
"pid": os.getpid()}
|
artinforge/text.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Unicode-friendly text utilities."""
|
|
2
|
+
import re
|
|
3
|
+
import unicodedata
|
|
4
|
+
|
|
5
|
+
def normalize_whitespace(value: str) -> str:
|
|
6
|
+
return " ".join(str(value).split())
|
|
7
|
+
|
|
8
|
+
def word_count(value: str) -> int:
|
|
9
|
+
return len(str(value).split())
|
|
10
|
+
|
|
11
|
+
def truncate(value: str, length: int, suffix: str = "...") -> str:
|
|
12
|
+
if length < 0:
|
|
13
|
+
raise ValueError("length must be >= 0")
|
|
14
|
+
value = str(value)
|
|
15
|
+
if len(value) <= length:
|
|
16
|
+
return value
|
|
17
|
+
if length <= len(suffix):
|
|
18
|
+
return suffix[:length]
|
|
19
|
+
return value[:length-len(suffix)] + suffix
|
|
20
|
+
|
|
21
|
+
def slugify(value: str, separator: str = "-") -> str:
|
|
22
|
+
value = unicodedata.normalize("NFKD", str(value)).encode("ascii", "ignore").decode()
|
|
23
|
+
return re.sub(r"[^a-zA-Z0-9]+", separator, value.lower()).strip(separator)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: artinforge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight, modular Python toolkit for developer utilities.
|
|
5
|
+
Author: Artin Karimian
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: python,developer-tools,automation,files,json,networking
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# ArtinForge
|
|
17
|
+
|
|
18
|
+
**A lightweight, modular Python toolkit for everyday developer tasks.**
|
|
19
|
+
|
|
20
|
+
ArtinForge starts with useful, real features and avoids mandatory third-party runtime dependencies.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Text utilities:** normalize whitespace, count words, truncate text, create slugs.
|
|
25
|
+
- **File utilities:** UTF-8 read/write, directory creation, file listing and size.
|
|
26
|
+
- **JSON storage:** read and save JSON, including Persian/Unicode text.
|
|
27
|
+
- **HTTP helpers:** GET text/JSON and POST JSON using Python's standard library.
|
|
28
|
+
- **Local development server:** serve a folder for local testing.
|
|
29
|
+
- **System information:** Python version, platform, executable and current directory.
|
|
30
|
+
- **Command line:** inspect runtime details and launch the local server.
|
|
31
|
+
|
|
32
|
+
> ArtinForge is a developer toolkit, not a bundled AI model. AI features require a model or service to be connected separately.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python -m pip install artinforge
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If a package mirror is stale, check the official PyPI index:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python -m pip install --index-url https://pypi.org/simple artinforge
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The name must be available on PyPI before the first release. Check the project page before publishing.
|
|
47
|
+
|
|
48
|
+
## Quick example
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from artinforge import text, files, jsonstore
|
|
52
|
+
|
|
53
|
+
print(text.slugify("Hello ArtinForge!"))
|
|
54
|
+
files.write_text("demo/hello.txt", "سلام آرتین")
|
|
55
|
+
jsonstore.save("demo/settings.json", {"language": "fa", "theme": "dark"})
|
|
56
|
+
print(jsonstore.load("demo/settings.json", default={}))
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Start a local test server
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
artinforge serve --host 127.0.0.1 --port 8765 --directory .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then open `http://127.0.0.1:8765` in your browser. This is for local development; do not expose it to the public internet without a security review and appropriate protections.
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
artinforge --help
|
|
71
|
+
artinforge info
|
|
72
|
+
artinforge serve --host 127.0.0.1 --port 8765 --directory .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Package the project
|
|
76
|
+
|
|
77
|
+
From the folder containing `pyproject.toml`:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python -m pip install --upgrade build twine
|
|
81
|
+
python -m build
|
|
82
|
+
python -m twine check dist/*
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Only after checks pass, upload with your PyPI token:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python -m twine upload dist/*
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Release rule:** every upload to PyPI needs a version that has not been published before. If you need to fix a release, increment `version` in `pyproject.toml`, rebuild from a clean `dist/` directory, and upload the new version. A mirror may take time to sync, so verify against the official PyPI index.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
artinforge/__init__.py,sha256=obt0lVZLWKkTj0clJkh9AIcL_KlCq17MkwHeBhPLQq4,193
|
|
2
|
+
artinforge/cli.py,sha256=Fy18P2HVLk9cytID7D_6tdFwjIqyQnThQ8YAKRG6CPY,904
|
|
3
|
+
artinforge/files.py,sha256=kEn-96x-N9sP3o7DYTUT7McnpnCIbBK-Gg25twciQKA,660
|
|
4
|
+
artinforge/jsonstore.py,sha256=LxNTA5egPTDxMYn5QSrKNr4-jI-XIGvmL_G4xSOph94,831
|
|
5
|
+
artinforge/net.py,sha256=9U1lmljCJaNyfAvYG-_FzUeSn2nVfJ3bruJQSZmNGdE,1636
|
|
6
|
+
artinforge/server.py,sha256=DgZu80j2K5U9u3l3fy4V1A2cQrrjdWxoY1yOc3Hwpag,1040
|
|
7
|
+
artinforge/system.py,sha256=lW2URGbJX-S-9owF9Kzw-hCKiS8_Cfbn889vmNbb1BE,366
|
|
8
|
+
artinforge/text.py,sha256=9BLy4QONMy9d0b_X97XxCeHLtv4l8vnIh8RGCCDQdDo,764
|
|
9
|
+
artinforge/assets/artinforge-banner.png,sha256=9ThHZ9QVh6GixUZmzluU35FfcR8aKK8XMwBIuL-6CMU,1991563
|
|
10
|
+
artinforge-0.1.0.dist-info/licenses/LICENSE,sha256=qTWUb-x1Rt-H5lDFtGSiTzxcKgVaUro8MxTyhMuTph0,812
|
|
11
|
+
artinforge-0.1.0.dist-info/METADATA,sha256=QiJ95OqKCFdTXHbtgH4JbNzBQR9uYH98wwk-P-ZKsC0,3096
|
|
12
|
+
artinforge-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
13
|
+
artinforge-0.1.0.dist-info/entry_points.txt,sha256=-PFKVE-z34ej8hzKsHx5tQSiiovuJaVWdjl8v9NSrxE,51
|
|
14
|
+
artinforge-0.1.0.dist-info/top_level.txt,sha256=iprDOQxUc35LeDbVx3-a0nXXEfOvFDVAgRseBveUTZs,11
|
|
15
|
+
artinforge-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Artin Karimian
|
|
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 @@
|
|
|
1
|
+
artinforge
|