summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2020-09-12 16:16:32 +0000
committerDaniel Silverstone <dsilvers+gitlab@digital-scurf.org>2020-09-12 16:16:32 +0000
commit9243b189a1a12bc0ed819d59cd3cbe9ce5d37c2a (patch)
tree1f3a846448a2fcb943e805c58ac371ee0ea59bb7
parentbddb592c4cb89e76796b17dd5bed823eba7e2b0e (diff)
parentd4e8b0409d6bc99522a6a995349e9779b9c9cd62 (diff)
downloadjt2-9243b189a1a12bc0ed819d59cd3cbe9ce5d37c2a.tar.gz
Merge branch 'init' into 'master'
feat: create and initialise a new journal Closes #1 See merge request larswirzenius/jt!2
-rw-r--r--.gitignore5
-rw-r--r--Cargo.lock251
-rw-r--r--Cargo.toml10
-rwxr-xr-xcheck63
-rw-r--r--jt.md33
-rw-r--r--jt.py28
-rw-r--r--jt.yaml16
-rw-r--r--runcmd.py77
-rw-r--r--runcmd.yaml13
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs54
11 files changed, 550 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3b10284
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/target
+*.pdf
+*.html
+test.py
+test.log
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..8f97a58
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,251 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "jt2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "structopt",
+ "thiserror",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175c513d55719db99da20232b06cda8bab6b83ec2d04e3283edf0213c37c1a29"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..2066c46
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "jt2"
+version = "0.1.0"
+authors = ["Lars Wirzenius <liw@liw.fi>"]
+edition = "2018"
+
+[dependencies]
+structopt = "0.3"
+anyhow = "1"
+thiserror = "1"
diff --git a/check b/check
new file mode 100755
index 0000000..753de3d
--- /dev/null
+++ b/check
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+set -eu
+
+verbose=false
+if [ "$#" -gt 0 ]
+then
+ case "$1" in
+ verbose | -v | --verbose)
+ verbose=true
+ ;;
+ esac
+fi
+
+hideok=
+
+if command -v chronic > /dev/null
+then
+ hideok=chronic
+fi
+
+if $verbose
+then
+ hideok=
+fi
+
+
+codegen() {
+ $hideok sp-codegen "$1" --output "$2" --run
+}
+
+docgen() {
+ $hideok sp-docgen "$1" --output "$2"
+}
+
+$hideok cargo build --all-targets
+if cargo --list | awk '{ print $1 }' | grep 'clippy$' > /dev/null
+then
+ $hideok cargo clippy
+fi
+$hideok cargo test
+
+if command -v rustfmt > /dev/null
+then
+ find src -type f -name '*.rs' -exec rustfmt --check '{}' +
+fi
+
+if command -v black > /dev/null
+then
+ $hideok find . -type f -name '*.py' ! -name template.py ! -name test.py \
+ -exec black --check '{}' +
+fi
+
+for md in [^CR]*.md
+do
+ $hideok echo "$md ====================================="
+ codegen "$md" test.py
+ docgen "$md" "$(basename "$md" .md).pdf"
+ docgen "$md" "$(basename "$md" .md).html"
+ $hideok echo
+done
+
+echo "Everything seems to be in order."
diff --git a/jt.md b/jt.md
index c765d03..c8beace 100644
--- a/jt.md
+++ b/jt.md
@@ -23,17 +23,48 @@ journal for the user, and a new journal entry. The entry is a draft
until it's finished.
~~~sh
-$ jt create ~/Journal default "My private journal"
+$ jt init ~/Journal default "My private journal"
$ jt new --tag meta --tag journalling "My first journal entry"
... # text editor is opened so the new entry can be written
$ jt finish
~~~
+# Acceptance criteria and their verification
+
+This chapter defines detailed acceptance criteria and how they're
+verified using *scenarios* for the [Subplot][] tool.
+
+[Subplot]: https://subplot.liw.fi/
+
+## Create a new local journal repository
+
+`jt` works on a local repository, and it can be created an initialised
+using the tool.
+
+~~~scenario
+when I run jt init jrnl default "My test journal"
+then program finished successfully
+and directory jrnl exists
+
+when I run jt is-journal jrnl
+then program finished successfully
+
+when I run jt is-journal bogus
+then exit code is non-zero
+~~~
+
+
---
title: "jt&mdash;a journalling tool"
author:
- Lars Wirzenius
- Daniel Silverstone
+bindings:
+- jt.yaml
+- runcmd.yaml
+functions:
+- jt.py
+- runcmd.py
...
diff --git a/jt.py b/jt.py
new file mode 100644
index 0000000..2c5a4fc
--- /dev/null
+++ b/jt.py
@@ -0,0 +1,28 @@
+import logging
+import os
+
+
+def run_jt_init(ctx, dirname=None, journalname=None, title=None):
+ runcmd(ctx, [_binary("jt"), "init", dirname, journalname, title])
+
+
+def run_jt_list_journals(ctx):
+ runcmd(ctx, [_binary("jt"), "list-journals"])
+
+
+def run_jt_is_journal(ctx, dirname=None):
+ runcmd(ctx, [_binary("jt"), "is-journal", dirname])
+
+
+def _binary(name):
+ return os.path.join(srcdir, "target", "debug", "jt2")
+
+
+def is_directory(ctx, dirname=None):
+ logging.debug("checking if %r is a directory", dirname)
+ assert_eq(os.path.isdir(dirname), True)
+
+
+def output_contains(ctx, pattern=None):
+ logging.debug("checking if %r contains", ctx["stdout"], pattern)
+ assert_eq(pattern in ctx["stdout"], True)
diff --git a/jt.yaml b/jt.yaml
new file mode 100644
index 0000000..863dbeb
--- /dev/null
+++ b/jt.yaml
@@ -0,0 +1,16 @@
+- when: I run jt init (?P<dirname>\S+) (?P<journalname>\S+) "(?P<title>.*)"
+ regex: true
+ function: run_jt_init
+
+- when: I run jt list-journals
+ function: run_jt_list_journals
+
+- when: I run jt is-journal {dirname}
+ function: run_jt_is_journal
+
+- then: directory {dirname} exists
+ function: is_directory
+
+- then: output contains "(?P<pattern>.*)"
+ regex: true
+ function: output_contains
diff --git a/runcmd.py b/runcmd.py
new file mode 100644
index 0000000..7193c15
--- /dev/null
+++ b/runcmd.py
@@ -0,0 +1,77 @@
+# Some step implementations for running commands and capturing the result.
+
+import subprocess
+
+
+# Run a command, capture its stdout, stderr, and exit code in context.
+def runcmd(ctx, argv, **kwargs):
+ p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
+ stdout, stderr = p.communicate("")
+ ctx["argv"] = argv
+ ctx["stdout"] = stdout.decode("utf-8")
+ ctx["stderr"] = stderr.decode("utf-8")
+ ctx["exit"] = p.returncode
+
+
+# Check that latest exit code captured by runcmd was a specific one.
+def exit_code_is(ctx, wanted):
+ if ctx.get("exit") != wanted:
+ print("context:", ctx.as_dict())
+ assert_eq(ctx.get("exit"), wanted)
+
+
+# Check that latest exit code captured by runcmd was not a specific one.
+def exit_code_is_not(ctx, unwanted):
+ if ctx.get("exit") == unwanted:
+ print("context:", ctx.as_dict())
+ assert_ne(ctx.get("exit"), unwanted)
+
+
+# Check that latest exit code captured by runcmd was zero.
+def exit_code_zero(ctx):
+ exit_code_is(ctx, 0)
+
+
+# Check that latest exit code captured by runcmd was not zero.
+def exit_code_nonzero(ctx):
+ exit_code_is_not(ctx, 0)
+
+
+# Check that stdout of latest runcmd contains a specific string.
+def stdout_contains(ctx, pattern=None):
+ stdout = ctx.get("stdout", "")
+ if pattern not in stdout:
+ print("pattern:", repr(pattern))
+ print("stdout:", repr(stdout))
+ print("ctx:", ctx.as_dict())
+ assert_eq(pattern in stdout, True)
+
+
+# Check that stdout of latest runcmd does not contain a specific string.
+def stdout_does_not_contain(ctx, pattern=None):
+ stdout = ctx.get("stdout", "")
+ if pattern in stdout:
+ print("pattern:", repr(pattern))
+ print("stdout:", repr(stdout))
+ print("ctx:", ctx.as_dict())
+ assert_eq(pattern not in stdout, True)
+
+
+# Check that stderr of latest runcmd does contains a specific string.
+def stderr_contains(ctx, pattern=None):
+ stderr = ctx.get("stderr", "")
+ if pattern not in stderr:
+ print("pattern:", repr(pattern))
+ print("stderr:", repr(stderr))
+ print("ctx:", ctx.as_dict())
+ assert_eq(pattern in stderr, True)
+
+
+# Check that stderr of latest runcmd does not contain a specific string.
+def stderr_does_not_contain(ctx, pattern=None):
+ stderr = ctx.get("stderr", "")
+ if pattern not in stderr:
+ print("pattern:", repr(pattern))
+ print("stderr:", repr(stderr))
+ print("ctx:", ctx.as_dict())
+ assert_eq(pattern not in stderr, True)
diff --git a/runcmd.yaml b/runcmd.yaml
new file mode 100644
index 0000000..02e5ee1
--- /dev/null
+++ b/runcmd.yaml
@@ -0,0 +1,13 @@
+- then: exit code is non-zero
+ function: exit_code_nonzero
+
+- then: output matches /(?P<pattern>.+)/
+ function: stdout_contains
+ regex: true
+
+- then: stderr matches /(?P<pattern>.+)/
+ function: stderr_contains
+ regex: true
+
+- then: program finished successfully
+ function: exit_code_zero
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..4e0a887
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,54 @@
+use anyhow::Result;
+use std::fs;
+use std::path::{Path, PathBuf};
+use structopt::StructOpt;
+use thiserror::Error;
+
+#[derive(Debug, StructOpt)]
+#[structopt(about = "maintain a journal")]
+enum JT {
+ Init {
+ #[structopt(help = "Directory where journal should be stored")]
+ dirname: PathBuf,
+ #[structopt(help = "Short name for journal")]
+ journalname: String,
+ #[structopt(help = "Short description of journal, its title")]
+ description: String,
+ },
+ IsJournal {
+ #[structopt(help = "Directory that may or may not be a journal")]
+ dirname: PathBuf,
+ },
+}
+
+#[derive(Debug, Error)]
+enum JournalError {
+ #[error("directory {0} is not a journal")]
+ NotAJournal(String),
+}
+
+fn main() -> Result<()> {
+ let opt = JT::from_args();
+ match opt {
+ JT::Init {
+ dirname,
+ journalname,
+ description,
+ } => init(&dirname, &journalname, &description)?,
+ JT::IsJournal { dirname } => is_journal(&dirname)?,
+ }
+ Ok(())
+}
+
+fn init(dirname: &Path, _journalname: &str, _description: &str) -> anyhow::Result<()> {
+ std::fs::create_dir(dirname)?;
+ Ok(())
+}
+
+fn is_journal(dirname: &Path) -> anyhow::Result<()> {
+ let meta = fs::symlink_metadata(dirname)?;
+ if !meta.is_dir() {
+ return Err(JournalError::NotAJournal(dirname.display().to_string()).into());
+ }
+ Ok(())
+}