summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2019-04-29 23:06:25 +0300
committerLars Wirzenius <liw@liw.fi>2019-04-29 23:06:25 +0300
commit15e5eced9623c42383eaeded1bc127de814b3b23 (patch)
tree57e64ab27182aa0d995588fbf45b123df902244b
parentdf94371ba789dd4af9c186fefa6228bcdaf0989f (diff)
downloadsaga-poc-15e5eced9623c42383eaeded1bc127de814b3b23.tar.gz
Add: jt2 with binding, imlementation files
-rw-r--r--Makefile2
-rw-r--r--jt2.bind72
-rw-r--r--jt2.pdfbin0 -> 103578 bytes
-rw-r--r--jt2.py71
-rw-r--r--jt2.yarn145
5 files changed, 289 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 4589ec8..a7194c7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-all: muck1.pdf muck2.pdf muck3.pdf jt1.pdf
+all: muck1.pdf muck2.pdf muck3.pdf jt1.pdf jt2.pdf
%.pdf: %.yarn
pandoc -o $@ $< \
diff --git a/jt2.bind b/jt2.bind
new file mode 100644
index 0000000..b7dfd0a
--- /dev/null
+++ b/jt2.bind
@@ -0,0 +1,72 @@
+# Bind steps to functions that implement them. Each step is
+# represented as a dict in a list with specific keys in the dict.
+#
+# given/when/then: specifies the keyword and a regexp that matches the
+# step text, and captures parts of the step, using special syntax to
+# name each match
+#
+# args: specifies type of each named match (argument), with type names
+# being str (any text), int, pathname, ...
+#
+# function: name of function to be called, which will get the named
+# arguments as parameters, in the order given in the regexp; function
+# should return a value that indicates success or failure, and can
+# contain values with more detail. Might be Rust enums or Python
+# dict objects.
+#
+# capture_result: if set to true, a successful result is stored for
+# inspection by a later step. It's a parse-time error is the step is
+# not inspected by a later step in the scenario. Failure result still
+# fails the step.
+#
+# use_captured_result: if set to true, the captured result is given to
+# the function in a specific way, which depends on the language used
+# to implement the function (e.g., keyword arg in Python).
+#
+# set_it: a specific argument captured from the step is remembered as
+# "it".
+#
+# it: expects a value for "it" to have been rememered by a previous
+# step, and the value must be of the specified type. If it isn't,
+# that's a parsing-time error.
+
+- given: an empty journal repository
+ function: create_empty_git_repository
+
+- then: there is only (filename:\S+) in the journal repository
+ args:
+ filename: pathname
+ function: only_filename
+
+- when: I run (cmd:jt .+)
+ args:
+ cmd: {type: str, style: literal}
+ capture_result: true
+ function: run_jt
+
+- then: (filename:\S+) exists
+ args:
+ filename: pathname
+ set_it: filename
+ function: exists
+
+- then: it contains "(text:.+)"
+ args:
+ text: str
+ it: pathname
+ function: file_contains
+
+- then: it fails
+ use_captured_result: true
+ function: shell_command_failed
+
+- then: it fails with (error:.+)
+ args:
+ error: str
+ use_captured_result: true
+ function: stderr_contains
+
+- given: the date is (date:\d+-\d+-\d+)
+ args:
+ date: str
+ function: set_date
diff --git a/jt2.pdf b/jt2.pdf
new file mode 100644
index 0000000..46a3057
--- /dev/null
+++ b/jt2.pdf
Binary files differ
diff --git a/jt2.py b/jt2.py
new file mode 100644
index 0000000..7d5304e
--- /dev/null
+++ b/jt2.py
@@ -0,0 +1,71 @@
+# This defines the functions that implement each step. Functions
+# return either a saga.Success or a saga.Failure object. These are
+# subclasses of dict, and the runner generated by Saga will abort if a
+# step returns failure. Either dict may contain arbitrary fields.
+#
+# For simplicity, the runner will capture exceptions, and treats them
+# as failures with the exception stored within it. Also, it treats a
+# None return value as success with no keys.
+#
+# Values may be saved in named variables with the saga.set_variable
+# function and retrieved with the saga.get_variable function. It's an
+# error to retrieve an unset variable.
+
+
+import os
+
+import saga
+
+
+# This either returns None or raises an exception.
+def create_empty_git_repository():
+ subprocess.check-call(['git', 'init', '.'], stderr=subprocess.STDOUT)
+
+
+# This either returns None or raises an exception.
+def only_filename(filename):
+ assert os.listdir('.') == [filename]
+
+
+# This run jt, with the --date= option that tells jt to pretend the
+# current date is as given. The runner sets PATH to have the source
+# directory of the project being tested.
+def run_jt(cmd):
+ prefix = 'jt '
+ assert cmd.startswith(prefix)
+ args = cmd[len(prefix:)]
+
+ date = saga.get_variable('date')
+ shellcmd = "jt --date={} {}".format(date, args)
+
+ exitcode, output = subprocess.getstatusoutput(
+ ['sh', '-c', shellcmd], stderr=subprocess.STDOUT)
+
+ # We always return success, and expect the result to be checked by
+ # a later step.
+ return saga.Success(exitcode=exitcode, output=output)
+
+
+def exists(filename):
+ assert os.path.exists(filename)
+
+
+def file_contains(filename, text):
+ content = open(filename).read()
+ assert text in content
+
+
+def shell_command_failed(captured_result=None):
+ exitcode = captured['exitcode']
+ assert exitcode == 0
+
+
+def stderr_contains(error, captured_result=None):
+ stderr = captured_result['stderr']
+ assert error in stderr
+
+
+# These need to be passed to jt somehow, not sure how. That's not
+# important for now.
+def set_date(date):
+ saga.save_variable('date', date)
diff --git a/jt2.yarn b/jt2.yarn
new file mode 100644
index 0000000..e390893
--- /dev/null
+++ b/jt2.yarn
@@ -0,0 +1,145 @@
+---
+title: JT acceptance tests v2
+author: Lars Wirzenius / The Ick project
+...
+
+
+# Introduction
+
+JT is a command line tool for writing entries in my personal journal.
+It works by adding files to the git repository with the journal, for
+formatting by the Ikiwiki website compiler. This
+document presents its automated acceptance tests, using a (for now
+hypothetical) language similar to the Gherkin langauge implemented by
+Cucumber.
+
+All of these scenarios start with an empty directory and with JT
+configured to put drafts in the `drafts` directory, topics in
+`topics`, and finished journal entries in `notes`.
+
+# Happy path scenarios
+
+## Add a journal entry
+
+We start off with an empty journal repository. We only test that it's
+empty here; all other scenarios assume this works.
+
+```saga
+given an empty journal repository
+then there is only .git in the journal repository
+```
+
+We create a draft journal entry.
+
+```saga
+when I run jt new "my title"
+then drafts/0.mdwn exists
+and it contains "my title"
+```
+
+When we finish the entry, the draft is moved to the notes directory.
+
+```saga
+given the date is 2019-09-01
+when I run jt finish
+then notes/2019/09/01/my_title.mdwn exists
+and it contains "my title"
+```
+
+## Add a journal entry with an attachment
+
+This is similar to adding a journal entry, except it adds an
+attachment to the new entry. The attachment could be anything, such as
+a photo.
+
+```saga
+given an empty journal repository
+when I run jt new "my title"
+then drafts/0.mdwn exists
+and it contains "my title"
+```
+
+The attachment file needs to exist. It gets copied to the drafts
+directory.
+
+```saga
+given the file photo.jpg exists
+when I run jt attach 0 photo.jpg
+then drafts/0/photo.jpg exists
+```
+
+When we finish the entry, the draft is moved to the notes directory,
+with the attachment.
+
+```saga
+given the date is 2019-09-01
+when I run jt finish
+then notes/2019/09/01/my_title.mdwn exists
+and it contains "my title"
+and notes/2019/09/01/my_title/photo.jpg exists
+```
+
+
+## Add a topic
+
+JT allows "topic", which are meant to be pages that collect entries of
+specific topics. The formatted journal will have a page for each topic
+shows all entries that reference that topic. Similar to tags, but
+another dimension of metadata. It's up to the user to use tags or
+topics as they wish.
+
+```saga
+given an empty journal repository
+when I run jt new-topic 2019/my-project
+then topics/2019/my-project.mdwn exists
+```
+
+One can add an entry for a topic, but it must exist.
+
+```saga
+when I run jt new --topic nonexistent "my title"
+then it fails
+when I run jt new --topic 2019/my-project "my title"
+then drafts/0.mdwn exists
+and it contains "2019/my-project"
+```
+
+# Unhappy path scenarios
+
+## Finishing when there's more than one draft
+
+JT will pick the draft to act on if there's only one current draft. If
+there's more, it will fail with an error message saying "too many
+drafts".
+
+```saga
+given an empty journal repository
+when I run jt new "first"
+then drafts/0.mdwn exists
+when I run `jt new "second"
+then **drafts/1.mdwn_** exists
+when I run `jt finish`
+then it fails with "too many drafts"
+```
+
+## Attaching when there's no such draft
+
+Attaching to a draft that doesn't exist doesn't work.
+
+```saga
+> given an empty journal repository
+> and the file photo.jpg exists
+> when I run jt attach 0 photo.jpg
+> then it fails with "no such draft"
+```
+
+## Attaching when there's no such file
+
+Attaching a file that doesn't exist doesn't work.
+
+```saga
+given an empty journal repository
+when I run jt new "first"
+and I run jt attach 0 photo.jpg
+then it fails with "no such file"
+```