first commit

This commit is contained in:
2025-08-24 22:09:37 +03:00
commit 176fc62a24
14 changed files with 673 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__
aval.txt

235
main.py Normal file
View 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(' ', '&nbsp').replace('\t', '&nbsp'*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
View 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
View File

@@ -0,0 +1,3 @@
SERVER_DOWNLOADPY = False
SERVER_PORT = 8080
SERVER_HOST = '0.0.0.0'

60
www/docs/basics.pyp Normal file
View 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">&lt;py>! tag</h3>
<p class="text-center">in pywrp we have <span class="bg-black p-2 rounded-xl text-white font-mono">&lt;py!&gt;</span> and <span class="bg-black p-2 rounded-xl text-white font-mono">&lt;/py!&gt;</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">&lt;p?> tag</h3>
<p class="text-center">also there is <span class="bg-black p-2 rounded-xl text-white font-mono">&lt;p?>&lt;/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
View 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(' ', '&nbsp;').replace('\t', '&nbsp;'*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>
'''

View File

@@ -0,0 +1,2 @@
def header_comp(url):
pass

33
www/docs/index.pyp Normal file
View 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
View File

@@ -0,0 +1,4 @@
pages = [
['basics', './basics.pyp'],
['pyp | queries', './queries.pyp'],
]

77
www/docs/queries.pyp Normal file
View 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">&lt;form method="get"&gt;</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">&lt;/form&gt;</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:&nbsp;&nbsp;<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\">&lt;!py&gt;</span><div class=\"w-full text-center\">hello, <span class=\"font-mono\">{name}</span>!&nbsp;&nbsp;{moneyText}</div><span class=\"font-mono text-right w-full\">&lt;/py!&gt;</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
View 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
View 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
View 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
View File