first commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
aval.txt
|
||||
235
main.py
Normal file
235
main.py
Normal file
@@ -0,0 +1,235 @@
|
||||
import socket
|
||||
import threading
|
||||
import os
|
||||
import mimetypes
|
||||
from urllib.parse import unquote
|
||||
from html import escape
|
||||
import io
|
||||
import sys
|
||||
from contextlib import redirect_stdout
|
||||
import re
|
||||
import traceback
|
||||
import linecache
|
||||
|
||||
|
||||
class pyp_Functions:
|
||||
@staticmethod
|
||||
def e(text: str | int | float, ignoreType=False):
|
||||
if isinstance(text, (str, int, float)):
|
||||
print(text)
|
||||
elif ignoreType:
|
||||
print(escape(str(text)))
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported type for echo function,(req str or number, got {str(type(text))}) use ignoreType=True to force str conversion")
|
||||
|
||||
@staticmethod
|
||||
def f_r(path: str):
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
class pyp_IO:
|
||||
def __init__(self, path, query) -> None:
|
||||
self.path = path
|
||||
self.query = query
|
||||
|
||||
self.server_pipeline: dict[str, bool |
|
||||
str | None | int] = {'Redirect': False}
|
||||
|
||||
self.GET = {}
|
||||
if query:
|
||||
pairs = query.split('&')
|
||||
for pair in pairs:
|
||||
if '=' in pair:
|
||||
key, value = pair.split('=', 1)
|
||||
self.GET[unquote(key)] = unquote(value)
|
||||
else:
|
||||
self.GET[unquote(pair)] = ''
|
||||
|
||||
def redirect(self, url: str):
|
||||
self.server_pipeline['Redirect'] = url
|
||||
# TODO: Implement redirect logic in server
|
||||
|
||||
|
||||
class SandboxInstance:
|
||||
def __init__(self, initial_globals=None):
|
||||
# Базовые globals, плюс твои доп, если кинут
|
||||
self.globals_dict = {}
|
||||
if initial_globals:
|
||||
self.globals_dict.update(initial_globals)
|
||||
|
||||
def run(self, script_str, path, additional_globals=None):
|
||||
original_dir = os.getcwd()
|
||||
original_sys_path = sys.path.copy()
|
||||
if additional_globals:
|
||||
self.globals_dict.update(additional_globals)
|
||||
output_buffer = io.StringIO()
|
||||
fake_filename = "<pyp_script>"
|
||||
original_dont_write = sys.dont_write_bytecode
|
||||
sys.dont_write_bytecode = True
|
||||
linecache.cache[fake_filename] = (
|
||||
len(script_str), None, script_str.splitlines(True), fake_filename)
|
||||
with redirect_stdout(output_buffer):
|
||||
try:
|
||||
os.chdir(os.path.dirname(path)) # Сброс текущей директории
|
||||
# Добавление директории скрипта в sys.path
|
||||
sys.path.insert(0, os.path.dirname(path))
|
||||
exec(compile(script_str, fake_filename, 'exec'), self.globals_dict)
|
||||
except Exception as e:
|
||||
tb_lines = traceback.format_exception(
|
||||
type(e), e, e.__traceback__)
|
||||
tb = [
|
||||
line for line in tb_lines if "exec" not in line or fake_filename in line]
|
||||
return f"<div style=\"font-size:medium;font-family:sans-serif;\">error while running script: <span style=\"font-family:monospace;" +\
|
||||
f"background:black;color:white;padding:4px;border-radius:5px;margin:0 10px;" +\
|
||||
f"\">{escape(str(e))}</span> 💥<br><div style=\"" +\
|
||||
f"font-family:monospace;background:black;color:white;border-radius:15px;" +\
|
||||
f"padding:10px;margin:10px 0;\">{escape(''.join(tb)).replace('\n', '<br>').replace(' ', ' ').replace('\t', ' '*4)}</div><br><br></div>"
|
||||
finally:
|
||||
os.chdir(original_dir)
|
||||
sys.path = original_sys_path
|
||||
sys.dont_write_bytecode = original_dont_write
|
||||
linecache.cache.pop(fake_filename, None)
|
||||
return output_buffer.getvalue().strip()
|
||||
|
||||
|
||||
def normalize_indentation(s):
|
||||
if not s:
|
||||
return ''
|
||||
lines = s.splitlines()
|
||||
n = 0
|
||||
first_non_empty = next((line for line in lines if line.lstrip(' ')), None)
|
||||
if first_non_empty:
|
||||
n = len(first_non_empty) - len(first_non_empty.lstrip(' '))
|
||||
result = []
|
||||
for i, line in enumerate(lines):
|
||||
if line.lstrip(' ') and len(line) - len(line.lstrip(' ')) < n:
|
||||
# raise in sandbox
|
||||
return f"raise ValueError(\"Indentation error at line {i}\")"
|
||||
result.append(line[n:] if line.lstrip(' ') else '')
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
class PyWPRServer:
|
||||
def __init__(self, host='0.0.0.0', port=8000, doc_root='www'):
|
||||
self.host, self.port = host, port
|
||||
self.doc_root = os.path.abspath(doc_root)
|
||||
os.makedirs(self.doc_root, exist_ok=True)
|
||||
self.sock = None
|
||||
self._stop = False
|
||||
|
||||
def _response(self, status_line, headers=None, body=b''):
|
||||
headers = headers or {}
|
||||
if isinstance(body, str):
|
||||
body = body.encode('utf-8')
|
||||
headers.setdefault('Content-Type', 'text/html; charset=utf-8')
|
||||
headers.setdefault('Content-Length', str(len(body)))
|
||||
hdrs = '\r\n'.join(f'{k}: {v}' for k, v in headers.items())
|
||||
return (f'{status_line}\r\n{hdrs}\r\n\r\n').encode('utf-8') + body
|
||||
|
||||
def _not_found(self):
|
||||
return self._response('HTTP/1.1 404 Not Found', body=b'<h1>404 Not Found</h1>')
|
||||
|
||||
def _execute_pyp(self, body: str, path: str, query: str):
|
||||
script_path = self.doc_root + path
|
||||
sandbox = SandboxInstance(
|
||||
{'e': pyp_Functions.e, 'f_r': pyp_Functions.f_r, "__file__": script_path, "__name__": "__pyp__", "pyp": pyp_IO(path, query)})
|
||||
pattern = re.compile(r'<py!>(.*?)</py!>', re.DOTALL)
|
||||
snippets = pattern.findall(body)
|
||||
for snippet in snippets:
|
||||
snippet_code = normalize_indentation(snippet)
|
||||
result = sandbox.run(snippet_code, path=script_path)
|
||||
body = body.replace(f'<py!>{snippet}</py!>', result)
|
||||
eq_pattern = re.compile(r'<p\?>(.*?)</p\?>', re.DOTALL)
|
||||
eq_snippets = eq_pattern.findall(body)
|
||||
for eq_snippet in eq_snippets:
|
||||
result = sandbox.run(f'e({eq_snippet})', path=script_path)
|
||||
body = body.replace(f'<p?>{eq_snippet}</p?>', result)
|
||||
return body
|
||||
|
||||
def _serve_path(self, req_path):
|
||||
# print("Request:", req_path)
|
||||
path = unquote(req_path.split('?', 1)[0])
|
||||
if path.endswith('/'):
|
||||
path += 'index.pyp'
|
||||
elif os.path.isdir(self.doc_root + path):
|
||||
path += '/index.pyp'
|
||||
|
||||
full = os.path.normpath(os.path.join(self.doc_root, path.lstrip('/')))
|
||||
if not full.startswith(self.doc_root) or not os.path.isfile(full):
|
||||
return self._not_found()
|
||||
ctype = (mimetypes.guess_type(full)[
|
||||
0] or 'application/octet-stream') if not path.endswith(".pyp") else 'text/html'
|
||||
# if text/* or html, add charset=utf-8
|
||||
if ctype.startswith('text/') or ctype == 'application/json' or ctype == 'application/javascript':
|
||||
ctype = f'{ctype}; charset=utf-8'
|
||||
try:
|
||||
# Open text files as UTF-8 when possible, binary otherwise
|
||||
if ctype.startswith('text/') or ctype.endswith('charset=utf-8'):
|
||||
with open(full, 'r', encoding='utf-8', errors='replace') as f:
|
||||
body = f.read()
|
||||
if path.endswith('.pyp'):
|
||||
query = req_path.split(
|
||||
'?', 1)[1] if '?' in req_path else ''
|
||||
body = self._execute_pyp(body, path, query)
|
||||
|
||||
return self._response('HTTP/1.1 200 OK', headers={'Content-Type': ctype, 'Server': 'PyWRP-server'}, body=body.encode())
|
||||
else:
|
||||
with open(full, 'rb') as f:
|
||||
body = f.read()
|
||||
return self._response('HTTP/1.1 200 OK', headers={'Content-Type': ctype}, body=body)
|
||||
except Exception:
|
||||
return self._response('HTTP/1.1 500 Internal Server Error', body=b'<h1>500</h1>')
|
||||
|
||||
def _handle(self, client, addr):
|
||||
try:
|
||||
data = client.recv(4096).decode('utf-8', errors='replace')
|
||||
if not data:
|
||||
return
|
||||
first = data.splitlines()[0]
|
||||
parts = first.split()
|
||||
if len(parts) < 2:
|
||||
client.sendall(self._response(
|
||||
'HTTP/1.1 400 Bad Request', body=b'<h1>400</h1>'))
|
||||
return
|
||||
method, path = parts[0], parts[1]
|
||||
if method != 'GET':
|
||||
client.sendall(self._response(
|
||||
'HTTP/1.1 405 Method Not Allowed', headers={'Allow': 'GET'}, body=b'<h1>405</h1>'))
|
||||
return
|
||||
resp = self._serve_path(path)
|
||||
client.sendall(resp)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
def start(self):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.sock.bind((self.host, self.port))
|
||||
self.sock.listen(5)
|
||||
try:
|
||||
while not self._stop:
|
||||
client, addr = self.sock.accept()
|
||||
threading.Thread(target=self._handle, args=(
|
||||
client, addr), daemon=True).start()
|
||||
finally:
|
||||
self.sock.close()
|
||||
|
||||
def stop(self):
|
||||
self._stop = True
|
||||
try:
|
||||
with socket.create_connection((self.host, self.port), timeout=1):
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
s = PyWPRServer(host='127.0.0.1', port=8000, doc_root='www')
|
||||
print('Serving', s.doc_root, 'on', f'{s.host}:{s.port}')
|
||||
try:
|
||||
s.start()
|
||||
except KeyboardInterrupt:
|
||||
s.stop()
|
||||
print('Stopped.')
|
||||
42
readme.md
Normal file
42
readme.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# PyWPR - Python Web Page Render
|
||||
`.pyp` - **Py**thon **P**age *btw*
|
||||
## Get Started
|
||||
1. remove **www/**\*
|
||||
2. run main.py
|
||||
|
||||
## Syntax
|
||||
### integration
|
||||
```html
|
||||
<py!>
|
||||
# Here is Python Code
|
||||
</py!>
|
||||
```
|
||||
```html
|
||||
<p?>"Text to print"</p?>
|
||||
<!--- also supports functions-->
|
||||
<p?>foo()</p?>
|
||||
```
|
||||
|
||||
### queries
|
||||
```python
|
||||
# *.pyp?query
|
||||
pyp.GET['query']
|
||||
#or
|
||||
pyp.GET.get('query','default_value')
|
||||
```
|
||||
### echos
|
||||
```python
|
||||
e(str|int|float) # you can add ignoreType=True
|
||||
# or
|
||||
print(object)
|
||||
```
|
||||
|
||||
___
|
||||
___
|
||||
|
||||
## _TODO_
|
||||
+ ~~todo~~
|
||||
+ indetation fix for multile `<p?>`
|
||||
+ redirections
|
||||
+ cookies
|
||||
+ files upload
|
||||
3
www/_server_config.py
Normal file
3
www/_server_config.py
Normal file
@@ -0,0 +1,3 @@
|
||||
SERVER_DOWNLOADPY = False
|
||||
SERVER_PORT = 8080
|
||||
SERVER_HOST = '0.0.0.0'
|
||||
60
www/docs/basics.pyp
Normal file
60
www/docs/basics.pyp
Normal file
@@ -0,0 +1,60 @@
|
||||
<py!>
|
||||
import components.code as code
|
||||
import components.docs as docs
|
||||
</py!>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>pyp | queries</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="font-sans min-h-[90vh] flex flex-col items-center">
|
||||
<div class="w-full max-w-[80vw] flex gap-5 h-10 bg-slate-900/50 py-5 fixed my-5 p-6 rounded-full backdrop-blur-md lg:max-w-3xl items-center justift-around text-white">
|
||||
<a class="font-bold" href="./">PyWPR docs</a>
|
||||
</div>
|
||||
<div class="inline-flex flex-col gap-5 items-center max-w-[85vw] mt-20">
|
||||
<h1 class="text-4xl">
|
||||
basics
|
||||
</h1>
|
||||
<div class="block h-0.5 bg-zinc-400 w-full">
|
||||
</div>
|
||||
|
||||
<h3 class="text-2xl"><py>! tag</h3>
|
||||
<p class="text-center">in pywrp we have <span class="bg-black p-2 rounded-xl text-white font-mono"><py!></span> and <span class="bg-black p-2 rounded-xl text-white font-mono"></py!></span> HTML tags for embedding python <b>server</b> code,
|
||||
<br><i>(almost like in php)</i><br>
|
||||
for example:</p>
|
||||
<p?>
|
||||
code.code_comp(f"""<p>this is html element</p>
|
||||
{'<'}py!> # This is Python code</py!>""",'html')
|
||||
|
||||
</p?>
|
||||
<div class="block h-0.5 bg-zinc-400 w-full"></div>
|
||||
<h3 class="text-2xl">echo function</h3>
|
||||
<p class="text-center">
|
||||
<span class="bg-black p-2 rounded-xl text-white font-mono">e()</span> is a function that prints text to the page
|
||||
<br><i>(just like <span class="font-mono">echo</span> in php)</i>
|
||||
</p>
|
||||
<p?>code.code_comp(f"""{'<'}py!>
|
||||
e("hello word")
|
||||
# also you can use just regular python print
|
||||
print(object)
|
||||
# e() supports only str|int|float, but you can ignoreType=True
|
||||
e({'{'}'test':123{'}'},ignoreType=True)
|
||||
</py!>""")</p?>
|
||||
<div class="block h-0.5 bg-zinc-400 w-full"></div>
|
||||
<h3 class="text-2xl"><p?> tag</h3>
|
||||
<p class="text-center">also there is <span class="bg-black p-2 rounded-xl text-white font-mono"><p?></p?></span> tags, they are shortcuts to <b>echo</b>
|
||||
<br><i>(like in php too)</i><br>
|
||||
for example:</p>
|
||||
<p?>
|
||||
code.code_comp(f"""{'<'}p?>"hello world"{'<'}/p?>
|
||||
<!--it same as-->
|
||||
{'<'}py!>e("hello world"){'</'}py!>
|
||||
<!--- also supports functions-->
|
||||
{'<'}p?>foo(){'</'}p?>""",'html')
|
||||
</p?>
|
||||
</div></div>
|
||||
</body>
|
||||
</html>
|
||||
166
www/docs/components/code.py
Normal file
166
www/docs/components/code.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import io
|
||||
import html
|
||||
import token
|
||||
import tokenize
|
||||
import keyword
|
||||
import builtins
|
||||
import re
|
||||
import sys
|
||||
|
||||
PALETTE = {
|
||||
'kw': 'text-pink-400', 'builtin': 'text-violet-300', 'name': 'text-sky-300', 'func': 'text-sky-300',
|
||||
'attr': 'text-green-300', 'str': 'text-amber-300', 'num': 'text-cyan-300', 'op': 'text-red-400',
|
||||
'punct': 'text-red-400', 'cmt': 'text-gray-500 italic', 'err': 'bg-red-900 text-red-300',
|
||||
'ht_tag': 'text-fuchsia-300', 'ht_attr': 'text-green-300', 'ht_eq': 'text-red-400',
|
||||
'ht_str': 'text-amber-300', 'ht_comment': 'text-gray-500 italic', 'ht_text': 'text-slate-300'
|
||||
}
|
||||
|
||||
_builtin_names = set(dir(builtins))
|
||||
|
||||
|
||||
def esc(s):
|
||||
return html.escape(s).replace(' ', ' ').replace('\t', ' '*4)
|
||||
|
||||
|
||||
def py_highlight(src):
|
||||
out = []
|
||||
prev = None
|
||||
gen = tokenize.generate_tokens(io.StringIO(src).readline)
|
||||
for ttype, val, *_ in gen:
|
||||
cls = ''
|
||||
if ttype == token.NAME:
|
||||
if keyword.iskeyword(val):
|
||||
cls = 'kw'
|
||||
elif prev and prev[0] == token.OP and prev[1] == '.':
|
||||
cls = 'attr'
|
||||
elif val in _builtin_names:
|
||||
cls = 'builtin'
|
||||
else:
|
||||
cls = 'name'
|
||||
elif ttype == token.OP:
|
||||
cls = 'op' if re.match(r'[+\-*/%=<>!^|&~]', val) else 'punct'
|
||||
elif ttype == token.STRING:
|
||||
cls = 'str'
|
||||
elif ttype == token.NUMBER:
|
||||
cls = 'num'
|
||||
elif ttype == token.COMMENT:
|
||||
cls = 'cmt'
|
||||
elif ttype == token.ERRORTOKEN:
|
||||
cls = 'err'
|
||||
prev = (ttype, val)
|
||||
piece = esc(val).replace('\n', '\n') # keep newlines
|
||||
out.append(
|
||||
f'<span class="{PALETTE.get(cls, "")}">{piece}</span>' if cls else piece)
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
# HTML regexes
|
||||
FULL_TAG = r'<\s*/?\s*[A-Za-z0-9:-]+[!?]?(?:\s[^<>]*?)?>'
|
||||
HT_RE = re.compile(r'(?s)(<!--.*?-->)|(' + FULL_TAG + r')|([^<]+)')
|
||||
TAG_NAME_RE = re.compile(r'(?s)(<\s*/?\s*)([A-Za-z0-9:-]+[!?]?)(.*?)(>)')
|
||||
ATTR_RE = re.compile(r'([A-Za-z0-9:-]+)(\s*=\s*)?', re.S)
|
||||
QSTR_RE = re.compile(r'(".*?"|\'.*?\')', re.S)
|
||||
PY_OPEN = re.compile(r'(?i)^<\s*([A-Za-z0-9:-]+[!?]?)') # capture tag name
|
||||
|
||||
PY_NAMES = {'py!', 'p?'}
|
||||
|
||||
|
||||
def process_fulltag(fulltag):
|
||||
m = TAG_NAME_RE.match(fulltag)
|
||||
if not m:
|
||||
return f'<span class="{PALETTE["ht_tag"]}">{esc(fulltag)}</span>'
|
||||
pre, name, rest, gt = m.groups()
|
||||
out = [esc(pre), f'<span class="{PALETTE["ht_tag"]}">{esc(name)}</span>']
|
||||
if rest:
|
||||
i = 0
|
||||
while i < len(rest):
|
||||
ma = ATTR_RE.match(rest, i)
|
||||
if ma:
|
||||
an, eq = ma.groups()
|
||||
out.append(
|
||||
f'<span class="{PALETTE["ht_attr"]}">{esc(an)}</span>')
|
||||
if eq:
|
||||
out.append(esc(eq))
|
||||
i = ma.end()
|
||||
continue
|
||||
mq = QSTR_RE.match(rest, i)
|
||||
if mq:
|
||||
q = mq.group(1)
|
||||
out.append(
|
||||
f'<span class="{PALETTE["ht_str"]}">{esc(q)}</span>')
|
||||
i = mq.end()
|
||||
continue
|
||||
out.append(esc(rest[i]))
|
||||
i += 1
|
||||
out.append(esc(gt))
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def _html_tokens(src):
|
||||
out = []
|
||||
i = 0
|
||||
L = len(src)
|
||||
# iterate through matches but manage consumption manually to support multi-line py containers
|
||||
for m in HT_RE.finditer(src):
|
||||
if m.start() < i:
|
||||
continue
|
||||
com, fulltag, text = m.groups()
|
||||
if com:
|
||||
out.append(
|
||||
f'<span class="{PALETTE["ht_comment"]}">{esc(com)}</span>')
|
||||
i = m.end()
|
||||
continue
|
||||
if fulltag:
|
||||
# detect tag name
|
||||
mt = TAG_NAME_RE.match(fulltag)
|
||||
name = mt.group(2) if mt else ''
|
||||
lname = name.lower()
|
||||
is_open = not re.match(r'<\s*/', fulltag)
|
||||
# if opening py-container, find the matching closing tag (first occurrence)
|
||||
if lname in PY_NAMES and is_open:
|
||||
out.append(process_fulltag(fulltag)) # opening
|
||||
# closing tag pattern, case-insensitive
|
||||
close_re = re.compile(rf'(?i)</\s*{re.escape(name)}\s*>')
|
||||
mclose = close_re.search(src, m.end())
|
||||
if mclose:
|
||||
inner = src[m.end():mclose.start()]
|
||||
out.append(py_highlight(inner))
|
||||
out.append(process_fulltag(mclose.group(0))) # closing
|
||||
i = mclose.end()
|
||||
# continue scanning after closing
|
||||
continue
|
||||
else:
|
||||
# no closing found: just output opening and continue
|
||||
i = m.end()
|
||||
continue
|
||||
else:
|
||||
out.append(process_fulltag(fulltag))
|
||||
i = m.end()
|
||||
continue
|
||||
if text:
|
||||
out.append(
|
||||
f'<span class="{PALETTE["ht_text"]}">{esc(text)}</span>')
|
||||
i = m.end()
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def highlight(src, lang='py'):
|
||||
return _html_tokens(src) if lang and lang.lower().startswith('h') else py_highlight(src)
|
||||
|
||||
|
||||
def code_comp(code, lang='py'):
|
||||
code = code.split('\n')
|
||||
lines = '\n'.join(
|
||||
[f'<span class="before:content-[\'{str(line).zfill(len(str(len(code))))}\'] ">{highlight(code_line, lang)}</span>' for line, code_line in enumerate(code, 1)])
|
||||
return f'''
|
||||
<div class="border border-slate-500 border-4 rounded-xl p-5 bg-gradient-to-br from-zinc-900 to-slate-800 w-full text-center">
|
||||
<div class="flex gap-1.5 *:w-[12px] *:h-[12px] *:bg-white *:rounded-full *:transition pb-3">
|
||||
<span class="hover:bg-red-600"></span>
|
||||
<span class="hover:bg-yellow-300"></span>
|
||||
<span class="hover:bg-green-500"></span>
|
||||
</div>
|
||||
<div class="*:text-white font-mono flex flex-col gap-0 my-2 *:before:mr-3 *:text-sm *:text-left *:before:px-4 *:before:border-r *:before:border-white/20 *:flex">
|
||||
{lines}
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
2
www/docs/components/docs.py
Normal file
2
www/docs/components/docs.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def header_comp(url):
|
||||
pass
|
||||
33
www/docs/index.pyp
Normal file
33
www/docs/index.pyp
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>pywpr docs</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="font-sans min-h-[90vh] flex flex-col items-center">
|
||||
<div class="w-full max-w-[80vw] flex gap-5 h-10 bg-slate-900/50 py-5 fixed my-5 p-6 rounded-full backdrop-blur-md lg:max-w-3xl items-center justift-around text-white">
|
||||
<a class="font-bold" href="./">PyWPR docs</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="inline-flex flex-col gap-5 items-center max-w-[85vw] mt-20">
|
||||
<h1 class="text-4xl">
|
||||
PyWPR docs
|
||||
</h1>
|
||||
|
||||
<div class="flex flex-col gap-3 w-full p-5 border rounded-xl bg-gradient-to-br from-green-50 to-purple-100">
|
||||
<div class="flex gap-1.5 *:w-[12px] *:h-[12px] *:bg-slate-800 mb-3 *:rounded-full *:transition ">
|
||||
<span class="hover:bg-red-600"></span>
|
||||
<span class="hover:bg-yellow-300"></span>
|
||||
<span class="hover:bg-green-500"></span>
|
||||
</div>
|
||||
<py!>
|
||||
from pages import pages
|
||||
for i, (name, url) in enumerate(pages):
|
||||
e(f'<a href="{url}" class="text-blue-600 font-mono text-lg">{i+1}. {name}</a>')
|
||||
</py!>
|
||||
</div></div></div>
|
||||
</body>
|
||||
</html>
|
||||
4
www/docs/pages.py
Normal file
4
www/docs/pages.py
Normal file
@@ -0,0 +1,4 @@
|
||||
pages = [
|
||||
['basics', './basics.pyp'],
|
||||
['pyp | queries', './queries.pyp'],
|
||||
]
|
||||
77
www/docs/queries.pyp
Normal file
77
www/docs/queries.pyp
Normal file
@@ -0,0 +1,77 @@
|
||||
<py!>
|
||||
import components.code as code
|
||||
import components.docs as docs
|
||||
</py!>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>pyp | queries</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="font-sans min-h-[90vh] flex flex-col items-center">
|
||||
<div class="w-full max-w-[80vw] flex gap-5 h-10 bg-slate-900/50 py-5 fixed my-5 p-6 rounded-full backdrop-blur-md lg:max-w-3xl items-center justift-around text-white">
|
||||
<a class="font-bold" href="./">PyWPR docs</a>
|
||||
</div>
|
||||
<div class="inline-flex flex-col gap-5 items-center max-w-[85vw] lg:max-w-3xl mt-20">
|
||||
<h1 class="text-4xl">
|
||||
pyp | queries
|
||||
</h1>
|
||||
<p class="text-center">in pywrp we use <span class="bg-black p-2 rounded-xl text-white font-mono">pyp.GET</span> object for ?get=queries<br>
|
||||
you can access query parameters like dictionary keys<br>
|
||||
for example:</p>
|
||||
<p?>
|
||||
code.code_comp("""pyp.GET['name']
|
||||
# or
|
||||
pyp.GET.get('name', 'default_value')""")
|
||||
</p?>
|
||||
<p>try it out:</p>
|
||||
<div class="flex flex-col gap-3 w-full p-5 border rounded-xl bg-gradient-to-br from-blue-50 to-red-100">
|
||||
<div class="flex gap-1.5 *:w-[12px] *:h-[12px] *:bg-slate-800 mb-3 *:rounded-full *:transition ">
|
||||
<span class="hover:bg-red-600"></span>
|
||||
<span class="hover:bg-yellow-300"></span>
|
||||
<span class="hover:bg-green-500"></span>
|
||||
</div>
|
||||
<span class="font-mono text-left w-full"><form method="get"></span>
|
||||
<form method="get" class="flex flex-col gap-3 items-center justify-center w-full ">
|
||||
<div class="flex items-center justify-center w-full *:max-w-40">
|
||||
<input type="text" name="name" placeholder="name" class="p-2 border rounded-xl bg-black/40 text-white placeholder-white/60 text-center" required>
|
||||
<span class="flex items-center"><input type="number " name="money" placeholder="your money" class="p-2 border rounded-xl w-full text-center bg-black/40 text-white placeholder-white/60">$</span></div>
|
||||
<button type="submit" class="bg-gradient-to-br from-blue-500 to-blue-300 w-full max-w-40 text-white p-2 rounded-xl">submit</button>
|
||||
</form>
|
||||
<span class="font-mono text-right w-full"></form></span>
|
||||
<py!>
|
||||
if 'name' in pyp.GET and pyp.GET['name'].strip() != "":
|
||||
name = pyp.GET['name']
|
||||
money = pyp.GET.get('money', "")
|
||||
if money != "" and not money.isdigit():
|
||||
money = ""
|
||||
moneyText = f"you have a {'big' if int(money) >= 100 else "little"} money: <span class=\"font-mono\">{money}$</span>!" if money != "" else "you didn't tell me about your money"
|
||||
e(f"<span class=\"w-full bg-zinc-300 h-0.5\"></span><span class=\"font-mono text-left w-full\"><!py></span><div class=\"w-full text-center\">hello, <span class=\"font-mono\">{name}</span>! {moneyText}</div><span class=\"font-mono text-right w-full\"></py!></span>")
|
||||
</py!>
|
||||
</div>
|
||||
|
||||
<p>source</p>
|
||||
<p?>
|
||||
code.code_comp(f"""<form action="get">
|
||||
<input type="text" name="name" placeholder="name">
|
||||
<input type="number" name="money" placeholder="your money">
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
{'<'}py!>
|
||||
if 'name' in pyp.GET and pyp.GET['name'].strip() != "":
|
||||
name = pyp.GET['name']
|
||||
money = pyp.GET.get('money', '')
|
||||
if money != "" and not money.isdigit():
|
||||
money = ""
|
||||
moneyText = f"you have a {'{'}'big' if int(money) >= 100
|
||||
else "little"{'}'} money: {'{'}money{'}'}$!" \\
|
||||
if money != "" else "you didn't tell me about your money"
|
||||
e(f"<div>hello, {'{'}name{'}'}! {'{'}moneyText{'}'}</div>")
|
||||
</py!>""",'html')
|
||||
</p?>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
2
www/import_test.py
Normal file
2
www/import_test.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def hello():
|
||||
return "Hello, world!<span style=\"font-size:small\">this was imported from another python script</span>"
|
||||
16
www/import_test.pyp
Normal file
16
www/import_test.pyp
Normal file
@@ -0,0 +1,16 @@
|
||||
<py!>
|
||||
from import_test import hello
|
||||
</py!>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>pyd test </title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="font-sans flex items-center justify-center h-screen flex-col gap-5">
|
||||
<div class="flex gap-5 items-center text-6xl">
|
||||
<?p>hello()</p?></div>
|
||||
</body>
|
||||
</html>
|
||||
31
www/index.pyp
Normal file
31
www/index.pyp
Normal file
@@ -0,0 +1,31 @@
|
||||
<py!>
|
||||
import os
|
||||
a = 0
|
||||
FILE = 'aval.txt'
|
||||
if os.path.exists(FILE):
|
||||
try:
|
||||
with open(FILE, "r") as f:
|
||||
a = int(f.read())
|
||||
except Exception:
|
||||
os.remove(FILE)
|
||||
with open(FILE, "w") as f:
|
||||
f.write(str(a + 1))
|
||||
</py!>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>pyd test </title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="font-sans flex items-center justify-center h-screen flex-col gap-5">
|
||||
|
||||
<h1 class="text-5xl font-black text-transparent bg-gradient-to-r from-green-500 to-orange-200 bg-clip-text p-2 block">welcome to PyWPR</h1>
|
||||
<div class="text-2xl">a php-like framework<br>get stated by removing <code>www</code> contents and creating <code>index.pyp</code>
|
||||
<br>or go to <a class="text-blue-600" href="./docs/">docs</a></div>
|
||||
<div class="flex gap-5 items-center text-xs">
|
||||
Renders <a href="./" class="text-red-500 font-bold"><p?>a+1</p?></a></div>
|
||||
</body>
|
||||
</html>
|
||||
0
www/redirection_test.pyp
Normal file
0
www/redirection_test.pyp
Normal file
Reference in New Issue
Block a user