rynz 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rynz/__init__.py +25 -0
- rynz/builder.py +242 -0
- rynz/creator.py +410 -0
- rynz/manage.py +287 -0
- rynz/rynz.py +257 -0
- rynz/server.py +77 -0
- rynz-1.0.0.dist-info/METADATA +349 -0
- rynz-1.0.0.dist-info/RECORD +12 -0
- rynz-1.0.0.dist-info/WHEEL +5 -0
- rynz-1.0.0.dist-info/entry_points.txt +2 -0
- rynz-1.0.0.dist-info/licenses/LICENSE +21 -0
- rynz-1.0.0.dist-info/top_level.txt +1 -0
rynz/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/python
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# ███╗ ██╗██╗██╗ ██╗ █████╗ ██████╗ ███████╗
|
|
5
|
+
# ████╗ ██║██║██║ ██║██╔══██╗██╔══██╗██╔════╝
|
|
6
|
+
# ██╔██╗ ██║██║███████║███████║██████╔╝███████╗
|
|
7
|
+
# ██║╚██╗██║██║██╔══██║██╔══██║██╔══██╗╚════██║
|
|
8
|
+
# ██║ ╚████║██║██║ ██║██║ ██║██║ ██║███████║
|
|
9
|
+
# ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝
|
|
10
|
+
# DRAFTED BY [https://nih.ar] ON 14-04-2025
|
|
11
|
+
# SOURCE [__init__.py] LAST MODIFIED ON 28-04-2025.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
Module for versioning of the rynz package.
|
|
16
|
+
|
|
17
|
+
This module defines the version number for the rynz static site generator
|
|
18
|
+
and provides a function to retrieve the current version.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
__version__ = "1.0.0"
|
|
22
|
+
|
|
23
|
+
def get_version():
|
|
24
|
+
"""Returns the current version of the rynz package."""
|
|
25
|
+
return __version__
|
rynz/builder.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/bin/python
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# ███╗ ██╗██╗██╗ ██╗ █████╗ ██████╗ ███████╗
|
|
5
|
+
# ████╗ ██║██║██║ ██║██╔══██╗██╔══██╗██╔════╝
|
|
6
|
+
# ██╔██╗ ██║██║███████║███████║██████╔╝███████╗
|
|
7
|
+
# ██║╚██╗██║██║██╔══██║██╔══██║██╔══██╗╚════██║
|
|
8
|
+
# ██║ ╚████║██║██║ ██║██║ ██║██║ ██║███████║
|
|
9
|
+
# ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝
|
|
10
|
+
# DRAFTED BY [https://nih.ar] ON 28-04-2025
|
|
11
|
+
# SOURCE [builder.py] LAST MODIFIED ON 28-04-2025.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
from shutil import copytree, rmtree
|
|
15
|
+
from os import path, makedirs, sep
|
|
16
|
+
from glob import glob
|
|
17
|
+
from yaml import safe_load
|
|
18
|
+
from jinja2 import Environment, FileSystemLoader
|
|
19
|
+
from markdown2 import markdown
|
|
20
|
+
from time import time
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
from rich.panel import Panel
|
|
23
|
+
from rich.table import Table
|
|
24
|
+
|
|
25
|
+
# Initialize console for colorful output
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def print_message(message, style="info"):
|
|
30
|
+
"""
|
|
31
|
+
Prints a styled message using the Rich console.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
message (str): The message to display.
|
|
35
|
+
style (str): Message style ("success", "error", or "info").
|
|
36
|
+
"""
|
|
37
|
+
styles = {
|
|
38
|
+
"success": "bold green",
|
|
39
|
+
"error": "bold red",
|
|
40
|
+
"info": "yellow"
|
|
41
|
+
}
|
|
42
|
+
console.print(message, style=styles.get(style, "yellow"))
|
|
43
|
+
|
|
44
|
+
def buildRynz():
|
|
45
|
+
"""
|
|
46
|
+
Builds the static site by converting Markdown notes into HTML pages,
|
|
47
|
+
using Jinja2 templates and configuration from config.yml.
|
|
48
|
+
"""
|
|
49
|
+
start_time = time()
|
|
50
|
+
total_notes = 0
|
|
51
|
+
failed_notes = 0
|
|
52
|
+
skipped_notes = []
|
|
53
|
+
|
|
54
|
+
console.rule("[bold green]🔨 Starting Build Process")
|
|
55
|
+
|
|
56
|
+
def readmd(filepath):
|
|
57
|
+
"""
|
|
58
|
+
Reads and returns the raw content of a markdown file.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
filepath (str): Path to the markdown file.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str: File content as a string.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
with open(filepath, 'r', encoding='utf8', errors='ignore') as data:
|
|
68
|
+
return data.read()
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print_message(f"❌ Error reading {filepath}: {e}", "error")
|
|
71
|
+
return ""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def create_page(template, post_detail, md_content, filename):
|
|
75
|
+
"""
|
|
76
|
+
Renders and writes a page from a Jinja2 template and given content.
|
|
77
|
+
Handles both single notes and special pages like index.html and RSS feeds.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
template (str): Template filename.
|
|
81
|
+
post_detail (dict): Frontmatter details for the post.
|
|
82
|
+
md_content (str): Markdown content to render.
|
|
83
|
+
filename (str): Output filename.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
str: Rendered file name or an empty string in case of error.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
env = Environment(loader=FileSystemLoader(searchpath='./'))
|
|
91
|
+
template_obj = env.get_template(template)
|
|
92
|
+
|
|
93
|
+
# Initialize variables
|
|
94
|
+
post_title = ""
|
|
95
|
+
post_subtitle = ""
|
|
96
|
+
post_date = ""
|
|
97
|
+
post_meta = ""
|
|
98
|
+
posts_list = ""
|
|
99
|
+
last_date = ""
|
|
100
|
+
post_data = filename.split(sep)
|
|
101
|
+
post_path = home_path
|
|
102
|
+
|
|
103
|
+
# Determine post details based on the filename
|
|
104
|
+
if filename == "index.html":
|
|
105
|
+
post_file = filename
|
|
106
|
+
posts_list = posts
|
|
107
|
+
elif filename.endswith(".xml"):
|
|
108
|
+
post_file = filename
|
|
109
|
+
posts_list = posts
|
|
110
|
+
last_date = posts_list[0].get('date')
|
|
111
|
+
elif post_detail is None:
|
|
112
|
+
post_file = filename.replace('.md', '.html')
|
|
113
|
+
else:
|
|
114
|
+
post_title = post_detail.get("title", "")
|
|
115
|
+
post_subtitle = post_detail.get("subtitle", "")
|
|
116
|
+
post_date = post_detail.get("date", "")
|
|
117
|
+
post_meta = post_detail.get("meta", "")
|
|
118
|
+
post_data = filename.split(sep)
|
|
119
|
+
post_path = path.join(home_path)
|
|
120
|
+
post_file = post_data[2].replace('.md', '.html')
|
|
121
|
+
post_data = post_data[1]
|
|
122
|
+
makedirs(post_path, exist_ok=True)
|
|
123
|
+
|
|
124
|
+
output_filepath = path.join(post_path, post_file)
|
|
125
|
+
with open(output_filepath, 'w', encoding='utf8', errors='ignore') as output_file:
|
|
126
|
+
output_file.write(
|
|
127
|
+
template_obj.render(
|
|
128
|
+
title=title,
|
|
129
|
+
post_title=post_title,
|
|
130
|
+
post_subtitle=post_subtitle,
|
|
131
|
+
date=post_date,
|
|
132
|
+
metad=post_meta,
|
|
133
|
+
url=path.join(url, post_file),
|
|
134
|
+
article=markdown(md_content),
|
|
135
|
+
posts=posts_list,
|
|
136
|
+
home=home_md,
|
|
137
|
+
header=markdown(readmd(header_md)),
|
|
138
|
+
footer=markdown(readmd(footer_md)),
|
|
139
|
+
last_date=last_date,
|
|
140
|
+
config=config
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
return post_file
|
|
144
|
+
except Exception as e:
|
|
145
|
+
print_message(f"❌ Error creating page {filename}: {e}", "error")
|
|
146
|
+
return ""
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# Load configuration
|
|
150
|
+
with open('config.yml', encoding='utf8', errors='ignore') as conf_file:
|
|
151
|
+
config = safe_load(conf_file.read())
|
|
152
|
+
|
|
153
|
+
# Set config values as globals
|
|
154
|
+
for key, val in config.items():
|
|
155
|
+
globals()[key] = val
|
|
156
|
+
|
|
157
|
+
# Ensure all required configuration keys are present
|
|
158
|
+
required_keys = [
|
|
159
|
+
'home_path', 'resource_path', 'content_path', 'note_template',
|
|
160
|
+
'home_template', 'feed_template', 'home_md', 'header_md',
|
|
161
|
+
'footer_md', 'title', 'url'
|
|
162
|
+
]
|
|
163
|
+
for key in required_keys:
|
|
164
|
+
if key not in config:
|
|
165
|
+
print_message(f"❌ Missing config key: {key}", "error")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
# Reset output directory
|
|
169
|
+
if path.exists(home_path):
|
|
170
|
+
rmtree(home_path)
|
|
171
|
+
copytree(resource_path, home_path)
|
|
172
|
+
|
|
173
|
+
posts = []
|
|
174
|
+
|
|
175
|
+
# Process each note
|
|
176
|
+
for note_path in glob(path.join(content_path, "note", "*.md")):
|
|
177
|
+
try:
|
|
178
|
+
yaml_lines = []
|
|
179
|
+
frontmatter, markdown_content = '', ''
|
|
180
|
+
|
|
181
|
+
# Read frontmatter and body
|
|
182
|
+
with open(note_path, encoding='utf8', errors='ignore') as infile:
|
|
183
|
+
for line in infile:
|
|
184
|
+
if line.startswith('---'):
|
|
185
|
+
for line in infile:
|
|
186
|
+
if line.startswith('---'):
|
|
187
|
+
break
|
|
188
|
+
yaml_lines.append(line)
|
|
189
|
+
frontmatter = ''.join(yaml_lines)
|
|
190
|
+
markdown_content = ''.join(infile)
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
post_detail = safe_load(frontmatter)
|
|
194
|
+
|
|
195
|
+
if post_detail is not None:
|
|
196
|
+
# Build the note page
|
|
197
|
+
post_url = create_page(
|
|
198
|
+
note_template, post_detail, markdown_content, note_path
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Add to posts list for homepage/rss
|
|
202
|
+
post_entry = post_detail
|
|
203
|
+
post_entry.update({'url': '/' + post_url})
|
|
204
|
+
post_entry.update({'note': markdown(markdown_content)})
|
|
205
|
+
posts.append(post_entry)
|
|
206
|
+
|
|
207
|
+
total_notes += 1
|
|
208
|
+
print_message(f"✔ Built: {post_url}", "success")
|
|
209
|
+
else:
|
|
210
|
+
# If no valid frontmatter found
|
|
211
|
+
skipped_notes.append(note_path)
|
|
212
|
+
print_message(f"⚠️ Skipped: No frontmatter in {note_path}", "info")
|
|
213
|
+
except Exception:
|
|
214
|
+
failed_notes += 1
|
|
215
|
+
print_message(f"❌ Error in: {note_path}", "error")
|
|
216
|
+
console.print_exception()
|
|
217
|
+
|
|
218
|
+
# Sort posts by latest date
|
|
219
|
+
posts = sorted(posts, key=lambda post: post.get('date', ''), reverse=True)
|
|
220
|
+
|
|
221
|
+
# Build index.html and RSS feed
|
|
222
|
+
create_page(home_template, None, readmd(home_md), "index.html")
|
|
223
|
+
create_page(feed_template, None, readmd(home_md), "rss.xml")
|
|
224
|
+
|
|
225
|
+
# Build summary table
|
|
226
|
+
console.rule("[bold cyan]✅ Build Summary")
|
|
227
|
+
summary = Table(show_header=True, header_style="bold magenta")
|
|
228
|
+
summary.add_column("Metric")
|
|
229
|
+
summary.add_column("Count", justify="right")
|
|
230
|
+
summary.add_row("Notes Processed", str(total_notes))
|
|
231
|
+
summary.add_row("Skipped Notes", str(len(skipped_notes)))
|
|
232
|
+
summary.add_row("Failed Notes", str(failed_notes))
|
|
233
|
+
summary.add_row("Time Taken (s)", f"{(time() - start_time):.2f}")
|
|
234
|
+
console.print(summary)
|
|
235
|
+
|
|
236
|
+
except Exception:
|
|
237
|
+
console.print(Panel(
|
|
238
|
+
"[bold red]Something went wrong while building your site![/bold red]\n\n"
|
|
239
|
+
"[italic]Make sure you're inside a valid Rynz directory.[/italic]",
|
|
240
|
+
title="❌ Build Failed"
|
|
241
|
+
))
|
|
242
|
+
console.print_exception()
|
rynz/creator.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/bin/python3
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# ███╗ ██╗██╗██╗ ██╗ █████╗ ██████╗ ███████╗
|
|
5
|
+
# ████╗ ██║██║██║ ██║██╔══██╗██╔══██╗██╔════╝
|
|
6
|
+
# ██╔██╗ ██║██║███████║███████║██████╔╝███████╗
|
|
7
|
+
# ██║╚██╗██║██║██╔══██║██╔══██║██╔══██╗╚════██║
|
|
8
|
+
# ██║ ╚████║██║██║ ██║██║ ██║██║ ██║███████║
|
|
9
|
+
# ╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝
|
|
10
|
+
# DRAFTED BY [https://nih.ar] ON 18-04-2021.
|
|
11
|
+
# SOURCE [creator.py] LAST MODIFIED ON 15-04-2025.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
from os import mkdir, path, makedirs
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
import sys
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
|
|
19
|
+
# Initialize console for colorful output
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_message(message, style="info", timestamp=False):
|
|
24
|
+
"""
|
|
25
|
+
Prints a styled message using the Rich console.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
message (str): The message to display.
|
|
29
|
+
style (str): Message style ("success", "error", or "info").
|
|
30
|
+
timestamp (bool): Whether to include a timestamp in the message.
|
|
31
|
+
"""
|
|
32
|
+
styles = {
|
|
33
|
+
"success": "bold green",
|
|
34
|
+
"error": "bold red",
|
|
35
|
+
"info": "yellow"
|
|
36
|
+
}
|
|
37
|
+
if timestamp:
|
|
38
|
+
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
39
|
+
message = f"[{current_time}] {message}"
|
|
40
|
+
console.print(message, style=styles.get(style, "yellow"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_input(prompt, default_value, validator=None):
|
|
44
|
+
"""
|
|
45
|
+
Prompts user for input with an optional default and validator.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
prompt (str): The input prompt.
|
|
49
|
+
default_value (str): Default input value if the user provides none.
|
|
50
|
+
validator (callable): A function to validate input (optional).
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: The validated user input.
|
|
54
|
+
"""
|
|
55
|
+
while True:
|
|
56
|
+
user_input = input(prompt).strip() or default_value
|
|
57
|
+
if validator and not validator(user_input):
|
|
58
|
+
print_message(
|
|
59
|
+
"Invalid input. Please try again.", "error", timestamp=True
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
return user_input
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def is_valid_url(url):
|
|
66
|
+
"""
|
|
67
|
+
Checks if the given string contains a basic valid domain structure.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
url (str): The URL string to validate.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
bool: True if the URL is valid, False otherwise.
|
|
74
|
+
"""
|
|
75
|
+
return "." in url
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def safe_create_directory(directory):
|
|
79
|
+
"""
|
|
80
|
+
Safely creates a directory if it doesn't already exist.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
directory (str): Path to the directory to create.
|
|
84
|
+
|
|
85
|
+
Exits:
|
|
86
|
+
On failure to create the directory.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
if not path.exists(directory):
|
|
90
|
+
makedirs(directory)
|
|
91
|
+
print_message(
|
|
92
|
+
f"Created directory: {directory}", "success", timestamp=True
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
print_message(
|
|
96
|
+
f"Directory already exists: {directory}", "info", timestamp=True
|
|
97
|
+
)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print_message(
|
|
100
|
+
f"❌ Error creating directory {directory}: {e}", "error", timestamp=True
|
|
101
|
+
)
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def createConfig(rynzName):
|
|
106
|
+
"""
|
|
107
|
+
Creates the default configuration file `config.yml`.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
rynzName (str): The name of the project directory.
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
with open(path.join(rynzName, 'config.yml'), 'w') as f:
|
|
114
|
+
title = rynzName
|
|
115
|
+
url = 'yourdomain.tld'
|
|
116
|
+
|
|
117
|
+
conf_data = f"""
|
|
118
|
+
# Mandatory Configuration
|
|
119
|
+
title: {title}
|
|
120
|
+
url: {url}
|
|
121
|
+
note_template: template/note_template.html
|
|
122
|
+
home_template: template/home_template.html
|
|
123
|
+
feed_template: template/feed_template.xml
|
|
124
|
+
home_path: public
|
|
125
|
+
content_path: content
|
|
126
|
+
resource_path: static
|
|
127
|
+
home_md: content/home.md
|
|
128
|
+
header_md: content/header.md
|
|
129
|
+
footer_md: content/footer.md
|
|
130
|
+
|
|
131
|
+
# Optional Configuration
|
|
132
|
+
site-title: {title}
|
|
133
|
+
css: demo.css
|
|
134
|
+
desc: Write anything that human and machine can understand.
|
|
135
|
+
mail: some@mail.com
|
|
136
|
+
"""
|
|
137
|
+
f.write(conf_data)
|
|
138
|
+
print_message("config.yml created.", "success", timestamp=True)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print_message(
|
|
141
|
+
f"❌ Error creating config.yml: {e}", "error", timestamp=True
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def createTemplate(rynzName, template_type, file_content):
|
|
146
|
+
"""
|
|
147
|
+
Creates a template file with the provided content.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
rynzName (str): The name of the project directory.
|
|
151
|
+
template_type (str): The name of the template file.
|
|
152
|
+
file_content (str): The content to write to the template file.
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
template_file_path = path.join(rynzName, 'template', template_type)
|
|
156
|
+
with open(template_file_path, 'w') as f:
|
|
157
|
+
f.write(file_content)
|
|
158
|
+
print_message(
|
|
159
|
+
f"{template_type} created.", "success", timestamp=True
|
|
160
|
+
)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
print_message(
|
|
163
|
+
f"❌ Error creating {template_type}: {e}", "error", timestamp=True
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Template: Home Page
|
|
167
|
+
home_template_content = """
|
|
168
|
+
<!DOCTYPE html>
|
|
169
|
+
<html lang="en-IN" data-theme="dark">
|
|
170
|
+
<head>
|
|
171
|
+
<meta charset="utf-8">
|
|
172
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
173
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
174
|
+
<meta name="description" content=" {{ config.get('desc') }} ">
|
|
175
|
+
<title>{{ config.get('site-title') }}</title>
|
|
176
|
+
<link rel="stylesheet" href="{{ config.get('css') }}">
|
|
177
|
+
<link rel="alternate" type="application/atom+xml" title="Recent blog posts" href="/rss.xml">
|
|
178
|
+
</head>
|
|
179
|
+
<body>
|
|
180
|
+
<header>
|
|
181
|
+
<h1><a href="/">{{ title | lower }}</a></h1>
|
|
182
|
+
{{ header }}
|
|
183
|
+
</header>
|
|
184
|
+
<section>
|
|
185
|
+
{{ article }}
|
|
186
|
+
<ul>
|
|
187
|
+
{% for post in posts %}
|
|
188
|
+
{% if "note" in post.tags %}
|
|
189
|
+
<li>{{ post.date.strftime('%d %m %Y') }} ; <a href="{{ post.url }}">{{ post.title | lower }}</a></li>
|
|
190
|
+
{% endif %}
|
|
191
|
+
{% endfor %}
|
|
192
|
+
{% if nextpage %} <a href="{{ nextpage }}">Older Notes >> </a> {% endif %}
|
|
193
|
+
</ul>
|
|
194
|
+
</section>
|
|
195
|
+
<footer>
|
|
196
|
+
{{ footer }}
|
|
197
|
+
</footer>
|
|
198
|
+
</body>
|
|
199
|
+
</html>
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
# Template: Note Page
|
|
203
|
+
note_template_content = """
|
|
204
|
+
<!DOCTYPE html>
|
|
205
|
+
<html lang="en" data-theme="dark">
|
|
206
|
+
<head>
|
|
207
|
+
<meta charset="utf-8">
|
|
208
|
+
<title>{{ post_title }}</title>
|
|
209
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
210
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
211
|
+
<meta name="description" content=" {{ post_subtitle }} ">
|
|
212
|
+
<link rel="stylesheet" href="{{ config.get('css') }}">
|
|
213
|
+
<link rel="alternate" type="application/atom+xml" title="Recent blog posts" href="/rss.xml">
|
|
214
|
+
</head>
|
|
215
|
+
<body>
|
|
216
|
+
<header>
|
|
217
|
+
<h1>{{ post_title }}</h1>
|
|
218
|
+
</header>
|
|
219
|
+
<article>
|
|
220
|
+
{{ article }}
|
|
221
|
+
{% if date %}
|
|
222
|
+
<p># Last updated on <time>{{ date.strftime('%d %b %Y') }}.</time></p>
|
|
223
|
+
{% endif %}
|
|
224
|
+
</article>
|
|
225
|
+
<footer>
|
|
226
|
+
{{ footer }}
|
|
227
|
+
</footer>
|
|
228
|
+
</body>
|
|
229
|
+
</html>
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# Template: Feed (RSS)
|
|
233
|
+
feed_template_content = """
|
|
234
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
235
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
236
|
+
<channel>
|
|
237
|
+
<title>{{ title }}</title>
|
|
238
|
+
<atom:link href="{{ url }}" rel="self" type="application/rss+xml" />
|
|
239
|
+
<link>{{ url }}</link>
|
|
240
|
+
<description>{{ subtitle }}</description>
|
|
241
|
+
<lastBuildDate>{{ last_date.strftime('%a, %d %b %Y %H:%M:%S GMT') }}</lastBuildDate>
|
|
242
|
+
<language>en-IN</language>
|
|
243
|
+
<image>
|
|
244
|
+
<url>{{ config.get('url') }}/{{ config.get('pht') }}</url>
|
|
245
|
+
<title>{{ title }}</title>
|
|
246
|
+
<link>{{ url }}</link>
|
|
247
|
+
<width>32</width>
|
|
248
|
+
<height>32</height>
|
|
249
|
+
</image>
|
|
250
|
+
{% for post in posts %}
|
|
251
|
+
{% if "note" in post.tags %}
|
|
252
|
+
<item>
|
|
253
|
+
<title>{{ post.title }}</title>
|
|
254
|
+
<link>{{ config.get('url') }}{{ post.url }}</link>
|
|
255
|
+
<pubDate>{{ post.date.strftime('%a, %d %b %Y %H:%M:%S GMT') }}</pubDate>
|
|
256
|
+
<guid isPermaLink="false">{{ config.get('url') }}{{ post.url }}</guid>
|
|
257
|
+
<description><![CDATA[{{ post.subtitle }} - {{ post.note }} ]]></description>
|
|
258
|
+
</item>
|
|
259
|
+
{% endif %}
|
|
260
|
+
{% endfor %}
|
|
261
|
+
</channel>
|
|
262
|
+
</rss>
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
# Create content/header/footer/note
|
|
266
|
+
def createContent(rynzName):
|
|
267
|
+
"""
|
|
268
|
+
Creates initial content, including `header.md`, `footer.md`, and `home.md`.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
rynzName (str): The name of the project directory.
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
safe_create_directory(path.join(rynzName, 'content', 'note'))
|
|
275
|
+
|
|
276
|
+
with open(path.join(rynzName, 'content', 'header.md'), 'w') as f:
|
|
277
|
+
f.write(
|
|
278
|
+
"* [Home](/)\n* [Other Links](/)\n"
|
|
279
|
+
"* [Edit content/header.md](/)"
|
|
280
|
+
)
|
|
281
|
+
print_message("header.md created.", "success", timestamp=True)
|
|
282
|
+
|
|
283
|
+
with open(path.join(rynzName, 'content', 'footer.md'), 'w') as f:
|
|
284
|
+
f.write(
|
|
285
|
+
"* [Home](/)\n* [RSS](/)\n"
|
|
286
|
+
"* [Edit content/footer.md](/)\n"
|
|
287
|
+
"* powered by [Rynz](https://rynz.de)"
|
|
288
|
+
)
|
|
289
|
+
print_message("footer.md created.", "success", timestamp=True)
|
|
290
|
+
|
|
291
|
+
with open(path.join(rynzName, 'content', 'home.md'), 'w') as f:
|
|
292
|
+
f.write(
|
|
293
|
+
"Welcome to Rynz.\nThis is a sample homepage which can be "
|
|
294
|
+
"edited at /content/home.md"
|
|
295
|
+
)
|
|
296
|
+
print_message("home.md created.", "success", timestamp=True)
|
|
297
|
+
|
|
298
|
+
# Ensure the note directory exists before calling createNote
|
|
299
|
+
note_dir = path.join(rynzName, 'content', 'note')
|
|
300
|
+
safe_create_directory(note_dir)
|
|
301
|
+
|
|
302
|
+
# Generate a sample note
|
|
303
|
+
createNote("notepage1.md", rynzName)
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
print_message(f"❌ Error creating content: {e}", "error", timestamp=True)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def createNote(noteName, rynzName=''):
|
|
310
|
+
"""
|
|
311
|
+
Creates a markdown note file with sample content.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
noteName (str): Name of the note file to create.
|
|
315
|
+
rynzName (str): Parent directory for the note file. Defaults to ''.
|
|
316
|
+
"""
|
|
317
|
+
try:
|
|
318
|
+
note_dir = path.join(rynzName, 'content', 'note')
|
|
319
|
+
|
|
320
|
+
# Ensure the directory exists
|
|
321
|
+
if not path.exists(note_dir):
|
|
322
|
+
makedirs(note_dir)
|
|
323
|
+
|
|
324
|
+
if not noteName.lower().endswith('.md'):
|
|
325
|
+
noteName += '.md'
|
|
326
|
+
|
|
327
|
+
with open(path.join(note_dir, noteName), 'w') as f:
|
|
328
|
+
conf_data = (
|
|
329
|
+
"""---
|
|
330
|
+
title: "Sample Note Page."
|
|
331
|
+
subtitle: "Sample subtitle"
|
|
332
|
+
date: {t}
|
|
333
|
+
tags: [note,generic]
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
# This is a sample note page which can be edited/renamed at /content/note/{noteName}
|
|
337
|
+
# This is heading 1
|
|
338
|
+
|
|
339
|
+
## This is heading 2
|
|
340
|
+
|
|
341
|
+
### This is heading 3
|
|
342
|
+
|
|
343
|
+
#### This is heading 4
|
|
344
|
+
|
|
345
|
+
##### This is heading 5
|
|
346
|
+
|
|
347
|
+
###### This is heading 6
|
|
348
|
+
|
|
349
|
+
**Bold**
|
|
350
|
+
|
|
351
|
+
*italic*
|
|
352
|
+
|
|
353
|
+
* list
|
|
354
|
+
* list
|
|
355
|
+
|
|
356
|
+
1. ordered list
|
|
357
|
+
2. ordered list
|
|
358
|
+
|
|
359
|
+
[link](/)
|
|
360
|
+
|
|
361
|
+
Sample paragraph is written like this with lorem ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
362
|
+
|
|
363
|
+
""").format(t=datetime.now().strftime("%Y-%m-%d"), noteName=noteName)
|
|
364
|
+
|
|
365
|
+
f.write(conf_data)
|
|
366
|
+
print_message(
|
|
367
|
+
f"{noteName} is created at {path.join(note_dir, noteName)}",
|
|
368
|
+
"success",
|
|
369
|
+
timestamp=True
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
except Exception as e:
|
|
373
|
+
print_message(
|
|
374
|
+
f"While generating page {noteName}, some issue occurred: {e}",
|
|
375
|
+
"error",
|
|
376
|
+
timestamp=True
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def createRynz(rynzName):
|
|
381
|
+
"""
|
|
382
|
+
Initializes a Rynz project with all necessary directories and files.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
rynzName (str): The name of the project directory.
|
|
386
|
+
"""
|
|
387
|
+
try:
|
|
388
|
+
safe_create_directory(rynzName)
|
|
389
|
+
safe_create_directory(path.join(rynzName, 'template'))
|
|
390
|
+
safe_create_directory(path.join(rynzName, 'static'))
|
|
391
|
+
safe_create_directory(path.join(rynzName, 'content'))
|
|
392
|
+
safe_create_directory(path.join(rynzName, 'content', 'note'))
|
|
393
|
+
|
|
394
|
+
createConfig(rynzName)
|
|
395
|
+
createTemplate(rynzName, 'home_template.html', home_template_content)
|
|
396
|
+
createTemplate(rynzName, 'note_template.html', note_template_content)
|
|
397
|
+
createTemplate(rynzName, 'feed_template.xml', feed_template_content)
|
|
398
|
+
createContent(rynzName)
|
|
399
|
+
|
|
400
|
+
print_message(
|
|
401
|
+
f"Rynz project {rynzName} created successfully!", "success",
|
|
402
|
+
timestamp=True
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
except Exception as e:
|
|
406
|
+
print_message(
|
|
407
|
+
f"❌ Error creating Rynz project {rynzName}: {e}", "error",
|
|
408
|
+
timestamp=True
|
|
409
|
+
)
|
|
410
|
+
sys.exit(1)
|