diff --git a/.buildpacks b/.buildpacks new file mode 100644 index 0000000..0d654d3 --- /dev/null +++ b/.buildpacks @@ -0,0 +1,2 @@ +https://github.com/heroku/heroku-buildpack-nodejs +https://github.com/heroku/heroku-buildpack-python diff --git a/.eslintrc b/.eslintrc index 0fa2039..b63cb78 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { - "extends": "./node_modules/eslint-config/.eslintrc", + "extends": "eslint-config-dabapps/base/.eslintrc", "globals": { - $: true + "$": true } } diff --git a/.gitignore b/.gitignore index 0e1697c..17f4cf4 100644 --- a/.gitignore +++ b/.gitignore @@ -60,7 +60,5 @@ target/ # Pelican stuff output/ theme/static/build/ -pelican_plugins/ -theme/static/src/scss/pygment.css deploy/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5246012 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pelican_plugins"] + path = pelican_plugins + url = https://github.com/getpelican/pelican-plugins diff --git a/.nvmrc b/.nvmrc index 4e32c7b..c250d84 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -5.10.1 +6.9.4 diff --git a/.sass-lint.yml b/.sass-lint.yml index 38af2b8..503b76e 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -56,6 +56,7 @@ rules: force-pseudo-nesting: 0 force-element-nesting: 0 placeholder-in-extend: 0 + no-url-domains: 0 nesting-depth: - 2 - diff --git a/Makefile b/Makefile deleted file mode 100644 index cad9b58..0000000 --- a/Makefile +++ /dev/null @@ -1,80 +0,0 @@ -BASEDIR=$(PWD) -ENV=$(BASEDIR)/env/bin -NODE_BIN=node_modules/.bin -PELICAN=$(ENV)/pelican - -OUTPUTDIR=$(BASEDIR)/output -PLUGINS_DIR=$(BASEDIR)/pelican_plugins -DEPLOY_DIR=$(BASEDIR)/deploy -CONFIG_FILE=$(BASEDIR)/config/pelicanconf.py - -FLAKE8_IGNORE=--ignore=E128,E501,E401,E402 - -build: install - rm -rf $(OUTPUTDIR)/* - @echo ">> Building static data..." - mkdir -p theme/static/build/js/lib theme/static/build/fonts theme/static/build/css theme/static/build/img - cp -R node_modules/font-awesome/fonts theme/static/build/ - npm run build-js - npm run build-scss - @echo ">> Building pelican..." - $(PELICAN) -o $(OUTPUTDIR) -vs $(CONFIG_FILE) - mv $(OUTPUTDIR)/assets/robots.txt $(OUTPUTDIR) - cp -R $(OUTPUTDIR)/assets/* $(OUTPUTDIR)/static - rm -rf $(OUTPUTDIR)/assets - -clean: - rm -rf $(OUTPUTDIR)/* - rm -rf $(BASEDIR)/env/ - rm -rf $(BASEDIR)/node_modules/ - rm -rf $(PLUGINS_DIR)/* - - -install: env node_modules pelican_plugins - -pelican_plugins: env - rm -rf $(PLUGINS_DIR) || "No existing extensions" - git clone --recursive https://github.com/getpelican/pelican-plugins $(PLUGINS_DIR) || "Git Fail" - @echo ">> Hotfixing..." - rm -rf $(PLUGINS_DIR)/pelican-jinja2content - git clone https://github.com/RealOrangeOne/pelican-jinja2content -b patch-1 --depth=1 $(PLUGINS_DIR)/pelican-jinja2content - -env: - pyvenv env - $(ENV)/pip install -r requirements.txt --upgrade - -node_modules: - npm install - - -test: unittest lint spellcheck securitycheck - -unittest: - $(ENV)/nose2 --verbose - -lint: - $(NODE_BIN)/eslint 'theme/static/src/js/' - $(NODE_BIN)/sass-lint -vqc .sass-lint.yml - $(ENV)/flake8 $(BASEDIR)/plugins/ $(FLAKE8_IGNORE) - $(ENV)/flake8 $(BASEDIR)/scripts/ $(FLAKE8_IGNORE) - $(ENV)/flake8 $(BASEDIR)/config/ $(FLAKE8_IGNORE) - $(ENV)/flake8 $(BASEDIR)/tests/ $(FLAKE8_IGNORE) - $(ENV)/yamllint config/config.yml - -spellcheck: - $(NODE_BIN)/mdspell --en-gb -ranx theme/templates/**/*.* theme/templates/*.* - $(NODE_BIN)/mdspell --en-gb -ranx content/**/*.md content/*.md content/**/*.html content/*.html - -securitycheck: - $(NODE_BIN)/nsp check - $(ENV)/bandit -r plugins/ config/ tests/ - - -upload: - git clone https://github.com/RealOrangeOne/host-container.git $(DEPLOY_DIR) - cp -rf $(OUTPUTDIR)/. $(DEPLOY_DIR)/site/ - @cd $(DEPLOY_DIR) && git remote add dokku $(DEPLOY_URL) && git add . && git commit -m "add files" && git push -f dokku master --quiet - rm -rf $(DEPLOY_DIR) - - -.PHONY: build clean test lint install upload diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..4246df3 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: bash scripts/run-build-server.sh diff --git a/README.md b/README.md index 6c84003..3bb2208 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ [![Circle CI](https://circleci.com/gh/RealOrangeOne/theorangeone.net.svg?style=svg)](https://circleci.com/gh/RealOrangeOne/theorangeone.net) +Find it here: [https://theorangeone.net](http://theorangeone.net) ## Installation ```bash -make +./scripts/build ``` ## Run Tests ```bash -make test +./scripts/test ``` diff --git a/circle.yml b/circle.yml index 1db10c7..8fd641b 100644 --- a/circle.yml +++ b/circle.yml @@ -2,24 +2,22 @@ machine: python: version: 3.5.1 node: - version: 5.11.1 + version: 6.9.4 environment: BUILD_PRODUCTION: true NODE_ENV: production NPM_CONFIG_PRODUCTION: false +checkout: + post: + - git submodule sync + - git submodule update --init --recursive + dependencies: - pre: - - make -B + override: + - scripts/build + - pelican -v test: override: - - make test - -deployment: - production: - branch: master - commands: - - git config --global user.email "git@theorangeone.net" - - git config --global user.name "TheOrangeOne" - - make upload + - scripts/test diff --git a/config/__init__.py b/config/__init__.py index e7e2577..36e7039 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -2,29 +2,12 @@ import yaml import os.path -class DotDictionary(dict): - def __getattr__(self, attr): - value = self[attr] - if type(value) == dict: - value = DotDictionary(value) - return value - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ +settings_dir = os.path.dirname(__file__) -class WrappedSettings: - def __init__(self): - self.settings_dir = os.path.join(os.path.dirname(__file__), 'config.yml') - settings = open(self.settings_dir) - self.settings = yaml.safe_load(settings) +def get_config(filename): + with open(os.path.join(settings_dir, '{}.yml'.format(filename))) as f: + return yaml.safe_load(f) - def __getattr__(self, name): - value = self.settings[name] - if type(value) == dict: - value = DotDictionary(value) - return value - def __str__(self): - return str(self.settings) - -settings = WrappedSettings() +social = get_config('social') diff --git a/config/config.yml b/config/social.yml similarity index 81% rename from config/config.yml rename to config/social.yml index 9f71cda..25d2e5a 100644 --- a/config/config.yml +++ b/config/social.yml @@ -1,22 +1,4 @@ --- - -author: Jake Howard -site_name: TheOrangeOne -url: https://theorangeone.net -timezone: Europe/London -language: en - -pelican_plugins: - - sitemap - - filetime_from_git - - pelican-jinja2content - - metatags - - autopages - - screenfetch - -sitemap_format: xml - - accounts: github: - GitHub @@ -103,7 +85,3 @@ footer_accounts: - instagram - youtube - flickr - -piwik: - url: piwik.theorangeone.net - site_id: 1 diff --git a/etc/environments/development/procfile b/etc/environments/development/procfile deleted file mode 100644 index 765d951..0000000 --- a/etc/environments/development/procfile +++ /dev/null @@ -1 +0,0 @@ -web: bash scripts/server.sh diff --git a/package.json b/package.json index c55595a..1b3d835 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,9 @@ "version": "4.0.0", "description": " Source code for TheOrangeOne.net", "scripts": { - "build-js": "./scripts/build-js.sh", - "build-scss": "./scripts/build-scss.sh" + "postinstall": "npm run create-build-dirs", + "create-build-dirs": "mkdir -p theme/static/build/js theme/static/build/fonts theme/static/build/css theme/static/build/img", + "start": "tstatic output/" }, "repository": { "type": "git", @@ -14,27 +15,23 @@ "animate.css": "=3.5.2", "ansi_up": "=1.3.0", "bootstrap-sass": "=3.3.7", - "font-awesome": "=4.6.3", - "jquery": "=2.2.3", - "jquery.easing": "=1.3.2", + "font-awesome": "=4.7.0", + "jquery": "=3.1.1", + "pygments-css": "=1.0.0", + "tstatic": "RealOrangeOne/tstatic", "underscore": "=1.8.3", - "wow.js": "=1.2.0" + "wow.js": "=1.2.2" }, "devDependencies": { - "autoprefixer": "=6.5.0", - "babel-preset-es2015": "=6.13.2", - "babel-preset-react": "=6.11.1", - "babelify": "=7.3.0", - "browserify": "=13.1.0", - "clean-css": "=3.4.20", - "eslint": "=1.5.0", - "eslint-config": "git://github.com/dabapps/eslint-config.git", - "eslint-plugin-react": "=3.4.2", + "autoprefixer": "=6.6.1", + "browserify": "=13.3.0", + "clean-css": "=3.4.23", + "eslint-config": "dabapps/eslint-config.git#2.0.4", "markdown-spellcheck": "=0.11.0", - "node-sass": "=3.8.0", - "nsp": "=2.6.1", + "node-sass": "=4.3.0", + "nsp": "=2.6.2", "postcss-cli": "=2.6.0", - "sass-lint": "=1.9.1", - "uglify-js": "=2.7.3" + "sass-lint": "=1.10.2", + "uglify-js": "=2.7.5" } } diff --git a/pelican_plugins b/pelican_plugins new file mode 160000 index 0000000..6cd5a60 --- /dev/null +++ b/pelican_plugins @@ -0,0 +1 @@ +Subproject commit 6cd5a608b45fdc21bb11ed1f72a151cdb71c1169 diff --git a/config/pelicanconf.py b/pelicanconf.py similarity index 65% rename from config/pelicanconf.py rename to pelicanconf.py index 41c225a..3679276 100644 --- a/config/pelicanconf.py +++ b/pelicanconf.py @@ -1,23 +1,25 @@ # -*- coding: utf-8 -*- # from __future__ import unicode_literals -from git import Repo import sys, os sys.path.insert(0, os.path.realpath('./')) -from config import settings - # Global core settings -AUTHOR = settings.author -SITENAME = settings.site_name -SITEURL = settings.url -PATH = '../content' -TIMEZONE = settings.timezone -DEFAULT_LANG = settings.language +AUTHOR = "Jake Howard" +SITENAME = "TheOrangeOne" +SITEURL = "https://theorangeone.net" +PATH = 'content' +TIMEZONE = "Europe/London" +DEFAULT_LANG = "en" PAGE_PATHS = ["pages"] -THEME = "../theme" +THEME = "theme" THEME_STATIC_DIR = "static" THEME_STATIC_PATHS = ["static/build"] STATIC_PATHS = ["assets"] +DEFAULT_DATE = 'fs' +WITH_FUTURE_DATES = True +LOAD_CONTENT_CACHE = False +CACHE_CONTENT = False +DELETE_OUTPUT_DIRECTORY = True USE_FOLDER_AS_CATEGORY = True DEFAULT_PAGINATION = False @@ -30,11 +32,13 @@ FOOTER_LINKS = links.footer() INDEX_PROJECTS = links.index_projects() # Extra config -REPO = Repo(search_parent_directories=True) BUILD_PRODUCTION = 'BUILD_PRODUCTION' in os.environ from plugins import image_resizer META_IMAGES = image_resizer.generate() -PIWIK = settings.piwik +PIWIK = { + 'url': 'piwik.theorangeone.net', + 'site_id': 1 +} # Disable some pages TAG_URL = False @@ -61,14 +65,22 @@ FEED_ATOM = 'feed.atom' FEED_DOMAIN = SITEURL # Setup plugins -PLUGIN_PATHS = ["../pelican_plugins", "../plugins"] -PLUGINS = settings.pelican_plugins +PLUGIN_PATHS = ["plugins", "pelican_plugins"] +PLUGINS = [ + 'sitemap', + 'pelican-jinja2content', + 'metatags', + 'autopages', + 'screenfetch', + 'post_build', + 'static_build' +] if BUILD_PRODUCTION: PLUGINS.append("minify") # only minify on production build SITEMAP = { - "format": settings.sitemap_format + "format": 'xml' } CATEGORY_PAGE_PATH = "theme/templates/categories" MINIFY = { @@ -81,14 +93,16 @@ MINIFY = { from fontawesome_markdown import FontAwesomeExtension from pyembed.markdown import PyEmbedMarkdown from mkdcomments import CommentsExtension -MD_EXTENSIONS = [ - FontAwesomeExtension(), - PyEmbedMarkdown(), - CommentsExtension(), - 'codehilite(css_class=highlight)', - 'extra' -] - +MARKDOWN = { + 'extensions': [ + FontAwesomeExtension(), + PyEmbedMarkdown(), + CommentsExtension(), + 'codehilite(css_class=highlight)', + 'extra' + ], + "output_format": "html5" +} # Setup jinja2 filters from plugins import filters JINJA_FILTERS = { @@ -96,3 +110,12 @@ JINJA_FILTERS = { "category_find": filters.category_find, "limit": filters.limit } + +JINJA_ENVIRONMENT = { + 'trim_blocks': True, + 'lstrip_blocks': True, + 'extensions': [ + 'jinja2.ext.with_', + 'plugins.include_with.IncludeWith' + ] +} diff --git a/plugins/include_with.py b/plugins/include_with.py new file mode 100644 index 0000000..3380bda --- /dev/null +++ b/plugins/include_with.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +""" + # Jinja-IncludeWith + + A Jinja2 preprocessor extension that let you update the `include` + context like this: + + {% include "something.html" with foo=bar %} + {% include "something.html" with a=3, b=2+2, c='yes' %} + + You **must** also include 'jinja2.ext.with_' in the extensions list. + + :copyright: [Juan-Pablo Scaletti] (http://jpscaletti.com). + :license: [MIT] (http://www.opensource.org/licenses/mit-license.php). + + Copied from https://github.com/jpscaletti/jinja-includewith due to not pip-installable + +""" +import re + +from jinja2.ext import Extension + + +__version__ = '0.1' + +rx = re.compile(r'\{\%\s*include\s+(?P[^\s]+)\s+with\s+(?P.*?)\s*\%\}', + re.IGNORECASE) + + +class IncludeWith(Extension): + + def preprocess(self, source, name, filename=None): + lastpos = 0 + while 1: + m = rx.search(source, lastpos) + if not m: + break + + lastpos = m.end() + d = m.groupdict() + context = d['context'].strip() + if context == 'context': + continue + + source = ''.join([ + source[:m.start()], + '{% with ', context, ' %}', + '{% include ', d['tmpl'].strip(), ' %}', + '{% endwith %}', + source[m.end():] + ]) + + return source diff --git a/plugins/links.py b/plugins/links.py index b8d21e4..f1de30f 100644 --- a/plugins/links.py +++ b/plugins/links.py @@ -1,6 +1,6 @@ from collections import namedtuple from random import shuffle -from config import settings, DotDictionary +from config import social ProjectLink = namedtuple("ProjectLink", ["name", "url", "image"]) @@ -8,20 +8,20 @@ ProjectLink = namedtuple("ProjectLink", ["name", "url", "image"]) def accounts(): links = {} - for key, (site, user, url, icon) in settings.accounts.items(): - links[key] = DotDictionary({ + for key, (site, user, url, icon) in social['accounts'].items(): + links[key] = { 'key': key, 'site': site, 'username': user, 'url': url.format(user), 'icon': icon - }) + } return links def footer(): all_accounts = accounts() - return [all_accounts[account] for account in settings.footer_accounts] + return [all_accounts[account] for account in social['footer_accounts']] def index_projects(): diff --git a/plugins/metatags.py b/plugins/metatags.py index 8e6e28c..6c23684 100644 --- a/plugins/metatags.py +++ b/plugins/metatags.py @@ -17,10 +17,10 @@ def html_to_raw(html): def get_twiter_tags(instance): return { "twitter:card": "summary_large_image", - "twitter:site": instance.settings.get("ACCOUNTS")["twitter"].username, + "twitter:site": instance.settings.get("ACCOUNTS")["twitter"]['username'], "twitter:title": instance.metadata.get("title", ""), "twitter:description": html_to_raw(instance.metadata.get("summary", "")), - "twitter:creator": instance.settings.get("ACCOUNTS")["twitter"].username, + "twitter:creator": instance.settings.get("ACCOUNTS")["twitter"]['username'], "twitter:image": instance.metadata.get("image", ""), "twitter:image:alt": html_to_raw(instance.metadata.get("summary", "")), "twitter:url": os.path.join(instance.settings.get("SITEURL", ""), instance.url) diff --git a/plugins/pelican-jinja2content.py b/plugins/pelican-jinja2content.py new file mode 100644 index 0000000..902d26d --- /dev/null +++ b/plugins/pelican-jinja2content.py @@ -0,0 +1,39 @@ +import os +from pelican import signals, contents +from jinja2 import Environment, ChoiceLoader, FileSystemLoader +from config import social + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def execjinja2(instance): + if type(instance) in (contents.Article, contents.Page): + jinja2_env = Environment( # nosec + loader=ChoiceLoader([ + FileSystemLoader( + os.path.join(BASE_DIR, instance.settings['THEME'], 'templates') + ), + FileSystemLoader( + os.path.join(BASE_DIR, instance.settings['PATH']) + ) + ]), + **instance.settings['JINJA_ENVIRONMENT'], + ) + + jinja2_env.filters.update(instance.settings['JINJA_FILTERS']) + + jinja2_template = jinja2_env.from_string(instance._content) + + kwargs = instance._context + if type(instance) is contents.Article: + kwargs['article'] = instance + elif type(instance) is contents.Page: + kwargs['page'] = instance + + kwargs['social'] = social + + instance._content = jinja2_template.render(**kwargs) + + +def register(): + signals.content_object_init.connect(execjinja2) diff --git a/plugins/post_build.py b/plugins/post_build.py new file mode 100644 index 0000000..35096f4 --- /dev/null +++ b/plugins/post_build.py @@ -0,0 +1,25 @@ +from pelican import signals +import os +from plugins.utils import run_command + + +OUTPUT_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'output') + + +def post_build(*args, **kwargs): + run_command('Copying Robots.txt', [ + 'mv', os.path.join(OUTPUT_PATH, 'assets', 'robots.txt'), OUTPUT_PATH + ]) + run_command('Copying Assets', [ + 'cp', '-R', os.path.join(OUTPUT_PATH, 'assets', '*'), os.path.join(OUTPUT_PATH, 'static') + ], True) + run_command('Remove Old Assets', [ + 'rm', '-rf', os.path.join(OUTPUT_PATH, 'assets') + ]) + run_command('Remove Drafts', [ + 'rm', '-rf', os.path.join(OUTPUT_PATH, 'drafts') + ]) + + +def register(): + signals.finalized.connect(post_build) diff --git a/plugins/static_build.py b/plugins/static_build.py new file mode 100644 index 0000000..e8a5965 --- /dev/null +++ b/plugins/static_build.py @@ -0,0 +1,36 @@ +from pelican import signals +from plugins.utils import node_bin, run_command, NODE_PRODUCTION +import logging + +logger = logging.getLogger(__file__) + + +def static_build(*args, **kwargs): + if NODE_PRODUCTION: + logger.info('Building Production...') + UGLIFY_ARGS = ['--compress', '--screw-ie8', '--define', '--stats', '--keep-fnames'] + else: + UGLIFY_ARGS = [] + run_command('Copying Fonts', ['cp', '-r', 'node_modules/font-awesome/fonts', 'theme/static/build/']) + run_command('Building Bootstrap', [node_bin('uglifyjs'), 'node_modules/bootstrap-sass/assets/javascripts/bootstrap.js', UGLIFY_ARGS, '-o', 'theme/static/build/js/bootstrap.js']) + run_command('Building jQuery', [node_bin('uglifyjs'), 'node_modules/jquery/dist/jquery.js', UGLIFY_ARGS, '-o', 'theme/static/build/js/jquery.js']) + run_command('Building Application', [ + node_bin('browserify'), + 'theme/static/src/js/app.js', + '-o', 'theme/static/build/js/app.js' + ]) + + logger.info('JS built!') + + run_command('Building Styles', [node_bin('node-sass'), 'theme/static/src/scss/index.scss', 'theme/static/build/css/index.css', '--source-map-embed']) + + logger.info('SCSS Built!') + + if NODE_PRODUCTION: + run_command('Compressing Application', [node_bin('uglifyjs'), 'theme/static/build/js/app.js', UGLIFY_ARGS, '-o', 'theme/static/build/js/app.js']) + run_command('Prefixing Styles', [node_bin('postcss'), '-u', 'autoprefixer', '-o', 'theme/static/build/css/index.css', 'theme/static/build/css/index.css']) + run_command('Compressing Styles', [node_bin('cleancss'), '-d', '--s0', '-o', 'theme/static/build/css/index.css', 'theme/static/build/css/index.css']) + + +def register(): + signals.static_generator_init.connect(static_build) diff --git a/plugins/utils.py b/plugins/utils.py new file mode 100644 index 0000000..5817b1f --- /dev/null +++ b/plugins/utils.py @@ -0,0 +1,31 @@ +import subprocess # nosec +import logging +import os + + +logger = logging.getLogger(__file__) + + +NODE_PRODUCTION = os.environ.get('NODE_ENV') == 'production' + + +def flatten_list(array): + res = [] + for el in array: + if isinstance(el, (list, tuple)): + res.extend(flatten_list(el)) + continue + res.append(el) + return res + + +def run_command(detail, args, wrap=False): + if wrap: + run_command(detail, ['bash', '-c', ' '.join(flatten_list(args))]) + else: + logger.info(detail + '...') + subprocess.run(flatten_list(args), check=True) + + +def node_bin(exec): + return os.path.join('node_modules', '.bin', exec) diff --git a/requirements.txt b/requirements.txt index ac1dced..fa30064 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ -bandit==1.1.0 -flake8==3.0.4 -fontawesome_markdown==0.2.5 +bandit==1.4.0 +flake8==3.2.1 +fontawesome_markdown==0.2.6 git+https://github.com/ryneeverett/python-markdown-comments.git -gitpython==2.0.8 +gitpython==2.1.1 iso8601==0.1.11 markdown==2.6.7 nose2==0.6.5 pelican-minify==0.9 -pelican==3.6.3 +pelican==3.7.1 pyembed-markdown==1.1.0 -pygments-style-github==0.4 python-resize-image==1.1.3 pyyaml==3.12 -sh==1.11 -yamllint==1.4.1 +safety==0.5.1 +sh==1.12.9 +yamllint==1.6.0 diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000..78082e3 --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.5.1 diff --git a/scripts/build b/scripts/build new file mode 100755 index 0000000..71635b4 --- /dev/null +++ b/scripts/build @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +if [ -z "$NVM_DIR" ] +then + NVM_DIR="$HOME/.nvm" +fi + +. $NVM_DIR/nvm.sh +nvm install +nvm use + +set -e + +pyvenv env + +env/bin/pip install -r requirements.txt + +npm install diff --git a/scripts/build-js.sh b/scripts/build-js.sh deleted file mode 100755 index 2071800..0000000 --- a/scripts/build-js.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$NODE_ENV" = "production" ] -then - echo ">>> WARNING: Building in Production Mode!" -fi - -mkdir -p theme/static/build/js/lib - -if [ "$NODE_ENV" = "production" ] -then - echo ">> Compressing Libraries..." - uglifyjs node_modules/bootstrap-sass/assets/javascripts/bootstrap.js --compress --screw-ie8 --define --stats --keep-fnames -o theme/static/build/js/lib/bootstrap.js - uglifyjs theme/static/build/js/lib/* --compress --screw-ie8 --define --stats --keep-fnames -o theme/static/build/js/libs.js -else - echo ">> Building Libraries..." - cp node_modules/bootstrap-sass/assets/javascripts/bootstrap.js theme/static/build/js/lib/bootstrap.js - uglifyjs theme/static/build/js/lib/* --screw-ie8 --stats --keep-fnames -o theme/static/build/js/libs.js -fi - -rm -rf theme/static/build/js/lib - -if [ "$NODE_ENV" = "production" ] -then - echo ">> Compressing jQuery..." - uglifyjs node_modules/jquery/dist/jquery.js --compress --screw-ie8 --define --stats --keep-fnames -o theme/static/build/js/jquery.js -else - echo ">> Building jQuery..." - cp node_modules/jquery/dist/jquery.js theme/static/build/js/jquery.js -fi - - -echo ">> Building Application JS..." -browserify -t [ babelify --presets [ es2015 react ] ] theme/static/src/js/app.js -o theme/static/build/js/app.js - -if [ "$NODE_ENV" = "production" ] -then - echo ">> Compressing Application..." - uglifyjs theme/static/build/js/app.js --compress --screw-ie8 --define --stats --keep-fnames -o theme/static/build/js/app.js -fi - -echo "> JS Built!" diff --git a/scripts/build-scss.sh b/scripts/build-scss.sh deleted file mode 100755 index 164454a..0000000 --- a/scripts/build-scss.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$NODE_ENV" = "production" ] -then - echo ">>> WARNING: Building in Production Mode!" -fi - -echo ">> Generating Pygments styles..." -env/bin/pygmentize -S github -f html -a .highlight > theme/static/src/scss/pygment.css - -echo ">> Building SCSS..." -node-sass theme/static/src/scss/index.scss theme/static/build/css/index.css --source-map-embed - -echo ">> Post-Processing..." -postcss -u autoprefixer -o theme/static/build/css/index.css theme/static/build/css/index.css - -if [ "$NODE_ENV" = "production" ] -then - echo ">> Compressing CSS..." - cleancss -d --s0 -o theme/static/build/css/index.css theme/static/build/css/index.css -fi diff --git a/scripts/run-build-server.sh b/scripts/run-build-server.sh new file mode 100755 index 0000000..2781419 --- /dev/null +++ b/scripts/run-build-server.sh @@ -0,0 +1,10 @@ +#/usr/bin/env bash + +# Make pelican build on startup + + +echo "> Building Site" +pelican -v + +echo "Starting Server" +npm start diff --git a/scripts/server.sh b/scripts/server.sh deleted file mode 100755 index f09ca56..0000000 --- a/scripts/server.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -cd output/ -python3 -m pelican.server $PORT diff --git a/scripts/test b/scripts/test new file mode 100755 index 0000000..0217206 --- /dev/null +++ b/scripts/test @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +export PATH=env/bin:${PATH} +export PATH=node_modules/.bin:${PATH} + +FLAKE8_IGNORE=--ignore=E128,E501,E401,E402 + +set -e + +echo "> Running tests" + +nose2 + +echo ">> Testing client-side" +eslint 'theme/static/src/js/' +sass-lint -vqc .sass-lint.yml + +echo ">> Running flake8" +flake8 plugins/ $FLAKE8_IGNORE +flake8 config/ $FLAKE8_IGNORE +flake8 tests/ $FLAKE8_IGNORE +flake8 pelicanconf.py $FLAKE8_IGNORE + +echo ">> Checking config" +yamllint config/social.yml + +echo ">> Running spellcheck" +mdspell --en-gb -ranx theme/templates/**/*.* theme/templates/*.* +mdspell --en-gb -ranx content/**/*.md content/*.md content/**/*.html content/*.html + +echo ">> Running security check" +nsp check +safety check +bandit -r plugins/ config/ tests/ + +echo "> Tests complete!" diff --git a/tests/__init__.py b/tests/__init__.py index 6183f0b..83d67b0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -29,7 +29,7 @@ class TestCase(unittest.TestCase): client = TestClient() def get_children(self, content): - return str(list(content.children)[0]) + return str(list(content.children)[0]).strip() def assertTitle(self, content, title): self.assertIn(title, content.title.string) @@ -37,3 +37,6 @@ class TestCase(unittest.TestCase): def assertHeaderTitle(self, content, title): header_title = content.find('h1', class_="section-heading") self.assertIn(title, self.get_children(header_title)) + + def assertSamePath(self, p1, p2): + self.assertEqual(self.client.build_path(p1), self.client.build_path(p2)) diff --git a/tests/tests_common.py b/tests/tests_common.py index ebc8e68..3d6c70d 100644 --- a/tests/tests_common.py +++ b/tests/tests_common.py @@ -1,6 +1,7 @@ from tests import TestCase -from config import settings, DotDictionary from bs4 import BeautifulSoup +import pelicanconf as settings +from config import social as social_settings from unittest import skipIf from os import environ @@ -16,22 +17,24 @@ class CorePagesTestCase(TestCase): def test_has_sitemap(self): content = self.client.get('sitemap.xml') - self.assertIn(settings.url, content) + self.assertIn(settings.SITEURL, content) def test_has_atom_feed(self): content = self.client.get('feed.atom') - self.assertIn(settings.url, content) + self.assertIn(settings.SITEURL, content) def test_has_404_page(self): content = self.client.get('.404.html') self.assertTitle(content, '404') + +class CommonPagesTestCase(TestCase): def test_has_scripts(self): content = self.client.get('index.html') for script in content.find_all('script'): if script.attrs.get('id') == 'piwik': continue - self.client.exists(script.attrs['src']) + self.assertTrue(self.client.exists(script.attrs['src'])) def test_has_stylesheet(self): content = self.client.get('index.html') @@ -49,9 +52,25 @@ class CorePagesTestCase(TestCase): content = self.client.get('index.html') footer = content.footer for link in footer.find('p', class_="social").find_all('a'): - self.assertIn(link.attrs['alt'], settings.footer_accounts) + self.assertIn(link.attrs['alt'], social_settings['footer_accounts']) self.assertIn("fa fa-", str(list(link.children)[0])) + def test_navbar_links(self): + content = self.client.get('.404.html') # a page that isnt home + links = content.find('ul', class_='navbar-nav').find_all('a') + self.assertEqual(len(links), 5) + for link in links: + element = self.get_children(link) + self.assertEqual(link.attrs['href'], '/{}/'.format(element.lower())) + self.assertTrue(self.client.exists(link.attrs['href'])) + + def test_navbar_index_link(self): + content = self.client.get('.404.html') # a page that isnt home + link = content.find('a', class_='navbar-brand') + self.assertTrue(self.client.exists(link.attrs['href'])) + self.assertSamePath(link.attrs['href'], '/') + self.assertEqual(self.get_children(link), settings.SITENAME) + @skipIf(not environ.get('BUILD_PRODUCTION', False), 'Not building production') def test_has_analytics(self): content = self.client.get('index.html', False) @@ -59,60 +78,10 @@ class CorePagesTestCase(TestCase): self.assertNotEqual(piwik_script_tag, None) piwik_script = self.get_children(piwik_script_tag) self.assertIn('piwik.js', piwik_script) - self.assertIn(str(settings.piwik.site_id), piwik_script) + self.assertIn(str(settings.PIWIK['site_id']), piwik_script) piwik_img = content.find('noscript', id='piwik').find('img') - self.assertIn(settings.piwik.url, piwik_img.attrs['src']) - self.assertIn(str(settings.piwik.site_id), piwik_img.attrs['src']) - - -class DotDictionaryTestCase(TestCase): - def setUp(self): - self.test_dict = DotDictionary({ - 'foo': 'bar', - 'bar': { - 'foo': 'bar' - } - }) - - def test_returns_value(self): - self.assertEqual(self.test_dict.foo, 'bar') - - def test_returns_self_on_dict(self): - self.assertEqual(self.test_dict.bar, { - 'foo': 'bar' - }) - self.assertIsInstance(self.test_dict.bar, DotDictionary) - - def test_set(self): - self.test_dict.baz = 'foo' - self.assertEqual(self.test_dict, { - 'foo': 'bar', - 'baz': 'foo', - 'bar': { - 'foo': 'bar' - } - }) - - def test_delete(self): - del self.test_dict.bar - with self.assertRaises(KeyError): - print(self.test_dict.bar) - self.assertEqual(self.test_dict, { - 'foo': 'bar' - }) - - -class WrappedSettingTestCase(TestCase): - def test_has_data(self): - self.assertIsInstance(settings.settings, dict) - self.assertTrue(len(settings.settings)) - - def test_returns_values(self): - self.assertEqual(settings.language, 'en') - self.assertEqual(settings.timezone, 'Europe/London') - - def test_returns_dict(self): - self.assertIsInstance(settings.accounts, DotDictionary) + self.assertIn(settings.PIWIK['url'], piwik_img.attrs['src']) + self.assertIn(str(settings.PIWIK['site_id']), piwik_img.attrs['src']) class TestClientTestCase(TestCase): diff --git a/tests/tests_pages.py b/tests/tests_pages.py index 12d4333..e7ddde2 100644 --- a/tests/tests_pages.py +++ b/tests/tests_pages.py @@ -1,12 +1,24 @@ from tests import TestCase -from config import settings +from config import social as social_settings import os.path class HomepageTestCase(TestCase): + def test_about_section(self): + content = self.client.get('index.html') + about = content.find('section', id='about') + self.assertIsNotNone(about) + about_content = about.find('p') + self.assertNotEqual(self.get_children(about_content), '') + about_link = about.find('a') + self.assertTrue(self.client.exists(about_link.attrs['href'])) + def test_blog_links(self): content = self.client.get('index.html') - blogs = content.find('section', id='blog').find_all('div', class_="col-xs-12") + blog = content.find('section', id='blog') + blog_link = blog.find('a', class_='btn') + self.assertTrue(self.client.exists(blog_link.attrs['href'])) + blogs = blog.find_all('div', class_="col-xs-12") self.assertTrue(len(blogs) <= 4) for post in blogs: url = os.path.join(post.find('a').attrs['href'], 'index.html') @@ -19,6 +31,15 @@ class HomepageTestCase(TestCase): url = os.path.join(project.attrs['href'], 'index.html') self.assertTrue(self.client.exists(url)) + def test_navbar(self): + content = self.client.get('index.html') + links = content.find('ul', class_='navbar-nav').find_all('a') + self.assertEqual(len(links), 5) + for link in links: + self.assertIn('page-scroll', link.attrs['class']) + element = self.get_children(link) + self.assertEqual(link.attrs['href'], '#' + element.lower()) + class AboutPageTestCase(TestCase): def test_title(self): @@ -44,7 +65,7 @@ class AboutPageTestCase(TestCase): self.assertEqual(len(tags), 1) tag = tags[0] self.assertEqual('medium', tag.attrs['data-theme']) - self.assertEqual(settings.accounts.github[1], tag.attrs['data-github']) + self.assertEqual(social_settings['accounts']['github'][1], tag.attrs['data-github']) class Page404TestCase(TestCase): diff --git a/theme/static/src/js/app.js b/theme/static/src/js/app.js index adfebec..df81682 100644 --- a/theme/static/src/js/app.js +++ b/theme/static/src/js/app.js @@ -1,9 +1,12 @@ -import './creative'; -import ansi_up from 'ansi_up'; +'use strict'; + +require('./creative'); +var ansi_up = require('ansi_up'); +var consts = require('./consts'); $('.image').each(function () { // setup div-image hybrids - const ele = $(this); + var ele = $(this); if (ele.data('image')) { ele.css('background-image', 'url(' + ele.data('image') + ')'); } else { @@ -12,6 +15,18 @@ $('.image').each(function () { // setup div-image hybrids }); $('.ansi-up').each(function () { - const ele = $(this); + var ele = $(this); ele.html(ansi_up.ansi_to_html(ele.html())); }); + + +$('.navbar-brand').bind('click', function (event) { + if ($('html').scrollTop() > consts.NAVBAR_HEIGHT) { + $('html, body').stop().animate({ + scrollTop: 0 + }, consts.SCROLL_SPEED); + } else { + window.location = '/'; + } + event.preventDefault(); +}); diff --git a/theme/static/src/js/consts.js b/theme/static/src/js/consts.js new file mode 100644 index 0000000..038ceea --- /dev/null +++ b/theme/static/src/js/consts.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = { + NAVBAR_HEIGHT: $('#main-nav').height(), + SCROLL_SPEED: 750 +}; diff --git a/theme/static/src/js/creative/creative.js b/theme/static/src/js/creative/creative.js deleted file mode 100644 index da1676c..0000000 --- a/theme/static/src/js/creative/creative.js +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Start Bootstrap - Creative Bootstrap Theme (http://startbootstrap.com) - * Code licensed under the Apache License v2.0. - * For details, see http://www.apache.org/licenses/LICENSE-2.0. - */ - -// jQuery for page scrolling feature - requires jQuery Easing plugin -$('a.page-scroll').bind('click', function(event) { - const anchor = $(this); - $('html, body').stop().animate( - { - scrollTop: ($(anchor.attr('href')).offset().top - 50) - }, - 1250, - 'easeInOutExpo' - ); - event.preventDefault(); -}); - -// Highlight the top nav as scrolling occurs -$('body').scrollspy({ - target: '.navbar-fixed-top', - offset: 51 -}); - -// Closes the Responsive Menu on Menu Item Click -$('.navbar-collapse ul li a').click(function() { - $('.navbar-toggle:visible').click(); -}); - - -// Offset for Main Navigation -$('#main-nav').affix({ - offset: { - top: 50 - } -}); diff --git a/theme/static/src/js/creative/index.js b/theme/static/src/js/creative/index.js index 43b0721..41329c1 100644 --- a/theme/static/src/js/creative/index.js +++ b/theme/static/src/js/creative/index.js @@ -1,6 +1,41 @@ -import 'jquery.easing'; -import './creative'; +'use strict'; -import WOW from 'wow.js'; +var WOW = require('wow.js'); +var consts = require('../consts'); new WOW().init(); + +/*! + * Start Bootstrap - Creative Bootstrap Theme (http://startbootstrap.com) + * Code licensed under the Apache License v2.0. + * For details, see http://www.apache.org/licenses/LICENSE-2.0. + * (Edited for theorangeone.net) + */ + +// jQuery for page scrolling feature +$('a.page-scroll').bind('click', function(event) { + $('html, body').stop().animate( + { scrollTop: $($(this).attr('href')).offset().top - consts.NAVBAR_HEIGHT }, + consts.SCROLL_SPEED + ); + event.preventDefault(); +}); + +// Highlight the top nav as scrolling occurs +$('body').scrollspy({ + target: '.navbar-fixed-top', + offset: consts.NAVBAR_HEIGHT + 1 +}); + +// Closes the Responsive Menu on Menu Item Click +$('.navbar-collapse ul li a').click(function() { + $('.navbar-toggle:visible').click(); +}); + + +// Offset for Main Navigation +$('#main-nav').affix({ + offset: { + top: consts.NAVBAR_HEIGHT * 1.5 + } +}); diff --git a/theme/static/src/scss/index.scss b/theme/static/src/scss/index.scss index 2e68d1a..cdf148f 100644 --- a/theme/static/src/scss/index.scss +++ b/theme/static/src/scss/index.scss @@ -10,6 +10,7 @@ /* @group Libraries */ @import "node_modules/bootstrap-sass/assets/stylesheets/_bootstrap"; @import "node_modules/animate.css/animate"; +@import "node_modules/pygments-css/github"; $fa-font-path: "../fonts"; @import "node_modules/font-awesome/scss/font-awesome"; @@ -18,7 +19,6 @@ $fa-font-path: "../fonts"; /* @group Other Imports */ @import "creative/creative"; -@import "pygment"; @import "homepage"; @import "footer"; @import "library-overrides"; diff --git a/theme/templates/article.html b/theme/templates/article.html index 3c284f7..82d27a4 100644 --- a/theme/templates/article.html +++ b/theme/templates/article.html @@ -9,7 +9,7 @@ {% endblock %} {% block content %} - {% include 'extras/header.html' with context %} + {% include 'extras/header.html' with instance=article %}
{{ article.content }} diff --git a/theme/templates/base.html b/theme/templates/base.html index 845daa8..246a66a 100644 --- a/theme/templates/base.html +++ b/theme/templates/base.html @@ -31,7 +31,7 @@ {% include "extras/footer.html" %} - + diff --git a/theme/templates/blog.html b/theme/templates/blog.html index 20b657c..a6ed1d1 100644 --- a/theme/templates/blog.html +++ b/theme/templates/blog.html @@ -9,7 +9,7 @@ {% endblock %} {% block content %} - {% include 'extras/header.html' with context %} + {% include 'extras/header.html' with instance=article %}

