This commit is contained in:
Fred 2020-06-21 16:38:34 +02:00
parent a80ceeb9ed
commit 9e2d1451bc
16 changed files with 370 additions and 1 deletions

5
.markdownlint.json Normal file
View File

@ -0,0 +1,5 @@
{
"MD003": false,
"MD013": false,
"MD014": false
}

View File

@ -1,3 +1,9 @@
# grnx
A short (unfinished) static blog generator.
A short (unfinished) static blog generator.
Ideas:
* Fetch the author through Git repository:
* by using [Dulwich](https://github.com/dulwich/dulwich),
* or [GitPython](https://github.com/gitpython-developers/GitPython).

0
grnx/__init__.py Normal file
View File

16
grnx/editor.py Normal file
View File

@ -0,0 +1,16 @@
import os
from os.path import join
from bottle import Bottle, run
app = Bottle()
run(app, host='localhost', port=5000)
if __name__ == "__main__":
for root, dirs, files in os.walk('articles'):
for file in files:
print(os.path.join(root))

17
grnx/grnx.py Normal file
View File

@ -0,0 +1,17 @@
# coding: utf-8
from grnx.models import Site
from jinja2 import Environment, PackageLoader, select_autoescape
env = Environment(
loader=PackageLoader('grnx', 'templates'),
autoescape=select_autoescape(['html'])
)
if __name__ == "__main__":
root = Site('content')
root.serialize()
template = env.get_template('single.html')
for article in root.articles:
print(template.render(article.__dict__))

178
grnx/models.py Normal file
View File

@ -0,0 +1,178 @@
# coding: utf-8
import datetime
import json
import re
import os
import logging
from slugify import slugify
logger = logging.getLogger(__name__)
RE_HEADER = re.compile('---((.)?(\n)?)*---')
date_handler = lambda obj: (
obj.isoformat()
if isinstance(obj, datetime.datetime)
or isinstance(obj, datetime.date)
else None
)
def json_handler(obj):
"""Handles the JSON object serializer.
Returns:
The iso format if the object is a date.
The __dict__ attribute for any other JSON serializable object.
Excepts:
TypeError when object is not JSON serialisable.
"""
if hasattr(obj, 'isoformat'):
return obj.isoformat()
elif obj.__dict__:
return obj.__dict__
else:
raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj)))
def split(file_path):
return os.path.normpath(file_path).split(os.sep)
class Article(object):
def __init__(self, path, content, publication_date=None):
self.path = path
self.content = content
split_path = split(path)
self.filename = split_path[-1] # the last element
self.filename_without_extension = os.path.splitext(self.filename)[0]
self.publication_date = get_date_from_article_filename(self.filename)
self.slug = self.filename_without_extension
if self.publication_date:
self.slug = self.slug.replace(
self.publication_date.strftime('%Y-%m-%d'), ''
)
if self.slug and self.slug.startswith('-'):
self.slug = self.slug[1:]
self.slug = slugify(self.slug)
try:
self.category = split_path[1] #
self.keywords = split_path[2:-1]
except IndexError:
self.category = ''
self.keywords = []
try:
self.title = content.splitlines()[0]
except IndexError:
self.title = self.filename
def __str__(self):
"""Returns the title and the publication date of the article."""
return '{} ({})'.format(self.title, self.publication_date)
def get_date_from_article_filename(article_filename):
"""Get the date from a file name.
Args:
article_filename (str): the file name of the article.
Example:
2018-02-01-firewatch.md -> 1st of February 2018
2017-02-03-divinity-origin-sin.md -> None
lynis.md -> None
"""
values = article_filename[0:10]
date = None
try:
date = datetime.datetime.strptime(values, '%Y-%m-%d').date()
except (ValueError, TypeError):
logger.warn('No publication date found %s', article_filename)
return date
class Site(object):
"""Represents a Site object.
Args:
root_path (path): The path where articles are stored.
articles (array): contain all articles.
"""
def __init__(self, root_path='articles'):
self.articles = []
self.categories = {}
self.keywords = {}
for root, *_, files in os.walk(root_path):
for file in [file for file in files if file.endswith(".md")]:
try:
self.build_article(root, file)
except UnicodeDecodeError:
continue
def build_article(self, filepath, filename):
"""Build a new article from an existing file.
The newly built article is added to the property `self.articles`.
Args:
root_path (str): the path where the file is stored.
filename (str): the filename of the file.
Returns:
None if no publication date can be built.
An `grnx.models.Article` instance instead.
"""
article_file_path = os.path.join(filepath, filename)
article = None
with open(article_file_path, encoding="utf8") as f:
content = f.read()
article = Article(article_file_path, content)
if article:
logger.warn('article found in %s: %s', article.category, article)
self.articles.append(article)
if article.category not in self.categories:
self.categories[article.category] = []
self.categories[article.category].append(article)
return article
def to_json(self):
"""Serialize the content of the current structure to JSON format."""
json_dumps = json.dumps(
self,
default=json_handler,
sort_keys=True,
indent=4
)
return json_dumps
def serialize(self):
"""Serialize the current files structure to index.json"""
with open('index.json', 'w') as json_serialized_file:
json_serialized_file.write(self.to_json())

