Skip to content

Instantly share code, notes, and snippets.

@jakub-stastny
Last active February 24, 2023 18:03
Show Gist options
  • Save jakub-stastny/c46c456e800dc8adf2633673631d5f0b to your computer and use it in GitHub Desktop.
Save jakub-stastny/c46c456e800dc8adf2633673631d5f0b to your computer and use it in GitHub Desktop.

About

Script that adds question IDs.

Installation

brew install babashka

Usage

./add-q-ids.clj <input-file-path> > <output-file-path>

Docs

#!/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")))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment