Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changes for 0.6 #11

Merged
merged 6 commits into from
Sep 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ resulting .html file will be named.

### Generating the blog

`russell generate` will create a file `run.py` which you can invoke to generate
your static site.
`russell generate` will run the `generate` function in your `config.py`, which
should contain all the instructions for generating HTML and other assets.

To test your newly generated site, run `russell serve`.

Expand Down
45 changes: 45 additions & 0 deletions example/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python3

import os.path
import logging
import sass
import russell

root_path = os.path.dirname(__file__)
args = russell.get_cli_args()
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format="%(asctime)s %(levelname)8s [%(name)s] %(message)s",
)
blog = russell.BlogEngine(
root_path=root_path,
root_url=args.root_url or "//localhost",
site_title="Russell example",
site_desc=("An example Russell site."),
)

# add content
blog.add_pages()
blog.add_posts()


def generate():
# copy and generate assets
blog.copy_assets()
blog.write_file(
"assets/style.css",
sass.compile(filename=os.path.join(blog.root_path, "style.sass")),
)
blog.add_asset_hashes()

# generate HTML pages
blog.generate_index(num_posts=3)
blog.generate_archive()
blog.generate_pages()
blog.generate_posts()
blog.generate_tags()

# generate other stuff
blog.generate_sitemap(https=False)
blog.generate_rss()
blog.write_file("robots.txt", "User-agent: *\nDisallow:\n")
40 changes: 0 additions & 40 deletions example/run.py

This file was deleted.

1 change: 1 addition & 0 deletions russell/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .__version__ import __version__
from russell.engine import BlogEngine
from russell.cli import get_args as get_cli_args
2 changes: 1 addition & 1 deletion russell/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.5.10"
__version__ = "0.6.0"
88 changes: 75 additions & 13 deletions russell/cli.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import argparse
import datetime
import functools
import http.server
import importlib.machinery
import importlib.util
import os
import os.path
import re
import shutil
import subprocess

import dateutil.tz
import slugify

import russell
from russell.__version__ import __version__


def load_config_py(path=None):
if path is None:
path = os.getcwd() + "/config.py"
loader = importlib.machinery.SourceFileLoader("russell_config", path)
spec = importlib.util.spec_from_loader(loader.name, loader)
mod = importlib.util.module_from_spec(spec)
loader.exec_module(mod)
return mod


def setup(dest):
Expand Down Expand Up @@ -96,23 +111,61 @@ def publish(draft_file, update_pubdate=True):


def generate():
subprocess.check_call(["python", "run.py"])


def serve():
russell_config = load_config_py()
russell_config.generate()


class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
hash_pattern = re.compile(r"[0-9a-f]{8,12,16,24,32,48,64}")

def translate_path(self, path):
path = super().translate_path(path)
try_paths = []

directory, filename = path.rsplit("/", maxsplit=1)
if "." in filename:
file_parts = filename.split(".")
if (
len(file_parts) > 2
and len(file_parts[1]) % 8 == 0
and re.match(r"[0-9a-f]", file_parts[1])
):
del file_parts[1]
unbusted_path = "/".join([directory, ".".join(file_parts)])
try_paths.append(unbusted_path)
else:
try_paths.extend([path + ".html", path + "/index.html"])

for try_path in try_paths:
if os.path.exists(try_path):
return try_path
return path


def serve(dist_dir):
try:
subprocess.check_call(["sh", "-c", "cd dist && python -m http.server"])
httpd = http.server.HTTPServer(
("127.0.0.1", 8000),
functools.partial(CustomHTTPRequestHandler, directory=dist_dir),
)
sa = httpd.socket.getsockname()
print("Serving HTTP on http://%s:%s/ ..." % sa)
httpd.serve_forever()
except KeyboardInterrupt:
pass
finally:
httpd.server_close()


def get_parser():
parser = argparse.ArgumentParser("russell")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-r", "--root-path", default=os.getcwd())
parser.add_argument(
"-v",
"-V",
"--version",
action="version",
version="russell version " + str(russell.__version__),
version="russell version " + str(__version__),
)

cmd_subparsers = parser.add_subparsers(dest="command")
Expand All @@ -139,20 +192,29 @@ def get_parser():
)

generate_parser = cmd_subparsers.add_parser("generate")
generate_parser.add_argument("--root-url")

serve_parser = cmd_subparsers.add_parser("serve")
serve_parser.add_argument(
"-d", "--dist-dir", default=os.path.join(os.getcwd(), "dist")
)

return parser


def parse_args(parser=None, args=None):
parser = parser or get_parser()
return parser.parse_args(args)
_args = None