8
requirements.txt Normal file
View File

@ -0,0 +1,8 @@
bottle
pylint==2.1.1
clize==4.0.3
pytest==3.7.2
pytest-cov==2.5.1
jinja==2.10
python-slugify==1.2.5
markdown==2.6.11

5
setup.cfg Normal file
View File

@ -0,0 +1,5 @@
[flake8]
max-line-length=100
[tool:pytest]
addopts = -ra -q

9
templates/base.html Normal file
View File

@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>

30
templates/index.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css">
<meta charset="utf-8">
<title>Cryptocurrency Pricing Application</title>
</head>
<body>
<div class="container" id="app">
<h3 class="text-center">Cryptocurrency Pricing</h3>
<div class="columns medium-4" v-for="(result, index) in results">
<div class="card">
<div class="card-section">
<p> {{ index }} </p>
</div>
<div class="card-divider">
<p>$ {{ result.USD }}</p>
</div>
<div class="card-section">
<p> &#8364 {{ result.EUR }}</p>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="vueApp.js"></script>
</body>
</html>

6
templates/list.html Normal file
View File

@ -0,0 +1,6 @@
{% extends "base.html" }
{% block content %}
I'm a list.
{% endblock %}

6
templates/single.html Normal file
View File

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
I'm an article. I'm called {{ title }}. I was published at {{ published_date }}
{% endblock %}

16
templates/vueApp.js Normal file
View File

@ -0,0 +1,16 @@
const vm = new Vue({
el: '#app',
data: {
results: {
"BTC": {
"USD":3759.91,
"EUR":3166.21
},
"ETH": {
"USD":281.7,
"EUR":236.25
},
"NEW Currency":{"USD":5.60,"EUR":4.70}
}
}
});

0
tests/__init__.py Normal file
View File

4
tests/integration.py Normal file
View File

@ -0,0 +1,4 @@
from grnx.models import Site
if __name__ == "__main__":
site = Site()

63
tests/test_models.py Normal file
View File

@ -0,0 +1,63 @@
"""Checks structure associated with articles."""
from datetime import date
from grnx.models import get_date_from_article_filename, split
from grnx.models import Article
def test_split_path():
assert split('articles/sys/2018-01-01 Scaleway Review.md') == ['articles', 'sys', '2018-01-01 Scaleway Review.md']
def test_filename():
article = Article("2019-01-01-blabla.md", "")
assert "2019-01-01-blabla" == article.filename_without_extension
assert "2019-01-01-blabla.md" == article.filename
def test_article_match_date():
"""Checks that a date can be extracted from a filename."""
assert get_date_from_article_filename('2018-09-01-test.md') == date(2018, 9, 1)
assert get_date_from_article_filename('2017-02-30-divinity-origin-sin.md') == None
assert get_date_from_article_filename('lynis.md') == None
assert get_date_from_article_filename('2018-01-01 scaleway-review.md') == date(2018, 1, 1)
def test_article_slug():
"""Check the article slug is well built."""
assert "scaleway-review" == Article('articles/sys/2018-01-01 Scaleway Review.md', '').slug
def test_article_category():
"""Asserts that the category of an article is found, based on the filepath."""
assert "home" == Article('articles/home/2019-01-01-blabla.md', "").category
assert "dev" == Article('articles/dev/python/django/2019-01-01-blabla.md', "").category
assert '' == Article('articles', "").category
def test_article_keywords():
"""Asserts that the keywords of an article are found, based on the filepath."""
article = Article('articles/dev/python/django/2019-01-01-blabla.md', "")
assert "python" in article.keywords
assert "django" in article.keywords
assert "dev" not in article.keywords
article = Article('articles/dev/2019-01-01-blabla.md', "")
assert len(article.keywords) == 0
def test_article_properties():
article = Article('articles/dev/python/django/2019-01-01-blabla.md', "")
assert article.filename == '2019-01-01-blabla.md'
assert article.filename_without_extension == '2019-01-01-blabla'
assert article.slug == 'blabla'
assert article.publication_date == date(2019, 1, 1)