Upgrade.
This commit is contained in:
parent
2c448a951c
commit
542ba6a3fc
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
__pycache__
|
||||
.backup*
|
||||
dreamii.d*
|
||||
.venv
|
||||
.rcontext.txt
|
||||
|
10
Makefile
Normal file
10
Makefile
Normal 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
16
base.html
Normal 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
55
index.md
Normal 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 %}
|
27
pyproject.toml
Normal file
27
pyproject.toml
Normal 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"
|
52
src/Dreamii.egg-info/PKG-INFO
Normal file
52
src/Dreamii.egg-info/PKG-INFO
Normal 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.
|
14
src/Dreamii.egg-info/SOURCES.txt
Normal file
14
src/Dreamii.egg-info/SOURCES.txt
Normal 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
|
1
src/Dreamii.egg-info/dependency_links.txt
Normal file
1
src/Dreamii.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
||||
|
2
src/Dreamii.egg-info/entry_points.txt
Normal file
2
src/Dreamii.egg-info/entry_points.txt
Normal file
@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
dreamii = dreamii.__main__:main
|
7
src/Dreamii.egg-info/requires.txt
Normal file
7
src/Dreamii.egg-info/requires.txt
Normal file
@ -0,0 +1,7 @@
|
||||
shed
|
||||
app@ git+https://retoor.molodetz.nl/retoor/app
|
||||
beautifulsoup4
|
||||
gunicorn
|
||||
mistune
|
||||
aiohttp-session
|
||||
cryptography
|
1
src/Dreamii.egg-info/top_level.txt
Normal file
1
src/Dreamii.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
||||
dreamii
|
0
src/dreamii/__init__.py
Normal file
0
src/dreamii/__init__.py
Normal file
8
src/dreamii/__main__.py
Normal file
8
src/dreamii/__main__.py
Normal 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
80
src/dreamii/app.py
Normal 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
79
src/dreamii/markdown.py
Normal 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
131
src/dreamii/python.py
Normal 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(
|
||||
|
Loading…
Reference in New Issue
Block a user