def get_args():
global _args
return _args


def main(args=None):
parser = get_parser()
args = parse_args(parser)
args = parser.parse_args()
global _args
_args = args

if not args.command:
return parser.print_help()
Expand All @@ -174,7 +236,7 @@ def main(args=None):
if args.command == "generate":
return generate()
if args.command == "serve":
return serve()
return serve(os.path.join(os.getcwd(), "dist"))


if __name__ == "__main__":
Expand Down
8 changes: 5 additions & 3 deletions russell/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
SYSTEM_TZINFO = dateutil.tz.tzlocal()


def render_markdown(md):
extensions = ["markdown.extensions.fenced_code"]
return markdown.markdown(md, extensions=extensions)
md = markdown.Markdown(extensions=["markdown.extensions.fenced_code"])


def render_markdown(text):
return md.convert(text)


def schema_url(url, https=False):
Expand Down
45 changes: 39 additions & 6 deletions russell/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ class BlogEngine:
generating end results.
"""

def __init__(self, root_path, root_url, site_title, site_desc=None):
def __init__(
self,
root_path,
root_url,
site_title,
site_desc=None,
cache_busting_strategy="qs",
):
"""
Constructor.

Expand All @@ -56,7 +63,10 @@ def __init__(self, root_path, root_url, site_title, site_desc=None):
root_url (str): The root URL of your website.
site_title (str): The title of your website.
site_desc (str): A subtitle or description of your website.
cache_busting_strategy (str): None, "qs" or "part"
"""
assert os.path.exists(root_path), "root_path must be an existing directory"
assert root_url, "root_url must be set"
self.root_path = root_path
self.root_url = root_url
self.site_title = site_title
Expand All @@ -70,6 +80,13 @@ def __init__(self, root_path, root_url, site_title, site_desc=None):
self.tags = self.cm.tags

self.asset_hash = {}
if cache_busting_strategy == "qs":
self.get_asset_url = self.get_asset_url_qs
elif cache_busting_strategy == "part":
self.get_asset_url = self.get_asset_url_part
else:
LOG.warning("no cache busting will be used!")
self.get_asset_url = str

self.jinja = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.join(root_path, "templates")),
Expand All @@ -87,18 +104,34 @@ def __init__(self, root_path, root_url, site_title, site_desc=None):
}
)

def get_asset_url(self, path):
def get_asset_url_qs(self, path):
"""
Get the URL of an asset. If asset hashes are added and one exists for
the path, it will be appended as a query string.

Args:
path (str): Path to the file, relative to your "assets" directory.
"""
if path.endswith(self.bust_extensions) and path in self.asset_hash:
path += "?" + self.asset_hash[path]
return self.root_url + "/assets/" + path

bust_extensions = (".js", ".min.js", ".js.map", ".css", ".min.css", ".css.map")

def get_asset_url_part(self, path):
"""
Get the URL of an asset. If asset hashes are added and one exists for
the path, it will be appended as a query string.

Args:
path (str): Path to the file, relative to your "assets" directory.
"""
url = self.root_url + "/assets/" + path
if path in self.asset_hash:
url += "?" + self.asset_hash[path]
return url
if path.endswith(self.bust_extensions) and path in self.asset_hash:
*dirs, filename = path.split("/")
file_parts = filename.split(".", maxsplit=1)
file_parts.insert(1, self.asset_hash[path])
path = "/".join(dirs + [".".join(file_parts)])
return self.root_url + "/assets/" + path

def add_pages(self, path="pages"):
"""
Expand Down
5 changes: 3 additions & 2 deletions tests/engine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ def test_get_asset_link(engine):


def test_get_asset_link_with_hash(engine):
engine.asset_hash["test.css"] = "asdf"
assert "//localhost/assets/test.css?asdf" == engine.get_asset_url("test.css")
engine.asset_hash["test.css"] = "0f4c"
assert "//localhost/assets/test.css?0f4c" == engine.get_asset_url_qs("test.css")
assert "//localhost/assets/test.0f4c.css" == engine.get_asset_url_part("test.css")


def test_get_posts_does_not_mutate_posts(engine):
Expand Down
2 changes: 1 addition & 1 deletion tests/functional_cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def russell_dir_variation(tmpdir, request):
def test_setup_new_and_existing(russell_dir_variation):
d = russell_dir_variation
russell.cli.setup(str(d))
assert d.join("run.py").check()
assert d.join("config.py").check()
assert d.join("requirements.txt").check()
assert d.join(".gitignore").check()
assert d.join("style.sass").check()
Expand Down