Commit 6cfd61ee authored by phil's avatar phil

new bin `collect`, replaces yq, annotates events with source-path, applys frontmatter templates

parent 13f96dcd
/.cpcache
/node_modules
/.classpath
/.lumo_cache/
* Welcome to EASY - Evented Accounting Sourced from Yaml
** Introductions
** Introduction
=easy= is a command line utility which generates artifacts typically
used in accounting (but also other fields of business like invoicing)
......@@ -8,7 +8,7 @@ based on events provided as [[https://yaml.org/][YAML]] files.
** Overview
In principle easy is used to transform the business events given in
In principle =easy= is used to transform the business events given in
YAML into other business artifacts like invoices (PDF documents) or
journal files for use with [[https://www.ledger-cli.org/][ledger]] (a command line utility for plain
text accounting.) Processing of events happens in 5 steps:
......@@ -16,7 +16,7 @@ text accounting.) Processing of events happens in 5 steps:
1. Read input events
2. Check input events against spec
3. Augment input events
3.1. Calculate drived values
3.1. Calculate derived values
3.2. Resolve related events
4. Check augmented event against spec
5. Render event(s) through templates
......
#!/bin/sh
# the classpath usually doesn't change, hence it can be cached, which
# saves quite some time, because we don't need to fire up a jvm just
# to establish a classpath
BASEDIR=$(dirname "$0")/..
KLASSPATHFILE=./.classpath
if [ ! -f $KLASSPATHFILE ]; then
KLASSPATH=$(cd "$BASEDIR" || exit 1; clj -Spath)
echo "${BASEDIR}/src:${KLASSPATH}" > $KLASSPATHFILE
fi
KLASSPATH=$(cat $KLASSPATHFILE)
export NODE_PATH=$BASEDIR/node_modules
exec $BASEDIR/node_modules/.bin/lumo -K -c "$KLASSPATH" -m easy.collect $@
(ns easy.collect
(:require [lumo.util :as lumo]
[easy.util :as util]
[easy.log :as log]
[clojure.tools.cli :refer [parse-opts]]
[cljs.pprint :refer [pprint]]
[cljs.spec.alpha :as s]
[clojure.string :refer [join]]))
(def cli-options
[;; ["-d" "--debug" "Debug output"]
])
(defn- read-and-parse
"Reads & parses YAML files (incl. applying any frontmatter event
templates & source annotation)."
[path]
(-> path
util/slurp
util/parse-yaml
(util/annotate path)))
(defn- harmonize [events]
(map (partial util/harmonize-date-field :date) events))
(defn -main
[& args]
(let [cli (parse-opts args cli-options)
path (or (-> cli :arguments first) ".")
options (-> cli :options)]
(->> path
lumo/file-seq
(filter #(re-matches #".+\.yml$" %))
(map read-and-parse)
(apply concat)
harmonize
(sort-by :date)
util/write-yaml
println)))
......@@ -40,16 +40,6 @@
;; helpers
(defn harmonize-date-field [field evt]
(if-let [date (field evt)]
(if (string? date)
;; NOTE don't use this, this does not return an instance of Date
;; (assoc evt field (time/parse util/iso-formatter date))
(assoc evt field (js/Date. date))
evt)
evt))
(defn ignore-warning?
"Checks if `evt` has `:ignore-warnings` set for `key`."
[evt key]
......@@ -59,9 +49,9 @@
(defn harmonize [evt]
(->> evt
(harmonize-date-field :date)
(util/harmonize-date-field :date)
;; TODO: remove, we don' use `:settled` anymore
(harmonize-date-field :settled)))
(util/harmonize-date-field :settled)))
(defn validate!
......
......@@ -16,9 +16,9 @@
;; TODO: don't read and parse the same template over and over again
(defn- apply-template [template-key event]
;; (println (:type event))
;; (println (:type event) " " (:source event))
(let [path (template-key event)
;; _ (println path)
;; _ (println "hello" path)
source (util/slurp path)
renderer (hbs/compile source)]
(renderer (clj->js event))))
......
......@@ -55,6 +55,15 @@
(.writeFileSync fs path content))
(defn exit [c]
(.exit js/process (or c 0)))
(defn die [s]
(warn s)
(exit 1))
(defn indent
"Indents a multiline string `s` by `n` spaces."
[s n]
......@@ -69,9 +78,43 @@
(indent 2)))
(defn parse-yaml [string]
(-> (yaml/load string)
(js->clj :keywordize-keys true)))
(defn apply-frontmatter-template
"Takes a vector of documents. Dies if the vector has 0 or more than 2
entries. If it has 1 entry it will just return that entry. If it
finds 2 entries it will use the first doc as a event template (with
defaults) for the list of events found in the 2nd entry. It returns
then the list of events found in the 2nd entry merged into the
defaults of the event template found in the 1st entry."
[doc]
(case (count doc)
;; zero docs? something went wrong
0 (die "No document?")
;; one doc, the regular case, just unwrap it from the docs vector
1 (first doc)
;; two docs: the first is an event template (with defaults), which
;; is used as a basis for the list of events in the 2nd doc
2 (map #(merge (first doc) %) (second doc))
;; else
;; TODO: this could be used to have templates and event lists in
;; alternating fashion
(die "Sorry, don't know how to handle more then two docs.")))
(defn annotate
"Annotates all events with `:source-path '<path>:e<index>'`, where
index is the position of the event in the source file."
[events path]
(map-indexed #(assoc %2 :source-path (str path ":e" %1)) events))
(defn parse-yaml
"Parses YAML, applys any YAML frontmatter event templates
and annotates the events with their `:source`."
[string]
(-> string
yaml/loadAll
(js->clj :keywordize-keys true)
apply-frontmatter-template))
(def date? (partial instance? js/Date))
......@@ -131,10 +174,11 @@
x))
(defn exit [c]
(.exit js/process (or c 0)))
(defn die [s]
(warn s)
(exit 1))
(defn harmonize-date-field [field evt]
(if-let [date (field evt)]
(if (string? date)
;; NOTE don't use this, this does not return an instance of Date
;; (assoc evt field (time/parse util/iso-formatter date))
(assoc evt field (js/Date. date))
evt)
evt))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment