# pyssg - Static Site Generator written in Python
Generates HTML files from MD files for a static site, personally using it for a blog-like site.
Initially inspired 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 [`blogit`](https://pedantic.software/git/blogit/).
## Features and to-do
**NOTE:** WIP, there will be changes that will break the setup.
- [x] Build static site parsing `markdown` files ( `*.md` -> `*.html`)
- [x] Uses [`jinja`](https://jinja.palletsprojects.com/en/3.0.x/) for templating.
- [x] Preserves hand-made `*.html` files.
- [x] Tag functionality, useful for blog-style sites.
- [ ] 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 (for example, [newsboat](https://newsboat.org/) parses `xml:base`).
- [ ] Include manually added `*.html` files.
- [x] YAML for configuration file, uses [`PyYAML`](https://pyyaml.org/).
- [ ] Handle multiple "documents".
- [ ] More complex directory structure to support multiple subdomains and different types of pages.
- [ ] Option/change to using an SQL database instead of the custom solution.
- [x] Checksum checking because the timestamp of the file is not enough.
- [ ] Use external markdown extensions.
### Markdown features
This program uses the base [`markdown` syntax](https://daringfireball.net/projects/markdown/syntax) plus additional syntax, all thanks to [`python-markdown`](https://python-markdown.github.io/) that provides [extensions](https://python-markdown.github.io/extensions/). The following extensions are used:
- Extra (collection of QoL extensions).
- Meta-Data.
- Sane Lists.
- SmartyPants.
- Table of Contents.
- WikiLinks.
- [yafg - Yet Another Figure Generator](https://git.sr.ht/~ferruck/yafg)
- [Markdown Checklist](https://github.com/FND/markdown-checklist)
- [PyMdown Extensions](https://facelessuser.github.io/pymdown-extensions/)
- [Caret](https://facelessuser.github.io/pymdown-extensions/extensions/caret/)
- [Tilde](https://facelessuser.github.io/pymdown-extensions/extensions/tilde/)
- [Mark](https://facelessuser.github.io/pymdown-extensions/extensions/mark/)
## Installation
Install with `pip`:
```sh
pip install pyssg
```
Probably will add a PKBUILD (and possibly submit it to the AUR) in the future.
## Usage
1. Get the default configuration file:
```sh
pyssg --copy-default-config -c <path/to/config>
```
- Where `-c` is optional as by default `$XDG_CONFIG_HOME/pyssg/config.yaml` is used.
2. Edit the config file created as needed.
- `config.yaml` is parsed using [`PyYAML`](https://pyyaml.org/), [more about the config file](#config-file).
3. Initialize the directory structures (source, destination, template) and move template files:
```sh
pyssg -i
```
- 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.
- Recommended (no longer mandatory) metadata keys that can be added to the top of `.md` files:
```
title: the title of your blog entry or whatever
author: your name or online handle
another name maybe for multiple authors?
lang: the language the entry is written on
summary: a summary of the entry
tags: english
short
tutorial
etc
```
- 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).
5. Build the `*.html` with:
```sh
pyssg -b
```
- After this, you have ready to deploy `*.html` files.
## Config file
All sections/options need to be compliant with [`PyYAML`](https://pyyaml.org/) which should be compliant with [`YAML 1.2`](https://yaml.org/spec/1.2.2/). Additionaly, I've added the custom tag `!join` which concatenates strings from an array, which an be used as follows:
```yaml
variable: &variable_reference_name "value"
other_variable: !join [*variable_reference_name, "other_value", 1]
```
Which would produce `other_variable: "valueother_value1"`. Also environment variables will be expanded internally.
The following is a list of config items that need to be present in the config unless stated otherwise:
```yaml
%YAML 1.2
---
# not needed, shown here as an example of the !join tag
define: &root "$HOME/path/to/" # $HOME expands to /home/user, for example
title: "Example site"
path:
src: !join [*root, "src"] # $HOME/path/to/src
dst: "$HOME/some/other/path/to/dst"
plt: "plt"
db: !join [*root, "src/", "db.psv"]
url:
main: "https://example.com"
fmt:
date: "%a, %b %d, %Y @ %H:%M %Z"
list_date: "%b %d"
list_sep_date: "%B %Y"
dirs:
/: # root "dir_path", whatever is sitting directly under "src"
cfg:
plt: "page.html"
# the template can be specified instead of just True/False, a default template will used
tags: False
index: True
rss: True
sitemap: True
exclude_dirs: ["articles", "blog"] # optional; list of subdirectories to exclude when parsing the / dir_path
# below are other example "dir_paths", can be named anything, only the / (above) is mandatory
articles:
cfg:
plt: "page.html"
tags: True
index: True
rss: True
sitemap: True
blog:
cfg:
# ...
...
```
The config under `dirs` are just per-subdirectory configuration of directories under `src`. Only the `/` "dir_path" is required as it is the config for the root `src` path files.
The following will be added on runtime:
```yaml
%YAML 1.2
---
fmt:
rss_date: "%a, %d %b %Y %H:%M:%S GMT" # fixed
sitemap_date: "%Y-%m-%d" # fixed
info:
version: "x.y.z" # current 'pyssg' version (0.5.1.dev16, for example)
debug: True/False # depending if --debug was used when executing
force: True/False # depending if --force was used when executing
rss_run_date: # date the program was run, formatted with 'fmt.rss_date'
sitemap_run_date: # date the program was run, formatted with 'fmt.sitemap_date'
...
```
You can add any other option/section that you can later use in the Jinja templates via the exposed config object. URL's shouldn't have the trailing slash `/`
## Available Jinja variables
These variables are exposed to use within the templates. The below list is displayed in the form of *variable (type) (available from): description*. `field1/field2/field3/...` describe config file section from the YAML file and option and `object.attribute` corresponding object and it's attribute.
- `config` (`dict`) (all): parsed config file plus the added options internally (as described in [config file](#config-file)).
- `dir_config` (`dict`) (all*): parsed dir_config file plus the added options internally (as described in [config file](#config-file)). *This is for all of the specific "dir_path" files, as per configured in the YAML file `dirs.dir_path.cfg` (for exmaple `dirs./.cfg` for the required dir_path).
- `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` (`list[str]`): list of authors of the page.
- `lang` (`str`): page language, used for the general `html` tag `lang` attribute.
- `summary` (`str`): summary of the page, as specified in the `*.md` file.
- `content` (`str`): actual content of the page, this is the `html`.
- `cdatetime` (`datetime.datetime`): creation datetime object of the page.
- `cdate` (`method`): method thtat takes the name of the `fmt.FMT` and applies it to the `cdatetime` object.
- `cdate_rss` (`str`): formatted `cdatetime` as required by rss.
- `cdate_sitemap` (`str`): formatted `cdatetime` as required by sitemap.
- `mdatetime` (`datetime.datetime`): modification datetime object of the page. Defaults to `None`.
- `mdate` (`method`): method thtat takes the name of the `fmt.FMT` and applies it to the `mdatetime` object.
- `mdate_rss` (`str`): formatted `mdatetime` as required by rss.
- `mdate_sitemap` (`str`): formatted `mdatetime` as required by sitemap.
- `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.
Raw data
{
"_id": null,
"home_page": "https://github.com/luevano/pyssg",
"name": "pyssg",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "python,static,site,generator,markdown",
"author": "David Luevano Alvarado",
"author_email": "david@luevano.xyz",
"download_url": "https://files.pythonhosted.org/packages/42/f7/856b7b1db77f83e6ec31631c4362de9f513a99764b7a4f77667acdc28221/pyssg-0.8.3.tar.gz",
"platform": null,
"description": "# pyssg - Static Site Generator written in Python\n\nGenerates HTML files from MD files for a static site, personally using it for a blog-like site.\n\nInitially inspired 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 [`blogit`](https://pedantic.software/git/blogit/).\n\n## Features and to-do\n\n**NOTE:** WIP, there will be changes that will break the setup.\n\n- [x] Build static site parsing `markdown` files ( `*.md` -> `*.html`)\n\t- [x] Uses [`jinja`](https://jinja.palletsprojects.com/en/3.0.x/) for templating.\n\t- [x] Preserves hand-made `*.html` files.\n\t- [x] Tag functionality, useful for blog-style sites.\n\t- [ ] Open Graph (and similar) support.\n\t\t- Technically, this works if you add the correct metadata to the `*.md` files and use the variables available for Jinja.\n- [x] Build `sitemap.xml` file.\n\t- [ ] Include manually added `*.html` files.\n- [x] Build `rss.xml` file.\n\t- [ ] 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).\n\t\t- 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 (for example, [newsboat](https://newsboat.org/) parses `xml:base`).\n\t- [ ] Include manually added `*.html` files.\n- [x] YAML for configuration file, uses [`PyYAML`](https://pyyaml.org/).\n\t- [ ] Handle multiple \"documents\".\n\t- [ ] More complex directory structure to support multiple subdomains and different types of pages.\n- [ ] Option/change to using an SQL database instead of the custom solution.\n- [x] Checksum checking because the timestamp of the file is not enough.\n- [ ] Use external markdown extensions.\n\n### Markdown features\n\nThis program uses the base [`markdown` syntax](https://daringfireball.net/projects/markdown/syntax) plus additional syntax, all thanks to [`python-markdown`](https://python-markdown.github.io/) that provides [extensions](https://python-markdown.github.io/extensions/). The following extensions are used:\n\n- Extra (collection of QoL extensions).\n- Meta-Data.\n- Sane Lists.\n- SmartyPants.\n- Table of Contents.\n- WikiLinks.\n- [yafg - Yet Another Figure Generator](https://git.sr.ht/~ferruck/yafg)\n- [Markdown Checklist](https://github.com/FND/markdown-checklist)\n- [PyMdown Extensions](https://facelessuser.github.io/pymdown-extensions/)\n\t- [Caret](https://facelessuser.github.io/pymdown-extensions/extensions/caret/)\n\t- [Tilde](https://facelessuser.github.io/pymdown-extensions/extensions/tilde/)\n\t- [Mark](https://facelessuser.github.io/pymdown-extensions/extensions/mark/)\n\n## Installation\n\nInstall with `pip`:\n\n```sh\npip install pyssg\n```\n\nProbably will add a PKBUILD (and possibly submit it to the AUR) in the future.\n\n## Usage\n\n1. Get the default configuration file:\n\n```sh\npyssg --copy-default-config -c <path/to/config>\n```\n\n- Where `-c` is optional as by default `$XDG_CONFIG_HOME/pyssg/config.yaml` is used.\n\n2. Edit the config file created as needed.\n\n- `config.yaml` is parsed using [`PyYAML`](https://pyyaml.org/), [more about the config file](#config-file).\n\n3. Initialize the directory structures (source, destination, template) and move template files:\n\n```sh\npyssg -i\n```\n\n- You can modify the basic templates as needed (see [variables available for Jinja](#available-jinja-variables)).\n\n- Strongly recommended to edit the `rss.xml` template.\n\n4. Place your `*.md` files somewhere inside the source directory. It accepts sub-directories.\n\n- Recommended (no longer mandatory) metadata keys that can be added to the top of `.md` files:\n\n```\ntitle: the title of your blog entry or whatever\nauthor: your name or online handle\n\tanother name maybe for multiple authors?\nlang: the language the entry is written on\nsummary: a summary of the entry\ntags: english\n\tshort\n\ttutorial\n\tetc\n```\n\n- 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).\n\n5. Build the `*.html` with:\n\n```sh\npyssg -b\n```\n\n- After this, you have ready to deploy `*.html` files.\n\n## Config file\n\nAll sections/options need to be compliant with [`PyYAML`](https://pyyaml.org/) which should be compliant with [`YAML 1.2`](https://yaml.org/spec/1.2.2/). Additionaly, I've added the custom tag `!join` which concatenates strings from an array, which an be used as follows:\n\n```yaml\nvariable: &variable_reference_name \"value\"\nother_variable: !join [*variable_reference_name, \"other_value\", 1]\n```\n\nWhich would produce `other_variable: \"valueother_value1\"`. Also environment variables will be expanded internally.\n\nThe following is a list of config items that need to be present in the config unless stated otherwise:\n\n```yaml\n%YAML 1.2\n---\n# not needed, shown here as an example of the !join tag\ndefine: &root \"$HOME/path/to/\" # $HOME expands to /home/user, for example\n\ntitle: \"Example site\"\npath:\n src: !join [*root, \"src\"] # $HOME/path/to/src\n dst: \"$HOME/some/other/path/to/dst\"\n plt: \"plt\"\n db: !join [*root, \"src/\", \"db.psv\"]\nurl:\n main: \"https://example.com\"\nfmt:\n date: \"%a, %b %d, %Y @ %H:%M %Z\"\n list_date: \"%b %d\"\n list_sep_date: \"%B %Y\"\ndirs:\n /: # root \"dir_path\", whatever is sitting directly under \"src\"\n\tcfg:\n\t plt: \"page.html\"\n\t # the template can be specified instead of just True/False, a default template will used\n\t tags: False\n\t index: True\n\t rss: True\n\t sitemap: True\n\t exclude_dirs: [\"articles\", \"blog\"] # optional; list of subdirectories to exclude when parsing the / dir_path\n# below are other example \"dir_paths\", can be named anything, only the / (above) is mandatory\n articles:\n cfg:\n\t plt: \"page.html\"\n\t tags: True\n\t index: True\n\t rss: True\n\t sitemap: True\n blog:\n cfg:\n\t # ...\n...\n```\n\nThe config under `dirs` are just per-subdirectory configuration of directories under `src`. Only the `/` \"dir_path\" is required as it is the config for the root `src` path files.\n\nThe following will be added on runtime:\n\n```yaml\n%YAML 1.2\n---\nfmt:\n rss_date: \"%a, %d %b %Y %H:%M:%S GMT\" # fixed\n sitemap_date: \"%Y-%m-%d\" # fixed\ninfo:\n version: \"x.y.z\" # current 'pyssg' version (0.5.1.dev16, for example)\n debug: True/False # depending if --debug was used when executing\n force: True/False # depending if --force was used when executing\nrss_run_date: # date the program was run, formatted with 'fmt.rss_date'\nsitemap_run_date: # date the program was run, formatted with 'fmt.sitemap_date'\n...\n```\n\nYou can add any other option/section that you can later use in the Jinja templates via the exposed config object. URL's shouldn't have the trailing slash `/`\n\n## Available Jinja variables\n\nThese variables are exposed to use within the templates. The below list is displayed in the form of *variable (type) (available from): description*. `field1/field2/field3/...` describe config file section from the YAML file and option and `object.attribute` corresponding object and it's attribute.\n\n- `config` (`dict`) (all): parsed config file plus the added options internally (as described in [config file](#config-file)).\n- `dir_config` (`dict`) (all*): parsed dir_config file plus the added options internally (as described in [config file](#config-file)). *This is for all of the specific \"dir_path\" files, as per configured in the YAML file `dirs.dir_path.cfg` (for exmaple `dirs./.cfg` for the required dir_path).\n- `all_pages` (`list(Page)`) (all): list of all the pages, sorted by creation time, reversed.\n- `page` (`Page`) (`page.html`): contains the following attributes (genarally these are parsed from the metadata in the `*.md` files):\n\t- `title` (`str`): title of the page.\n\t- `author` (`list[str]`): list of authors of the page.\n\t- `lang` (`str`): page language, used for the general `html` tag `lang` attribute.\n\t- `summary` (`str`): summary of the page, as specified in the `*.md` file.\n\t- `content` (`str`): actual content of the page, this is the `html`.\n\t- `cdatetime` (`datetime.datetime`): creation datetime object of the page.\n\t- `cdate` (`method`): method thtat takes the name of the `fmt.FMT` and applies it to the `cdatetime` object.\n\t- `cdate_rss` (`str`): formatted `cdatetime` as required by rss.\n\t- `cdate_sitemap` (`str`): formatted `cdatetime` as required by sitemap.\n\t- `mdatetime` (`datetime.datetime`): modification datetime object of the page. Defaults to `None`.\n\t- `mdate` (`method`): method thtat takes the name of the `fmt.FMT` and applies it to the `mdatetime` object.\n\t- `mdate_rss` (`str`): formatted `mdatetime` as required by rss.\n\t- `mdate_sitemap` (`str`): formatted `mdatetime` as required by sitemap.\n\t- `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.\n\t- `url` (`str`): url of the page, this already includes the `url/main` from config file.\n\t- `image_url` (`str`): image url of the page, this already includes the `url/static`. Defaults to the `url/default_image` config option.\n\t- `next/previous` (`Page`): reference to the next or previous page object (containing all these attributes). Defaults to `None`.\n\t- `og` (`dict(str, str)`): dict for object graph metadata.\n\t- `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.\n- `tag` (`tuple(str)`) (`tag.html`): tuple of name and url of the current tag.\n- `tag_pages` (`list(Page)`) (`tag.html`): similar to `all_pages` but contains all the pages for the current tag.\n- `all_tags` (`list(tuple(str))`) (all): similar to `page.tags` but contains all the tags.\n\n",
"bugtrack_url": null,
"license": "GPLv3",
"summary": "A Static Site Generator using markdown files",
"version": "0.8.3",
"split_keywords": [
"python",
"static",
"site",
"generator",
"markdown"
],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "73ee4310aed779809e0f2e69adde2b9c",
"sha256": "7c5a29723dfa88608c92b1bd5435aee3f8cfeb815a432cc7af141c3b270be7d3"
},
"downloads": -1,
"filename": "pyssg-0.8.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "73ee4310aed779809e0f2e69adde2b9c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 37213,
"upload_time": "2022-12-27T09:00:39",
"upload_time_iso_8601": "2022-12-27T09:00:39.290971Z",
"url": "https://files.pythonhosted.org/packages/7c/c9/f24fa3057ec4ba9be00faecff8bf4575d7d962c6a278801a636b8e6e56a8/pyssg-0.8.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "cec99d23d386084f42db2df7401c97d6",
"sha256": "518e16e2a96ff94dd8972d92670e3a09201c4522224b66573058ac36d0dbd0a4"
},
"downloads": -1,
"filename": "pyssg-0.8.3.tar.gz",
"has_sig": false,
"md5_digest": "cec99d23d386084f42db2df7401c97d6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 6507458,
"upload_time": "2022-12-27T09:00:46",
"upload_time_iso_8601": "2022-12-27T09:00:46.167805Z",
"url": "https://files.pythonhosted.org/packages/42/f7/856b7b1db77f83e6ec31631c4362de9f513a99764b7a4f77667acdc28221/pyssg-0.8.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2022-12-27 09:00:46",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "luevano",
"github_project": "pyssg",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [],
"lcname": "pyssg"
}