diff --git a/.gitignore b/.gitignore index 946caa1c..6ae7730d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ output/* !output/README.md -webpage/*.ots +webpage/v # Manubot cache directory ci/cache diff --git a/README.md b/README.md index 20c938c5..966f1bfc 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ Manubot automates citations and references, versions manuscripts using git, and The [Manubot Rootstock repository](https://git.io/vQSvo) is a general purpose template for creating new Manubot instances. See [`USAGE.md`](USAGE.md) for documentation how to write a manuscript. +Please open [an issue](https://github.com/greenelab/manubot-rootstock/issues) for questions related to Manubot usage, bug reports, or general inquiries. + ### Repository directories & files The directories are as follows: @@ -101,6 +103,12 @@ sh build/build.sh # when a change is detected. sh build/autobuild.sh +# At this point, the HTML & PDF outputs will have been created. The remaining +# commands are for serving the webpage to view the HTML manuscript locally. + +# Configure the webpage directory +python build/webpage.py + # View the manuscript locally at http://localhost:8000/ cd webpage python -m http.server diff --git a/USAGE.md b/USAGE.md index fcd4bd10..d6ad4deb 100644 --- a/USAGE.md +++ b/USAGE.md @@ -95,7 +95,7 @@ For example, the following citations all refer to the same study, but will be tr The system also supports citation tags, which are recommended for the following applications: -1. A citation's identifier contains forbidden characters, such as `;` or ending with a non-alphanumeric character other than `/`. +1. A citation's identifier contains forbidden characters, such as `;` or `=`, or ends with a non-alphanumeric character other than `/`. In these instances, you must use a tag. 2. A single reference is cited many times. Therefore, it might make sense to define a tag, so if the citation updates (e.g. a newer version becomes available), only a single change is required. diff --git a/build/assets/redirect-template.html b/build/assets/redirect-template.html new file mode 100644 index 00000000..9fd83c23 --- /dev/null +++ b/build/assets/redirect-template.html @@ -0,0 +1,19 @@ + + + + + + + + Page Redirection + + + If you are not redirected automatically, follow this link. + + diff --git a/build/environment.yml b/build/environment.yml index ac8bdf43..071bd661 100644 --- a/build/environment.yml +++ b/build/environment.yml @@ -7,22 +7,22 @@ dependencies: - conda-forge::ghp-import=0.5.5 - conda-forge::jinja2=2.10 - conda-forge::pandas=0.22.0 - - conda-forge::pandoc=2.1 + - conda-forge::pandoc=2.1.1 - conda-forge::python=3.6.4 - conda-forge::pyyaml=3.12 - conda-forge::requests=2.18.4 - conda-forge::watchdog=0.8.3 - pip: - errorhandler==2.0.1 - - git+https://github.com/greenelab/manubot@7ca386db0610d1e1113abba10c109dcffd3e4c9a + - git+https://github.com/greenelab/manubot@27456594c407f3fed35804db4bcf3b5bba88a716 - opentimestamps-client==0.5.1 - opentimestamps==0.2.0.1 - pandoc-eqnos==1.2.0 - pandoc-fignos==1.2.0 - pandoc-tablenos==1.2.0 - - pandoc-xnos==0.15 + - pandoc-xnos==1.0.1 - pybase62==0.3.2 - pysha3==1.0.2 - python-bitcoinlib==0.9.0 - requests-cache==0.4.13 - - weasyprint==0.42 + - weasyprint==0.42.2 diff --git a/build/webpage.py b/build/webpage.py new file mode 100644 index 00000000..bf0b0218 --- /dev/null +++ b/build/webpage.py @@ -0,0 +1,155 @@ +import argparse +import os +import pathlib +import shutil +import subprocess + + +def parse_arguments(): + """ + Read and process command line arguments. + """ + parser = argparse.ArgumentParser() + parser.add_argument( + '--checkout', + nargs='?', const='gh-pages', default=None, + help='branch to checkout /v directory contents from. For example, --checkout=upstream/gh-pages. --checkout is equivalent to --checkout=gh-pages. If --checkout is ommitted, no checkout is performed.', + ) + parser.add_argument( + '--version', + default=os.environ.get('TRAVIS_COMMIT', 'local'), + help="Used to create webpage/v/{version} directory. Generally a commit hash, tag, or 'local'", + ) + args = parser.parse_args() + return args + + +def configure_directories(args): + """ + Add directories to args and create them if neccessary. + Note that versions_directory is the parent of version_directory. + """ + args_dict = vars(args) + + # Directory where Manubot outputs reside + args_dict['output_directory'] = pathlib.Path('output') + + # Set webpage directory + args_dict['webpage_directory'] = pathlib.Path('webpage') + + # Create webpage/v directory (if it doesn't already exist) + args_dict['versions_directory'] = args.webpage_directory.joinpath('v') + args.versions_directory.mkdir(exist_ok=True) + + # Checkout existing version directories + checkout_existing_versions(args) + + # Create empty webpage/v/version directory + version_directory = args.versions_directory.joinpath(args.version) + if version_directory.is_dir(): + print(f'{version_directory} exists: replacing it with an empty directory') + shutil.rmtree(version_directory) + version_directory.mkdir() + args_dict['version_directory'] = version_directory + + # Symlink webpage/v/latest to point to webpage/v/commit + latest_directory = args.versions_directory.joinpath('latest') + if latest_directory.is_symlink() or latest_directory.is_file(): + latest_directory.unlink() + elif latest_directory.is_dir(): + shutil.rmtree(latest_directory) + latest_directory.symlink_to(args.version, target_is_directory=True) + args_dict['latest_directory'] = latest_directory + + # Create freeze directory + freeze_directory = args.versions_directory.joinpath('freeze') + freeze_directory.mkdir(exist_ok=True) + args_dict['freeze_directory'] = freeze_directory + + return args + + +def checkout_existing_versions(args): + """ + Must populate webpage/v from the gh-pages branch to get history + + References: + http://clubmate.fi/git-checkout-file-or-directories-from-another-branch/ + https://stackoverflow.com/a/2668947/4651668 + https://stackoverflow.com/a/16493707/4651668 + + Command modeled after: + git --work-tree=webpage checkout upstream/gh-pages -- v + """ + if not args.checkout: + return + command = [ + 'git', + f'--work-tree={args.webpage_directory}', + 'checkout', + args.checkout, + '--', + 'v', + ] + print('Attempting checkout with the following command:', ' '.join(command), sep='\n') + process = subprocess.run(command, stderr=subprocess.PIPE) + if process.returncode == 0: + # Addresses an odd behavior where git checkout stages v/* files that don't actually exist + subprocess.run(['git', 'add', 'v']) + else: + print(f'Checkout returned a nonzero exit status. See stderr:\n{process.stderr.decode()}') + + +def create_version(args): + """ + Populate the version directory for a new version. + """ + + # Copy content/images to webpage/v/commit/images + shutil.copytree( + src=pathlib.Path('content/images'), + dst=args.version_directory.joinpath('images'), + ) + + # Copy output files to to webpage/v/version/ + renamer = { + 'manuscript.html': 'index.html', + 'manuscript.pdf': 'manuscript.pdf', + } + for src, dst in renamer.items(): + shutil.copy2( + src=args.output_directory.joinpath(src), + dst=args.version_directory.joinpath(dst), + ) + + # Copy webpage/github-pandoc.css to to webpage/v/version/github-pandoc.css + shutil.copy2( + src=args.webpage_directory.joinpath('github-pandoc.css'), + dst=args.version_directory.joinpath('github-pandoc.css'), + ) + + # Create v/freeze to redirect to v/commit + path = pathlib.Path('build/assets/redirect-template.html') + redirect_html = path.read_text() + redirect_html = redirect_html.format(url=f'../{args.version}/') + args.freeze_directory.joinpath('index.html').write_text(redirect_html) + + +def get_versions(): + """ + Extract versions from the webpage/v directory, which should each contain + a manuscript. + """ + versions = {x.name for x in args.versions_directory.iterdir() if x.is_dir()} + versions -= {'freeze', 'latest'} + versions = sorted(versions) + return versions + + +if __name__ == '__main__': + args = parse_arguments() + configure_directories(args) + print(args) + create_version(args) + versions = get_versions() + print(versions) diff --git a/ci/deploy.sh b/ci/deploy.sh index 1badbf10..dd87d85e 100644 --- a/ci/deploy.sh +++ b/ci/deploy.sh @@ -7,15 +7,10 @@ export REPO_NAME=`basename $TRAVIS_REPO_SLUG` envsubst < webpage/README.md > webpage/README-complete.md mv webpage/README-complete.md webpage/README.md -# Generate OpenTimestamps -ots stamp \ - webpage/index.html \ - webpage/manuscript.pdf - # Configure git git config --global push.default simple git config --global user.email `git log --max-count=1 --format='%ae'` -git config --global user.name `git log --max-count=1 --format='%an'` +git config --global user.name "`git log --max-count=1 --format='%an'`" git checkout $TRAVIS_BRANCH git remote set-url origin git@github.com:$TRAVIS_REPO_SLUG.git @@ -34,6 +29,16 @@ ssh-add ci/deploy.key git remote set-branches --add origin gh-pages output git fetch origin gh-pages:gh-pages output:output +# Configure versioned webpage +python build/webpage.py \ + --checkout=gh-pages \ + --version=$TRAVIS_COMMIT + +# Generate OpenTimestamps +ots stamp \ + webpage/v/$TRAVIS_COMMIT/index.html \ + webpage/v/$TRAVIS_COMMIT/manuscript.pdf + # Commit message MESSAGE="\ `git log --max-count=1 --format='%s'` diff --git a/content/00.front-matter.md b/content/00.front-matter.md index 2f9669cb..54c2613f 100644 --- a/content/00.front-matter.md +++ b/content/00.front-matter.md @@ -13,6 +13,9 @@ This manuscript was automatically generated from [{{ci_source.repo_slug}}@{{ci_source.commit | truncate(length=7, end='', leeway=0)}}](https://github.com/{{ci_source.repo_slug}}/tree/{{ci_source.commit}}) {% endif -%} on {{date}}. +{% if ci_source is defined -%} +The permalink for this manuscript version is . +{% endif -%} ## Authors diff --git a/webpage/README.md b/webpage/README.md index 407f3e9f..b347eccc 100644 --- a/webpage/README.md +++ b/webpage/README.md @@ -1,6 +1,8 @@ # Output directory containing the formatted manuscript The [`gh-pages`](https://github.com/$TRAVIS_REPO_SLUG/tree/gh-pages) branch hosts the contents of this directory at https://$OWNER_NAME.github.io/$REPO_NAME/. +The permalink for this webpage version is https://$OWNER_NAME.github.io/$REPO_NAME/v/$TRAVIS_COMMIT/. +To redirect to the permalink for the latest manuscript version at anytime, use the link https://$OWNER_NAME.github.io/$REPO_NAME/v/freeze/. ## Files @@ -9,8 +11,23 @@ This directory contains the following files, which are mostly ignored on the `ma + [`index.html`](index.html) is an HTML manuscript. + [`github-pandoc.css`](github-pandoc.css) sets the display style for `index.html`. + [`manuscript.pdf`](manuscript.pdf) is a PDF manuscript. -+ `*.ots` files are OpenTimestamps which can be used to verify manuscript existence at or before a given time. - [OpenTimestamps](opentimestamps.org) uses the Bitcoin blockchain to attest to file hash existence. + +The `v` directory contains directories for each manuscript version. +In general, a version is identified by the commit hash of the source content that created it. + +### Timestamps + +The `*.ots` files in version directories are OpenTimestamps which can be used to verify manuscript existence at or before a given time. +[OpenTimestamps](https://opentimestamps.org/) uses the Bitcoin blockchain to attest to file hash existence. +The `deploy.sh` script run during continuous deployment creates the `.ots` files. +However, these files are initially dependent on calendar services and must be upgraded at a later time by running the following in the `gh-pages` branch: + +```sh +# Requires a local bitcoin node with JSON-RPC configured +ots upgrade v/*/*.ots +rm v/*/*.ots.bak +git add v/*/*.ots +``` ## Source diff --git a/webpage/github-pandoc.css b/webpage/github-pandoc.css index 6445e2bb..3189ab30 100644 --- a/webpage/github-pandoc.css +++ b/webpage/github-pandoc.css @@ -87,8 +87,9 @@ body { margin: 0; } -/***Page margins when printing***/ -@page { +/***Page size and margins when printing***/ +@page { + size: Letter; margin-top: 19mm; margin-bottom: 16mm; margin-left: 0mm; diff --git a/webpage/images b/webpage/images index a0fc2191..2b55bac8 120000 --- a/webpage/images +++ b/webpage/images @@ -1 +1 @@ -../content/images \ No newline at end of file +v/latest/images \ No newline at end of file diff --git a/webpage/index.html b/webpage/index.html index 936379e7..2f982aca 120000 --- a/webpage/index.html +++ b/webpage/index.html @@ -1 +1 @@ -../output/manuscript.html \ No newline at end of file +v/latest/index.html \ No newline at end of file diff --git a/webpage/manuscript.pdf b/webpage/manuscript.pdf index 90c08c69..b8867276 120000 --- a/webpage/manuscript.pdf +++ b/webpage/manuscript.pdf @@ -1 +1 @@ -../output/manuscript.pdf \ No newline at end of file +v/latest/manuscript.pdf \ No newline at end of file