├── tests ├── __init__.py ├── test_requirements.py ├── test_exports.py ├── test_image_extractor.py ├── test_license_generator.py └── test_schema_generator.py ├── screenshot-480px.png ├── screenshot-840px.png ├── screenshot-980px.png ├── screenshot-1440px.png ├── screenshot-social.png ├── bulrush ├── templates │ ├── tag.html │ ├── category.html │ ├── comments.html │ ├── taglist.html │ ├── translations.html │ ├── github.html │ ├── disqus_script.html │ ├── mermaid.html │ ├── tags.html │ ├── archives.html │ ├── period_archives.html │ ├── page.html │ ├── pagination.html │ ├── article_list.html │ ├── article_infos.html │ ├── index.html │ ├── article.html │ ├── meta_tags.html │ ├── analytics.html │ ├── social.html │ ├── mailchimp.html │ └── base.html ├── static │ ├── images │ │ └── made-with-bulma--black.png │ └── css │ │ ├── main.less │ │ └── highlight.less ├── __init__.py ├── image_extractor.py ├── schema_generator.py └── license_generator.py ├── LICENSE ├── setup.py ├── .github └── workflows │ └── push.yml ├── .gitignore └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshot-480px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textbook/bulrush/HEAD/screenshot-480px.png -------------------------------------------------------------------------------- /screenshot-840px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textbook/bulrush/HEAD/screenshot-840px.png -------------------------------------------------------------------------------- /screenshot-980px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textbook/bulrush/HEAD/screenshot-980px.png -------------------------------------------------------------------------------- /screenshot-1440px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textbook/bulrush/HEAD/screenshot-1440px.png -------------------------------------------------------------------------------- /screenshot-social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textbook/bulrush/HEAD/screenshot-social.png -------------------------------------------------------------------------------- /bulrush/templates/tag.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block title %}{{ SITENAME }} - {{ tag }}{% endblock %} 3 | -------------------------------------------------------------------------------- /bulrush/templates/category.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block title %}{{ SITENAME }} - {{ category }}{% endblock %} 3 | -------------------------------------------------------------------------------- /bulrush/templates/comments.html: -------------------------------------------------------------------------------- 1 | {% if DISQUS_SITENAME %}

{% endif %} 2 | -------------------------------------------------------------------------------- /bulrush/static/images/made-with-bulma--black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textbook/bulrush/HEAD/bulrush/static/images/made-with-bulma--black.png -------------------------------------------------------------------------------- /tests/test_requirements.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class TestRequirements(TestCase): 5 | 6 | def test_webassets(self): 7 | import webassets 8 | -------------------------------------------------------------------------------- /bulrush/templates/taglist.html: -------------------------------------------------------------------------------- 1 | {% if article.tags %} 2 | {% for tag in article.tags %} 3 | 4 | {{ tag | escape }} 5 | 6 | {% endfor %} 7 | {% endif %} 8 | -------------------------------------------------------------------------------- /bulrush/templates/translations.html: -------------------------------------------------------------------------------- 1 | {% macro translations_for(article) %} 2 | {% if article.translations %} 3 | Translations: 4 | {% for translation in article.translations %} 5 | {{ translation.lang }} 6 | {% endfor %} 7 | {% endif %} 8 | {% endmacro %} 9 | -------------------------------------------------------------------------------- /bulrush/templates/github.html: -------------------------------------------------------------------------------- 1 | {% if GITHUB_URL %} 2 |
3 |
4 | 5 | 6 | Fork me on GitHub 7 | 8 |
9 |
10 | {% endif %} 11 | -------------------------------------------------------------------------------- /bulrush/templates/disqus_script.html: -------------------------------------------------------------------------------- 1 | {% if DISQUS_SITENAME %} 2 | 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /bulrush/templates/mermaid.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /bulrush/templates/tags.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ SITENAME }} - Tags{% endblock %} 3 | {% block content %} 4 |

5 | 6 | Tags for {{ SITENAME }} 7 |

8 | 9 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /bulrush/templates/archives.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ SITENAME }} - Archive{% endblock %} 3 | {% block content %} 4 |

5 | 6 | Archives for {{ SITENAME }} 7 |

8 | 9 | 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /bulrush/templates/period_archives.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ SITENAME }} - Archive{% endblock %} 3 | {% block content %} 4 |

