From 542ba6a3fcc36f14248d80917453b404c4218aab Mon Sep 17 00:00:00 2001 From: retoor Date: Mon, 27 Jan 2025 19:27:26 +0100 Subject: [PATCH] Upgrade. --- .gitignore | 6 + Makefile | 10 ++ base.html | 16 +++ index.md | 55 +++++++++ main.py | 1 + pyproject.toml | 27 +++++ src/Dreamii.egg-info/PKG-INFO | 52 +++++++++ src/Dreamii.egg-info/SOURCES.txt | 14 +++ src/Dreamii.egg-info/dependency_links.txt | 1 + src/Dreamii.egg-info/entry_points.txt | 2 + src/Dreamii.egg-info/requires.txt | 7 ++ src/Dreamii.egg-info/top_level.txt | 1 + src/dreamii/__init__.py | 0 src/dreamii/__main__.py | 8 ++ src/dreamii/app.py | 80 +++++++++++++ src/dreamii/markdown.py | 79 +++++++++++++ src/dreamii/python.py | 131 ++++++++++++++++++++++ 17 files changed, 490 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 base.html create mode 100644 index.md create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 src/Dreamii.egg-info/PKG-INFO create mode 100644 src/Dreamii.egg-info/SOURCES.txt create mode 100644 src/Dreamii.egg-info/dependency_links.txt create mode 100644 src/Dreamii.egg-info/entry_points.txt create mode 100644 src/Dreamii.egg-info/requires.txt create mode 100644 src/Dreamii.egg-info/top_level.txt create mode 100644 src/dreamii/__init__.py create mode 100644 src/dreamii/__main__.py create mode 100644 src/dreamii/app.py create mode 100644 src/dreamii/markdown.py create mode 100644 src/dreamii/python.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13974b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__ +.backup* +dreamii.d* +.venv +.rcontext.txt + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..496d021 --- /dev/null +++ b/Makefile @@ -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 diff --git a/base.html b/base.html new file mode 100644 index 0000000..fa1e655 --- /dev/null +++ b/base.html @@ -0,0 +1,16 @@ + + + Dreamii + + + +
+
+

Dreamii

+
+
+ {% block content %}{% endblock %} +
+
+ + diff --git a/index.md b/index.md new file mode 100644 index 0000000..bbadbd1 --- /dev/null +++ b/index.md @@ -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 + +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"

This page had been visited {counter['count']} times.

") +{% endpy3 %} + +{% markdown %}## Python sub process execution{% endmarkdown %} +{% py3 %} +print("

List first file of directory using `ls`

") +print("
First file: ",system("ls",output=False).split("\n")[0],"
") +print("

Memory information

") +print("
",system("free -h",output=False),"
") +print("

Disk information

") +print("
",system("df -h",output=False),"
") +print("

Process information

") +print("
",system("ps aux",output=False,stderr=True),"
") +{% endpy3 %} +{% endblock content %} diff --git a/main.py b/main.py new file mode 100644 index 0000000..01d9c80 --- /dev/null +++ b/main.py @@ -0,0 +1 @@ +print("OK") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2f87840 --- /dev/null +++ b/pyproject.toml @@ -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" diff --git a/src/Dreamii.egg-info/PKG-INFO b/src/Dreamii.egg-info/PKG-INFO new file mode 100644 index 0000000..997d4bb --- /dev/null +++ b/src/Dreamii.egg-info/PKG-INFO @@ -0,0 +1,52 @@ +Metadata-Version: 2.2 +Name: Dreamii +Version: 1.0.0 +Summary: Dreamii CMS by Molodetz +Author-email: retoor +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. diff --git a/src/Dreamii.egg-info/SOURCES.txt b/src/Dreamii.egg-info/SOURCES.txt new file mode 100644 index 0000000..dec5c77 --- /dev/null +++ b/src/Dreamii.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/src/Dreamii.egg-info/dependency_links.txt b/src/Dreamii.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Dreamii.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/Dreamii.egg-info/entry_points.txt b/src/Dreamii.egg-info/entry_points.txt new file mode 100644 index 0000000..1356d66 --- /dev/null +++ b/src/Dreamii.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +dreamii = dreamii.__main__:main diff --git a/src/Dreamii.egg-info/requires.txt b/src/Dreamii.egg-info/requires.txt new file mode 100644 index 0000000..bffb8ae --- /dev/null +++ b/src/Dreamii.egg-info/requires.txt @@ -0,0 +1,7 @@ +shed +app@ git+https://retoor.molodetz.nl/retoor/app +beautifulsoup4 +gunicorn +mistune +aiohttp-session +cryptography diff --git a/src/Dreamii.egg-info/top_level.txt b/src/Dreamii.egg-info/top_level.txt new file mode 100644 index 0000000..83a4152 --- /dev/null +++ b/src/Dreamii.egg-info/top_level.txt @@ -0,0 +1 @@ +dreamii diff --git a/src/dreamii/__init__.py b/src/dreamii/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dreamii/__main__.py b/src/dreamii/__main__.py new file mode 100644 index 0000000..f560424 --- /dev/null +++ b/src/dreamii/__main__.py @@ -0,0 +1,8 @@ +from dreamii.app import Dreamii + +def main(): + dreamii = Dreamii() + dreamii.run() + +if __name__ == '__main__': + main() diff --git a/src/dreamii/app.py b/src/dreamii/app.py new file mode 100644 index 0000000..ca45691 --- /dev/null +++ b/src/dreamii/app.py @@ -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() diff --git a/src/dreamii/markdown.py b/src/dreamii/markdown.py new file mode 100644 index 0000000..f062a35 --- /dev/null +++ b/src/dreamii/markdown.py @@ -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"
{code}
" + # return '\n
%s
\n' % escape(code) + lexer = get_lexer_by_name(lang, stripall=True) + formatter = html.HtmlFormatter(lineseparator="
") + 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()) diff --git a/src/dreamii/python.py b/src/dreamii/python.py new file mode 100644 index 0000000..af84560 --- /dev/null +++ b/src/dreamii/python.py @@ -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( +