Script that adds question IDs.
brew install babashka
./add-q-ids.clj <input-file-path> > <output-file-path>
.cpcache |
Script that adds question IDs.
brew install babashka
./add-q-ids.clj <input-file-path> > <output-file-path>
#!/usr/bin/env bb | |
(defn dbg [msg] (println (json/generate-string msg))) | |
(defn exit-with-error [message] | |
(throw (ex-info | |
(str \u001b "[31m" "Error: " \u001b "[0m" message) | |
{:babashka/exit 1}))) | |
(def input-file-path (first *command-line-args*)) | |
(when-not (= (count *command-line-args*) 1) | |
(exit-with-error "only 1 argument (input file path) expected.")) | |
(when-not (fs/exists? input-file-path) | |
(exit-with-error (str "no such file '" input-file-path "'."))) | |
(when-not (fs/readable? input-file-path) | |
(exit-with-error (str "file not readable: ." input-file-path "'."))) | |
;; Why reversing: indexOf will be wrong if a category value repeats. | |
;; So then, we need to get the most recent one going from the back | |
;; of the list. | |
(defn get-category-start-index [column] | |
(- (count column) | |
(+ 1 (.indexOf (vec (reverse column)) | |
(last (filterv #(not (empty? %)) column)))))) | |
(defn get-selection [buffer-with-current-row column-index] | |
(let [parent-column (map #(get % (- column-index 1)) buffer-with-current-row) | |
parent-category-start-index (get-category-start-index parent-column)] | |
(subvec | |
(vec (map #(get % column-index) buffer-with-current-row)) | |
parent-category-start-index))) | |
(defn get-last-id [buffer-with-current-row column] | |
;; Selection: remove the header for the 1st order category. | |
;; Otherwise selection is taken from the last change of parent category. | |
(let [selection (if (= column 0) | |
(map #(get % 0) (rest buffer-with-current-row)) | |
(get-selection (rest buffer-with-current-row) column))] | |
(count (filterv #(not (empty? %)) selection)))) | |
(defn calculate-q-number [prev-q-id q-category-id] | |
(let [prev-q-category-id (str/join "." (take 3 (str/split prev-q-id #"\.")))] | |
(if (= prev-q-category-id q-category-id) | |
(+ 1 (Integer/parseInt (last (str/split prev-q-id #"\.")))) 1))) | |
(defn calculate-q-category-id [buffer-with-current-row] | |
(str | |
(get-last-id buffer-with-current-row 0) "." | |
(get-last-id buffer-with-current-row 1) "." | |
(get-last-id buffer-with-current-row 2))) | |
(comment (get-q-index [["TOPIC" "VIEW" "GROUP" "Q ID"]])) | |
(defn get-q-index | |
"Get the index of the column that contains Q IDs" | |
[buffer] (.indexOf (first buffer) "Q ID")) | |
(comment | |
(.str (QID. 4 0 0 1)) | |
(= "4.1.2" (.add "4.0.7" "0.1.2")) | |
) | |
(defprotocol Version | |
"Version numbers of various levels connected by a dot: X.Y.Z" | |
(add [base addition] | |
"Adds additional version to the base") | |
(as-string [base] "Until babaska learn to stringify properly")) | |
(deftype QID | |
[topic-id view-id group-id question-id] | |
Version | |
(add [base addition] | |
(QID. (+ (.topic-id base) (.topic-id addition)) | |
(+ (.view-id base) (.view-id addition)) | |
(+ (.group-id base) (.group-id addition)) | |
(+ (.question-id base) (.question-id addition)))) | |
(as-string [this] | |
(str (.topic-id this) "." | |
(.view-id this) "." | |
(.group-id this) "." | |
(.question-id this))) | |
;; Babashka doesn't currently respect toString on types: | |
;; https://github.com/babashka/babashka/issues/1494 | |
Object | |
(toString [this] | |
(str (.topic-id this) "." | |
(.view-id this) "." | |
(.group-id this) "." | |
(.question-id this)))) | |
(defn calculate-version [base line] | |
(QID. | |
(if (empty? (get line 0)) 0 1) | |
(if (empty? (get line 1)) 0 1) | |
(if (empty? (get line 2)) 0 1))) | |
(defmethod print-method QID [object out] (.write out (str object))) | |
(println (QID. 1 2 3 4)) | |
(println (str (QID. 1 2 3 4))) | |
(println) | |
(defn calculate-q-id [buffer row] | |
(let [q-category-id (calculate-q-category-id (conj buffer row))] | |
(str q-category-id "." | |
(calculate-q-number (get | |
(last buffer) | |
(get-q-index buffer)) q-category-id)))) | |
(defn process-unempty-row [buffer row] | |
(let [header-row (peek buffer) q-id-index (get-q-index buffer)] | |
(conj buffer (apply conj | |
(subvec row 0 q-id-index) | |
(calculate-q-id buffer row) | |
(subvec row (+ 1 q-id-index)))))) | |
(defn process-row [buffer row] | |
(if (every? empty? row) buffer (process-unempty-row buffer row))) | |
(defn process-data [data] | |
(reduce process-row [(first data)] (rest data))) | |
(with-open [reader (io/reader input-file-path)] | |
(doall | |
(csv/write-csv *out* (process-data (csv/read-csv reader))))) |
{:aliases {:src {:extra-paths ["."]}}} |
(ns fun | |
"Trying to implement Version/QID in a functional way (learning)" | |
(:require [clojure.string :as str] | |
[utils :refer :all])) | |
(defn qid-str | |
"....." | |
[version-number-list] (str/join "." version-number-list)) | |
(defn mkqid | |
"....." | |
[qid-str] (vec (map #(Integer/parseInt %) (str/split qid-str #"\.")))) | |
(defn add | |
"Add additional (differential) version to the base" | |
[base addition] | |
(reduce (fn [buffer index] | |
(if (some #(= 1 %) ; FIXME: some greater than 0 | |
(subvec addition 0 index)) | |
;; true | |
(conj buffer (get addition index)) | |
;; false | |
(conj buffer (+ (get base index) (get addition index)))) | |
) | |
[] | |
(range 0 (count base)))) | |
;; Reports | |
(defn report-printing [] | |
(println | |
(colour 30 1 "Validate visually, should just print 1.2.3.4:")) | |
(println (qid-str [1 2 3 4]))) | |
(defn report-equality [] | |
(assert | |
(not= (mkqid "1.0.0.0") (mkqid "0.0.0.0")) | |
"Expected 1.0.0.0 NOT to equal 0.0.0.0.") | |
(println "(= 1.0.0.0 0.0.0.0)" (= (mkqid "1.0.0.0") (mkqid "0.0.0.0"))) | |
(assert | |
(= (mkqid "1.0.0.0") (mkqid "1.0.0.0")) | |
"Expected 1.0.0.0 to equal 1.0.0.0.") | |
(println "(= 1.0.0.0 1.0.0.0)" (= (mkqid "1.0.0.0") (mkqid "1.0.0.0")))) | |
(defn report-arithmentics [] | |
(let [base (mkqid "1.1.1.1")] | |
(doseq [entry [["0.0.0.1" "1.1.1.2"] | |
["0.0.1.0" "1.1.2.0"] | |
["0.1.0.1" "1.2.0.1"] | |
["1.0.1.0" "2.0.1.0"]]] ; FIXME: I think this is wrong. | |
(let [qid (mkqid (get entry 0)) | |
result (add base qid) | |
expected (mkqid (get entry 1))] | |
(assert (= result expected) | |
(str "Expected " base " + " qid " to equal " | |
(get entry 1) ", got " result)) | |
(println (str (qid-str base) " + " | |
(qid-str qid) " = " | |
(qid-str result))))))) |
; The issue: methods compete with functions. | |
; What happens if I define say count? | |
(ns oop | |
"Trying to implement Version/QID in an OOP way (learning)" | |
(:require [clojure.string :as str] | |
[utils :refer :all])) | |
(defprotocol Version | |
"Version numbers connected by a dot (such as 1.0.0.2)" | |
(add [base addition] "Adds differential version to the base")) | |
(deftype QID | |
;; TODO: validate as a list of ints. | |
[version-number-list] | |
Version | |
;; TODO: validate same length of members. | |
(add [base addition] | |
"Add additional (differential) version to the base" | |
(QID. (reduce (fn [buffer index] | |
(if (some #(= 1 %) | |
(subvec (.version-number-list addition) 0 index)) | |
;; true | |
(conj buffer | |
(get (.version-number-list addition) index)) | |
;; false | |
(conj buffer | |
(+ | |
(get (.version-number-list base) index) | |
(get (.version-number-list addition) index))))) | |
[] | |
(range 0 (count (.version-number-list base)))))) | |
Object | |
(toString [this] | |
(str/join "." (.version-number-list this))) | |
;; https://github.com/babashka/babashka/issues/1501 | |
(equals [this other] | |
(= (str this) (str other)))) | |
;; Redefined in order to convert its list to a vector. | |
;; TODO: validate is a collection. | |
(defn ->QID | |
"Positional factory function for class QID" | |
[version-number-list] (new QID (vec version-number-list))) | |
(defn mkqid | |
"Factory fn to build a QID object from QID string (such as 1.2.3.4)" | |
[qid-string] | |
(->QID (map #(Integer/parseInt %) (str/split qid-string #"\.")))) | |
(defmethod print-method QID | |
[object out] (.write out (str object))) | |
(defn make-differential-version-from-line | |
"Make a differential version based on a line from the CSV" | |
[line] (->QID (map #(do (if (empty? %) 0 1)) (take 3 line)))) | |
;; Reports | |
(defn report-printing [] | |
(println | |
(colour 30 1 | |
"Validate these visually, should just print 1.2.3.4 on both lines:")) | |
(println (mkqid "1.2.3.4")) | |
(println (str (mkqid "1.2.3.4")))) | |
(defn report-equality [] | |
(assert | |
(not= (mkqid "1.0.0.0") (mkqid "0.0.0.0")) | |
"Expected 1.0.0.0 NOT to equal 0.0.0.0.") | |
(println "(= 1.0.0.0 0.0.0.0)" (= (mkqid "1.0.0.0") (mkqid "0.0.0.0"))) | |
(assert | |
(= (mkqid "1.0.0.0") (mkqid "1.0.0.0")) | |
"Expected 1.0.0.0 to equal 1.0.0.0.") | |
(println "(= 1.0.0.0 1.0.0.0)" (= (mkqid "1.0.0.0") (mkqid "1.0.0.0")))) | |
(defn report-arithmentics [] | |
(let [base (mkqid "1.1.1.1")] | |
(doseq [entry [["0.0.0.1" "1.1.1.2"] | |
["0.0.1.0" "1.1.2.0"] | |
["0.1.0.1" "1.2.0.1"] | |
["1.0.1.0" "2.0.1.0"]]] ; FIXME: I think this is wrong. | |
(let [qid (mkqid (get entry 0)) | |
result (add base qid) | |
expected (mkqid (get entry 1))] | |
(assert (= result expected) | |
(str "Expected " base " + " qid " to equal " | |
(get entry 1) ", got " result)) | |
(println (str base " + " qid " = " result)))))) | |
(defn report-diffs [] | |
(doseq [entry [[["A" "B" "C"] "1.1.1"] | |
[["" "B" "C"] "0.1.1"]]] ; TODO: More thourough testing. | |
(let [line (get entry 0) | |
result (make-differential-version-from-line line) | |
expected (mkqid (get entry 1))] | |
(assert (= result expected) | |
(str "Expected " line " to equal " expected)) | |
(println | |
(str "(make-differential-version-from-line " line ") -> " result))))) |
#!/usr/bin/env clojure -A:src -M | |
(require '[utils :refer :all]) | |
;; Babashka | |
;; | |
;; bb -cp . qid.clj | |
;; | |
;; Currently doesn't work due to https://github.com/babashka/babashka/issues/1501 | |
(when-not (System/getProperty "babashka.version") | |
(require '[clojure.string :as str])) | |
(defn get-fn [ns fn-name] | |
(requiring-resolve (symbol (str ns) (str fn-name)))) | |
;; Replace malli with this: | |
;; (defn xxx [s] {:pre [(string? s)]} s) | |
;; (xxx "s") | |
;; (xxx 1) | |
(doseq [ns ['oop 'fun]] | |
(println (str "# " (colour 32 (str "Namespace " ns)))) | |
(require ns) | |
(doseq [fn-name (filter #(re-find #"^report-" (str %)) (keys (ns-publics ns)))] | |
(let [fn (get-fn ns fn-name)] | |
(println (colour 35 fn)) | |
(try (fn) | |
(catch Exception error | |
(println | |
(str (colour 31 (.getName (class error))) ":") | |
(.getMessage error) | |
;; TODO: Filter by local clj files. | |
(str/join "\n- " | |
(map | |
#(StackTraceElement->vec %) | |
(.getStackTrace error)))))) | |
(println)))) |
(ns utils) | |
(defn colour | |
([code message] (str \u001b (str "[" code "m") message \u001b "[0m")) | |
([code-1 code-2 message] (str \u001b (str "[" code-1 ";" code-2 "m") message \u001b "[0m"))) |