diff --git a/theme/templates/categories/wall-of-sheep.md b/theme/templates/categories/wall-of-sheep.md index 452300f..35adeb5 100644 --- a/theme/templates/categories/wall-of-sheep.md +++ b/theme/templates/categories/wall-of-sheep.md @@ -1,3 +1,8 @@ +--- +hide_from_nav: true +--- + + I've always had a keen interest with security and privacy. Whether it's protecting my personal information, or needlessly encrypting things, security is very important to me. With an interest in keeping things secure, comes an interest in breaking through others security. Whenever I start using a new tool, or piece of software, I almost instantly go to the security settings and check out what it supports. The same goes for making the software, I've been known to spend a lot of extra time working on the security aspect more than actual features. diff --git a/theme/templates/extras/header.html b/theme/templates/extras/header.html index fdd06bc..f1538a7 100644 --- a/theme/templates/extras/header.html +++ b/theme/templates/extras/header.html @@ -1,6 +1,6 @@ -

diff --git a/theme/templates/extras/page-header.html b/theme/templates/extras/page-header.html deleted file mode 100644 index 8c80e3b..0000000 --- a/theme/templates/extras/page-header.html +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/theme/templates/page.html b/theme/templates/page.html index dd8af45..2e16bbd 100644 --- a/theme/templates/page.html +++ b/theme/templates/page.html @@ -10,7 +10,7 @@ {% block content %} {% if not page.no_container %} - {% include 'extras/page-header.html' with context %} + {% include 'extras/header.html' with instance=page %}
{{ page.content }} diff --git a/theme/templates/projects.html b/theme/templates/projects.html index 73d4962..3d35ed5 100644 --- a/theme/templates/projects.html +++ b/theme/templates/projects.html @@ -9,7 +9,7 @@ {% endblock %} {% block content %} - {% include 'extras/header.html' with context %} + {% include 'extras/header.html' with instance=article %}

@@ -18,15 +18,19 @@ {{ article.content }}

-
-
- {% if article.download_link %} - Download {{ article.title }} - {% endif %} + {% if article.download_link or article.repo %} +
+
+
+ {% if article.download_link %} + Download {{ article.title }} + {% endif %} - {% if article.repo %} - View on Github - {% endif %} -
-
+ {% if article.repo %} + View on Github + {% endif %} +
+
+
+ {% endif %} {% endblock %}