summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Luevano Alvarado <david@luevano.xyz>2022-04-17 02:13:36 -0600
committerDavid Luevano Alvarado <david@luevano.xyz>2022-04-17 02:13:36 -0600
commit3c0691df1b7ff1fd04e22ab6055e84cc2137504e (patch)
treedded20edb6c37f2b6904447684db693fcddf16a7
parentf203d11cff37d6809396d0ea39bf53abe9bad182 (diff)
refactor config handler and morev0.6.0
Refactored the configuration handling to use configparser; also the argument parser; generally added more 'logging'; updated template resources; and more minor things
-rw-r--r--README.md169
-rw-r--r--src/pyssg/arg_parser.py50
-rw-r--r--src/pyssg/builder.py32
-rw-r--r--src/pyssg/configuration.py151
-rw-r--r--src/pyssg/page.py34
-rw-r--r--src/pyssg/parser.py18
-rw-r--r--src/pyssg/plt/default.ini22
-rw-r--r--src/pyssg/plt/index.html6
-rw-r--r--src/pyssg/plt/page.html4
-rw-r--r--src/pyssg/plt/rss.xml18
-rw-r--r--src/pyssg/plt/sitemap.xml2
-rw-r--r--src/pyssg/plt/tag.html4
-rw-r--r--src/pyssg/pyssg.py85
-rw-r--r--src/pyssg/utils.py28
14 files changed, 311 insertions, 312 deletions
diff --git a/README.md b/README.md
index 9e4275b..1a0a3b4 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,8 @@
Inspired (initially) by Roman Zolotarev's [`ssg5`](https://rgz.ee/bin/ssg5) and [`rssg`](https://rgz.ee/bin/rssg), Luke Smith's [`lb` and `sup`](https://github.com/LukeSmithxyz/lb) and, pedantic.software's great (but *"mamador"*, as I would say in spanish) [`blogit`](https://pedantic.software/git/blogit/).
-I'm writing this in *pYtHoN* (thought about doing it in Go, but I'm most comfortable with Python at the moment) because I want features from all of these minimal programs (and more), but I don't really want to be stitching each one of the features on any of these programs, because they're written in a way to only work as how they were first imagined to work like; I already tried adding features to `ssg` and ended up rewriting it in POSIX shell, but it was a pain in the ass when I tried to add even more, and don't get me started on trying to extend `blogit`... And also because I want to.
-
## Current features
-**This is still a WIP. Still doesn't build `sitemap.xml` or `rss.xml` files.**
-
- [x] Build static site parsing `markdown` files ( `*.md` -> `*.html`)
- [x] ~~Using plain `*.html` files for templates.~~ Changed to Jinja templates.
- [x] Would like to change to something more flexible and easier to manage ([`jinja`](https://jinja.palletsprojects.com/en/3.0.x/), for example).
@@ -15,15 +11,20 @@ I'm writing this in *pYtHoN* (thought about doing it in Go, but I'm most comfort
- [x] Tag functionality.
- [ ] Open Graph (and similar) support. (Technically, this works if you add the correct metadata to the `*.md` files and use the variables available for Jinja)
- [x] Build `sitemap.xml` file.
+ - [ ] Include manually added `*.html` files.
- [x] Build `rss.xml` file.
- [ ] Join the `static_url` to all relative URLs found to comply with the [RSS 2.0 spec](https://validator.w3.org/feed/docs/rss2.html) (this would be added to the parsed HTML text extracted from the MD files, so it would be available to the created `*.html` and `*.xml` files). Note that depending on the reader, it will append the URL specified in the RSS file or use the [`xml:base`](https://www.rssboard.org/news/151/relative-links) specified ([newsboat](https://newsboat.org/) parses `xml:base`).
+ - [ ] Include manually added `*.html` files.
- [x] Only build page if `*.md` is new or updated.
- [ ] Extend this to tag pages and index (right now all tags and index is built no matter if no new/updated file is present).
-- [x] Configuration file as an alternative to using command line flags (configuration file options are prioritized).
+- [x] Configuration file. ~~as an alternative to using command line flags (configuration file options are prioritized).~~
+ - [x] Use [`configparser`](https://docs.python.org/3/library/configparser.html) instead of custom config handler.
+
+**Please note that I've removed the use of command line flags for now as it was too much bloat and unnecessary.**
### To be added/fixed
-- [ ] Avoid the program to freak out when there are directories created in advance.
+- [x] Avoid the program to freak out when there are directories created in advance.
- [ ] Provide more meaningful error messages when you are missing mandatory tags in your `.md` files.
### Markdown features
@@ -48,31 +49,35 @@ Just install it with `pip`:
pip install pyssg
```
-*EW!*, I know..., I will try to make a PKBUILD and release it in AUR or something; hit me up if you do it to add it here.
+Will add a PKBUILD (and possibly submit it to the AUR) sometime later.
## Usage
-It is intended to be used as a standalone terminal program running on the "root" directory where you have the `src` and `dst` directories in (defaults for both flags).
-
-First initialize the directories you're going to use for the source files and destination files:
+1. Get the default configuration file:
```sh
-pyssg -s src_dir -d dst_dir -i
+pyssg --copy-default-config -c <path/to/config>
```
-You do not have to create any directories, in advance, the command above will do it.
-Actually for the moment I will encourage you to **not create** any directories in advance.
-That creates the desired directories with the basic templates that can be edited as desired (see variables available for Jinja below). Place your `*.md` files somewhere inside the source directory (`src_dir` in the command above), but outside of the `templates` directory. It accepts sub-directories.
+Where `-c` is optional as by default `$XDG_CONFIG_HOME/pyssg/config.ini` is used.
+
+2. Edit the config file created as needed.
-Strongly recommended to edit the `rss.xml` template.
+- `config.ini` is parsed using Python's [`configparser`](https://docs.python.org/3/library/configparser.html), [more about the config file](#config-file).
-Build the site with:
+3. Initialize the directory structures (source, destination, template) and move template files:
```sh
-pyssg -s src_dir -d dst_dir -t plt_dir -u https://base.url -b
+pyssg -i
```
-Remember to add the mandatory meta-data keys to your `.md` files, these are:
+- You can modify the basic templates as needed (see [variables available for Jinja](#available-jinja-variables)).
+
+- Strongly recommended to edit the `rss.xml` template.
+
+4. Place your `*.md` files somewhere inside the source directory. It accepts sub-directories.
+
+- Remember to add the mandatory meta-data keys to your `.md` files, these are:
```
title: the title of your blog entry or whatever
@@ -81,54 +86,94 @@ lang: the language the entry is written on
summary: a summary of the entry
```
-You can add more meta-data keys as long as it is [Python-Markdown compliant](https://python-markdown.github.io/extensions/meta_data/).
+- You can add more meta-data keys as long as it is [Python-Markdown compliant](https://python-markdown.github.io/extensions/meta_data/), and these will ve [available as Jinja variables](#available-jinja-variables).
+
+- Also strongly recomended to add the `tags` metadata so that `pyssg` generates some nice filtering tags.
-Also strongly recomended to add the `tags` test for `pyssg` to generate some nice filtering tags.
+5. Build the `*.html` with:
+
+```sh
+pyssg -b
+```
+
+- After this, you have ready to deploy `*.html` files.
+
+- For now, the building option also creates the `rss.xml` and `sitemap.xml` files based on templates, including only all converted `*.md` files (and processed tags in case of the sitemap), meaning that separate `*.html` files should be included manually in the template.
+
+## Config file
+
+All sections/options need to be compliant with the [`configparser`](https://docs.python.org/3/library/configparser.html).
+
+At least the sections and options given in the default config should be present:
+
+```ini
+[path]
+src=src # source
+dst=dst # destination
+plt=plt # template
+[url]
+main=https://example.com
+static=https://static.example.com # used for static resources (images, js, css, etc)
+default_image=/images/default.png # this will be appended to 'static' at the end
+[fmt] # % needs to be escaped with another %
+date=%%a, %%b %%d, %%Y @ %%H:%%M %%Z
+list_date=%%b %%d
+list_sep_date=%%B %%Y
+[info]
+title=Example site
+[other]
+force=False
+```
+
+Along with these, these extra ones will be added on runtime:
+
+```ini
+[fmt]
+rss_date=%%a, %%d %%b %%Y %%H:%%M:%%S GMT # fixed
+sitemap_date=%%Y-%%m-%%d # fixed
+[info]
+version= # current 'pyssg' version (0.5.1.dev16, for example)
+rss_run_date= # date the program was run, formatted with 'rss_date'
+sitemap_run_date= # date the program was run, formatted with 'sitemap_date'
+```
-So...that creates all `*.html` for the site and can be easily moved to the server. Here, the `-u` flag is technically optional in the sense that you'll not receive a warning/error, but it's used to prepend links with this URL (not strictly required everywhere), so don't ignore it; also don't include the trailing `/`.
+You can add any other option/section that you can later use in the Jinja templates via the exposed config object.
-For now, the `-b`uild tag also creates the `rss.xml` and `sitemap.xml` files based on templates including only all converted `*.md` files (and processed tags in case of the sitemap), meaning that separate `*.html` files should be included manually in the template.
+Other requisites are:
-For more options/flags just checkout `pyssg -h`.
+- Urls shouldn't have the trailing slash `/`.
+- The only character that needs to be escaped is `%` with another `%`.
## Available Jinja variables
-Here is the list of variables that you can use specific Jinja templates with a short description. Note that all urls are without the trailing slash `/`.
-
-- `config` (`Configuration`) (all): configuration object containing general/global attributes, the useful ones being:
- - `title` (`str`): title of the website.
- - `url` (`str`): base url of the website.
- - `static_url` (`str`): base static url where all static files are located, mostly needed for correct rss feed generator when using a `base` tag and using relative links to files. For more, see [<base>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base).
- - `default_image_url` (`str`): as defined in `DEFAULT_IMAGE_URL` configuration option.
- - `version` (`str`): version in numeric form, i.e. `0.5.0`.
- - `run_date` (`str`): date when the program was run, with format required for rss.
-- Pages:
- - `all_pages` (`list(Page)`) (all): list of all the pages, sorted by creation time, reversed.
- - `page` (`Page`) (`page.html`): page object that contains the following attributes:
- - `title` (`str`): title of the page.
- - `author` (`str`): author of the page.
- - `content` (`str`): actual content of the page.
- - `cdatetime` (`str`): creation datetime object of the page.
- - `cdate` (`str`): formatted `cdatetime` as the configuration option `DATE_FORMAT`.
- - `cdate_list` (`str`): formatted `cdatetime` as the configuration option `LIST_DATE_FORMAT`.
- - `cdate_list_sep` (`str`): formatted `cdatetime` as the configuration option `LIST_SEP_DATE_FORMAT`.
- - `cdate_rss` (`str`): formatted `cdatetime` as required by rss.
- - `cdate_sitemap` (`str`): formatted `cdatetime` as required by sitemap.
- - `mdatetime` (`str`): modification datetime object of the page. Defaults to None.
- - `mdate` (`str`): formatted `mdatetime` as the configuration option `DATE_FORMAT`. Defaults to None.
- - `mdate_list` (`str`): formatted `mdatetime` as the configuration option `LIST_DATE_FORMAT`.
- - `mdate_list_sep` (`str`): formatted `mdatetime` as the configuration option `LIST_SEP_DATE_FORMAT`.
- - `mdate_rss` (`str`): formatted `mdatetime` as required by rss.
- - `mdate_sitemap` (`str`): formatted `mdatetime` as required by sitemap.
- - `summary` (`str`): summary of the page, as specified in the `*.md` file.
- - `lang` (`str`): page language, used for the general `html` tag `lang` attribute.
- - `tags` (`list(tuple(str))`): list of tuple of tags of the page, containing the name and the url of the tag, in that order. Defaults to empty list.
- - `url` (`str`): url of the page, this already includes the `config.url`.
- - `image_url` (`str`): image url of the page, this already includes the `config.static_url`. Defaults to the `DEFAULT_IMAGE_URL` configuration option.
- - `next/previous` (`Page`): reference to the next or previous page object (containing all these attributes). Defaults to None
- - `og` (`dict(str, str)`): dict for object graph metadata.
- - `meta` (`dict(str, list(str))`): meta dict as obtained from python-markdown, in case you use a meta tag not yet supported, it will be available there.
-- Tags:
- - `tag` (`tuple(str)`) (`tag.html`): tuple of name and url of the current tag.
- - `tag_pages` (`list(Page)`) (`tag.html`): similar to `all_pages` but contains all the pages for the current tag.
- - `all_tags` (`list(tuple(str))`) (all): similar to `page.tags` but contains all the tags.
+These variables are exposed to use within the templates. The below list is in the form of *variable (type) (available from): description*. `section/option` describe config file section and option and `object.attribute` corresponding object and it's attribute.
+
+- `config` (`ConfigParser`) (all): parsed config file plus the added options internally (as described in [config file](#config-file)).
+- `all_pages` (`list(Page)`) (all): list of all the pages, sorted by creation time, reversed.
+- `page` (`Page`) (`page.html`): contains the following attributes (genarally these are parsed from the metadata in the `*.md` files):
+ - `title` (`str`): title of the page.
+ - `author` (`str`): author of the page.
+ - `content` (`str`): actual content of the page, this is the `html`.
+ - `cdatetime` (`str`): creation datetime object of the page.
+ - `cdate` (`str`): formatted `cdatetime` as the config option `fmt/date`.
+ - `cdate_list` (`str`): formatted `cdatetime` as the config option `fmt/list_date`.
+ - `cdate_list_sep` (`str`): formatted `cdatetime` as the config option `fmt/list_sep_date`.
+ - `cdate_rss` (`str`): formatted `cdatetime` as required by rss.
+ - `cdate_sitemap` (`str`): formatted `cdatetime` as required by sitemap.
+ - `mdatetime` (`str`): modification datetime object of the page. Defaults to `None`.
+ - `mdate` (`str`): formatted `mdatetime` as the config option `fmt/date`. Defaults to `None`.
+ - `mdate_list` (`str`): formatted `mdatetime` as the config option `fmt/list_date`.
+ - `mdate_list_sep` (`str`): formatted `mdatetime` as the config option `fmt/list_sep_date`.
+ - `mdate_rss` (`str`): formatted `mdatetime` as required by rss.
+ - `mdate_sitemap` (`str`): formatted `mdatetime` as required by sitemap.
+ - `summary` (`str`): summary of the page, as specified in the `*.md` file.
+ - `lang` (`str`): page language, used for the general `html` tag `lang` attribute.
+ - `tags` (`list(tuple(str))`): list of tuple of tags of the page, containing the name and the url of the tag, in that order. Defaults to empty list.
+ - `url` (`str`): url of the page, this already includes the `url/main` from config file.
+ - `image_url` (`str`): image url of the page, this already includes the `url/static`. Defaults to the `url/default_image` config option.
+ - `next/previous` (`Page`): reference to the next or previous page object (containing all these attributes). Defaults to `None`.
+ - `og` (`dict(str, str)`): dict for object graph metadata.
+ - `meta` (`dict(str, list(str))`): meta dict as obtained from python-markdown, in case you use a meta tag not yet supported, it will be available there.
+- `tag` (`tuple(str)`) (`tag.html`): tuple of name and url of the current tag.
+- `tag_pages` (`list(Page)`) (`tag.html`): similar to `all_pages` but contains all the pages for the current tag.
+- `all_tags` (`list(tuple(str))`) (all): similar to `page.tags` but contains all the tags.
diff --git a/src/pyssg/arg_parser.py b/src/pyssg/arg_parser.py
index 4ee7d57..90fb8c1 100644
--- a/src/pyssg/arg_parser.py
+++ b/src/pyssg/arg_parser.py
@@ -3,21 +3,40 @@ from argparse import ArgumentParser, Namespace
def get_parsed_arguments() -> Namespace:
parser = ArgumentParser(prog='pyssg',
- description='''Static Site Generator that reads
- Markdown files and creates HTML files.\nIf
- [-c]onfig file is provided (or exists in default
- location) all other options are ignored.\nFor
- datetime formats see:
+ description='''Static Site Generator that parses
+ Markdown files into HTML files. For datetime
+ formats see:
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes''')
parser.add_argument('-v', '--version',
action='store_true',
help='''print program version''')
parser.add_argument('-c', '--config',
- default='$XDG_CONFIG_HOME/pyssg/pyssgrc',
+ # don't give a default here, as it would seem like
+ # --config was passed
+ # default='$XDG_CONFIG_HOME/pyssg/config.ini',
type=str,
- help='''config file (path) to read from; defaults to
- 'pyssgrc' first, then
- '$XDG_CONFIG_HOME/pyssg/pyssgrc' ''')
+ help='''config file (path) to read from; if not passed,
+ '$XDG_CONFIG_HOME/pyssg/config.ini' is used''')
+ parser.add_argument('--copy-default-config',
+ action='store_true',
+ help='''copies the default config to path specified in
+ --config flag''')
+ parser.add_argument('-i', '--init',
+ action='store_true',
+ help='''initializes the directory structures and copies
+ over default templates''')
+ parser.add_argument('-b', '--build',
+ action='store_true',
+ help='''generates all HTML files by parsing MD files
+ present in source directory and copies over manually
+ written HTML files''')
+ parser.add_argument('-f', '--force',
+ action='store_true',
+ help='''force building all pages and not only the
+ updated ones''')
+ # really not needed, too much bloat and case scenarios to check for,
+ # instead, just read from config file or default config file
+ """
parser.add_argument('-s', '--src',
default='src',
type=str,
@@ -68,17 +87,6 @@ def get_parsed_arguments() -> Namespace:
help='''date format used for the separator between page
entries in a list; defaults to '%%B %%Y' ('March 2021',
for example)''')
- parser.add_argument('-i', '--init',
- action='store_true',
- help='''initializes the dir structure, templates,
- as well as the 'src' and 'dst' directories''')
- parser.add_argument('-b', '--build',
- action='store_true',
- help='''generates all html files and passes over
- existing (handmade) ones''')
- parser.add_argument('-f', '--force',
- action='store_true',
- help='''force building all pages and not only the
- updated ones''')
+ """
return parser.parse_args()
diff --git a/src/pyssg/builder.py b/src/pyssg/builder.py
index 84494da..130062e 100644
--- a/src/pyssg/builder.py
+++ b/src/pyssg/builder.py
@@ -4,8 +4,8 @@ from copy import deepcopy
from operator import itemgetter
from jinja2 import Environment, Template
from markdown import Markdown
+from configparser import ConfigParser
-from .configuration import Configuration
from .database import Database
from .parser import MDParser
from .page import Page
@@ -13,11 +13,11 @@ from .discovery import get_file_list, get_dir_structure
class Builder:
- def __init__(self, config: Configuration,
+ def __init__(self, config: ConfigParser,
env: Environment,
db: Database,
md: Markdown):
- self.config: Configuration = config
+ self.config: ConfigParser = config
self.env: Environment = env
self.db: Database = db
self.md: Markdown = md
@@ -33,15 +33,19 @@ class Builder:
def build(self) -> None:
- self.dirs = get_dir_structure(self.config.src, ['templates'])
- self.md_files = get_file_list(self.config.src, ['.md'], ['templates'])
- self.html_files = get_file_list(self.config.src, ['.html'], ['templates'])
+ self.dirs = get_dir_structure(self.config.get('path', 'src'),
+ ['templates'])
+ self.md_files = get_file_list(self.config.get('path', 'src'),
+ ['.md'],
+ ['templates'])
+ self.html_files = get_file_list(self.config.get('path', 'src'),
+ ['.html'],
+ ['templates'])
self.__create_dir_structure()
self.__copy_html_files()
- parser: MDParser = MDParser(self.config.src,
- self.md_files,
+ parser: MDParser = MDParser(self.md_files,
self.config,
self.db,
self.md)
@@ -69,7 +73,7 @@ class Builder:
# for the dir structure,
# doesn't matter if the dir already exists
try:
- os.makedirs(os.path.join(self.config.dst, d))
+ os.makedirs(os.path.join(self.config.get('path', 'dst'), d))
except FileExistsError:
pass
@@ -79,18 +83,18 @@ class Builder:
dst_file: str = None
for f in self.html_files:
- src_file = os.path.join(self.config.src, f)
- dst_file = os.path.join(self.config.dst, f)
+ src_file = os.path.join(self.config.get('path', 'src'), f)
+ dst_file = os.path.join(self.config.get('path', 'dst'), f)
# only copy files if they have been modified (or are new)
- if self.db.update(src_file, remove=f'{self.config.src}/'):
+ if self.db.update(src_file, remove=f'{self.config.get("path", "src")}/'):
shutil.copy2(src_file, dst_file)
def __render_articles(self) -> None:
article_vars: dict = deepcopy(self.common_vars)
# check if only updated should be created
- if self.config.force:
+ if self.config.getboolean('other', 'force'):
for p in self.all_pages:
article_vars['page'] = p
self.__render_template("page.html",
@@ -132,5 +136,5 @@ class Builder:
template: Template = self.env.get_template(template_name)
content: str = template.render(**template_vars)
- with open(os.path.join(self.config.dst, file_name), 'w') as f:
+ with open(os.path.join(self.config.get('path', 'dst'), file_name), 'w') as f:
f.write(content)
diff --git a/src/pyssg/configuration.py b/src/pyssg/configuration.py
index 5a09a7a..dd6bfaa 100644
--- a/src/pyssg/configuration.py
+++ b/src/pyssg/configuration.py
@@ -1,133 +1,42 @@
-from typing import Union
+import sys
from importlib.metadata import version
+from importlib.resources import path as rpath
from datetime import datetime, timezone
+from configparser import ConfigParser
-class Configuration:
- def __init__(self, path: str):
- self.path: str = path
- # config file specific
- self.src: str = None
- self.dst: str = None
- self.plt: str = None
- self.url: str = None
- self.static_url: str = None
- self.default_image_url: str = None
- self.title: str = None
- self.dformat: str = None
- self.l_dformat: str = None
- self.lsep_dformat: str = None
- self.force: bool = None
+DEFAULT_CONFIG_PATH = '$XDG_CONFIG_HOME/pyssg/config.ini'
+VERSION = version('pyssg')
- # other
- self.version: str = version('pyssg')
- self.dformat_rss: str = '%a, %d %b %Y %H:%M:%S GMT'
- self.dformat_sitemap: str = '%Y-%m-%d'
- self.run_date_rss = datetime.now(tz=timezone.utc).strftime(self.dformat_rss)
- self.run_date_sitemap = \
- datetime.now(tz=timezone.utc).strftime(self.dformat_sitemap)
+def __check_well_formed_config(config: ConfigParser) -> None:
+ default_config: ConfigParser = ConfigParser()
+ with rpath('pyssg.plt', 'default.ini') as p:
+ default_config.read(p)
- def read(self):
- try:
- lines: list[str] = None
- with open(self.path, 'r') as f:
- lines = f.readlines()
+ for section in default_config.sections():
+ if not config.has_section(section):
+ print(f'config does not have section "{section}"')
+ sys.exit(1)
+ for option in default_config.options(section):
+ if not config.has_option(section, option):
+ print(f'config does not have option "{option}" in section "{section}"')
+ sys.exit(1)
- opts: dict[str, Union[str, bool]] = dict()
- for l in lines:
- kv: list[str] = l.split('=', 1)
- if len(kv) != 2:
- raise Exception('wrong config syntax')
- k: str = kv[0].strip().lower()
- v_temp: str = kv[1].strip()
- # check if value should be a boolean true
- v: Union[str, bool] = v_temp\
- if v_temp.lower() not in ['true', '1', 'yes']\
- else True
+def get_parsed_config(path: str) -> ConfigParser:
+ config: ConfigParser = ConfigParser()
+ config.read(path)
- opts[k] = v
+ __check_well_formed_config(config)
- try:
- self.src = opts['src']
- except KeyError: pass
+ # set other required options
+ config.set('fmt', 'rss_date', '%%a, %%d %%b %%Y %%H:%%M:%%S GMT')
+ config.set('fmt', 'sitemap_date', '%%Y-%%m-%%d')
+ config.set('info', 'version', VERSION)
+ config.set('info', 'rss_run_date', datetime.now(
+ tz=timezone.utc).strftime(config.get('fmt', 'rss_date')))
+ config.set('info', 'sitemap_run_date', datetime.now(
+ tz=timezone.utc).strftime(config.get('fmt', 'sitemap_date')))
- try:
- self.dst = opts['dst']
- except KeyError: pass
-
- try:
- self.plt = opts['plt']
- except KeyError: pass
-
- try:
- self.url = opts['url']
- except KeyError: pass
-
- try:
- self.static_url = opts['static_url']
- except KeyError: pass
-
- try:
- self.default_image_url = opts['default_image_url']
- except KeyError: pass
-
- try:
- self.title = opts['title']
- except KeyError: pass
-
- try:
- self.dformat = opts['date_format']
- except KeyError: pass
-
- try:
- self.l_dformat = opts['list_date_format']
- except KeyError: pass
-
- try:
- self.lsep_dformat = opts['list_sep_date_format']
- except KeyError: pass
-
- try:
- # if the parser above didn't read a boolean true, then take it
- # as a false anyways
- self.force = opts['force'] if opts['force'] is True else False
- except KeyError: pass
-
- except OSError: pass
-
-
- def fill_missing(self, opts: dict[str, Union[str, bool]]) -> None:
- if self.src is None:
- self.src = opts['src']
-
- if self.dst is None:
- self.dst = opts['dst']
-
- if self.plt is None:
- self.plt = opts['plt']
-
- if self.url is None:
- self.url = opts['url']
-
- if self.static_url is None:
- self.static_url = opts['static_url']
-
- if self.default_image_url is None:
- self.default_image_url = opts['default_image_url']
-
- if self.title is None:
- self.title = opts['title']
-
- if self.dformat is None:
- self.dformat = opts['date_format']
-
- if self.l_dformat is None:
- self.l_dformat = opts['list_date_format']
-
- if self.lsep_dformat is None:
- self.lsep_dformat = opts['list_sep_date_format']
-
- if self.force is None:
- self.force = opts['force']
+ return config
diff --git a/src/pyssg/page.py b/src/pyssg/page.py
index 6b83d39..784749c 100644
--- a/src/pyssg/page.py
+++ b/src/pyssg/page.py
@@ -1,6 +1,6 @@
from datetime import datetime, timezone
-from .configuration import Configuration
+from configparser import ConfigParser
class Page:
@@ -10,14 +10,14 @@ class Page:
mtime: float,
html: str,
meta: dict,
- config: Configuration):
+ config: ConfigParser):
# initial data
self.name: str = name
self.ctimestamp: float = ctime
self.mtimestamp: float = mtime
self.content: str = html
self.meta: dict = meta
- self.config: Configuration = config
+ self.config: ConfigParser = config
# data from self.meta
self.title: str = ''
@@ -66,23 +66,23 @@ class Page:
# dates
self.cdatetime = datetime.fromtimestamp(self.ctimestamp,
tz=timezone.utc)
- self.cdate = self.cdatetime.strftime(self.config.dformat)
- self.cdate_list = self.cdatetime.strftime(self.config.l_dformat)
- self.cdate_list_sep = self.cdatetime.strftime(self.config.lsep_dformat)
- self.cdate_rss = self.cdatetime.strftime(self.config.dformat_rss)
+ self.cdate = self.cdatetime.strftime(self.config.get('fmt', 'date'))
+ self.cdate_list = self.cdatetime.strftime(self.config.get('fmt', 'list_date'))
+ self.cdate_list_sep = self.cdatetime.strftime(self.config.get('fmt', 'list_sep_date'))
+ self.cdate_rss = self.cdatetime.strftime(self.config.get('fmt', 'rss_date'))
self.cdate_sitemap = \
- self.cdatetime.strftime(self.config.dformat_sitemap)
+ self.cdatetime.strftime(self.config.get('fmt', 'sitemap_date'))
# only if file/page has been modified
if self.mtimestamp != 0.0:
self.mdatetime = datetime.fromtimestamp(self.mtimestamp,
tz=timezone.utc)
- self.mdate = self.mdatetime.strftime(self.config.dformat)
- self.mdate_list = self.mdatetime.strftime(self.config.l_dformat)
- self.mdate_list_sep = self.mdatetime.strftime(self.config.lsep_dformat)
- self.mdate_rss = self.mdatetime.strftime(self.config.dformat_rss)
+ self.mdate = self.mdatetime.strftime(self.config.get('fmt', 'date'))
+ self.mdate_list = self.mdatetime.strftime(self.config.get('fmt', 'list_date'))
+ self.mdate_list_sep = self.mdatetime.strftime(self.config.get('fmt', 'list_sep_date'))
+ self.mdate_rss = self.mdatetime.strftime(self.config.get('fmt', 'rss_date'))
self.mdate_sitemap = \
- self.mdatetime.strftime(self.config.dformat_sitemap)
+ self.mdatetime.strftime(self.config.get('fmt', 'sitemap_date'))
# not always contains tags
try:
@@ -91,17 +91,17 @@ class Page:
for t in tags_only:
self.tags.append((t,
- f'{self.config.url}/tag/@{t}.html'))
+ f'{self.config.get("url", "main")}/tag/@{t}.html'))
except KeyError: pass
- self.url = f'{self.config.url}/{self.name.replace(".md", ".html")}'
+ self.url = f'{self.config.get("url", "main")}/{self.name.replace(".md", ".html")}'
try:
self.image_url = \
- f'{self.config.static_url}/{self.meta["image_url"][0]}'
+ f'{self.config.get("url", "static")}/{self.meta["image_url"][0]}'
except KeyError:
self.image_url = \
- f'{self.config.static_url}/{self.config.default_image_url}'
+ f'{self.config.get("url", "static")}/{self.config.get("url", "default_image")}'
# if contains open graph elements
try:
diff --git a/src/pyssg/parser.py b/src/pyssg/parser.py
index f2d23eb..2888fcb 100644
--- a/src/pyssg/parser.py
+++ b/src/pyssg/parser.py
@@ -1,24 +1,21 @@
import os
from operator import itemgetter
-from datetime import datetime
from markdown import Markdown
+from configparser import ConfigParser
from .database import Database
-from .configuration import Configuration
from .page import Page
# parser of md files, stores list of pages and tags
class MDParser:
- def __init__(self, src: str,
- files: list[str],
- config: Configuration,
+ def __init__(self, files: list[str],
+ config: ConfigParser,
db: Database,
md: Markdown):
- self.src: str = src
self.files: list[str] = files
- self.config: Configuration = config
+ self.config: ConfigParser = config
self.db: Database = db
self.md: Markdown = md
@@ -32,12 +29,13 @@ class MDParser:
self.all_pages = []
self.updated_pages = []
self.all_tags = []
- all_tag_names: list[str] = []
+ # not used, not sure why i had this
+ # all_tag_names: list[str] = []
for f in self.files:
- src_file: str = os.path.join(self.src, f)
+ src_file: str = os.path.join(self.config.get('path', 'src'), f)
# get flag if update is successful
- updated: bool = self.db.update(src_file, remove=f'{self.src}/')
+ updated: bool = self.db.update(src_file, remove=f'{self.config.get("path", "src")}/')
content: str = self.md.reset().convert(open(src_file).read())
page: Page = Page(f,
diff --git a/src/pyssg/plt/default.ini b/src/pyssg/plt/default.ini
index 2700d28..ab4eac1 100644
--- a/src/pyssg/plt/default.ini
+++ b/src/pyssg/plt/default.ini
@@ -1,14 +1,16 @@
-[dir_paths]
+[path]
src=src
dst=dst
plt=plt
-[urls]
-url=https://example.com
-static_url=https://static.example.com
-default_image_url=/images/default.png
-[formats]
-date_format=%%a, %%b %%d, %%Y @ %%H:%%M %%Z
-list_date_format=%%b %%d
-list_sep_date_format=%%B %%Y
+[url]
+main=https://example.com
+static=https://static.example.com
+default_image=/images/default.png
+[fmt]
+date=%%a, %%b %%d, %%Y @ %%H:%%M %%Z
+list_date=%%b %%d
+list_sep_date=%%B %%Y
[info]
-title=Example site \ No newline at end of file
+title=Example site
+[other]
+force=False \ No newline at end of file
diff --git a/src/pyssg/plt/index.html b/src/pyssg/plt/index.html
index e06efdb..09ca786 100644
--- a/src/pyssg/plt/index.html
+++ b/src/pyssg/plt/index.html
@@ -2,11 +2,11 @@
<html lang="en">
<head>
<meta charset="utf-8">
- <base href="{{config.static_url}}">
- <title>Index -- {{config.title}}</title>
+ <base href="{{config.get('url', 'static')}}">
+ <title>Index -- {{config.get('info', 'title')}}</title>
</head>
<body>
- <h1>Index -- {{config.title}}</h1>
+ <h1>Index -- {{config.get('info', 'title')}}</h1>
<p>Some text here.</p>
<p>Tags:
diff --git a/src/pyssg/plt/page.html b/src/pyssg/plt/page.html
index 2fc3943..15663fa 100644
--- a/src/pyssg/plt/page.html
+++ b/src/pyssg/plt/page.html
@@ -2,8 +2,8 @@
<html lang="{{page.lang}}">
<head>
<meta charset="utf-8">
- <base href="{{config.static_url}}">
- <title>{{page.title}} -- {{config.title}}</title>
+ <base href="{{config.get('url', 'static')}}">
+ <title>{{page.title}} -- {{config.get('info', 'title')}}</title>
</head>
<body>
<h1>{{page.title}}</h1>
diff --git a/src/pyssg/plt/rss.xml b/src/pyssg/plt/rss.xml
index 42020d7..be6ddf0 100644
--- a/src/pyssg/plt/rss.xml
+++ b/src/pyssg/plt/rss.xml
@@ -3,24 +3,24 @@
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
- <title>{{config.title}}</title>
- <link>{{config.url}}</link>
- <atom:link href="{{config.url}}/rss.xml" rel="self" type="application/rss+xml"/>
+ <title>{{config.get('info', 'title')}}</title>
+ <link>{{config.get('url', 'main')}}</link>
+ <atom:link href="{{config.get('url', 'main')}}/rss.xml" rel="self" type="application/rss+xml"/>
<description>Short site description.</description>
<language>en-us</language>
<category>Blog</category>
<copyright>Copyright 2021 Somebody</copyright>
<managingEditor>some@one.com (Sombody)</managingEditor>
<webMaster>some@one.com (Sombody)</webMaster>
- <pubDate>{{config.run_date_rss}}</pubDate>
- <lastBuildDate>{{run_date_rss}}</lastBuildDate>
- <generator>pyssg v{{config.version}}</generator>
+ <pubDate>{{config.get('info', 'rss_run_date')}}</pubDate>
+ <lastBuildDate>{{config.get('info', 'rss_run_date')}}</lastBuildDate>
+ <generator>pyssg v{{config.get('info', 'version')}}</generator>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<ttl>30</ttl>
<image>
- <url>{{config.static_url}}/images/blog.png</url>
- <title>{{config.title}}</title>
- <link>{{config.url}}</link>
+ <url>{{config.get('url', 'static')}}/images/blog.png</url>
+ <title>{{config.get('info', 'title')}}</title>
+ <link>{{config.get('url', 'main')}}</link>
</image>
{%for p in all_pages%}
<item>
diff --git a/src/pyssg/plt/sitemap.xml b/src/pyssg/plt/sitemap.xml
index 26ee5c1..af1212a 100644
--- a/src/pyssg/plt/sitemap.xml
+++ b/src/pyssg/plt/sitemap.xml
@@ -14,7 +14,7 @@
{%for t in all_tags%}
<url>
<loc>{{t[1]}}</loc>
- <lastmod>{{config.run_date_sitemap}}</lastmod>
+ <lastmod>{{config.get('info', 'sitemap_run_date')}}</lastmod>
<changefreq>daily</changefreq>
<priority>0.5</priority>
</url>
diff --git a/src/pyssg/plt/tag.html b/src/pyssg/plt/tag.html
index d856ce4..ffd1956 100644
--- a/src/pyssg/plt/tag.html
+++ b/src/pyssg/plt/tag.html
@@ -2,8 +2,8 @@
<html lang="en">
<head>
<meta charset="utf-8">
- <base href="{{config.static_url}}">
- <title>Posts filtered by {{tag[0]}} -- {{config.title}}</title>
+ <base href="{{config.get('url', 'static')}}">
+ <title>Posts filtered by {{tag[0]}} -- {{config.get('info', 'title')}}</title>
</head>
<body>
<h1>Posts filtered by {{tag[0]}}</h1>
diff --git a/src/pyssg/pyssg.py b/src/pyssg/pyssg.py
index e694565..9b82231 100644
--- a/src/pyssg/pyssg.py
+++ b/src/pyssg/pyssg.py
@@ -1,7 +1,8 @@
import os
-import shutil
-from importlib.resources import path
+import sys
+from importlib.resources import path as rpath
from typing import Union
+from configparser import ConfigParser
from jinja2 import Environment, FileSystemLoader
from markdown import Markdown
@@ -9,61 +10,65 @@ from yafg import YafgExtension
from MarkdownHighlight.highlight import HighlightExtension
from markdown_checklist.extension import ChecklistExtension
+from .utils import create_dir, copy_file, sanity_check_path
from .arg_parser import get_parsed_arguments
-from .configuration import Configuration
+from .configuration import get_parsed_config, DEFAULT_CONFIG_PATH, VERSION
from .database import Database
from .builder import Builder
def main() -> None:
- opts: dict[str, Union[str, bool]] = vars(get_parsed_arguments())
- conf_path: str = opts['config']
- conf_path = os.path.expandvars(conf_path)
-
-
- config: Configuration = None
- if os.path.exists('pyssgrc'):
- config = Configuration('pyssgrc')
- else:
- config = Configuration(conf_path)
-
- config.read()
- config.fill_missing(opts)
-
- if opts['version']:
- print(f'pyssg v{config.version}')
- return
-
- if opts['init']:
- try:
- os.mkdir(config.src)
- os.makedirs(os.path.join(config.dst, 'tag'))
- os.mkdir(config.plt)
- except FileExistsError:
- pass
-
- # copy basic template files
+ args: dict[str, Union[str, bool]] = vars(get_parsed_arguments())
+ if not len(sys.argv) > 1:
+ print(f'pyssg v{VERSION} - no arguments passed, --help for more')
+ sys.exit(0)
+
+ if args['version']:
+ print(f'pyssg v{VERSION}')
+ sys.exit(0)
+
+ config_path: str = args['config'] if args['config'] else DEFAULT_CONFIG_PATH
+ config_path = os.path.normpath(os.path.expandvars(config_path))
+ sanity_check_path(config_path)
+ config_dir, _ = os.path.split(config_path)
+
+ if args['copy_default_config']:
+ create_dir(config_dir)
+ with rpath('pyssg.plt', 'default.ini') as p:
+ copy_file(p, config_path)
+ sys.exit(0)
+
+ if not os.path.exists(config_path):
+ print(f'''config file does't exist in path "{config_path}"; make sure
+ the path is correct; use --copy-default-config to if you
+ haven't already''')
+ sys.exit(1)
+
+ config: ConfigParser = get_parsed_config(config_path)
+
+ if args['init']:
+ create_dir(config.get('path', 'src'))
+ create_dir(os.path.join(config.get('path', 'dst'), 'tag'), True)
+ create_dir(config.get('path', 'plt'))
files: list[str] = ('index.html',
'page.html',
'tag.html',
'rss.xml',
'sitemap.xml')
for f in files:
- plt_file: str = os.path.join(config.plt, f)
- with path('pyssg.plt', f) as p:
- if not os.path.exists(plt_file):
- shutil.copy(p, plt_file)
-
- return
+ plt_file: str = os.path.join(config.get('path', 'plt'), f)
+ with rpath('pyssg.plt', f) as p:
+ copy_file(p, plt_file)
+ sys.exit(0)
- if opts['build']:
+ if args['build']:
# start the db
- db: Database = Database(os.path.join(config.src, '.files'))
+ db: Database = Database(os.path.join(config.get('path', 'src'), '.files'))
db.read()
# the autoescape option could be a security risk if used in a dynamic
# website, as far as i can tell
- env: Environment = Environment(loader=FileSystemLoader(config.plt),
+ env: Environment = Environment(loader=FileSystemLoader(config.get('path', 'plt')),
autoescape=False,
trim_blocks=True,
lstrip_blocks=True)
@@ -92,4 +97,4 @@ def main() -> None:
builder.build()
db.write()
- return
+ sys.exit(0)
diff --git a/src/pyssg/utils.py b/src/pyssg/utils.py
new file mode 100644
index 0000000..8e5d90e
--- /dev/null
+++ b/src/pyssg/utils.py
@@ -0,0 +1,28 @@
+import os
+import sys
+import shutil
+
+
+def create_dir(path: str, p: bool=False) -> None:
+ try:
+ if p:
+ os.makedirs(path)
+ else:
+ os.mkdir(path)
+ print(f'created directory "{path}"')
+ except FileExistsError:
+ print(f'directory "{path}" already exists')
+
+
+def copy_file(src: str, dst: str) -> None:
+ if not os.path.exists(dst):
+ shutil.copy(src, dst)
+ print(f'copied file "{src}" to "{dst}"')
+ else:
+ print(f'"{dst}" already exists')
+
+
+def sanity_check_path(path: str) -> None:
+ if '$' in path:
+ print(f'"$" character found in path: "{path}"; could be due to non-existant env var.')
+ sys.exit(1)