5 | 6 | Archives for {{ period | reverse | join(' ') }} 7 |

8 | 9 | 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /bulrush/templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ page.title|striptags }}{% endblock %} 3 | {% block tags %} 4 | {% with item=page %} 5 | {% include 'meta_tags.html' %} 6 | {% endwith %} 7 | {% endblock %} 8 | {% block content %} 9 |
10 |
11 |

{{ page.title }}

13 | {% import 'translations.html' as translations with context %} 14 | {{ translations.translations_for(page) }} 15 |
{{ page.content }}
16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Jonathan Sharpe 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /bulrush/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from .image_extractor import extract_images 4 | from .license_generator import generate_license 5 | from .schema_generator import generate_jsonld_schema 6 | 7 | __all__ = ['ENVIRONMENT', 'FILTERS', 'PATH'] 8 | 9 | 10 | ENVIRONMENT = { 11 | 'extensions': ['webassets.ext.jinja2.AssetsExtension'], 12 | } 13 | """The required Jinja environment for the Bulrush theme.""" 14 | 15 | 16 | FILTERS = dict( 17 | images=extract_images, 18 | license=generate_license, 19 | schema=generate_jsonld_schema, 20 | ) 21 | """The filters defined by the Bulrush theme.""" 22 | 23 | 24 | # https://github.com/getpelican/pelican/issues/1564#issuecomment-282136049 25 | PATH = str(Path(__file__).parent) 26 | """The path to the Bulrush theme directory.""" 27 | -------------------------------------------------------------------------------- /bulrush/image_extractor.py: -------------------------------------------------------------------------------- 1 | from html.parser import HTMLParser 2 | 3 | 4 | class ImageExtractor(HTMLParser): 5 | """Class to extract image sources from article content.""" 6 | 7 | def __init__(self, *, convert_charrefs=True): 8 | super().__init__(convert_charrefs=convert_charrefs) 9 | self.images = [] 10 | 11 | def handle_starttag(self, tag, attrs): 12 | if tag == 'img': 13 | self._extract_source_attr(attrs) 14 | 15 | def _extract_source_attr(self, attrs): 16 | self.images.append(next(val for attr, val in attrs if attr == 'src')) 17 | 18 | 19 | def extract_images(article_content): 20 | """Extract the sources of all images in the article.""" 21 | extractor = ImageExtractor() 22 | extractor.feed(article_content) 23 | return extractor.images 24 | -------------------------------------------------------------------------------- /bulrush/templates/pagination.html: -------------------------------------------------------------------------------- 1 | 26 |
27 | -------------------------------------------------------------------------------- /tests/test_exports.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from unittest import TestCase 3 | 4 | import bulrush 5 | 6 | 7 | class ExportTest(TestCase): 8 | 9 | def test_environment(self): 10 | extensions = bulrush.ENVIRONMENT.get('extensions', []) 11 | self.assertIn('webassets.ext.jinja2.AssetsExtension', extensions) 12 | 13 | def test_filters(self): 14 | self.assertIn('images', bulrush.FILTERS) 15 | self.assertIs(bulrush.FILTERS['images'], bulrush.extract_images) 16 | self.assertIn('license', bulrush.FILTERS) 17 | self.assertIs(bulrush.FILTERS['license'], bulrush.generate_license) 18 | self.assertIn('schema', bulrush.FILTERS) 19 | self.assertIs(bulrush.FILTERS['schema'], bulrush.generate_jsonld_schema) 20 | 21 | def test_path(self): 22 | expected = str(Path(__file__).parent.parent.joinpath('bulrush')) 23 | self.assertEqual(bulrush.PATH, expected) 24 | -------------------------------------------------------------------------------- /bulrush/templates/article_list.html: -------------------------------------------------------------------------------- 1 | {% if article_list %} 2 |
3 |

Other articles

