jasd 1.0.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.
- jasd-1.0.0/LICENSE +26 -0
- jasd-1.0.0/PKG-INFO +22 -0
- jasd-1.0.0/README.md +8 -0
- jasd-1.0.0/jasd/__init__.py +221 -0
- jasd-1.0.0/jasd/tools.py +62 -0
- jasd-1.0.0/jasd.egg-info/PKG-INFO +22 -0
- jasd-1.0.0/jasd.egg-info/SOURCES.txt +9 -0
- jasd-1.0.0/jasd.egg-info/dependency_links.txt +1 -0
- jasd-1.0.0/jasd.egg-info/top_level.txt +1 -0
- jasd-1.0.0/pyproject.toml +19 -0
- jasd-1.0.0/setup.cfg +4 -0
jasd-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
### 3. `LICENSE`
|
|
3
|
+
Open `jasd-package/LICENSE` and paste this:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Xlueisadev
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
jasd-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jasd
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Just Another Static Database - A human-readable, zero-ceremony data format for Python.
|
|
5
|
+
Author: Xlueisadev
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# JASD (Just Another Static Database)
|
|
16
|
+
|
|
17
|
+
JSON has no comments, demands quotes on every single key, and rejects trailing commas. JASD is a human-readable data format built for Python. It keeps the structure of JSON but removes the ceremony.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install jasd
|
jasd-1.0.0/README.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# JASD (Just Another Static Database)
|
|
2
|
+
|
|
3
|
+
JSON has no comments, demands quotes on every single key, and rejects trailing commas. JASD is a human-readable data format built for Python. It keeps the structure of JSON but removes the ceremony.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install jasd
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import csv
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import hashlib
|
|
6
|
+
import base64
|
|
7
|
+
from .tools import serve, watch
|
|
8
|
+
|
|
9
|
+
# DONT TOUCH THIS
|
|
10
|
+
__version__ = "1.0.0"
|
|
11
|
+
|
|
12
|
+
# ==========================================
|
|
13
|
+
# CORE PARSER
|
|
14
|
+
# ==========================================
|
|
15
|
+
|
|
16
|
+
def loads(text):
|
|
17
|
+
lines = text.strip().split('\n')
|
|
18
|
+
if not lines or lines[0].strip() != 'JASD-V1':
|
|
19
|
+
raise ValueError("Invalid JASD file: Missing 'JASD-V1' header.")
|
|
20
|
+
|
|
21
|
+
data = []
|
|
22
|
+
current_obj = None
|
|
23
|
+
|
|
24
|
+
for line in lines[1:]:
|
|
25
|
+
line = re.sub(r'--.*$', '', line).strip()
|
|
26
|
+
if not line or line in ('[', ']'): continue
|
|
27
|
+
|
|
28
|
+
if line.startswith('{'):
|
|
29
|
+
current_obj = {}
|
|
30
|
+
continue
|
|
31
|
+
if line.startswith('}'):
|
|
32
|
+
if current_obj is not None:
|
|
33
|
+
data.append(current_obj)
|
|
34
|
+
current_obj = None
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
if current_obj is not None and ':' in line:
|
|
38
|
+
key, _, val = line.partition(':')
|
|
39
|
+
key = key.strip()
|
|
40
|
+
val = val.strip().strip(',').strip('"').strip("'")
|
|
41
|
+
|
|
42
|
+
if val.startswith('[') and val.endswith(']'):
|
|
43
|
+
items = val[1:-1].split(',')
|
|
44
|
+
val = [i.strip().strip('"').strip("'") for i in items if i.strip()]
|
|
45
|
+
elif val.lower() in ('true', 'yes'): val = True
|
|
46
|
+
elif val.lower() in ('false', 'no'): val = False
|
|
47
|
+
elif val.lower() in ('null', 'none', '') or not val: val = None
|
|
48
|
+
else:
|
|
49
|
+
try: val = float(val) if '.' in val else int(val)
|
|
50
|
+
except ValueError: pass
|
|
51
|
+
current_obj[key] = val
|
|
52
|
+
return data
|
|
53
|
+
|
|
54
|
+
def dumps(data):
|
|
55
|
+
out = ["JASD-V1", "", "["]
|
|
56
|
+
for i, item in enumerate(data):
|
|
57
|
+
out.append(" {")
|
|
58
|
+
for key, val in item.items():
|
|
59
|
+
if isinstance(val, bool): val_str = "true" if val else "false"
|
|
60
|
+
elif val is None: val_str = ""
|
|
61
|
+
elif isinstance(val, list): val_str = "[" + ", ".join(str(v) for v in val) + "]"
|
|
62
|
+
else: val_str = str(val)
|
|
63
|
+
|
|
64
|
+
if val_str: out.append(f" {key}: {val_str}")
|
|
65
|
+
else: out.append(f" {key}:")
|
|
66
|
+
out.append(" }," if i < len(data) - 1 else " }")
|
|
67
|
+
out.append("]")
|
|
68
|
+
return "\n".join(out)
|
|
69
|
+
|
|
70
|
+
def load_file(filename):
|
|
71
|
+
with open(filename, 'r', encoding='utf-8') as f: return loads(f.read())
|
|
72
|
+
|
|
73
|
+
def dump_file(data, filename):
|
|
74
|
+
with open(filename, 'w', encoding='utf-8') as f: f.write(dumps(data))
|
|
75
|
+
|
|
76
|
+
# ==========================================
|
|
77
|
+
# CSV CONVERSION
|
|
78
|
+
# ==========================================
|
|
79
|
+
|
|
80
|
+
def from_csv(filename):
|
|
81
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
82
|
+
reader = csv.DictReader(f)
|
|
83
|
+
return [row for row in reader]
|
|
84
|
+
|
|
85
|
+
def to_csv(data, filename):
|
|
86
|
+
if not data: return
|
|
87
|
+
with open(filename, 'w', encoding='utf-8', newline='') as f:
|
|
88
|
+
writer = csv.DictWriter(f, fieldnames=data[0].keys())
|
|
89
|
+
writer.writeheader()
|
|
90
|
+
writer.writerows(data)
|
|
91
|
+
|
|
92
|
+
# ==========================================
|
|
93
|
+
# ADVANCED DATA PROCESSING
|
|
94
|
+
# ==========================================
|
|
95
|
+
|
|
96
|
+
def query(data, condition):
|
|
97
|
+
results = []
|
|
98
|
+
conditions = [c.strip() for c in condition.split('and')]
|
|
99
|
+
|
|
100
|
+
for item in data:
|
|
101
|
+
match = True
|
|
102
|
+
for cond in conditions:
|
|
103
|
+
match_obj = re.search(r'([\w]+)\s*(>=|<=|>|<|=|~)\s*(.+)', cond)
|
|
104
|
+
if not match_obj: continue
|
|
105
|
+
|
|
106
|
+
key, op, val = match_obj.groups()
|
|
107
|
+
val = val.strip().strip("'").strip('"')
|
|
108
|
+
|
|
109
|
+
item_val = item.get(key)
|
|
110
|
+
if item_val is None: match = False; break
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
if '.' in val: val = float(val)
|
|
114
|
+
else: val = int(val)
|
|
115
|
+
except ValueError:
|
|
116
|
+
if val.lower() == 'true': val = True
|
|
117
|
+
elif val.lower() == 'false': val = False
|
|
118
|
+
|
|
119
|
+
if op == '=' and item_val != val: match = False
|
|
120
|
+
if op == '>' and not (item_val > val): match = False
|
|
121
|
+
if op == '<' and not (item_val < val): match = False
|
|
122
|
+
if op == '>=' and not (item_val >= val): match = False
|
|
123
|
+
if op == '<=' and not (item_val <= val): match = False
|
|
124
|
+
if op == '~' and val.lower() not in str(item_val).lower(): match = False
|
|
125
|
+
if match: results.append(item)
|
|
126
|
+
return results
|
|
127
|
+
|
|
128
|
+
def sum_data(data, key):
|
|
129
|
+
return sum(item.get(key, 0) for item in data if isinstance(item.get(key), (int, float)))
|
|
130
|
+
|
|
131
|
+
def average(data, key):
|
|
132
|
+
valid = [item.get(key) for item in data if isinstance(item.get(key), (int, float))]
|
|
133
|
+
return sum(valid) / len(valid) if valid else 0
|
|
134
|
+
|
|
135
|
+
def find_duplicates(data, key):
|
|
136
|
+
seen = set()
|
|
137
|
+
duplicates = []
|
|
138
|
+
for item in data:
|
|
139
|
+
val = item.get(key)
|
|
140
|
+
if val in seen: duplicates.append(item)
|
|
141
|
+
else: seen.add(val)
|
|
142
|
+
return duplicates
|
|
143
|
+
|
|
144
|
+
# ==========================================
|
|
145
|
+
# ENCRYPTION & SECURITY
|
|
146
|
+
# ==========================================
|
|
147
|
+
|
|
148
|
+
def dump_secure(data, filename, password):
|
|
149
|
+
text = dumps(data)
|
|
150
|
+
key = hashlib.sha256(password.encode()).digest()
|
|
151
|
+
encoded = bytes([text.encode()[i] ^ key[i % len(key)] for i in range(len(text))])
|
|
152
|
+
with open(filename, 'wb') as f:
|
|
153
|
+
f.write(base64.b64encode(encoded))
|
|
154
|
+
|
|
155
|
+
def load_secure(filename, password):
|
|
156
|
+
key = hashlib.sha256(password.encode()).digest()
|
|
157
|
+
with open(filename, 'rb') as f:
|
|
158
|
+
encoded = base64.b64decode(f.read())
|
|
159
|
+
text = bytes([encoded[i] ^ key[i % len(key)] for i in range(len(encoded))]).decode()
|
|
160
|
+
return loads(text)
|
|
161
|
+
|
|
162
|
+
def validate(data, schema):
|
|
163
|
+
errors = []
|
|
164
|
+
for i, item in enumerate(data):
|
|
165
|
+
for key, expected_type in schema.items():
|
|
166
|
+
if key not in item:
|
|
167
|
+
errors.append(f"Row {i+1}: Missing key '{key}'")
|
|
168
|
+
elif not isinstance(item[key], expected_type):
|
|
169
|
+
errors.append(f"Row {i+1}: '{key}' should be {expected_type.__name__}")
|
|
170
|
+
return errors
|
|
171
|
+
|
|
172
|
+
# ==========================================
|
|
173
|
+
# SYSTEM TOOLS
|
|
174
|
+
# ==========================================
|
|
175
|
+
|
|
176
|
+
def watch(filename, callback):
|
|
177
|
+
print(f"Watching {filename} for changes...")
|
|
178
|
+
last_mtime = os.path.getmtime(filename)
|
|
179
|
+
try:
|
|
180
|
+
while True:
|
|
181
|
+
time.sleep(1)
|
|
182
|
+
current_mtime = os.path.getmtime(filename)
|
|
183
|
+
if current_mtime != last_mtime:
|
|
184
|
+
last_mtime = current_mtime
|
|
185
|
+
data = load_file(filename)
|
|
186
|
+
callback(data)
|
|
187
|
+
except KeyboardInterrupt:
|
|
188
|
+
print("Stopped watching.")
|
|
189
|
+
|
|
190
|
+
def serve(filename, port=8080):
|
|
191
|
+
import http.server
|
|
192
|
+
import socketserver
|
|
193
|
+
import threading
|
|
194
|
+
import webbrowser
|
|
195
|
+
|
|
196
|
+
data = load_file(filename)
|
|
197
|
+
html = "<html><body style='font-family:sans-serif; background:#0f172a; color:white; padding:2rem;'>"
|
|
198
|
+
html += f"<h1 style='color:#22d3ee'>{filename}</h1><table border='1' style='border-collapse:collapse; width:100%; text-align:left;'>"
|
|
199
|
+
|
|
200
|
+
if data:
|
|
201
|
+
html += "<tr>" + "".join(f"<th style='padding:10px; background:#1e293b;'>{k}</th>" for k in data[0].keys()) + "</tr>"
|
|
202
|
+
for row in data:
|
|
203
|
+
html += "<tr>" + "".join(f"<td style='padding:10px; border-bottom:1px solid #334155;'>{v}</td>" for v in row.values()) + "</tr>"
|
|
204
|
+
html += "</table></body></html>"
|
|
205
|
+
|
|
206
|
+
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
207
|
+
def do_GET(self):
|
|
208
|
+
self.send_response(200)
|
|
209
|
+
self.send_header("Content-type", "text/html")
|
|
210
|
+
self.end_headers()
|
|
211
|
+
self.wfile.write(html.encode())
|
|
212
|
+
|
|
213
|
+
def start_server():
|
|
214
|
+
with socketserver.TCPServer(("", port), Handler) as httpd:
|
|
215
|
+
webbrowser.open(f"http://localhost:{port}")
|
|
216
|
+
print(f"Serving JASD data at http://localhost:{port}")
|
|
217
|
+
httpd.serve_forever()
|
|
218
|
+
|
|
219
|
+
thread = threading.Thread(target=start_server, daemon=True)
|
|
220
|
+
thread.start()
|
|
221
|
+
input("Press Enter to stop the server...\n")
|
jasd-1.0.0/jasd/tools.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import http.server
|
|
4
|
+
import socketserver
|
|
5
|
+
import threading
|
|
6
|
+
import webbrowser
|
|
7
|
+
|
|
8
|
+
def serve(filename, port=8080):
|
|
9
|
+
"""Spins up a local web server to view your JASD data as a table."""
|
|
10
|
+
from . import load_file
|
|
11
|
+
|
|
12
|
+
data = load_file(filename)
|
|
13
|
+
html = "<html><body style='font-family:sans-serif; background:#0f172a; color:white; padding:2rem;'>"
|
|
14
|
+
html += f"<h1 style='color:#22d3ee'>{filename}</h1><table border='1' style='border-collapse:collapse; width:100%; text-align:left;'>"
|
|
15
|
+
|
|
16
|
+
if data:
|
|
17
|
+
html += "<tr>" + "".join(f"<th style='padding:10px; background:#1e293b;'>{k}</th>" for k in data[0].keys()) + "</tr>"
|
|
18
|
+
for row in data:
|
|
19
|
+
html += "<tr>" + "".join(f"<td style='padding:10px; border-bottom:1px solid #334155;'>{v}</td>" for v in row.values()) + "</tr>"
|
|
20
|
+
html += "</table></body></html>"
|
|
21
|
+
|
|
22
|
+
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
23
|
+
def do_GET(self):
|
|
24
|
+
self.send_response(200)
|
|
25
|
+
self.send_header("Content-type", "text/html")
|
|
26
|
+
self.end_headers()
|
|
27
|
+
self.wfile.write(html.encode())
|
|
28
|
+
|
|
29
|
+
def start_server():
|
|
30
|
+
with socketserver.TCPServer(("", port), Handler) as httpd:
|
|
31
|
+
webbrowser.open(f"http://localhost:{port}")
|
|
32
|
+
print(f"Serving JASD data at http://localhost:{port}")
|
|
33
|
+
httpd.serve_forever()
|
|
34
|
+
|
|
35
|
+
thread = threading.Thread(target=start_server, daemon=True)
|
|
36
|
+
thread.start()
|
|
37
|
+
input("Press Enter to stop the server...\n")
|
|
38
|
+
|
|
39
|
+
def watch(filename, callback):
|
|
40
|
+
"""Watches a file and triggers a callback when it is edited."""
|
|
41
|
+
from . import load_file
|
|
42
|
+
|
|
43
|
+
print(f"Watching {filename} for changes...")
|
|
44
|
+
try:
|
|
45
|
+
last_mtime = os.path.getmtime(filename)
|
|
46
|
+
except FileNotFoundError:
|
|
47
|
+
print(f"Error: {filename} not found.")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
while True:
|
|
52
|
+
time.sleep(1)
|
|
53
|
+
try:
|
|
54
|
+
current_mtime = os.path.getmtime(filename)
|
|
55
|
+
if current_mtime != last_mtime:
|
|
56
|
+
last_mtime = current_mtime
|
|
57
|
+
data = load_file(filename)
|
|
58
|
+
callback(data)
|
|
59
|
+
except FileNotFoundError:
|
|
60
|
+
pass
|
|
61
|
+
except KeyboardInterrupt:
|
|
62
|
+
print("\nStopped watching.")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jasd
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Just Another Static Database - A human-readable, zero-ceremony data format for Python.
|
|
5
|
+
Author: Xlueisadev
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# JASD (Just Another Static Database)
|
|
16
|
+
|
|
17
|
+
JSON has no comments, demands quotes on every single key, and rejects trailing commas. JASD is a human-readable data format built for Python. It keeps the structure of JSON but removes the ceremony.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install jasd
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jasd
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jasd"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Xlueisadev" },
|
|
10
|
+
]
|
|
11
|
+
description = "Just Another Static Database - A human-readable, zero-ceremony data format for Python."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
license = {text = "MIT"}
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
jasd-1.0.0/setup.cfg
ADDED