summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/baseline.sh33
-rwxr-xr-xscripts/codacy_coverage.sh10
-rw-r--r--scripts/debug_rebuilds/README.md11
-rwxr-xr-xscripts/debug_rebuilds/step1_build_and_dumpdb.py50
-rwxr-xr-xscripts/debug_rebuilds/step2_analyze_py_files.py58
-rwxr-xr-xscripts/document_path_handlers.py28
-rwxr-xr-xscripts/generate_conf.py9
-rwxr-xr-xscripts/generate_symlinked_list.sh8
-rwxr-xr-xscripts/getbaseline.sh14
-rwxr-xr-xscripts/getwheelhouse.sh7
-rwxr-xr-xscripts/github-release.py20
-rwxr-xr-xscripts/import_po.py11
-rwxr-xr-xscripts/jinjify.py86
-rwxr-xr-xscripts/langmatrix.py20
-rwxr-xr-xscripts/langstatus.py22
-rwxr-xr-xscripts/release156
-rwxr-xr-xscripts/set_version.py47
-rwxr-xr-xscripts/snapcraft/nikola.sh21
-rwxr-xr-xscripts/update-bower.sh57
-rwxr-xr-xscripts/update-npm-assets.sh59
20 files changed, 559 insertions, 168 deletions
diff --git a/scripts/baseline.sh b/scripts/baseline.sh
new file mode 100755
index 0000000..d4bf2ba
--- /dev/null
+++ b/scripts/baseline.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+PYVER=$(scripts/getpyver.py short)
+if [[ $PYVER == '3.8' ]]; then
+ if [[ "$1" == "check" ]]; then
+ echo -e "\033[36m>> Downloading baseline for $PYVER...\033[0m"
+ # we only support 3.8
+ wget https://github.com/getnikola/invariant-builds/archive/v$PYVER'.zip'
+ unzip -q 'v'$PYVER'.zip'
+ rm -rf baseline$PYVER
+ mv invariant-builds-$PYVER baseline
+ rm 'v'$PYVER'.zip'
+ fi
+else
+ echo -e "\033[35m>> Version $PYVER does not support baseline testing.\033[0m"
+ exit 0
+fi
+nikola init -qd nikola-baseline-build
+cd nikola-baseline-build
+cp ../tests/data/1-nolinks.rst posts/1.rst
+rm "pages/creating-a-theme.rst" "pages/extending.rst" "pages/internals.rst" "pages/manual.rst" "pages/social_buttons.rst" "pages/theming.rst" "pages/path_handlers.rst" "pages/charts.rst"
+LC_ALL='en_US.UTF-8' PYTHONHASHSEED=0 nikola build --invariant
+if [[ "$1" == "check" ]]; then
+ echo -e "\033[36m>> Testing baseline...\033[0m"
+ diff -ubwr ../baseline output
+ if [[ $? == 0 ]]; then
+ echo -e "\033[32;1m>> Baseline test successful\033[0m"
+ else
+ CODE=$?
+ echo -e "\033[31;1m>> Failed with exit code $CODE\033[0m"
+ echo "If this change was intentional, the baseline site needs to be rebuilt (maintainers only). Otherwise, please fix this issue."
+ exit $CODE
+ fi
+fi
diff --git a/scripts/codacy_coverage.sh b/scripts/codacy_coverage.sh
new file mode 100755
index 0000000..3ba43ee
--- /dev/null
+++ b/scripts/codacy_coverage.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+PYVER=$(scripts/getpyver.py short)
+if [[ $PYVER == '3.6' ]]; then
+ if [[ "x$CODACY_PROJECT_TOKEN" == "x" ]]; then
+ echo "Warning: Codacy token not available (PR from non-member), unable to send coverage info."
+ exit 0
+ fi
+ coverage xml
+ python-codacy-coverage -r coverage.xml
+fi
diff --git a/scripts/debug_rebuilds/README.md b/scripts/debug_rebuilds/README.md
new file mode 100644
index 0000000..05305a9
--- /dev/null
+++ b/scripts/debug_rebuilds/README.md
@@ -0,0 +1,11 @@
+To debug unexpected Nikola rebuilds:
+
+1. In `nikola.utils.config_changed._calc_digest`, uncomment the line that says `self._write_into_debug_db(digest, data)`
+2. Create a copy of your site source.
+3. Run `python step1_build_and_dumpdb.py`. (It will delete cache/output/db, build the site twice, and write dumpdb to .py files)
+4. Run `python step2_analyze_py_files.py | tee analysis.txt`. It will compare the two .py files, using `cc_debug.sqlite3` and `{first,second}_dump.py`.
+5. Compare the produced dictionaries. Note that you will probably need a character-level diff tool, <https://prettydiff.com/> is pretty good as long as you change CSS for `li.replace` to `word-break: break-all; white-space: pre-wrap;`
+
+Copyright © 2019-2020, Chris Warrick.
+Portions Copyright © Eduardo Nafuel Schettino and Doit Contributors.
+License of .py files is MIT (same as Nikola)
diff --git a/scripts/debug_rebuilds/step1_build_and_dumpdb.py b/scripts/debug_rebuilds/step1_build_and_dumpdb.py
new file mode 100755
index 0000000..69f952d
--- /dev/null
+++ b/scripts/debug_rebuilds/step1_build_and_dumpdb.py
@@ -0,0 +1,50 @@
+import dbm
+import json
+import subprocess
+import sys
+
+
+def dbm_iter(db):
+ # try dictionary interface - ok in python2 and dumbdb
+ try:
+ return db.items()
+ except Exception:
+ # try firstkey/nextkey - ok for py3 dbm.gnu
+ def iter_gdbm(db):
+ k = db.firstkey()
+ while k is not None:
+ yield k, db[k]
+ k = db.nextkey(k)
+ return iter_gdbm(db)
+
+
+def dumpdb():
+ with dbm.open('.doit.db') as data:
+ return {key: json.loads(value_str.decode('utf-8'))
+ for key, value_str in dbm_iter(data)}
+
+
+print_ = print
+
+
+def print(*args, **kwargs):
+ print_(*args, file=sys.stdout)
+ sys.stdout.flush()
+
+
+print("==> Removing stuff...")
+subprocess.call(['rm', '-rf', '.doit.db', 'output', 'cache', 'cc_debug.sqlite3'])
+print("==> Running first build...")
+subprocess.call(['nikola', 'build'])
+print("==> Fetching database...")
+first = dumpdb()
+print("==> Running second build...")
+subprocess.call(['nikola', 'build'])
+print("==> Fetching database...")
+second = dumpdb()
+print("==> Saving dumps...")
+with open('first_dump.py', 'w', encoding='utf-8') as fh:
+ fh.write(repr(first))
+
+with open('second_dump.py', 'w', encoding='utf-8') as fh:
+ fh.write(repr(second))
diff --git a/scripts/debug_rebuilds/step2_analyze_py_files.py b/scripts/debug_rebuilds/step2_analyze_py_files.py
new file mode 100755
index 0000000..cb5c954
--- /dev/null
+++ b/scripts/debug_rebuilds/step2_analyze_py_files.py
@@ -0,0 +1,58 @@
+import sqlite3
+import sys
+print_ = print
+
+
+def print(*args, **kwargs):
+ print_(*args, file=sys.stdout)
+ sys.stdout.flush()
+
+
+with open('first_dump.py', 'r', encoding='utf-8') as fh:
+ first = eval(fh.read())
+
+with open('second_dump.py', 'r', encoding='utf-8') as fh:
+ second = eval(fh.read())
+
+if len(first) != len(second):
+ print(" [!] Databases differ in size.")
+ for k in first:
+ if k not in second:
+ print(" Item", k, "not found in second database.")
+ for k in second:
+ if k not in first:
+ print(" Item", k, "not found in first database.")
+
+conn = sqlite3.connect("cc_debug.sqlite3")
+
+
+def get_from_db(value):
+ cursor = conn.cursor()
+ try:
+ cursor.execute("SELECT json_data FROM hashes WHERE hash = ?", (value,))
+ return cursor.fetchone()[0]
+ except Exception:
+ print(" [!] Cannot find", value, "in database.")
+ return None
+
+
+if first == second:
+ print("==> Both files are identical.")
+ exit(0)
+
+VAL_KEY = '_values_:' # yes, ends with a colon
+for k in first:
+ fk, sk = first[k], second[k]
+ try:
+ first_values, second_values = fk[VAL_KEY], sk[VAL_KEY]
+ except KeyError:
+ print(" [!] Values not found for,", k)
+ continue
+
+ if first_values != second_values:
+ print(" -> Difference:", k)
+ for vk in first_values:
+ fv, sv = first_values[vk], second_values[vk]
+ if fv != sv:
+ print(" first :", fv, get_from_db(fv))
+ print(" second:", sv, get_from_db(sv))
diff --git a/scripts/document_path_handlers.py b/scripts/document_path_handlers.py
new file mode 100755
index 0000000..d86309a
--- /dev/null
+++ b/scripts/document_path_handlers.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+
+from nikola import nikola
+n = nikola.Nikola()
+n.init_plugins()
+
+print(""".. title: Path Handlers for Nikola
+.. slug: path-handlers
+.. author: The Nikola Team
+
+.. DO NOT EDIT, this file is auto-generated by scripts/document_path_handlers.py
+
+Nikola supports special links with the syntax ``link://kind/name``. In
+templates you can also use ``_link(kind, name)``. You can add query strings
+(``?key=value``) for extra arguments, or pass keyword arguments to ``_link`` in
+templates (support and behavior depends on path handlers themselves). Fragments
+(``#anchor``) will be appended to the transformed link.
+
+Here are the descriptions for all the supported kinds.
+
+.. class:: dl-horizontal
+""")
+
+for k in sorted(n.path_handlers.keys()):
+ v = n.path_handlers[k]
+ print(k)
+ print('\n'.join(' ' + l.strip() for l in v.__doc__.splitlines()))
+ print()
diff --git a/scripts/generate_conf.py b/scripts/generate_conf.py
deleted file mode 100755
index 37a3a94..0000000
--- a/scripts/generate_conf.py
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env python
-# Generate a conf.py file from the template, using default settings.
-
-import nikola.plugins.command.init
-
-try:
- print(nikola.plugins.command.init.CommandInit.create_configuration_to_string())
-except:
- print(nikola.plugins.command.init.CommandInit.create_configuration_to_string().encode('utf-8'))
diff --git a/scripts/generate_symlinked_list.sh b/scripts/generate_symlinked_list.sh
index fe7a7cc..56f514b 100755
--- a/scripts/generate_symlinked_list.sh
+++ b/scripts/generate_symlinked_list.sh
@@ -6,4 +6,12 @@ WDir="${PWD##*/}"
[[ $WDir == 'scripts' ]] && cd ..
dst='nikola/data/symlinked.txt'
+
+# Remove stale symlinks
+for f in $(git ls-files -s | awk '/120000/{print $4}'); do
+ if [[ ! -e $f ]]; then
+ git rm -f $f
+ fi
+done
+
git ls-files -s | awk '/120000/{print $4}' > $dst
diff --git a/scripts/getbaseline.sh b/scripts/getbaseline.sh
deleted file mode 100755
index 719fbe5..0000000
--- a/scripts/getbaseline.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-cd tests/data
-for i in $@; do
- if [[ $i == '2.7' ]]; then
- # we only support 2.7 now
- wget https://github.com/getnikola/invariant-builds/archive/v$i'.zip'
- unzip 'v'$i'.zip'
- rm -rf baseline$i
- mv invariant-builds-$i baseline$i
- rm 'v'$i'.zip'
- else
- echo 'Version '$i' does not support baseline testing.'
- fi
-done
diff --git a/scripts/getwheelhouse.sh b/scripts/getwheelhouse.sh
deleted file mode 100755
index 911ffbd..0000000
--- a/scripts/getwheelhouse.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-for i in $@; do
- wget https://github.com/getnikola/wheelhouse/archive/v$i'.zip'
- unzip 'v'$i'.zip'
- pip install --use-wheel --no-index --find-links=wheelhouse-$i lxml Pillow ipython
- rm -rf wheelhouse-$i 'v'$i'.zip'
-done
diff --git a/scripts/github-release.py b/scripts/github-release.py
index 8280fda..a9ab898 100755
--- a/scripts/github-release.py
+++ b/scripts/github-release.py
@@ -1,7 +1,7 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import subprocess
-import sys
import os
+import argparse
if not os.path.exists('.pypt/gh-token'):
print("To use this script, you must create a GitHub token first.")
@@ -9,11 +9,19 @@ if not os.path.exists('.pypt/gh-token'):
print("Then, put it in a file named .pypt/gh-token")
exit(1)
-inpf = input if sys.version_info[0] == 3 else raw_input
+parser = argparse.ArgumentParser(description="GitHub Release helper")
+parser.add_argument("FILE", nargs=1, help="Markdown file to use")
+parser.add_argument("TAG", nargs=1, help="Tag name (usually vX.Y.Z)")
+
+args = parser.parse_args()
+
+if not args.TAG[0].startswith("v"):
+ print("WARNING: tag should start with v")
+ i = input("Add `v` to tag? [y/n] ")
+ if i.lower().strip().startswith('y'):
+ args.TAG[0] = 'v' + args.TAG[0]
-FILE = inpf("Markdown file to use: ")
BASEDIR = os.getcwd()
REPO = 'getnikola/nikola'
-TAG = inpf("Tag name (usually vX.Y.Z): ")
-subprocess.call(['.pypt/ghrel', FILE, BASEDIR, REPO, TAG])
+subprocess.call(['.pypt/ghrel', args.FILE[0], BASEDIR, REPO, args.TAG[0]])
diff --git a/scripts/import_po.py b/scripts/import_po.py
index 0429b42..3f2c984 100755
--- a/scripts/import_po.py
+++ b/scripts/import_po.py
@@ -1,25 +1,28 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Download translations from transifex and regenerate files."""
-from __future__ import unicode_literals, print_function
import io
from glob import glob
import os
import sys
import polib
-if 'nopull' not in sys.argv:
+if 'nopull' not in sys.argv or '--nopull' in sys.argv:
os.system("tx pull -a")
+elif '-h' in sys.argv or '--help' in sys.argv:
+ print("Internal use only. Takes optional 'nopull' argument to prevent pulling from Transifex.")
+ exit()
trans_files = glob(os.path.join('translations', 'nikola.messages', '*.po'))
for fname in trans_files:
lang = os.path.splitext(os.path.basename(fname))[0].lower()
+ lang = lang.replace('@', '_')
outf = os.path.join('nikola', 'data', 'themes', 'base',
'messages', 'messages_{0}.py'.format(lang))
po = polib.pofile(fname)
lines = """# -*- encoding:utf-8 -*-
-from __future__ import unicode_literals
+\"\"\"Autogenerated file, do not edit. Submit translations on Transifex.\"\"\"
MESSAGES = {""".splitlines()
lines2 = []
diff --git a/scripts/jinjify.py b/scripts/jinjify.py
index abd18e9..629ffb1 100755
--- a/scripts/jinjify.py
+++ b/scripts/jinjify.py
@@ -1,4 +1,6 @@
#!/usr/bin/env python
+"""Script to convert templates from Mako to Jinja2."""
+
import io
import glob
import sys
@@ -6,20 +8,25 @@ import os
import re
import json
import shutil
+import tempfile
-import colorama
import jinja2
dumb_replacements = [
- ["{% if any(post.is_mathjax for post in posts) %}", '{% if posts|selectattr("is_mathjax")|list %}'],
+ ["{% if any(post.has_math for post in posts) %}", '{% if posts|selectattr("has_math")|list %}'],
["json.dumps(title)", "title|tojson"],
["{{ parent.extra_head() }}", "{{ super() }}"],
+ ["{{ parent.content() }}", "{{ super() }}"],
["prefix='\\", "prefix='"],
["og: http://ogp.me/ns# \\", "og: http://ogp.me/ns#"],
["article: http://ogp.me/ns/article# \\", "article: http://ogp.me/ns/article#"],
["fb: http://ogp.me/ns/fb# \\", "fb: http://ogp.me/ns/fb#"],
['dir="rtl" \\', 'dir="rtl"'],
['sorted(translations)', 'translations|sort'],
+ ['abs(i - current_page)', '(i - current_page)|abs'],
+ ['loop.index', 'loop.index0'],
+ ['is None', 'is none'],
+ ['is not None', 'is not none'],
]
dumber_replacements = [
@@ -37,11 +44,11 @@ def jinjify(in_theme, out_theme):
out_templates_path = os.path.join(out_theme, "templates")
try:
os.makedirs(out_templates_path)
- except:
+ except Exception:
pass
lookup = jinja2.Environment()
lookup.filters['tojson'] = json.dumps
- lookup.loader = jinja2.FileSystemLoader([out_templates_path], encoding='utf-8')
+ lookup.loader = jinja2.FileSystemLoader([out_templates_path], encoding='utf-8-sig')
for template in glob.glob(os.path.join(in_templates_path, "*.tmpl")):
out_template = os.path.join(out_templates_path, os.path.basename(template))
with io.open(template, "r", encoding="utf-8") as inf:
@@ -71,21 +78,17 @@ def jinjify(in_theme, out_theme):
child = os.path.basename(out_theme.rstrip('/'))
mappings = {
'base-jinja': 'base',
- 'bootstrap3-jinja': 'base-jinja',
+ 'bootstrap4-jinja': 'base-jinja',
}
if child in mappings:
parent = mappings[child]
- with io.open(os.path.join(out_theme, "parent"), "w+", encoding='utf-8') as outf:
- outf.write(u'{0}\n'.format(parent))
-
- with io.open(os.path.join(out_theme, "engine"), "w+", encoding='utf-8') as outf:
- outf.write(u"jinja\n")
-
- # Copy assets in bootstrap/bootstrap3
- if child == 'bootstrap3-jinja':
- shutil.rmtree(os.path.join(out_theme, "assets"))
+ # Copy assets in bootstrap/bootstrap4
+ if child == 'bootstrap4-jinja':
+ assets_dir = os.path.join(out_theme, "assets")
+ if os.path.exists(assets_dir):
+ shutil.rmtree(assets_dir)
shutil.copytree(
os.path.join(in_theme, "assets"), os.path.join(out_theme, "assets"),
symlinks=True)
@@ -99,7 +102,7 @@ def jinjify(in_theme, out_theme):
def error(msg):
- print(colorama.Fore.RED + "ERROR:" + msg)
+ print("\033[1;31mERROR: {0}\033[0m".format(msg))
def mako2jinja(input_file):
@@ -108,7 +111,7 @@ def mako2jinja(input_file):
# TODO: OMG, this code is so horrible. Look at it; just look at it:
- macro_start = re.compile(r'(.*)<%.*def name="(.*?)".*>(.*)', re.IGNORECASE)
+ macro_start = re.compile(r'(.*)<%\s*def name="([^"]*?)"\s*>(.*)', re.IGNORECASE)
macro_end = re.compile(r'(.*)</%def>(.*)', re.IGNORECASE)
if_start = re.compile(r'(.*)% *if (.*):(.*)', re.IGNORECASE)
@@ -158,6 +161,14 @@ def mako2jinja(input_file):
if m_func_len:
line = func_len.sub(r'\1|length', line)
+ # Macro start/end
+ m_macro_start = macro_start.search(line)
+ if m_macro_start:
+ line = m_macro_start.expand(r'\1{% macro \2 %}\3') + '\n'
+ m_macro_end = macro_end.search(line)
+ if m_macro_end:
+ line = m_macro_end.expand(r'\1{% endmacro %}\2') + '\n'
+
# Process line for single 'whole line' replacements
m_macro_start = macro_start.search(line)
m_macro_end = macro_end.search(line)
@@ -178,11 +189,6 @@ def mako2jinja(input_file):
if m_comment_single_line:
output += m_comment_single_line.expand(r'{# \1 #}') + '\n'
- elif m_macro_start:
- output += m_macro_start.expand(r'\1{% macro \2 %}\3') + '\n'
- elif m_macro_end:
- output += m_macro_end.expand(r'\1{% endmacro %}\1') + '\n'
-
elif m_if_start:
output += m_if_start.expand(r'\1{% if \2 %}\3') + '\n'
elif m_if_else:
@@ -215,16 +221,48 @@ def mako2jinja(input_file):
return output
+
+def jinjify_shortcodes(in_dir, out_dir):
+ for fname in os.listdir(in_dir):
+ if not fname.endswith('.tmpl'):
+ continue
+ in_file = os.path.join(in_dir, fname)
+ out_file = os.path.join(out_dir, fname)
+ with open(in_file) as inf:
+ data = mako2jinja(inf)
+ with open(out_file, 'w') as outf:
+ outf.write(data)
+
+
+def usage():
+ print("Usage: python {} [in-dir] [out-dir]".format(sys.argv[0]))
+ print("OR")
+ print("Usage: python {} [in-file] [out-file]".format(sys.argv[0]))
+
if __name__ == "__main__":
if len(sys.argv) == 1:
print('Performing standard conversions:')
for m, j in (
('nikola/data/themes/base', 'nikola/data/themes/base-jinja'),
- ('nikola/data/themes/bootstrap3', 'nikola/data/themes/bootstrap3-jinja')
+ ('nikola/data/themes/bootstrap4', 'nikola/data/themes/bootstrap4-jinja'),
+ ('nikola/data/themes/bootblog4', 'nikola/data/themes/bootblog4-jinja'),
):
print(' {0} -> {1}'.format(m, j))
jinjify(m, j)
+ jinjify_shortcodes('nikola/data/shortcodes/mako', 'nikola/data/shortcodes/jinja')
elif len(sys.argv) != 3:
- print('ERROR: needs input and output directory, or no arguments for default conversions.')
- else:
+ print('ERROR: needs input and output directory (file), or no arguments for default conversions.')
+ usage()
+ elif os.path.isdir(sys.argv[1]) and (os.path.isdir(sys.argv[2]) or not os.path.exists(sys.argv[2])):
jinjify(sys.argv[1], sys.argv[2])
+ elif os.path.isfile(sys.argv[1]) and (os.path.isfile(sys.argv[2]) or not os.path.exists(sys.argv[2])):
+ tmpdir = tempfile.mkdtemp()
+ indir = os.path.sep.join((tmpdir, 'in', 'templates'))
+ outdir = os.path.sep.join((tmpdir, 'out', 'templates'))
+ os.makedirs(indir)
+ shutil.copy(sys.argv[1], indir)
+ jinjify(os.path.dirname(indir), os.path.dirname(outdir))
+ shutil.move(os.path.sep.join((outdir, os.path.basename(sys.argv[1]))), sys.argv[2])
+ else:
+ print('ERROR: the two arguments must be both directories or files')
+ usage()
diff --git a/scripts/langmatrix.py b/scripts/langmatrix.py
new file mode 100755
index 0000000..357ddea
--- /dev/null
+++ b/scripts/langmatrix.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+"""A matrix! Of languages!"""
+import nikola.nikola
+
+keys = ['LUXON_LOCALES', 'MOMENTJS_LOCALES', 'PYPHEN_LOCALES', 'DOCUTILS_LOCALES']
+keys_short = ['language', 'luxon', 'moment', 'pyphen', 'docutils']
+print('\t'.join(keys_short))
+
+for tr in nikola.nikola.LEGAL_VALUES['TRANSLATIONS']:
+ if isinstance(tr, tuple):
+ tr = tr[0]
+ out = tr
+ if len(out) < 8:
+ out += '\t'
+ for key in keys:
+ out += '\t'
+ out += '\x1b[37;42;1m+' if tr in nikola.nikola.LEGAL_VALUES[key] else '\x1b[37;41;1m-'
+ print(out + '\t\x1b[0m')
+
+print('\t'.join(keys_short))
diff --git a/scripts/langstatus.py b/scripts/langstatus.py
new file mode 100755
index 0000000..c31067d
--- /dev/null
+++ b/scripts/langstatus.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+import nikola.nikola
+import os.path
+import glob
+
+used = []
+exist = []
+for tr in nikola.nikola.LEGAL_VALUES['TRANSLATIONS']:
+ if isinstance(tr, tuple):
+ used.append(tr[0])
+ used.append(tr)
+
+for file in glob.glob(os.path.join('nikola', 'data', 'themes', 'base',
+ 'messages', 'messages_*.py')):
+ lang = file.split('_', 1)[1][:-3]
+ exist.append(lang)
+ if lang in used:
+ print('{0}: found'.format(lang))
+ elif os.path.islink(file):
+ print('\x1b[1;1m\x1b[1;30m{0}: symlink\x1b[0m'.format(lang))
+ else:
+ print('\x1b[1;1m\x1b[1;31m{0}: NOT found\x1b[0m'.format(lang))
diff --git a/scripts/release b/scripts/release
new file mode 100755
index 0000000..e7b1aaa
--- /dev/null
+++ b/scripts/release
@@ -0,0 +1,156 @@
+#!/usr/bin/env zsh
+# The Release Script
+# Based on Python Project Template by Chris Warrick
+# Copyright © 2013-2020, Chris Warrick.
+# All rights reserved.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+function status {
+ echo $@
+}
+
+function warning {
+ echo 'WARNING: '$@
+}
+
+function error {
+ echo 'ERROR: '$@
+}
+
+function cleanup {
+ rm -rf Nikola.egg-info build || true
+ rm -rf **/__pycache__ || true
+}
+
+status '*** Nikola Release Scripts'
+dates=$(date '+%s')
+
+# Make sure we're up-to-date
+git checkout master
+git pull origin master
+
+echo -n "Version (in format X.Y.Z): "
+read version
+
+echo $version | grep '^[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}$' > /dev/null
+
+if [[ $? != 0 ]]; then
+ echo $version | grep '^[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\-[0-9A-Za-z-]\{1,\}$' > /dev/null
+ if [[ $? != 0 ]]; then
+ warning 'version number is not compliant with versioning scheme (Semantic Versioning 2.0)'
+ echo -n 'Continue? [y/N] '
+ read vercont
+ if [[ $vercont == 'y' || $vercont == 'Y' ]]; then
+ echo 'Continuing.'
+ else
+ exit 2
+ fi
+ else
+ status 'NOTICE: pre-release version number in use.'
+ echo -n 'Continue? [Y/n] '
+ read vercont
+ if [[ $vercont == 'n' || $vercont == 'N' ]]; then
+ exit 2
+ else
+ echo 'Continuing.'
+ fi
+ fi
+fi
+
+
+cleanup
+
+status 'Replacing versions in files...'
+
+sed "s/version=.*/version='$version',/g" setup.py -i
+sed "s/version = .*/version = '$version'/g" docs/sphinx/conf.py -i
+sed "s/release = .*/release = '$version'/g" docs/sphinx/conf.py -i
+sed "s/:Version: .*/:Version: $version/g" docs/*.rst -i
+sed "s/:Version: .*/:Version: Nikola $version/g" docs/man/nikola.rst -i
+sed "s/__version__ = .*/__version__ = '$version'/g" nikola/__init__.py -i
+sed "s/version: .*/version: $version/g" snapcraft.yaml -i
+
+setopt errexit # Exit on errors
+
+# Slightly convoluted underline automation
+underline=$(python -c "import sys; sys.stdout.write((len('v$version')) * '=')")
+perl -0777 -i -pe "s/master\n======/v$version\n$underline/" CHANGES.txt
+# Man pages
+rst2man.py docs/man/nikola.rst > docs/man/nikola.1
+gzip -f docs/man/nikola.1
+
+status 'Updating path handler documentation...'
+scripts/document_path_handlers.py > docs/path_handlers.rst
+status 'Updating Jinja2 templates...'
+scripts/jinjify.py
+status 'Updating npm assets...'
+scripts/update-npm-assets.sh
+status 'Updating symlinked list...'
+scripts/generate_symlinked_list.sh
+status 'Updating website (conf.py and docs)...'
+python -m nikola default_config > ../nikola-site/listings/conf.py
+cp AUTHORS.txt CHANGES.txt ../nikola-site/stories
+cp docs/*.* ../nikola-site/stories
+sed 's/Nikola v[0-9\.A_Za-z-]\{1,\}/Nikola'" v$version/g" ../nikola-site/stories/conf.rst -i
+status 'Generating locales...'
+scripts/import_po.py
+status 'List of locales:'
+scripts/langstatus.py
+
+unsetopt errexit
+
+status 'If any locales are shown as "NOT found" and they have full translations, please add them in nikola.py. Check this in another terminal (or not) and press Enter to continue.'
+read localefix
+
+status 'Importing...'
+python -c "import nikola"
+if [[ $? == 1 ]]; then
+ error "Import failed. Fix your code or don't come back."
+ exit 1
+fi
+
+status 'This is the last chance to quit. Hit ^C now if you want to.'
+read bailout
+
+cleanup
+
+./setup.py sdist bdist_wheel || exit $?
+twine upload -s dist/Nikola-$version.tar.gz dist/Nikola-$version*.whl || exit $?
+
+cleanup
+
+git add -A --ignore-errors . || exit $?
+
+git commit -S -am "Version $version" || exit $?
+git tag -sm "Version $version" v$version || exit $?
+git push --follow-tags origin master || exit $?
+
+status "Done!"
+
+echo "Next steps (see Release Checklist):"
+echo " * Write announcements, create blog posts"
+echo " * Create a GitHub release"
+echo " * Update GitHub Issues milestones"
+echo " * Update Nikola’s website"
+echo " * Send out announcement e-mails"
diff --git a/scripts/set_version.py b/scripts/set_version.py
deleted file mode 100755
index 7e6c3e0..0000000
--- a/scripts/set_version.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""Script to set the version number wherever it's needed before a release."""
-
-from __future__ import unicode_literals, print_function
-import io
-import os
-import re
-import sys
-import glob
-import subprocess
-import io
-
-
-def sed_like_thing(pattern, repl, path):
- """Like re.sub but applies to a file instead of a string."""
-
- with io.open(path, 'r', encoding='utf8') as inf:
- data = inf.read()
-
- data = re.sub(pattern, repl, data)
-
- with io.open(path, 'w+', encoding='utf8') as outf:
- outf.write(data)
-
-if __name__ == "__main__":
- inpf = raw_input if sys.version_info[0] == 2 else input
- version = inpf("New version number (in format X.Y.Z): ").strip()
-
- for doc in glob.glob(os.path.join("docs/*.txt")):
- sed_like_thing(":Version: .*", ":Version: {0}".format(version), doc)
-
- sed_like_thing("version='.+'", "version='{0}'".format(version), 'setup.py')
- sed_like_thing("version = .*", "version = '{0}'".format(version), os.path.join('docs', 'sphinx', 'conf.py'))
- sed_like_thing("release = .*", "release = '{0}'".format(version), os.path.join('docs', 'sphinx', 'conf.py'))
- sed_like_thing('__version__ = ".*"', '__version__ = "{0}"'.format(version), os.path.join('nikola', '__init__.py'))
- sed_like_thing('New in master', 'New in v{0}'.format(version), 'CHANGES.txt')
- sed_like_thing(':Version: .*', ':Version: Nikola v{0}'.format(version), os.path.join('docs', 'man', 'nikola.rst'))
- man = subprocess.check_output(["rst2man", os.path.join('docs', 'man', 'nikola.rst')])
- with io.open(os.path.join('docs', 'man', 'nikola.1'), 'w', encoding='utf-8') as fh:
- try:
- man = man.decode('utf-8')
- except AttributeError:
- pass
- fh.write(man)
- subprocess.call(["gzip", "-f", os.path.join('docs', 'man', 'nikola.1')])
diff --git a/scripts/snapcraft/nikola.sh b/scripts/snapcraft/nikola.sh
new file mode 100755
index 0000000..34e04bb
--- /dev/null
+++ b/scripts/snapcraft/nikola.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+export HOME=$SNAP_USER_DATA
+
+export I18NPATH=$SNAP/usr/share/i18n
+export LOCPATH=$SNAP_USER_DATA
+
+APPLANG=en_US
+APPENC=UTF-8
+APPLOC="$APPLANG.$APPENC"
+
+# generate a locale so we get properly working charsets and graphics
+if [ ! -e $SNAP_USER_DATA/$APPLOC ]; then
+ localedef --prefix=$SNAP_USER_DATA -f $APPENC -i $APPLANG $SNAP_USER_DATA/$APPLOC
+fi
+
+export LC_ALL=$APPLOC
+export LANG=$APPLOC
+export LANGUAGE=${APPLANG%_*}
+
+$SNAP/bin/nikola "$@"
diff --git a/scripts/update-bower.sh b/scripts/update-bower.sh
deleted file mode 100755
index fbf69a0..0000000
--- a/scripts/update-bower.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/bash
-
-# Update all bower packages
-bower update
-
-# Link bootstrap3 theme to bootstrap
-pushd nikola/data/themes/bootstrap3/assets/js/
-ln -sf ../../../../../../bower_components/bootstrap/dist/js/*js .
-rm npm.js
-git add .
-popd
-
-pushd nikola/data/themes/bootstrap3/assets/css/
-ln -sf ../../../../../../bower_components/bootstrap/dist/css/* .
-git add .
-popd
-
-pushd nikola/data/themes/bootstrap3/assets/fonts/
-ln -sf ../../../../../../bower_components/bootstrap/dist/fonts/* .
-git add .
-popd
-
-# Link moment.js to base theme
-pushd nikola/data/themes/base/assets/js
-ln -sf ../../../../../../bower_components/moment/min/moment-with-locales.min.js .
-git add moment-with-locales.min.js
-popd
-
-# Link jQuery to bootstrap theme
-pushd nikola/data/themes/bootstrap3/assets/js
-ln -sf ../../../../../../bower_components/jquery/dist/* .
-git add .
-popd
-
-
-# Link colorbox into bootstrap theme
-pushd nikola/data/themes/bootstrap3/assets/js
-ln -sf ../../../../../../bower_components/jquery-colorbox/jquery.colorbox.js .
-git add jquery.colorbox.js
-popd
-
-pushd nikola/data/themes/bootstrap3/assets/js/colorbox-i18n
-ln -sf ../../../../../../../bower_components/jquery-colorbox/i18n/* .
-git add .
-popd
-
-pushd nikola/data/themes/bootstrap3/assets/css/
-ln -sf ../../../../../../bower_components/jquery-colorbox/example3/colorbox.css .
-git add colorbox.css
-popd
-
-pushd nikola/data/themes/bootstrap3/assets/css/images/
-ln -sf ../../../../../../../bower_components/jquery-colorbox/example3/images/* .
-git add .
-popd
-
-scripts/generate_symlinked_list.sh
diff --git a/scripts/update-npm-assets.sh b/scripts/update-npm-assets.sh
new file mode 100755
index 0000000..b7da0a5
--- /dev/null
+++ b/scripts/update-npm-assets.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# Update all npm packages
+cd npm_assets
+npm update
+cd ..
+
+# Link bootstrap assets to bootstrap
+pushd nikola/data/themes/bootstrap4/assets/js
+ln -sf ../../../../../../npm_assets/node_modules/bootstrap/dist/js/bootstrap.min.js .
+git add .
+popd
+
+pushd nikola/data/themes/bootstrap4/assets/css
+ln -sf ../../../../../../npm_assets/node_modules/bootstrap/dist/css/bootstrap.min.css .
+git add .
+popd
+
+# Link baguettebox.js to base theme
+pushd nikola/data/themes/base/assets/js
+ln -sf ../../../../../../npm_assets/node_modules/baguettebox.js/dist/baguetteBox.min.js .
+git add .
+popd
+pushd nikola/data/themes/base/assets/css
+ln -sf ../../../../../../npm_assets/node_modules/baguettebox.js/dist/baguetteBox.min.css .
+git add .
+popd
+
+# Link luxon and html5shiv to base theme
+pushd nikola/data/themes/base/assets/js
+ln -sf ../../../../../../npm_assets/node_modules/luxon/build/global/luxon.min.js .
+ln -sf ../../../../../../npm_assets/node_modules/html5shiv/dist/html5shiv-printshiv.min.js .
+ln -sf ../../../../../../npm_assets/node_modules/html5shiv/dist/html5shiv-printshiv.min.js html5.js
+git add moment-with-locales.min.js html5.js html5shiv-printshiv.min.js
+popd
+
+# Link jQuery to bootstrap theme
+pushd nikola/data/themes/bootstrap4/assets/js
+ln -sf ../../../../../../npm_assets/node_modules/jquery/dist/jquery.min.js .
+git add .
+popd
+
+# Link Popper.js to bootstrap theme
+pushd nikola/data/themes/bootstrap4/assets/js
+ln -sf ../../../../../../npm_assets/node_modules/popper.js/dist/umd/popper.min.js .
+git add .
+popd
+
+
+pushd nikola/plugins/command/auto
+ln -sf ../../../../npm_assets/node_modules/livereload-js/dist/livereload.js .
+popd
+
+scripts/generate_symlinked_list.sh
+
+# Verify baguetteBox patch
+grep PATCHED npm_assets/node_modules/baguettebox.js/dist/baguetteBox.js > /dev/null || printf '%b' '\033[1;31mWARNING: baguetteBox must be manually patched (in both unminified and minified versions), see npm_assets/baguetteBox-links-with-images-only.patch\033[0m\n'
+
+# vim:tw=0