diff --git a/.gitignore b/.gitignore index 72bd2bd..88707e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ .vuepress node_modules/ +.vscode +.pytest_cache +.coverage +*.pyc diff --git a/articles/dev/pytest.md b/articles/dev/pytest.md new file mode 100644 index 0000000..8cbbd73 --- /dev/null +++ b/articles/dev/pytest.md @@ -0,0 +1,35 @@ +# PyTest + +[Pytest](https://docs.pytest.org/en/latest/) se pose comme une alternative au module [unittest](https://docs.python.org/3/library/unittest.html), dont la syntaxe est un peu plus light que le modèle basé sur JUnit. + +Par exemple, on passe de + +```python +import unittest + +class MyTestClass(unittest.TestCase): + def setUp(self): + pass + + def test_bidule_brol(self): + self.assertEquals(1, 1) + self.assertContains(1, [1, 2]) + ... + + def tearDown(self): + pass +``` + +à + +```python +def test_equality(): + assert 1 == 1 + assert 1 in [1, 2] +``` + +On gagne donc pas mal en lisibilité et en rapidité d'écriture: il n'est pas utile de retenir toutes les fonctions d'assertion (et quand on voit [la liste disponible](../../old/testing.md), ce n'est pas un mal). + +Pour gagner en rapidité sur certains projets, n'hésitez pas à passer directement le répertoire dans lequel les tests sont disponibles. Sans cela, on passe par un processus de découverte, qui va parcourir chaque répertoire afin de trouver quelque chose à faire. + +En plus de cela, il y a pas mal de plugins, notamment [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/readme.html#usage) qui permet de simuler la couverture de code pour un package en particulier. Une fois l'installation terminée, il suffit de passer `--cov=package` à l'exécution des tests. diff --git a/editor.py b/editor.py index d0fddc1..970aa5e 100644 --- a/editor.py +++ b/editor.py @@ -1,7 +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)) diff --git a/grnx/__init__.py b/grnx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/grnx/grnx.py b/grnx/grnx.py new file mode 100644 index 0000000..1d4da06 --- /dev/null +++ b/grnx/grnx.py @@ -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__)) diff --git a/grnx/models.py b/grnx/models.py new file mode 100644 index 0000000..cc9a2f8 --- /dev/null +++ b/grnx/models.py @@ -0,0 +1,116 @@ +# coding: utf-8 + +import datetime +import json +import re +import os +import logging + +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))) + + +class Article(object): + + def __init__(self, path, content, publication_date): + self.path = path + self.content = content + self.publication_date = 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.split('-') + + date = None + try: + date = datetime.date(int(values[0]), int(values[1]), int(values[2])) + except (ValueError, TypeError): + logger.warn('Ignoring file %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")]: + self.build_article(root, file) + + def build_article(self, filepath, filename): + """Build a new article from an existing file. + + Args: + root_path (str): the path where the file is stored. + filename (str): the filename of the file. + """ + publication_date = get_date_from_article_filename(filename) + if not publication_date: + return + + with open(filepath, 'r') as f: + content = f.read() + + article = Article(filepath, content, publication_date) + + 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) + print('json result: ' + json_dumps) + 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()) diff --git a/requirements.txt b/requirements.txt index de4ecd0..b3c4751 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ bottle -flake8 -pylint +pylint==2.1.1 +clize==4.0.3 +pytest==3.7.2 +pytest-cov==2.5.1 +jinja==2.10 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..7ea244f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[flake8] +max-line-length=100 + +[tool:pytest] +addopts = -ra -q \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..2ac8f21 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,9 @@ + + + + + + + {% block content %}{% endblock %} + + \ No newline at end of file diff --git a/templates/list.html b/templates/list.html new file mode 100644 index 0000000..fd96fc6 --- /dev/null +++ b/templates/list.html @@ -0,0 +1,6 @@ +{% extends "base.html" } + +{% block content %} + I'm a list. + +{% endblock %} \ No newline at end of file diff --git a/templates/single.html b/templates/single.html new file mode 100644 index 0000000..76f7c3c --- /dev/null +++ b/templates/single.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} + +{% block content %} + I'm an article. I'm called {{ title }}. I was published at {{ published_date }} + +{% endblock %} \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..fd34b89 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,13 @@ +"""Checks structure associated with articles.""" + +from datetime import date + +from grnx.models import get_date_from_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