This commit is contained in:
retoor 2025-01-27 19:27:26 +01:00
parent 2c448a951c
commit 542ba6a3fc
17 changed files with 490 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
__pycache__
.backup*
dreamii.d*
.venv
.rcontext.txt

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
PYTHON=.venv/bin/python
all: install run
install:
python3 -m venv .venv
$(PYTHON) -m pip install -e .
run:
$(PYTHON) main.py

16
base.html Normal file
View File

@ -0,0 +1,16 @@
<html>
<head>
<title>Dreamii</title>
<style>{{highlight_styles}}</style>
</head>
<body>
<main>
<header>
<h1>Dreamii</h1>
</header>
<article>
{% block content %}{% endblock %}
</article>
</main>
</body>
</html>

55
index.md Normal file
View File

@ -0,0 +1,55 @@
{% extends "base.html" %}
{% block content %}
{% markdown %}
Welcome to Dreamii, the fastest web framework for Python to build sites with! Dynamic and static! Use Python as template language like PHP. Normally this requires CGI but Dreamii is way faster! Your application is async by default. Also, you can use async await in the py3 block tags.
## Syntax highligthing
### Python
```python
print("Hi, i'm syntax highlighted python")
```
## C
```c
#include <stdio.h>
int main() {
printf("Hi, i'm syntax highlighted c");
return 0;
}
```
{% endmarkdown %}
{% markdown %}# Python database{% endmarkdown %}
{% py3 %}
counter = dreamii.db["counter"].find_one(counter_name="main_counter")
if not counter:
counter = {"count":0,"counter_name":"main_counter"}
counter["count"] += 1
dreamii.db["counter"].upsert(counter,["counter_name"])
print(f"<p>This page had been visited {counter['count']} times.</p>")
{% endpy3 %}
{% markdown %}## Python sub process execution{% endmarkdown %}
{% py3 %}
print("<h3>List first file of directory using `ls`</h3>")
print("<pre>First file: ",system("ls",output=False).split("\n")[0],"</pre>")
print("<h3>Memory information</h3>")
print("<pre>",system("free -h",output=False),"</pre>")
print("<h3>Disk information</h3>")
print("<pre>",system("df -h",output=False),"</pre>")
print("<h3>Process information</h3>")
print("<pre>",system("ps aux",output=False,stderr=True),"</pre>")
{% endpy3 %}
{% endblock content %}

1
main.py Normal file
View File

@ -0,0 +1 @@
print("OK")

27
pyproject.toml Normal file
View File

@ -0,0 +1,27 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "Dreamii"
version = "1.0.0"
readme = "README.md"
license = { file = "LICENSE", content-type="text/markdown" }
description = "Dreamii CMS by Molodetz"
authors = [
{ name = "retoor", email = "retoor@molodetz.nl" }
]
keywords = ["cms", "dreamii", "jinja","jinja2","markdown","retoor","molodetz"]
requires-python = ">=3.12"
dependencies = [
"shed",
"app @ git+https://retoor.molodetz.nl/retoor/app",
"beautifulsoup4",
"gunicorn",
"mistune",
"aiohttp-session",
"cryptography"
]
[project.scripts]
dreamii = "dreamii.__main__:main"

View File

