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

Versioning #9

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
34 changes: 15 additions & 19 deletions pandocomatic-elsarticle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ templates:
# Elsevier only accepts natbib, hence:
natbib: true # - enable natbib, and
citeproc: false # - turn off the citeproc CSL engine.
bibliography: ./test_elsarticle.bib # The name of your bib-file. Respect
# the directory naming conventions of pandocomatic.
bibliography: ./src/bib/CitedByMe-2021-swSemantics.bib # The name of your
# bib-file. Respect the directory naming
# conventions of pandocomatic. DON'T USE
# UNDERSCORES IN THE BIBNAME
citation-abbreviations: cite-abbr.json # Journal abbreviations
reference-links: true # Refer to pandoc manual.
metadata:
Expand All @@ -61,9 +63,13 @@ templates:
link-citations: true # Refer to pandoc manual, section `Other
# relevant metadata fields`. Deactivate this for
# paper print.
#-----------------------------------------------------------------------------
version:
preprocessor:
- preprocessors/preludeGitVersion.py
#-----------------------------------------------------------------------------
elsevier:
extends: ['natbib-refs']
extends: ['natbib-refs','version']
pandoc:
verbose: true
from: markdown
Expand All @@ -72,30 +78,20 @@ templates:
filter:
- filters/assimilateMetadata.rb # Handle academic metadata.
template: elsarticle.latex # Latex template supporing elsarticle.cls
include-in-header: './addstyles.sty' # To easily include packages; refer
include-in-header: './../../templates/addstyles.sty' # To easily include packages; refer
# to elsarticle.latex
metadata:
documentclass: 'elsarticle' # Specifying Elsevier's documentclass.
twocolumn: true # Single column (False) or double column (True) style.
# DO NOT include 'twocolumn' in the classoption parameter;
# that will be taken care of by this parameter.
classoption: ['preprint','authoryear','3p'] # Elsevier defines
# several options for their various journal styles.
# several more options for their various journal styles.
# classoption: ['sort&compress','preprint','authoryear','3p'] #
# The `sort&compress` fails on the `&`; refer
# to elsarticle.latex
# classoption: ['preprint','authoryear','3p'] # e.g., single column style.
# biboptions: [longnamesfirst,angle,semicolon] # Add extra options of natbib.sty
colorlinks: true # Deactivate this for paper print.
lang: 'en-GB'
#-----------------------------------------------------------------------------
elsevier-1col: # FOR TESTING PURPOSES ONLY
extends: ['elsevier']
pandoc:
output: test_elsarticle_1col.tex
metadata:
classoption: ['preprint','authoryear','3p'] # Single column style.
#-----------------------------------------------------------------------------
elsevier-2col: # FOR TESTING PURPOSES ONLY
extends: ['elsevier']
pandoc:
output: test_elsarticle_2col.tex
metadata:
classoption: ['preprint','authoryear','3p','twocolumn'] # Double column style.
#-----------------------------------------------------------------------------
69 changes: 69 additions & 0 deletions preprocessors/preludeGitVersion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# preludeGitVersion

Bump the document version and adds/replaces the version: major.minor-commits to the (piped) document.

## GIT and versioning:
* The major demand that we have is to assure that our work is being saved in git, in order to prevent loosing our work when crashes occur. The minor demand that we have is to support versioning of saved work.
* Similar to sw-testing, unsuccessful pdf generation by pandocomatic is independent from versioning. We only want to commit a verified piece of work, i.e., all tests passed. Then, and only then, we want to have a bumped version denoted in the document.
* We want to have each and every committed progress versioned without the need to actively take action for that. This is easy since git will bump the release (the number of commits, placed after the first dash) automatically.
* We want to be able to (actively) indicate that the bumped version is a major, minor or patch.

## THE PROBLEM
Generating a document with pandocomatic into your choice of output, e.g., pdf, precedes the process of committing the latest generated document to git. Only when committing the document with git, the git version will be bumped. However, we need that bumped git version while generating the document, since that is the latest moment we can include that new version into our document. A classical catch-22.

## OBJECTIVE
To include the to-be-bumped version into the document generation process, prelude the git version that will result from eventually committing the new document.

## METHOD
* Step 1: Assume the document-to-generate is commit-worthy, i.e., exactly as we want it. We thus anticipate a successful git commit after document generation. This implies to simulate a version bump, to be included in the document. The git version itself remains unaltered until we actually perform a 'git commit'.
* Step 2: As we start the pandocomatic document generation, we don't know whether the document will show as commit-worthy. Two cases apply:
* First, our assumption fails and the document is erroneous or otherwise not to our liking: no matter what we have preluded as version-to-be, no harm to the actual git versioning is done. Consequently, we can repeat the preludation process time after time as long as we make sure that we don't retain the version-to-be but start fresh with the latest git version as seed to the preluding;
* Second, our assumption holds and we've generated a valid, commit-able document. The preluded version has been included in the document itself, however, the document is yet to be committed. Consequently, after the 'git commit' the actual git version has been bumped, and is in correspondence with the preluded version that has been included in the document.

