Migrating from https://sources.grimbox.be/fred/grimboite.
This commit is contained in:
parent
a80ceeb9ed
commit
9e2d1451bc
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"MD003": false,
|
||||||
|
"MD013": false,
|
||||||
|
"MD014": false
|
||||||
|
}
|
|
@ -1,3 +1,9 @@
|
||||||
# grnx
|
# 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,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))
|
|
@ -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__))
|
|
@ -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())
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
[flake8]
|
||||||
|
max-line-length=100
|
||||||
|
|
||||||
|
[tool:pytest]
|
||||||
|
addopts = -ra -q
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -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> € {{ result.EUR }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/vue"></script>
|
||||||
|
<script src="vueApp.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends "base.html" }
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
I'm a list.
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
I'm an article. I'm called {{ title }}. I was published at {{ published_date }}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -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,0 +1,4 @@
|
||||||
|
from grnx.models import Site
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
site = Site()
|
|
@ -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)
|
Loading…
Reference in New Issue