summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fable.py90
-rwxr-xr-xftt-codegen11
-rw-r--r--jt-prelude.py1
-rw-r--r--jt.md4
-rw-r--r--jt.py145
-rw-r--r--jt.yaml55
6 files changed, 288 insertions, 18 deletions
diff --git a/fable.py b/fable.py
index ec1d881..8e14357 100644
--- a/fable.py
+++ b/fable.py
@@ -1,7 +1,10 @@
# Note that the API of this module is guaranteed to change.
+import argparse
+import logging
import os
import sys
+import shutil
import tempfile
@@ -20,30 +23,111 @@ class Context:
self._vars[key] = value
+class Settingses:
+
+ def __init__(self):
+ self._scenarios = []
+
+ def add_scenario(self, name):
+ self._scenarios.append(name)
+
+ def is_wanted_scenario(self, name):
+ return (not self._scenarios or
+ any(name.casefold() == w.casefold() for w in self._scenarios))
+
+
+class Datadir:
+
+ def __init__(self):
+ self._dirname = None
+ self._ours = False
+
+ def get(self):
+ if self._dirname is None:
+ self._dirname = tempfile.mkdtemp()
+ logging.info('Created temporary directory %s', self._dirname)
+ self._ours = True
+ return self._dirname
+
+ def set(self, dirname):
+ self._dirname = os.path.abspath(dirname)
+ if not os.path.exists(self._dirname):
+ os.mkdir(self._dirname)
+ self._ours = False
+
+ def cleanup(self):
+ if self._ours:
+ logging.debug('Removing %s', self._dirname)
+ shutil.rmtree(self._dirname)
+
+
class Run:
def __init__(self):
self._name = None
self._context = None
self._srcdir = None
- self._tmpdir = None
+ self._settingses = Settingses()
+ self._datadir = None
+ self._tempdir = None
+ self._parse_options()
+
+ def _parse_options(self):
+ p = argparse.ArgumentParser()
+ p.add_argument('--log')
+ p.add_argument('--tempdir')
+ p.add_argument(
+ '--scenario', '-s', action='append', dest='scenarios', default=[])
+ args = p.parse_args()
+ self._setup_logging(args.log)
+ self._tempdir = args.tempdir
+ for s in args.scenarios:
+ self._settingses.add_scenario(s)
+
+ def _setup_logging(self, filename):
+ format = '%(asctime)s %(levelname)s %(message)s'
+ logging.basicConfig(
+ filename=filename, format=format, level=logging.DEBUG)
+ logging.info('test program starts =============')
def get_context(self):
return self._context
def start(self, name):
+ if not self._settingses.is_wanted_scenario(name):
+ logging.info('Skipping unwanted scenario %s', name)
+ return False
+
+ logging.info('Start scenario %s', name)
self._context = Context()
self._name = name
- self._tmpdir = tempfile.mkdtemp()
self._srcdir = os.getcwd()
- os.chdir(self._tmpdir)
+ self._datadir = Datadir()
+ self._datadir.set(self._tempdir)
+ dirname = tempfile.mkdtemp(dir=self._datadir.get())
+ os.chdir(dirname)
+ return True
def end(self):
+ logging.info('OK %s', self._name)
sys.stdout.write('OK: {}\n'.format(self._name))
os.chdir(self._srcdir)
+ self._datadir.cleanup()
+
+
+def assertTrue(a):
+ if not a:
+ raise Exception(
+ 'expected {!r} to be true, but was disappointed'.format(a, b))
def assertEqual(a, b):
if a != b:
raise Exception(
'expected {!r} == {!r}, but was disappointed'.format(a, b))
+
+
+def assertNotEqual(a, b):
+ if a == b:
+ raise Exception(
+ 'expected {!r} != {!r}, but was disappointed'.format(a, b))
diff --git a/ftt-codegen b/ftt-codegen
index c54a383..694cc05 100755
--- a/ftt-codegen
+++ b/ftt-codegen
@@ -118,8 +118,10 @@ def codegen(f, step, bindings):
keyword = words[0]
rest = ' '.join(words[1:])
function, args = find_binding(bindings, keyword, rest)
- f.write('args = {}\n'.format(json.dumps(args)))
- f.write('{}(run.get_context(), **args)\n'.format(function))
+ f.write(' logging.info("step %s", {})\n'.format(json.dumps(step)))
+ f.write(' args = {}\n'.format(json.dumps(args)))
+ f.write(' logging.debug("calling {} with args %s", args)\n'.format(function))
+ f.write(' {}(run.get_context(), **args)\n\n'.format(function))
def debug(msg):
if False:
@@ -134,6 +136,7 @@ debug('reading prelude')
prelude = open(sys.argv[2]).read()
sys.stdout.write(hardcoded)
sys.stdout.write(prelude)
+sys.stdout.write('\n')
debug('reading inputs')
text = ''.join(open(filename).read() for filename in sys.argv[3:])
@@ -154,9 +157,9 @@ for s in fable.get_scenarios():
f = sys.stdout
for s in scenarios:
name = s.get_name()
- f.write('run.start("{}")\n'.format(name))
+ f.write('\nif run.start("{}"):\n'.format(name))
for step in s.get_steps():
codegen(f, step, bindings)
- f.write('run.end()\n')
+ f.write(' run.end()\n')
debug('ok')
diff --git a/jt-prelude.py b/jt-prelude.py
new file mode 100644
index 0000000..be5c08f
--- /dev/null
+++ b/jt-prelude.py
@@ -0,0 +1 @@
+from jt import *
diff --git a/jt.md b/jt.md
index 3d68720..69dc534 100644
--- a/jt.md
+++ b/jt.md
@@ -109,7 +109,7 @@ and I run jt new "second entry"
then there are 2 drafts
and draft 0 has title "first entry"
and draft 1 has title "second entry"
-when I run jt finish
+when I try to run jt finish
then it fails
```
@@ -135,7 +135,7 @@ always requires specifying the draft.
given an empty journal
when I run jt new "random title"
then there is 1 draft
-when I run jt remove
+when I try to run jt remove
then it fails
when I run jt remove 0
then there are no drafts
diff --git a/jt.py b/jt.py
new file mode 100644
index 0000000..392af40
--- /dev/null
+++ b/jt.py
@@ -0,0 +1,145 @@
+import logging
+import os
+import subprocess
+import sys
+
+import fable
+
+def _jt_path():
+ return os.environ['JT_PATH']
+
+def _runcmd(ctx, argv):
+ logging.debug('Executing %r', argv)
+ p = subprocess.Popen(
+ argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ out, err = p.communicate()
+ ctx["stdout"] = out
+ ctx["stderr"] = err
+ ctx["exit_code"] = p.returncode
+ logging.debug('Result: exit_code=%r', ctx['exit_code'])
+ if ctx['exit_code'] != 0:
+ logging.warning(
+ 'jt failed, stdout=%r stderr=%r', ctx['stdout'], ctx['stderr'])
+
+def create_empty_journal(ctx):
+ ctx['journal'] = os.path.abspath('journal')
+ ctx['blog'] = os.path.join(ctx['journal'], 'blog')
+ os.mkdir(ctx['journal'])
+
+ ctx['config'] = os.path.abspath('jt.conf')
+ with open(ctx['config'], 'w') as f:
+ f.write('[config]\n')
+ f.write('source = %s\n' % ctx['journal'])
+ f.write('git = no\n')
+ f.write('push = no\n')
+ f.write('notes-dir = blog\n')
+ f.write('editor = /bin/true {editor}\n')
+ f.write('log = %s.log\n' % ctx['journal'])
+ logging.debug('Created config file %s', ctx['config'])
+
+
+def run_jt(ctx, command=None):
+ try_jt(ctx, command=command)
+ fable.assertEqual(ctx['exit_code'], 0)
+
+def try_jt(ctx, command=None):
+ args = command.split()
+ assert args[0] == 'jt'
+
+ opts = [
+ '--no-default-config',
+ '--config', ctx['config'],
+ ]
+
+ argv = [_jt_path()] + opts + args[1:]
+ argv = ['/bin/sh', '-c', ' '.join(argv)]
+ _runcmd(ctx, argv)
+
+def create_empty_file(ctx, filename=None):
+ with open(filename, 'w') as f:
+ pass
+
+def jt_failed(ctx):
+ fable.assertNotEqual(ctx['exit_code'], 0)
+
+def _find_drafts(ctx):
+ draftsdir = os.path.join(ctx['journal'], 'drafts')
+ if not os.path.isdir(draftsdir):
+ return []
+ drafts = [f for f in _find_files(draftsdir) if f.endswith('.mdwn')]
+ logging.debug('drafts = %r', drafts)
+ return drafts
+
+def no_drafts(ctx):
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(drafts, [])
+
+def one_draft(ctx):
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(len(drafts), 1)
+
+def one_draft_with_attachment(ctx, filename=None):
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(len(drafts), 1)
+ filename = drafts[0]
+ fable.assertTrue(filename.endswith('.mdwn'))
+ dirname = filename[:-len('.mdwn')]
+ fable.assertTrue(os.path.isdir(dirname))
+ attachment = os.path.join(dirname, filename)
+ fable.assertTrue(os.path.exists(attachment))
+
+def n_drafts(ctx, count=None):
+ n = int(count)
+ drafts = _find_drafts(ctx)
+ fable.assertEqual(len(drafts), n)
+
+def _get_draft(ctx, id):
+ filename = os.path.join(ctx['journal'], 'drafts', '%s.mdwn' % id)
+ return _cat(filename)
+
+def draft_has_title(ctx, id=None, title=None):
+ draft = _get_draft(ctx, id)
+ fable.assertTrue(title in draft)
+
+def _find_files(dirname):
+ for d, s, f in os.walk(dirname):
+ for filename in f:
+ yield os.path.join(d, filename)
+
+def _find_entries(ctx):
+ blogdir = ctx['blog']
+ logging.debug('blogdir: %r', blogdir)
+ if not os.path.isdir(blogdir):
+ return []
+ entries = [f for f in _find_files(blogdir) if f.endswith('.mdwn')]
+ logging.debug('entries = %r', entries)
+ return entries
+
+def one_entry(ctx):
+ entries = _find_entries(ctx)
+ fable.assertEqual(len(entries), 1)
+
+def n_entries(ctx, count=None):
+ n = int(count)
+ entries = _find_entries(ctx)
+ fable.assertEqual(len(entries), n)
+
+def no_entries(ctx):
+ entries = _find_entries(ctx)
+ fable.assertEqual(len(entries), 0)
+
+def _find_topics(ctx):
+ dirname = os.path.join(ctx['journal'], 'topics')
+ filenames = [f for f in _find_files(dirname) if f.endswith('.mdwn')]
+ logging.debug('topics: %r', filenames)
+ return filenames
+
+def _cat(filename):
+ with open(filename) as f:
+ return f.read()
+
+def one_topic(ctx, topic=None, title=None):
+ topics = _find_topics(ctx)
+ fable.assertEqual(len(topics), 1)
+ topic = _cat(topics[0])
+ fable.assertTrue(title in topic)
diff --git a/jt.yaml b/jt.yaml
index f33cd94..22a5c90 100644
--- a/jt.yaml
+++ b/jt.yaml
@@ -1,10 +1,47 @@
-- given: a file (?P<filename>.+)
-- when: I run (?P<cmd>jt .*)
-- then: there is (?P<count>\d+) draft
-- then: there is (?P<count>\d+) draft, with attachment (?P<filename>.+)
-- then: there is (?P<count>\d+) journal entry
-- then: there is (?P<count>\d+) journal entry, with attachment (?P<filename>.+)
-- then: there is (?P<count>\d+) journal entry, belonging to (?P<id>.+)
-- then: there is (?P<count>\d+) topic, (?P<id>.+), titled "(?P<title>.+)"
+- given: a file (?P<filename>\S+)
+ function: create_empty_file
+
+- given: an empty journal
+ function: create_empty_journal
+
+- then: draft (?P<id>\d+) has title "(?P<title>.+)"
+ function: draft_has_title
+
+- then: it fails
+ function: jt_failed
+
+- then: there are (?P<count>\d+) drafts
+ function: n_drafts
+
- then: there are (?P<count>\d+) journal entries
-- then: draft (?P<id>\d+) has title "(?P<title>.*)"
+ function: n_entries
+
+- then: there are no drafts
+ function: no_drafts
+
+- then: there are no journal entries
+ function: no_entries
+
+- then: there is 1 draft
+ function: one_draft
+
+- then: there is 1 draft, with attachment (?P<filename>\S+)
+ function: one_draft_with_attachment
+
+- then: there is 1 journal entry
+ function: one_entry
+
+- then: there is 1 journal entry, belonging to (?P<topic>\S+)
+ function: one_entry_on_topic
+
+- then: there is 1 journal entry, with attachment (?P<filename>\S+)
+ function: one_enry_with_attachment
+
+- then: there there is 1 topic, (?P<topic>\S+), titled "(?P<title>.+)"
+ function: one_topic
+
+- when: I run (?P<command>jt .*)
+ function: run_jt
+
+- when: I try to run (?P<command>jt .*)
+ function: try_jt