## ADVANCED METHOD
Assume we not only want to apply versioning on the level of patches, viz the number of commits, but also on the other two levels. In order to align/match the preluded version as included in the document with the actual git version, it is the responsibility of the user to foretell the type of versioning of the commit already during document processing, and include this as proper <major|minor|commit> parameter to the preludeGitVersion process. In case of a major or minor version bump, we need to tell git that we don't want a patch-level bump but a major or minor instead. Besides, we need to instruct git with the tag command to create a new tag at the correct level.

## USAGE

### Prerequisites

The folder that contains your `project.scriv` must be under git control and, hence, contain the `.git` folder.

### Testing

From the command line, one can test the proper operation of the command: input your markdown document into the command by pipe (```cat document.md```) and it will output the result back to the command line. The result is the very same markdown document, but modified to include a

version: x.y-z

line in its YAML-block.

```
usage: preludeGitVersion.py [-h] [-p PATH] [-i {commit,minor,major}]

-h, --help show this help message and exit
-p PATH, --path PATH the path name to the local directory that
includes the document git repo;
default = "./"
-i {commit,minor,major}, --increment {commit,minor,major}
indicates which version field is to be
bumped (major.minor-commits), preluding
the actual commit;
default = "commit"
```

### Integrated into the pandocomatic process line
In your ```document.md```, include a yaml block on top, as follows:
```
pandocomatic_:
preprocessors: ['preprocessors/preludeGitVersion.py’]
...
...
version-incr: commit # major, minor or commit
...
```
This specifies to use the ```preludeGitVersion``` preprocessor, and indicates to prelude on the version-level of your choice (major.minor-commit). The result of the preprocessor will be piped into pandocomatic for processing.

Logging can be found in ./preludeGitVersion.log

## RULES
* Command line arguments take precedence over YAML-block arguments (this is mostly for testing purposes)
191 changes: 191 additions & 0 deletions preprocessors/preludeGitVersion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#!/usr/local/bin/python3
import datetime
import logging
import sys

import git.exc
from git import Repo


def parseInput():
import argparse
default = {}
default['-p'] = './'
default['-i'] = ''

# Get all arguments and ACK the command
parser = argparse.ArgumentParser(
description='''Bump the document version and adds/replaces the version: mjr.mnr-commits to the (piped) document.''',
epilog='''OBJECTIVE: When the documents are managed by git, git commits are executed *after* document \n
generation in order to prevent erroneous docs to end up in git. Therefore, the current git version the \n
document is tagged with, denotes the version of the previous document. To end up with correct versioning, \n
the version to be inserted in the current document must prelude the git version. \n
METHOD: We assume the document-to-generate is commit-worthy, and we thus anticipate a successful git \n
commit after document generation. This implies to simulate a version bump, to be included in the document. \n
Because we simulate the git bump, the git version itself remains unaltered. Two cases apply: \n
First, whenever our assumption fails and the document is erroneous, no harm to the actual git versioning \n
is done and we can repeat the process time after time until the document is correct; \n
Second, for a valid, commit-able document the version bump is now included in the document itself, however, \n
the document is yet to be committed (resulting in an actual git version bump). In order to align/match the \n
documented version with the actual git version, it is the responsibility of the user to foretell the type \n
of versioning of the commit during document processing, and include this as proper <major|minor|commit> \n
parameter to the preludeGitVersion process.\n
RULES: command line arguments take precedence over YAML-block arguments
'''
)
parser.add_argument('-p', '--path',
help='''the path name to the local directory that
includes the document git repo; default = "{}"'''.format(default['-p']),
default=default['-p']
)
parser.add_argument('-i', '--increment',
choices=['commit', 'minor', 'major'],
help='indicates which version field is bumped (major.minor-commits), preluding the actual commit; default = "commit"'
)

args = parser.parse_args()
return args.path, args.increment


def getGitVersion(dir="./"):
try:
repo = Repo(dir, search_parent_directories=False)
except git.exc.InvalidGitRepositoryError as err:
print(
"Error: git repository {} not found - Create an empty Git repository or reinitialize an existing one with 'git init' ({})".format(
dir + '.git/', sys.argv[0]), file=sys.stderr)
exit(1)

if repo.bare:
version, commits, hash = "v0.0", "0", ""
repo.create_tag(version, message="initial tag")
logging.info("current git is bare git, hence version tag created: {}".format(version))
else:
describe = repo.git.describe("--tags")
logging.info("current git version: {}".format(describe))
if "-" in describe:
version, commits, hash = describe.split('-')
hash = hash[1:]
else:
version = describe
commits = "0"
hash = ""

return version + '-' + commits, hash


