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
|
||||
|
||||
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