@ -0,0 +1,52 @@
Metadata-Version: 2.2
Name: Dreamii
Version: 1.0.0
Summary: Dreamii CMS by Molodetz
Author-email: retoor <retoor@molodetz.nl>
Keywords: cms,dreamii,jinja,jinja2,markdown,retoor,molodetz
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: shed
Requires-Dist: app@ git+https://retoor.molodetz.nl/retoor/app
Requires-Dist: beautifulsoup4
Requires-Dist: gunicorn
Requires-Dist: mistune
Requires-Dist: aiohttp-session
Requires-Dist: cryptography
# Dreamii
The ultimate markdown CMS! Use the editors that you want! It can be serious article writing or just hacking something together live with VIM on a server. This CMS allows you to do both. For professionals and amateurs.
Also, consider this CMS for beginners. It's actually way easier than WYSIWYG and harder to destroy the site because everything style the markdown uses case be predefined by a designer. Anyone who knows HTML can still still switch to HTML. So you have a lot of freedom!
## Features
This project allows you to create a website using both of these technologies:
* Markdown
* HTML
It will resolve url's to template files what it will render.
By visiting /pony, dreamii will resolve the url and resolve template in this order and will try next one if it exists:
- pony.md
- pony.html
It will resolve / into resolving index.md or index.html. If both exist, index.md will be used. This if for every directory. If you want a file list of a directory use the file list template tag.
## Installation
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install git+https://github.com/retoor/dreamii.git
echo > "# My first dreamii site" > index.md
dreamii serve 7331
```
## Technologies used for creating this project
* python3 as programming language.
* aiohttp for server.
* jinja2 for templating.
* dataset for database.
## Notes
This project runs using the default aiohttp server. If you want to use another server, see the aiohttp documentation. There are several supported. But the default is good enough for thousands for requests per seconds. Caching is not enabled with a reason. There's literally no need to for static sites.

View File

@ -0,0 +1,14 @@
README.md
pyproject.toml
src/Dreamii.egg-info/PKG-INFO
src/Dreamii.egg-info/SOURCES.txt
src/Dreamii.egg-info/dependency_links.txt
src/Dreamii.egg-info/entry_points.txt
src/Dreamii.egg-info/requires.txt
src/Dreamii.egg-info/top_level.txt
src/dreamii/__init__.py
src/dreamii/__main__.py
src/dreamii/app.py
src/dreamii/fetch.py
src/dreamii/markdown.py
src/dreamii/python.py

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,2 @@
[console_scripts]
dreamii = dreamii.__main__:main

View File

@ -0,0 +1,7 @@
shed
app@ git+https://retoor.molodetz.nl/retoor/app
beautifulsoup4
gunicorn
mistune
aiohttp-session
cryptography

View File

@ -0,0 +1 @@
dreamii

0
src/dreamii/__init__.py Normal file
View File

8
src/dreamii/__main__.py Normal file
View File

@ -0,0 +1,8 @@
from dreamii.app import Dreamii
def main():
dreamii = Dreamii()
dreamii.run()
if __name__ == '__main__':
main()

80
src/dreamii/app.py Normal file
View File

@ -0,0 +1,80 @@
from app.app import Application as BaseApplication , BaseView
import pathlib
from aiohttp import web
from dreamii.markdown import MarkdownExtension
from dreamii.python import PythonExtension
import aiohttp_jinja2
class TemplateView(BaseView):
async def resolve_template(self, path):
path = pathlib.Path(self.request.app.template_path).joinpath(str(path).lstrip('/'))
if path.exists() and path.suffix in ['.html', '.md']:
return str(path)
elif path.joinpath('.html').exists():
return str(path.joinpath('.html'))
elif path.joinpath('.md').exists():
return str(path.joinpath('.md'))
elif path.is_dir():
if path.joinpath('index.html').exists():
return str(path.joinpath('index.html'))
elif path.joinpath('index.md').exists():
return str(path.joinpath('index.md'))
return None
async def render_template(self, path):
context = {}
context['request'] = self.request
context['post'] = await self.request.post()
if not context['post']:
context['post'] = None
try:
context['json'] = await self.request.json()
except:
context['json'] = None
pass
return await super().render_template(path, self.request)
async def get(self):
path = await self.resolve_template(self.request.match_info['tail'])
if path:
return await self.render_template(path)
path = pathlib.Path(self.request.app.template_path).joinpath(self.request.match_info['tail'].lstrip('/'))
if path.exists():
return await web.Response(body=path.read_bytes())
return web.Response(status=404)
async def post(self):
path = await self.resolve_template(self.request.match_info['tail'])
if path:
return await self.render_template(path)
path = pathlib.Path(self.request.app.template_path).joinpath(self.request.match_info['tail'].lstrip('/'))
if path.exists():
return await web.Response(body=path.read_bytes())
return web.Response(status=404)
class Dreamii (BaseApplication):
def __init__(self, *args, **kwargs):
super(Dreamii, self).__init__(template_path=".",*args, **kwargs)
self.router.add_view("/{tail:.*}", TemplateView)
self.jinja2_env.enable_async = False
self.jinja2_env.globals["app"] = self
self.jinja2_env.add_extension(MarkdownExtension)
self.jinja2_env.add_extension(PythonExtension)
def run(self,port=7331):
web.run_app(self,port=port)
if __name__ == '__main__':
app = Application()
app.run()

79
src/dreamii/markdown.py Normal file
View File

@ -0,0 +1,79 @@
from types import SimpleNamespace
from mistune import HTMLRenderer, Markdown
from pygments import highlight
from pygments.formatters import html
from pygments.lexers import get_lexer_by_name
from jinja2 import TemplateSyntaxError, nodes
from jinja2.ext import Extension
from jinja2.nodes import Const
class MarkdownRenderer(HTMLRenderer):
_allow_harmful_protocols = True
def __init__(self, app, template):
self.template = template
self.app = app
self.env = self.app.jinja2_env
formatter = html.HtmlFormatter()
self.env.globals["highlight_styles"] = formatter.get_style_defs()
def _escape(self, str):
return str ##escape(str)
def block_code(self, code, lang=None, info=None):
if not lang:
lang = info
if not lang:
return f"<div>{code}</div>"
# return '\n<pre><code>%s</code></pre>\n' % escape(code)
lexer = get_lexer_by_name(lang, stripall=True)
formatter = html.HtmlFormatter(lineseparator="<br>")
return highlight(code, lexer, formatter)
def render(self):
markdown_string = self.app.template_path.joinpath(self.template).read_text()
renderer = MarkdownRenderer(self.app, self.template)
markdown = Markdown(renderer=renderer)
return markdown(markdown_string)
def render_markdown_sync(app, markdown_string):
renderer = MarkdownRenderer(app, None)
markdown = Markdown(renderer=renderer)
return markdown(markdown_string)
async def render_markdown(app, markdown_string):
return render_markdown_sync(app, markdown_string)
# Source based on: https://ron.sh/how-to-write-a-jinja2-extension/
class MarkdownExtension(Extension):
tags = {"markdown"}
def __init__(self, environment):
self.app = SimpleNamespace(jinja2_env=environment)
super(MarkdownExtension, self).__init__(environment)
def parse(self, parser):
line_number = next(parser.stream).lineno
md_file = [Const("")]
body = ""
try:
md_file = [parser.parse_expression()]
except TemplateSyntaxError:
body = parser.parse_statements(["name:endmarkdown"], drop_needle=True)
return nodes.CallBlock(
self.call_method("_to_html", md_file), [], [], body
).set_lineno(line_number)
def _to_html(self, md_file, caller):
return render_markdown_sync(self.app, caller())
async def _to_html_async(self, md_file, caller):
return await render_markdown(self.app, caller())

131
src/dreamii/python.py Normal file
View File

@ -0,0 +1,131 @@
from types import SimpleNamespace
from mistune import HTMLRenderer, Markdown
from pygments import highlight
from pygments.formatters import html
from pygments.lexers import get_lexer_by_name
from jinja2 import TemplateSyntaxError, nodes
from jinja2.ext import Extension
from jinja2.nodes import Const
import subprocess
import asyncio
def render_markdown_sync(app, markdown_string):
renderer = MarkdownRenderer(app, None)
markdown = Markdown(renderer=renderer)
return markdown(markdown_string)
async def render_markdown(app, markdown_string):
return render_markdown_sync(app, markdown_string)
async def execute_source(source,locals):
db = locals['db']
system = locals['system']
dreamii = locals['app']
exec(source)
async def execute_python_source(source_code):
# Create a dictionary to act as the execution context
exec_globals = {}
exec_locals = {}
# Compile and execute the source code
exec(
f"async def __temp_function__():\n" + "\n".join(f" {line}" for line in source_code.splitlines()),
exec_globals,
exec_locals
)
# Run the temporary async function
return await exec_locals["__temp_function__"]()
class PythonExtension(Extension):
tags = {"py3"}
def __init__(self, environment):
SimpleNamespace(jinja2_env=environment)
self.app = environment.globals["app"]
self.context = SimpleNamespace()
super(PythonExtension, self).__init__(environment)
def parse(self, parser):
line_number = next(parser.stream).lineno
md_file = [Const("")]
body = ""
try:
md_file = [parser.parse_expression()]
except TemplateSyntaxError:
body = parser.parse_statements(["name:endpy3"], drop_needle=True)
return nodes.CallBlock(
self.call_method("_to_html", md_file), [], [], body
).set_lineno(line_number)
#return self.call_method("to_html", body).set_lineno(line_number)
#return self._to_html(md_file,md_file) # self.call_method("_to_html", md_file)
def _to_html(self, md_file, caller):
print("Did it without async")
base_source = "\n".join([
"from subprocess import *",
"import asyncio",
"import pathlib",
"from pathlib import *",
"import dataset",
])
user_source = str(caller()).strip("\n").strip(" ")
print(user_source)
source = "\n".join([base_source,user_source])
import sys
import io
import html
original_stdout = sys.stdout
original_stderr = sys.stderr
sys.stdout = io.StringIO()
sys.stderr = io.StringIO()
def system(command,output=True,stderr=False,escape=True):
if isinstance(command, str):
command = command.split(" ")
result = subprocess.run(
command,
text=True,
capture_output=True
)
if result.stdout:
original_stdout.write(result.stdout)
# Yes, Docker People, I thought about you guys!
original_stdout.flush()
if output:
if escape:
print(html.escape(result.stdout),end="")
else:
print(result.stdout,end="")
if result.stderr:
original_stderr.write(result.stderr)
# Yes, Docker People, I thought about you guys!
original_stderr.flush()
if output and stderr:
if escape:
print(html.escape(result.stderr,end=""))
else:
print(result.stderr,end="")
if stderr:
return html.escape(result.stdout + result.stderr)
return result.stdout
db= self.app.db
app = self.app
dreamii = self.app
context = self.context
exec(base_source)
exec(user_source)
result = sys.stdout.getvalue()
if not result:
result = sys.stderr.getvalue()
return result # nodes.CallBlock(