def incrementGitVersion(git_path="", incr_field="", yaml_version=""):
assert (git_path and incr_field), "Expected path/to/.git and the field to increment"
# Get current git status information
git_version, git_hash = getGitVersion(git_path)

# Assume a YAML version trumps a git version
if yaml_version and not yaml_version.isspace():
logging.info("Using version from yaml-block ({})".format(yaml_version))
return incrementVersion(yaml_version, incr_field)
else:
logging.info("Using version found in git ({})".format(git_version))
return incrementVersion(git_version, incr_field)


def incrementVersion(version, incr="commit"):
import re
reVersionFormat = re.compile(r'v(?P<mjr>\d+)\.(?P<mnr>\d+)(-(?P<cmts>\d+))?')
reresult = reVersionFormat.match(version)
assert reresult, "Error: version <major.minor[-commits]> expected, got {}".format(version)
assert incr in ["major", "minor", "commit"], "Error: increment 'major', 'minor', or 'commit' expected, got {}".format(incr)
logging.info("Incrementing version {} on level '{}'".format(version,incr))
major = int(reresult.group("mjr"))
minor = int(reresult.group("mnr"))
commits = int(reresult.group("cmts")) if reresult.group("cmts") else 0

if incr == "commit":
commits += 1
elif incr == "minor":
commits = 0
minor += 1
elif incr == "major":
commits = 0
minor = 0
major += 1
logging.info("Continuing with version {}".format("v" + str(major) + "." + str(minor) + '-' + str(commits)))

return "v" + str(major) + "." + str(minor) + '-' + str(commits)


def extractYAMLBlock(text):
import re
p0 = re.compile(r'^(---)[ ]*$(\n|\r)(?P<yaml>(^.*$(\n|\r))*?)(---|\.\.\.)[\s]*$', re.MULTILINE)
yaml = p0.match(text)
return yaml[p0.groupindex['yaml']]


def extractVersionData(text):
import re
regexIncr = r'^version-incr[ ]*:\s*(?P<incr>major|minor|commit)\s*(#.*)?$'
regexVersion = r'^version[ ]*:\s*(?P<vers>[\S*]*?)\s*(#.*)?$'
incr = re.search(regexIncr, text, re.MULTILINE)
vers = re.search(regexVersion, text, re.MULTILINE)
return vers.group('vers') if vers else None, incr.group('incr') if incr else None


def yamlSub(text=None, label=None, newvalue=None):
import re
assert label in ("version", "version-incr"), "YAML parameter 'version' or 'version-incr' expected, got {}".format(
label)
assert text, "YAML substitution for parameter '{}' requires a text in-place to substitute in".format(label)
assert newvalue, "YAML parameter '{}' requires new value, got {}".format(label, newvalue)
pattern = re.compile(r'^' + label + r'\s*:[\s*\S*]*?\s*(#.*)?$', re.MULTILINE)
return pattern.sub("{}: {}".format(label, newvalue), text)


def modifyDocument(git_path=None, arg_incr_field=None, file=None):
import re

# Extract YAML intended versioning information
yaml_start = re.compile(r'^(---)[ ]*$', re.MULTILINE)
# Read the text, either from file or from pipe
text = ""
if file:
with open(file) as f:
text = ''.join(f.readlines())
else:
text = ''.join(sys.stdin.readlines())
assert text, "Empty input. Provide markdown text, either in file or by pipe."

yamlVersion, yamlIncr = extractVersionData(extractYAMLBlock(text))
if yamlIncr:
logging.info("== Found increment level in YAML-block: {}".format(yamlIncr))

# Command line arguments take precedence over yaml values
incr_field = (arg_incr_field if arg_incr_field else yamlIncr)
logging.info("== Set increment level: {}".format(incr_field))

newtext = yamlSub(text, "version-incr", incr_field) if incr_field else text
if yamlVersion:
logging.info("== Found version in YAML-block: {}".format(yamlVersion))
newtext = yamlSub(newtext, "version", incrementGitVersion(git_path, incr_field, yamlVersion))
else:
newtext = yaml_start.sub("---\nversion: {}".format(incrementGitVersion(git_path, incr_field, "")), newtext, count=1)
return newtext


if __name__ == '__main__':
import os
from pathlib import Path

cwd = os.getcwd()
logfile = cwd + '/' + Path(sys.argv[0]).stem + '.log'
logging.basicConfig(filename=logfile,
encoding='utf-8',
level=logging.DEBUG,
filemode='w')
logging.info("======= running {} =========".format(sys.argv[0]))
logging.info("== {}".format(datetime.datetime.now().strftime("%c")))

# init: read command line arguments
git_path, incr_field = parseInput()
logging.info("== Path/to/.git: {}".format(git_path))
logging.info("== Increment level at command line: {}".format(incr_field if incr_field else 'None'))

print(modifyDocument(git_path, incr_field))
Loading