4 |
5 | {% for article in article_list %} 6 | 18 | {% endfor %} 19 |
20 | {% include 'pagination.html' %} 21 |
22 | {% endif %} 23 | -------------------------------------------------------------------------------- /bulrush/templates/article_infos.html: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from setuptools import setup 3 | 4 | here = path.abspath(path.dirname(__file__)) 5 | with open('{}/README.md'.format(here)) as readme: 6 | long_description = readme.read() 7 | 8 | setup( 9 | author='Jonathan Sharpe', 10 | author_email='mail@jonrshar.pe', 11 | classifiers=[ 12 | 'Development Status :: 4 - Beta', 13 | 'Framework :: Pelican :: Themes', 14 | 'License :: OSI Approved :: ISC License (ISCL)', 15 | 'Programming Language :: Python :: 3 :: Only', 16 | ], 17 | description='Bulrush theme for Pelican', 18 | install_requires=['markupsafe', 'webassets'], 19 | license='ISC', 20 | long_description=long_description, 21 | long_description_content_type='text/markdown', 22 | name='bulrush', 23 | package_data={ 24 | 'bulrush': [ 25 | 'templates/*.html', 26 | 'static/css/*.css', 27 | ] 28 | }, 29 | packages=['bulrush'], 30 | test_suite='tests', 31 | tests_require=['pelican'], 32 | url='https://github.com/textbook/bulrush', 33 | version='0.6.3', 34 | ) 35 | -------------------------------------------------------------------------------- /tests/test_image_extractor.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from bulrush import extract_images 4 | 5 | 6 | class ImageExtractorTest(TestCase): 7 | 8 | def test_no_image(self): 9 | result = extract_images('

No images here

') 10 | self.assertEqual(result, []) 11 | 12 | def test_only_image(self): 13 | result = extract_images('') 14 | self.assertEqual(result, ['hello.world']) 15 | 16 | def test_complex_with_images(self): 17 | result = extract_images(''' 18 | 19 | 20 | 21 | 22 | Title 23 | 24 | 25 |
26 |
27 | First dummy image 28 |

This is just some text in the article.

29 | Second dummy image 30 |
31 |
32 | 33 | 34 | ''') 35 | self.assertEqual(result, ['hello.world', 'foo.bar.baz']) 36 | -------------------------------------------------------------------------------- /bulrush/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content_title %}{% endblock %} 3 | {% block content %} 4 | {% if articles and not articles_page.has_previous() %} 5 | {% with article = articles[0] %} 6 | 23 | {% endwith %} 24 | {% with article_list = articles_page.object_list[1:] %} 25 | {% include 'article_list.html' %} 26 | {% endwith %} 27 | {% else %} 28 | {% with article_list = articles_page.object_list %} 29 | {% include 'article_list.html' %} 30 | {% endwith %} 31 | {% endif %} 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /bulrush/schema_generator.py: -------------------------------------------------------------------------------- 1 | from json import dumps 2 | 3 | from markupsafe import Markup 4 | 5 | from .image_extractor import extract_images 6 | 7 | _DEFAULT_SCHEMA = {'@context': 'http://schema.org', '@type': 'BlogPosting'} 8 | 9 | 10 | def generate_jsonld_schema(article, site_url): 11 | schema = _create_basic_schema(article, site_url) 12 | schema.update(_DEFAULT_SCHEMA) 13 | if hasattr(article, 'modified'): 14 | schema['dateModified'] = article.modified.isoformat() 15 | if hasattr(article, 'summary'): 16 | schema['description'] = Markup(article.summary).striptags() 17 | images = extract_images(article.content) 18 | if images: 19 | schema['image'] = _create_image(images[0]) 20 | return dumps(schema) 21 | 22 | 23 | def _create_basic_schema(article, site_url): 24 | return { 25 | 'articleSection': article.category.name, 26 | 'author': _create_person(article.author), 27 | 'datePublished': article.date.isoformat(), 28 | 'headline': article.title, 29 | 'mainEntityOfPage': _create_entity(article, site_url), 30 | } 31 | 32 | 33 | def _create_image(image_url): 34 | return {'@type': 'ImageObject', 'url': image_url} 35 | 36 | 37 | def _create_entity(article, site_url): 38 | return {'@type': 'WebPage', '@id': '{}/{}'.format(site_url, article.url)} 39 | 40 | 41 | def _create_person(person): 42 | return {'@type': 'Person', 'name': person.name} 43 | -------------------------------------------------------------------------------- /bulrush/templates/article.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ article.title|striptags }}{% endblock %} 3 | {% block tags %} 4 | {% with item=article %} 5 | {% include 'meta_tags.html' %} 6 | {% endwith %} 7 | {% endblock %} 8 | {% block content %} 9 |
10 |
11 |

12 | {{ article.title }}

14 | {% include 'article_infos.html' %} 15 |
{{ article.content }}
16 | {% if DISQUS_SITENAME and SITEURL and article.status != "draft" %} 17 |
18 |

Comments !

19 |
20 | 30 | 31 |
32 | {% endif %} 33 | 34 |
35 |
36 | 37 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /bulrush/templates/meta_tags.html: -------------------------------------------------------------------------------- 1 | 2 | {% if item.summary %} 3 | 4 | {% endif %} 5 | 6 | {% with images=(item.content | images) %} 7 | {% if images %} 8 | 9 | {% elif AVATAR %} 10 | 11 | {% endif %} 12 | {% if images or AVATAR %} 13 | 14 | {% endif %} 15 | {% endwith %} 16 | 17 | {% if TWITTER_USERNAME %} 18 | 19 | 20 | {% endif %} 21 | 22 | 23 | {% if item.date %} 24 | 25 | {% endif %} 26 | {% if item.modified %} 27 | 28 | {% endif %} 29 | {% if item.tags %} 30 | {% for tag in item.tags %} 31 | 32 | {% endfor %} 33 | {% endif %} 34 | {% if item.category %} 35 | 36 | {% endif %} 37 | -------------------------------------------------------------------------------- /bulrush/license_generator.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Mapping 2 | import re 3 | 4 | _HTML = ''' 5 | 15 | ''' 16 | 17 | _DEFAULT_ICON = 'file-text-o' 18 | 19 | _CC_LICENSE = re.compile( 20 | r'CC([-\s])(?P[A-Z-]+)\1(?P[\d.]+)', 21 | flags=re.VERBOSE | re.IGNORECASE 22 | ) 23 | 24 | 25 | def generate_license(license_): 26 | if isinstance(license_, Mapping): 27 | try: 28 | return _format_license(**license_) 29 | except TypeError: 30 | return '' 31 | return _generate_named_license(str(license_)) 32 | 33 | 34 | def _generate_named_license(license_name): 35 | if _CC_LICENSE.match(license_name): 36 | return _format_license(**_generate_cc_license(license_name)) 37 | return _format_license(**_generate_generic_license(license_name)) 38 | 39 | 40 | def _format_license(*, name, icon=_DEFAULT_ICON, url): 41 | return _HTML.format(url=url, icon=icon, content=name) 42 | 43 | 44 | def _generate_generic_license(license_name): 45 | return dict(name=license_name, icon=_DEFAULT_ICON, url='#') 46 | 47 | 48 | def _generate_cc_license(license_name): 49 | match = _CC_LICENSE.match(license_name) 50 | type_ = match.group('type').lower() 51 | version = match.group('version') 52 | url = 'http://creativecommons.org/licenses/{}/{}/'.format(type_, version) 53 | name = 'CC {} {}'.format(type_.upper(), version) 54 | return dict(name=name, icon='creative-commons', url=url) 55 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: GitHub Actions CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - v* 10 | 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - uses: actions/checkout@v3 20 | - uses: actions/setup-python@v4 21 | with: 22 | python-version: '3.11' 23 | - run: pip install wheel 24 | - run: python setup.py install 25 | - run: python setup.py test 26 | - run: npm install --location=global less 27 | - run: lessc ./bulrush/static/css/main.less ./bulrush/static/css/main.css 28 | - run: python setup.py sdist bdist_wheel 29 | - uses: actions/upload-artifact@v3 30 | with: 31 | if-no-files-found: error 32 | name: build-outputs 33 | path: dist/* 34 | deploy-github: 35 | runs-on: ubuntu-22.04 36 | if: startsWith(github.ref, 'refs/tags/v') 37 | needs: test 38 | steps: 39 | - uses: actions/checkout@v3 40 | - uses: actions/download-artifact@v4.1.7 41 | with: 42 | name: build-outputs 43 | path: dist/ 44 | - run: > 45 | gh release create 46 | '${{ github.ref_name }}' 47 | --title '${{ github.ref_name }}' 48 | ./dist/* 49 | env: 50 | GH_TOKEN: ${{ github.token }} 51 | deploy-pypi: 52 | runs-on: ubuntu-22.04 53 | if: startsWith(github.ref, 'refs/tags/v') 54 | needs: test 55 | environment: 56 | name: pypi 57 | permissions: 58 | id-token: write 59 | steps: 60 | - uses: actions/checkout@v3 61 | - uses: actions/download-artifact@v4.1.7 62 | with: 63 | name: build-outputs 64 | path: dist/ 65 | - uses: pypa/gh-action-pypi-publish@release/v1 66 | -------------------------------------------------------------------------------- /bulrush/templates/analytics.html: -------------------------------------------------------------------------------- 1 | {% if GOOGLE_ANALYTICS %} 2 | 12 | {% endif %} 13 | {% if GAUGES %} 14 | 27 | {% endif %} 28 | {% if PIWIK_URL and PIWIK_SITE_ID %} 29 | 46 | {% endif %} 47 | -------------------------------------------------------------------------------- /bulrush/templates/social.html: -------------------------------------------------------------------------------- 1 | 2 | 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### VirtualEnv template 3 | # Virtualenv 4 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 5 | .Python 6 | [Bb]in 7 | [Ii]nclude 8 | [Ll]ib 9 | [Ll]ib64 10 | [Ll]ocal 11 | [Ss]cripts 12 | pyvenv.cfg 13 | .venv 14 | pip-selfcheck.json 15 | ### JetBrains template 16 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 17 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 18 | 19 | # User-specific stuff: 20 | .idea/ 21 | 22 | ## File-based project format: 23 | *.iws 24 | 25 | ## Plugin-specific files: 26 | 27 | # IntelliJ 28 | /out/ 29 | 30 | # mpeltonen/sbt-idea plugin 31 | .idea_modules/ 32 | 33 | # JIRA plugin 34 | atlassian-ide-plugin.xml 35 | 36 | # Crashlytics plugin (for Android Studio and IntelliJ) 37 | com_crashlytics_export_strings.xml 38 | crashlytics.properties 39 | crashlytics-build.properties 40 | fabric.properties 41 | ### Python template 42 | # Byte-compiled / optimized / DLL files 43 | __pycache__/ 44 | *.py[cod] 45 | *$py.class 46 | 47 | # C extensions 48 | *.so 49 | 50 | # Distribution / packaging 51 | .Python 52 | env/ 53 | build/ 54 | develop-eggs/ 55 | dist/ 56 | downloads/ 57 | eggs/ 58 | .eggs/ 59 | lib/ 60 | lib64/ 61 | parts/ 62 | sdist/ 63 | var/ 64 | wheels/ 65 | *.egg-info/ 66 | .installed.cfg 67 | *.egg 68 | 69 | # PyInstaller 70 | # Usually these files are written by a python script from a template 71 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 72 | *.manifest 73 | *.spec 74 | 75 | # Installer logs 76 | pip-log.txt 77 | pip-delete-this-directory.txt 78 | 79 | # Unit test / coverage reports 80 | htmlcov/ 81 | .tox/ 82 | .coverage 83 | .coverage.* 84 | .cache 85 | nosetests.xml 86 | coverage.xml 87 | *,cover 88 | .hypothesis/ 89 | 90 | # Translations 91 | *.mo 92 | *.pot 93 | 94 | # Django stuff: 95 | *.log 96 | local_settings.py 97 | 98 | # Flask stuff: 99 | instance/ 100 | .webassets-cache 101 | 102 | # Scrapy stuff: 103 | .scrapy 104 | 105 | # Sphinx documentation 106 | docs/_build/ 107 | 108 | # PyBuilder 109 | target/ 110 | 111 | # Jupyter Notebook 112 | .ipynb_checkpoints 113 | 114 | # pyenv 115 | .python-version 116 | 117 | # celery beat schedule file 118 | celerybeat-schedule 119 | 120 | # SageMath parsed files 121 | *.sage.py 122 | 123 | # dotenv 124 | .env 125 | 126 | # virtualenv 127 | .venv 128 | venv/ 129 | ENV/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # Compiled CSS 138 | bulrush/static/css/*.css 139 | -------------------------------------------------------------------------------- /tests/test_license_generator.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from bulrush import generate_license 4 | 5 | 6 | class LicenseNameGeneratorTest(TestCase): 7 | 8 | @property 9 | def url(self): 10 | return '#' 11 | 12 | def _create_license(self): 13 | return generate_license('') 14 | 15 | def test_format(self): 16 | html = self._create_license() 17 | self.assertIn('