From 62e727f4dc7f299c016d90a1679a97748dfcd26f Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Tue, 23 Jun 2020 22:57:10 +0200 Subject: [PATCH 01/19] move helper functions to separate namespace --- project.clj | 2 +- src/de/mzuther/moccafaux/core.clj | 70 ++++++---------------------- src/de/mzuther/moccafaux/helpers.clj | 61 ++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 58 deletions(-) create mode 100644 src/de/mzuther/moccafaux/helpers.clj diff --git a/project.clj b/project.clj index 7494377..a211d52 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject de.mzuther/moccafaux.core "1.1.0" +(defproject de.mzuther/moccafaux.core "1.1.1-SNAPSHOT" :description "Adapt power management to changes in the environment." :url "https://github.com/mzuther/moccafaux" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 8cfc434..5d0190f 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -1,20 +1,19 @@ (ns de.mzuther.moccafaux.core - (:require [clojure.data.json :as json] + (:require [de.mzuther.moccafaux.helpers :as helpers] + [clojure.data.json :as json] [clojure.java.io :as io] - [clojure.string :as string] [chime.core :as chime] [com.rpl.specter :as sp] - [popen] - [trptcolin.versioneer.core :as version]) + [popen]) (:gen-class)) (def preferences (try (let [;; "io/file" takes care of line separators - file-name (io/file (System/getProperty "user.home") - ".config" "moccafaux" "config.json") - user-prefs (json/read-str (slurp file-name) - :key-fn keyword)] + file-name (io/file (System/getProperty "user.home") + ".config" "moccafaux" "config.json") + user-prefs (json/read-str (slurp file-name) + :key-fn keyword)] (if (map? user-prefs) user-prefs (throw (Exception. "JSON error (not handled by library)")))) @@ -36,49 +35,6 @@ (def task-states (ref {})) -(def page-width 80) - - -(defn- printfln - "Print formatted output, as per format, followed by (newline)." - [fmt & args] - (println (apply format fmt args))) - - -(defn- fill-string - "Create a string by n repetitions of ch. - - Return this string." - [n ch] - (->> ch - (repeat n) - (apply str))) - - -(defn- print-header - "Print a nicely formatted header with application name and version number." - [] - (let [add-borders (fn [s char] (string/join [char s char])) - - header (str "MoccaFaux v" (version/get-version "de.mzuther" "moccafaux.core")) - - page-width (- page-width 2) - header-width (count header) - left-margin (quot (- page-width header-width) 2) - right-margin (- page-width header-width left-margin) - - full-line (add-borders (fill-string page-width "-") "o") - full-header (add-borders - (string/join [(fill-string left-margin " ") - header - (fill-string right-margin " ")]) - "|")] - (println) - (println full-line) - (println full-header) - (println full-line))) - - (defn- shell-exec "Execute command in a shell compatible to the Bourne shell and fork process if fork? is true. @@ -154,12 +110,12 @@ message (sp/select-one [:message] prefs)] (when command (println) - (printfln "[%s] Task: %s" timestamp (name task)) - (printfln "%s State: %s (%s)" padding (name new-state) message) - (printfln "%s Command: %s" padding command) + (helpers/printfln "[%s] Task: %s" timestamp (name task)) + (helpers/printfln "%s State: %s (%s)" padding (name new-state) message) + (helpers/printfln "%s Command: %s" padding command) ;; execute command (finally) (let [exit-state (shell-exec command fork?)] - (printfln "%s Result: %s" padding (name exit-state)) + (helpers/printfln "%s Result: %s" padding (name exit-state)) exit-state)))) @@ -210,7 +166,7 @@ (update-task task)) (when update-needed? (newline) - (println (fill-string page-width \-))) + (helpers/print-line \-)) (dosync (ref-set task-states new-task-states)))) @@ -238,7 +194,7 @@ (defn -main "Print information on application and schedule watchers." [& _] - (print-header) + (helpers/print-header) (start-scheduler update-status (sp/select-one [:scheduler :probing-interval] preferences))) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj new file mode 100644 index 0000000..0b79ccc --- /dev/null +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -0,0 +1,61 @@ +(ns de.mzuther.moccafaux.helpers + (:require [clojure.string :as string] + [trptcolin.versioneer.core :as version])) + + +(def page-width 80) + + +(defn printfln + "Print formatted output, as per format, followed by (newline)." + [fmt & args] + (println (apply format fmt args))) + + +(defn fill-string + "Create a string by n repetitions of ch. + + Return this string." + [n ch] + (->> ch + (repeat n) + (apply str))) + + +(defn add-borders + "Create a string consisting of ch followed by s followed by ch." + [s ch] + (string/join [ch s ch])) + + +(defn print-line + "Print a simple line of length \"page-width\" by repeating ch, + followed by (newline). If border-ch is given, change the first and + last occurrence of ch to it." + ([ch] + (println (-> page-width + (fill-string ch)))) + ([ch border-ch] + (println (-> (- page-width 2) + (fill-string ch) + (add-borders border-ch))))) + + +(defn print-header + "Print a nicely formatted header with application name and version + number." + [] + (let [raw-header (str "MoccaFaux v" (version/get-version "de.mzuther" + "moccafaux.core")) + header-width (count raw-header) + left-margin (quot (- page-width 2 header-width) 2) + right-margin (- page-width 2 header-width left-margin) + + pre-header (string/join [(fill-string left-margin \space) + raw-header + (fill-string right-margin \space)]) + full-header (add-borders pre-header \|)] + (println) + (print-line \- \o) + (println full-header) + (print-line \- \o))) From 4cd97d370ea9a1466d8bd61e22581f886c37a38e Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Tue, 23 Jun 2020 23:00:37 +0200 Subject: [PATCH 02/19] print header when JSON parser throws an exception --- src/de/mzuther/moccafaux/core.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 5d0190f..62c5cbe 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -18,6 +18,7 @@ user-prefs (throw (Exception. "JSON error (not handled by library)")))) (catch Throwable e + (helpers/print-header) (newline) (println (str e)) (newline) From 88856fb7d016ae3d6e59174b6371fee180d22bb5 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Wed, 24 Jun 2020 10:23:10 +0200 Subject: [PATCH 03/19] test "enable-disable-or-nil?" --- src/de/mzuther/moccafaux/core.clj | 2 +- test/moccafaux/core_test.clj | 33 ++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 62c5cbe..0b6ec24 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -120,7 +120,7 @@ exit-state)))) -(defn- enable-disable-or-nil? +(defn enable-disable-or-nil? "Reduce coll to a scalar. Return :disable if coll contains a :disable value. If it doesn't diff --git a/test/moccafaux/core_test.clj b/test/moccafaux/core_test.clj index 2530cbe..df9d0a2 100644 --- a/test/moccafaux/core_test.clj +++ b/test/moccafaux/core_test.clj @@ -1,7 +1,34 @@ (ns moccafaux.core-test (:require [clojure.test :refer :all] - [moccafaux.core :refer :all])) + [de.mzuther.moccafaux.core :refer :all])) -(deftest a-test + +(deftest test-enable-disable-or-nil (testing "FIXME, I fail." - (is (= 0 1)))) + ;; empty vector + (is (= (enable-disable-or-nil? []) + nil)) + ;; vector of nils + (is (= (enable-disable-or-nil? [nil nil nil nil nil nil]) + nil)) + ;; vector with single :disable + (is (= (enable-disable-or-nil? [:disable]) + :disable)) + ;; vector with multiple :disable + (is (= (enable-disable-or-nil? [:disable :disable :disable :disable]) + :disable)) + ;; mixed vector with :disable + (is (= (enable-disable-or-nil? [nil 1 2 0 :enable :disable]) + :disable)) + ;; vector with single :enable + (is (= (enable-disable-or-nil? [:enable]) + :enable)) + ;; "disable" string instead of keyword + (is (= (enable-disable-or-nil? ["disable"]) + :enable)) + ;; vector with inrelated scalar value + (is (= (enable-disable-or-nil? [1]) + :enable)) + ;; mixed vector with "disable" string instead of keyword + (is (= (enable-disable-or-nil? [nil 1 2 0 :enable "disable"]) + :enable)))) From 0482c68e3e4ff844e4e1ca1aa471057927a5930f Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Wed, 24 Jun 2020 10:30:32 +0200 Subject: [PATCH 04/19] return process object from "shell-exec" --- project.clj | 2 +- src/de/mzuther/moccafaux/core.clj | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/project.clj b/project.clj index a211d52..3d843d4 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject de.mzuther/moccafaux.core "1.1.1-SNAPSHOT" +(defproject de.mzuther/moccafaux.core "1.1.2-SNAPSHOT" :description "Adapt power management to changes in the environment." :url "https://github.com/mzuther/moccafaux" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 0b6ec24..3d8420b 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -40,21 +40,22 @@ "Execute command in a shell compatible to the Bourne shell and fork process if fork? is true. - Return :forked if command has forked, :success if command has + Return a vector of a keyword and the created process object. The + keyword is :forked if command has forked, :success if command has exited with a zero exit code, and :failed in any other case." [command fork?] (let [new-process (popen/popen ["sh" "-c" command])] (if-not fork? (if (zero? (popen/exit-code new-process)) - :success - :failed) + [:success new-process] + [:failed new-process]) (do ;; wait for 10 ms to check whether the process is actually ;; created and running (Thread/sleep 10) (if (popen/running? new-process) - :forked - :failed))))) + [:forked new-process] + [:failed new-process]))))) (defn- watch-exec @@ -74,7 +75,7 @@ they do not find any matching lines or processes." [[watch-name {:keys [enabled command tasks]}]] (let [save-energy? (when enabled - (let [exit-state (shell-exec command false)] + (let [[exit-state _] (shell-exec command false)] (if (= exit-state :failed) :enable :disable)))] @@ -115,7 +116,7 @@ (helpers/printfln "%s State: %s (%s)" padding (name new-state) message) (helpers/printfln "%s Command: %s" padding command) ;; execute command (finally) - (let [exit-state (shell-exec command fork?)] + (let [[exit-state _] (shell-exec command fork?)] (helpers/printfln "%s Result: %s" padding (name exit-state)) exit-state)))) From 883695d820ddb81f8c6d0b42ded674440ed512e6 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Wed, 24 Jun 2020 10:59:10 +0200 Subject: [PATCH 05/19] improve console logging --- src/de/mzuther/moccafaux/core.clj | 31 +++++++++++++++++----------- src/de/mzuther/moccafaux/helpers.clj | 11 ++++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 3d8420b..24b15e0 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -2,6 +2,7 @@ (:require [de.mzuther.moccafaux.helpers :as helpers] [clojure.data.json :as json] [clojure.java.io :as io] + [clojure.string :as string] [chime.core :as chime] [com.rpl.specter :as sp] [popen]) @@ -101,23 +102,22 @@ (when (nil? new-state) (throw (IllegalArgumentException. "eeek, a NIL entered \"update-energy-saving\""))) - (let [timestamp (. (java.time.format.DateTimeFormatter/ofPattern "HH:mm:ss") - format - (java.time.LocalTime/now)) - padding " " - - prefs (sp/select-one [:tasks task new-state] preferences) + (let [prefs (sp/select-one [:tasks task new-state] preferences) command (sp/select-one [:command] prefs) fork? (sp/select-one [:fork] prefs) message (sp/select-one [:message] prefs)] (when command (println) - (helpers/printfln "[%s] Task: %s" timestamp (name task)) - (helpers/printfln "%s State: %s (%s)" padding (name new-state) message) - (helpers/printfln "%s Command: %s" padding command) + (helpers/printfln "[%s] Task: %s %s" + (helpers/get-timestamp) (name new-state) (name task)) + (helpers/printfln "%s State: %s" + helpers/padding message) + (helpers/printfln "%s Command: %s" + helpers/padding command) ;; execute command (finally) (let [[exit-state _] (shell-exec command fork?)] - (helpers/printfln "%s Result: %s" padding (name exit-state)) + (helpers/printfln "%s Result: %s" + helpers/padding (name exit-state)) exit-state)))) @@ -198,5 +198,12 @@ [& _] (helpers/print-header) - (start-scheduler update-status - (sp/select-one [:scheduler :probing-interval] preferences))) + (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] + (println) + (helpers/printfln "[%s] Interval: %s seconds" + (helpers/get-timestamp) interval) + (helpers/printfln "%s Tasks: %s" + helpers/padding + (string/join ", " (map name defined-tasks))) + + (start-scheduler update-status interval))) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index 0b79ccc..ff88601 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -4,6 +4,7 @@ (def page-width 80) +(def padding " ") (defn printfln @@ -22,6 +23,16 @@ (apply str))) +(defn get-timestamp + "Get current local time. + + Return a string formatted as \"HH:mm:ss\"." + [] + (. (java.time.format.DateTimeFormatter/ofPattern "HH:mm:ss") + format + (java.time.LocalTime/now))) + + (defn add-borders "Create a string consisting of ch followed by s followed by ch." [s ch] From b447ac7f80f84de24fa06dacb6abf27f9645b613 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Wed, 24 Jun 2020 11:31:31 +0200 Subject: [PATCH 06/19] display watch names on console log --- src/de/mzuther/moccafaux/core.clj | 35 +++++++++++++++++++++------- src/de/mzuther/moccafaux/helpers.clj | 8 +++---- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 24b15e0..108f204 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -27,12 +27,22 @@ (System/exit 1)))) -(def defined-tasks +(def task-names (->> preferences (sp/select [:tasks sp/MAP-KEYS]) sort)) +(def defined-watches + (->> preferences + (sp/select-one [:watches]) + sort)) + + +(def watch-names + (sp/select [sp/MAP-KEYS] defined-watches)) + + ;; empty ref forces an update on first call to "update-status" (def task-states (ref {})) @@ -88,7 +98,7 @@ (when (get tasks task) save-energy?))) {:watch watch-name} - defined-tasks))) + task-names))) (defn update-energy-saving @@ -108,7 +118,7 @@ message (sp/select-one [:message] prefs)] (when command (println) - (helpers/printfln "[%s] Task: %s %s" + (helpers/printfln "%s Task: %s %s" (helpers/get-timestamp) (name new-state) (name task)) (helpers/printfln "%s State: %s" helpers/padding message) @@ -149,7 +159,7 @@ Return new task states, consisting of a map containing keys for all defined tasks with values according to \"enable-disable-or-nil?\"." [_] - (let [exit-states (->> (sp/select-one [:watches] preferences) + (let [exit-states (->> defined-watches (map watch-exec)) new-task-states (reduce (fn [new-ts task] (assoc new-ts @@ -157,14 +167,14 @@ (enable-disable-or-nil? (map task exit-states)))) {} - defined-tasks) + task-names) update-needed? (not= new-task-states @task-states) update-task (fn [task] (let [new-state (sp/select-one [task] new-task-states) old-state (sp/select-one [task] @task-states)] (when (not= new-state old-state) (update-energy-saving task new-state))))] - (doseq [task defined-tasks] + (doseq [task task-names] (update-task task)) (when update-needed? (newline) @@ -200,10 +210,17 @@ (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] (println) - (helpers/printfln "[%s] Interval: %s seconds" - (helpers/get-timestamp) interval) + (helpers/printfln "%s Probe: every %s seconds" + (helpers/get-timestamp) + interval) (helpers/printfln "%s Tasks: %s" helpers/padding - (string/join ", " (map name defined-tasks))) + (string/join ", " (map name task-names))) + (helpers/printfln "%s Watches: %s" + helpers/padding + (string/join ", " (map name watch-names))) + + (newline) + (helpers/print-line \-) (start-scheduler update-status interval))) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index ff88601..7e70c77 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -26,11 +26,11 @@ (defn get-timestamp "Get current local time. - Return a string formatted as \"HH:mm:ss\"." + Return a string formatted as \"[HH:mm:ss]\"." [] - (. (java.time.format.DateTimeFormatter/ofPattern "HH:mm:ss") - format - (java.time.LocalTime/now))) + (->> (java.time.LocalTime/now) + (.format (java.time.format.DateTimeFormatter/ofPattern "HH:mm:ss")) + (format "[%s]"))) (defn add-borders From dcdad0cf678664afbb869bb8ece20cfa2b5ccccf Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Thu, 25 Jun 2020 22:54:44 +0200 Subject: [PATCH 07/19] improve settings display on console log --- src/de/mzuther/moccafaux/core.clj | 14 ++---------- src/de/mzuther/moccafaux/helpers.clj | 33 +++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 108f204..722b6f3 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -2,7 +2,6 @@ (:require [de.mzuther.moccafaux.helpers :as helpers] [clojure.data.json :as json] [clojure.java.io :as io] - [clojure.string :as string] [chime.core :as chime] [com.rpl.specter :as sp] [popen]) @@ -117,7 +116,7 @@ fork? (sp/select-one [:fork] prefs) message (sp/select-one [:message] prefs)] (when command - (println) + (newline) (helpers/printfln "%s Task: %s %s" (helpers/get-timestamp) (name new-state) (name task)) (helpers/printfln "%s State: %s" @@ -209,16 +208,7 @@ (helpers/print-header) (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] - (println) - (helpers/printfln "%s Probe: every %s seconds" - (helpers/get-timestamp) - interval) - (helpers/printfln "%s Tasks: %s" - helpers/padding - (string/join ", " (map name task-names))) - (helpers/printfln "%s Watches: %s" - helpers/padding - (string/join ", " (map name watch-names))) + (helpers/print-settings interval task-names watch-names) (newline) (helpers/print-line \-) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index 7e70c77..c51b4b6 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -66,7 +66,38 @@ raw-header (fill-string right-margin \space)]) full-header (add-borders pre-header \|)] - (println) + (newline) (print-line \- \o) (println full-header) (print-line \- \o))) + + +(defn print-list + "Print coll as a list with the first element prepended by + padding-first (probably containing an annotation) and the remaining + elements by padding-rest (usually just white space). The respective + padding and element will be spaced by sep and a single space. " + [padding-first padding-rest sep coll] + (let [paddings (concat [padding-first] + (repeat padding-rest)) + separator (str sep " ")] + ;; mapv is not lazy! + (mapv #(println (string/join separator [%1 %2])) + paddings + coll))) + + +(defn print-settings + "Print settings using a nice layout." + [interval task-names watch-names] + (let [padding-tasks (format "%s Tasks: " padding) + padding-watches (format "%s Watches: " padding) + padding-rest (format "%s " padding)] + (newline) + (printfln "%s Probe: every %s seconds" + (get-timestamp) + interval) + (print-list padding-tasks padding-rest "-" + (map name task-names)) + (print-list padding-watches padding-rest "-" + (map name watch-names)))) From e4577b988cdd88acc37ab9ff02d2f9d211321dc4 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Sun, 5 Jul 2020 18:57:25 +0200 Subject: [PATCH 08/19] spec functions --- config-SAMPLE.json | 4 +- project.clj | 2 +- src/de/mzuther/moccafaux/core.clj | 15 ++++- src/de/mzuther/moccafaux/helpers.clj | 13 +++- src/de/mzuther/moccafaux/spec.clj | 93 ++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 src/de/mzuther/moccafaux/spec.clj diff --git a/config-SAMPLE.json b/config-SAMPLE.json index 910f099..a758eaa 100644 --- a/config-SAMPLE.json +++ b/config-SAMPLE.json @@ -7,7 +7,7 @@ "sleep": { "enable": { "message": "allow computer to save energy", - "command": "xautolock -time 15 -locker 'systemctl suspend' -detectsleep", + "command": "xautolock -time 10 -locker 'systemctl suspend' -detectsleep", "fork": true }, "disable": { @@ -20,7 +20,7 @@ "dpms": { "enable": { "message": "allow screen to save energy", - "command": "xset dpms 300 600 900", + "command": "xset dpms 0 300 600", "fork": false }, "disable": { diff --git a/project.clj b/project.clj index 3d843d4..d815f52 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject de.mzuther/moccafaux.core "1.1.2-SNAPSHOT" +(defproject de.mzuther/moccafaux.core "1.1.3-SNAPSHOT" :description "Adapt power management to changes in the environment." :url "https://github.com/mzuther/moccafaux" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 722b6f3..13e502d 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -204,9 +204,22 @@ (defn -main "Print information on application and schedule watchers." - [& _] + [& args] (helpers/print-header) + ;; display help and exit + (when (some #{"help" "--help"} args) + (newline) + (println "Usage: MoccaFaux [--debug] [--help]") + (newline) + (flush) + (System/exit 0)) + + ;; enable debug mode (so far, this only instruments specs) + (when (some #{"debug" "--debug"} args) + (helpers/instrument-specs)) + + ;; display settings and enter main loop (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] (helpers/print-settings interval task-names watch-names) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index c51b4b6..3629044 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -1,5 +1,6 @@ (ns de.mzuther.moccafaux.helpers - (:require [clojure.string :as string] + (:require [de.mzuther.moccafaux.spec :as spec] + [clojure.string :as string] [trptcolin.versioneer.core :as version])) @@ -97,7 +98,17 @@ (printfln "%s Probe: every %s seconds" (get-timestamp) interval) + (newline) (print-list padding-tasks padding-rest "-" (map name task-names)) + (newline) (print-list padding-watches padding-rest "-" (map name watch-names)))) + + +(defn instrument-specs [] + (let [padding-first (format "%s Specs: " (get-timestamp)) + padding-rest (format "%s " padding)] + (newline) + (print-list padding-first padding-rest "-" + (sort (spec/instrument-specs))))) diff --git a/src/de/mzuther/moccafaux/spec.clj b/src/de/mzuther/moccafaux/spec.clj new file mode 100644 index 0000000..f0cfa19 --- /dev/null +++ b/src/de/mzuther/moccafaux/spec.clj @@ -0,0 +1,93 @@ +;; resolve circular dependency +(ns de.mzuther.moccafaux.core) + +(ns de.mzuther.moccafaux.spec + (:require [de.mzuther.moccafaux.core] + [clojure.spec.alpha :as s] + [clojure.spec.test.alpha :as stest])) + + +(s/def ::enabled + boolean?) + +(s/def ::interval + (s/and + int? + pos?)) + +(s/def ::command + (s/and + string? + #(pos? (count %)))) + +(s/def ::tasks + (s/map-of keyword? boolean?)) + +(s/def ::task-states + (s/coll-of (s/cat :name string? + :state (s/* ::task-state)))) + +(s/def ::exit-code + #{:success :forked :failed}) + +(s/def ::task-state + (s/nilable + #{:enable :disable})) + +(s/def ::task-state-coll + (s/coll-of ::task-state)) + +(s/def ::watch + (s/cat :name keyword? + :settings (s/keys :req-un [::enabled + ::command + ::tasks]))) + +;; ----------------------------------------------------------------------------- + +(s/def ::shell-exec-fn + (fn [{:keys [args ret]}] + (if (get args :fork?) + (contains? #{:forked :failed} ret) + (contains? #{:success :failed} ret)))) + +(s/fdef de.mzuther.moccafaux.core/shell-exec + :args (s/cat :command ::command + :fork? boolean?) + :ret ::exit-code + :fn ::shell-exec-fn) + +;; ----------------------------------------------------------------------------- + +(s/fdef de.mzuther.moccafaux.core/watch-exec + :args (s/cat :watch ::watch) + :ret ::task-states) + + +(s/fdef de.mzuther.moccafaux.core/update-energy-saving + :args (s/cat :task keyword? + :state ::task-state) + :ret ::exit-code) + + +(s/fdef de.mzuther.moccafaux.core/enable-disable-or-nil? + :args (s/cat :task-states ::task-state-coll) + :ret ::task-state) + + +(s/fdef de.mzuther.moccafaux.core/update-status + :args (s/cat :timestamp-ignored any?) + :ret ::task-states) + + +(s/fdef de.mzuther.moccafaux.core/start-scheduler + :args (s/cat :schedule-fn fn? + :interval ::interval) + :ret (s/coll-of (s/cat :name string? + :state (s/* ::task-state)))) + +;; ----------------------------------------------------------------------------- + +(defn instrument-specs [] + (stest/instrument (stest/enumerate-namespace + 'de.mzuther.moccafaux.core))) From 64d2776f1bec3e9392d7b8ad5b7a26ca519451ff Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Sun, 5 Jul 2020 20:12:32 +0200 Subject: [PATCH 09/19] parse command line parameters --- project.clj | 1 + src/de/mzuther/moccafaux/core.clj | 44 +++++++++++++++++----------- src/de/mzuther/moccafaux/helpers.clj | 20 +++++++++++++ 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/project.clj b/project.clj index d815f52..b4fe919 100644 --- a/project.clj +++ b/project.clj @@ -6,6 +6,7 @@ :dependencies [[org.clojure/clojure "1.10.1"] [org.clojure/data.json "1.0.0"] + [org.clojure/tools.cli "1.0.194"] [com.rpl/specter "1.1.3"] [jarohen/chime "0.3.2"] [popen "0.3.1"] diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 13e502d..d9edb36 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -1,5 +1,6 @@ (ns de.mzuther.moccafaux.core (:require [de.mzuther.moccafaux.helpers :as helpers] + [clojure.tools.cli :as cli] [clojure.data.json :as json] [clojure.java.io :as io] [chime.core :as chime] @@ -8,6 +9,13 @@ (:gen-class)) +(def cli-options + [["-d" "--debug" + :desc "instrument function specs"] + ["-h" "--help" + :desc "display version and usage information"]]) + + (def preferences (try (let [;; "io/file" takes care of line separators file-name (io/file (System/getProperty "user.home") @@ -204,26 +212,28 @@ (defn -main "Print information on application and schedule watchers." - [& args] + [& unparsed-args] (helpers/print-header) - ;; display help and exit - (when (some #{"help" "--help"} args) - (newline) - (println "Usage: MoccaFaux [--debug] [--help]") - (newline) - (flush) - (System/exit 0)) + (let [args (cli/parse-opts unparsed-args cli-options)] + (cond + ;; display errors and exit + (sp/select-one [:errors] args) + (helpers/exit-after-printing-help-and-errors args 2) - ;; enable debug mode (so far, this only instruments specs) - (when (some #{"debug" "--debug"} args) - (helpers/instrument-specs)) + ;; display help and exit + (sp/select-one [:options :help] args) + (helpers/exit-after-printing-help-and-errors args 0)) - ;; display settings and enter main loop - (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] - (helpers/print-settings interval task-names watch-names) + ;; enable debug mode (so far, this only instruments specs) + (when (sp/select-one [:options :debug] args) + (helpers/instrument-specs)) - (newline) - (helpers/print-line \-) + ;; display settings and enter main loop + (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] + (helpers/print-settings interval task-names watch-names) + + (newline) + (helpers/print-line \-) - (start-scheduler update-status interval))) + (start-scheduler update-status interval)))) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index 3629044..de7f5f9 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -88,6 +88,26 @@ coll))) +(defn exit-after-printing-help-and-errors + "Print help, command line parsing errors (if any) and exit with + given exit-code." + [args exit-code] + (let [{:keys [summary errors]} args] + (when errors + (newline) + (doseq [error errors] + (println "Error:" error))) + + (newline) + (println "Usage: java -jar moccafaux.jar [OPTION...]") + (newline) + ;; display command line help + (println summary) + (newline) + (flush) + (System/exit exit-code))) + + (defn print-settings "Print settings using a nice layout." [interval task-names watch-names] From fa6910b818d0e9f82b16764d16354e0c700f2473 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Mon, 6 Jul 2020 09:39:22 +0200 Subject: [PATCH 10/19] use records to improve reading of code --- src/de/mzuther/moccafaux/core.clj | 58 +++++++++++++++++++++---------- src/de/mzuther/moccafaux/spec.clj | 12 +++---- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index d9edb36..43f1303 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -1,8 +1,9 @@ (ns de.mzuther.moccafaux.core (:require [de.mzuther.moccafaux.helpers :as helpers] - [clojure.tools.cli :as cli] [clojure.data.json :as json] [clojure.java.io :as io] + [clojure.string :as string] + [clojure.tools.cli :as cli] [chime.core :as chime] [com.rpl.specter :as sp] [popen]) @@ -54,6 +55,31 @@ (def task-states (ref {})) +(defrecord TaskStates [id states] + Object + (toString [this] + (let [id (name (get this :id))] + (str id ": " (string/join ", " states))))) + + +(defrecord TaskState [id state] + Object + (toString [this] + (let [id (name (get this :id)) + state (if-let [state (get this :state)] + (name state) + "nil")] + (str id "=" state)))) + + +(defn make-task-states [id states] + (TaskStates. id states)) + + +(defn make-task-state [id state] + (TaskState. id state)) + + (defn- shell-exec "Execute command in a shell compatible to the Bourne shell and fork process if fork? is true. @@ -97,15 +123,14 @@ (if (= exit-state :failed) :enable :disable)))] - ;; Apply exit state of watch to all defined tasks (or nil when the + ;; apply exit state of watch to all defined tasks (or nil when the ;; watch has not been assigned to the given task). - (reduce (fn [exit-states task] - (assoc exit-states - task - (when (get tasks task) - save-energy?))) - {:watch watch-name} - task-names))) + (make-task-states watch-name + (map (fn [task] + (make-task-state task + (when (get tasks task) + save-energy?))) + task-names)))) (defn update-energy-saving @@ -166,15 +191,12 @@ Return new task states, consisting of a map containing keys for all defined tasks with values according to \"enable-disable-or-nil?\"." [_] - (let [exit-states (->> defined-watches - (map watch-exec)) - new-task-states (reduce (fn [new-ts task] - (assoc new-ts - task - (enable-disable-or-nil? - (map task exit-states)))) - {} - task-names) + (let [exit-states (map watch-exec defined-watches) + extract-state (fn [task] (->> exit-states + (sp/select [sp/ALL :states sp/ALL #(= (:id %) task) :state]) + (enable-disable-or-nil?) + (vector task))) + new-task-states (into {} (map extract-state task-names)) update-needed? (not= new-task-states @task-states) update-task (fn [task] (let [new-state (sp/select-one [task] new-task-states) diff --git a/src/de/mzuther/moccafaux/spec.clj b/src/de/mzuther/moccafaux/spec.clj index f0cfa19..55607c5 100644 --- a/src/de/mzuther/moccafaux/spec.clj +++ b/src/de/mzuther/moccafaux/spec.clj @@ -2,9 +2,9 @@ (ns de.mzuther.moccafaux.core) (ns de.mzuther.moccafaux.spec - (:require [de.mzuther.moccafaux.core] - [clojure.spec.alpha :as s] - [clojure.spec.test.alpha :as stest])) + (:require [de.mzuther.moccafaux.core] + [clojure.spec.alpha :as s] + [clojure.spec.test.alpha :as stest])) (s/def ::enabled @@ -38,10 +38,8 @@ (s/coll-of ::task-state)) (s/def ::watch - (s/cat :name keyword? - :settings (s/keys :req-un [::enabled - ::command - ::tasks]))) + ;; something seems to be broken in spec instrumentation + map-entry?) ;; ----------------------------------------------------------------------------- From 2178f45599da72781f3408663c932a56bd8aa2a1 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Mon, 6 Jul 2020 10:33:54 +0200 Subject: [PATCH 11/19] drop specs * it's not worth the hassle in this application ... --- project.clj | 2 +- src/de/mzuther/moccafaux/core.clj | 8 +-- src/de/mzuther/moccafaux/helpers.clj | 13 +--- src/de/mzuther/moccafaux/spec.clj | 91 ---------------------------- 4 files changed, 4 insertions(+), 110 deletions(-) delete mode 100644 src/de/mzuther/moccafaux/spec.clj diff --git a/project.clj b/project.clj index b4fe919..27aa715 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject de.mzuther/moccafaux.core "1.1.3-SNAPSHOT" +(defproject de.mzuther/moccafaux.core "1.1.4-SNAPSHOT" :description "Adapt power management to changes in the environment." :url "https://github.com/mzuther/moccafaux" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 43f1303..3cc494a 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -11,9 +11,7 @@ (def cli-options - [["-d" "--debug" - :desc "instrument function specs"] - ["-h" "--help" + [["-h" "--help" :desc "display version and usage information"]]) @@ -247,10 +245,6 @@ (sp/select-one [:options :help] args) (helpers/exit-after-printing-help-and-errors args 0)) - ;; enable debug mode (so far, this only instruments specs) - (when (sp/select-one [:options :debug] args) - (helpers/instrument-specs)) - ;; display settings and enter main loop (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] (helpers/print-settings interval task-names watch-names) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index de7f5f9..749559b 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -1,6 +1,5 @@ (ns de.mzuther.moccafaux.helpers - (:require [de.mzuther.moccafaux.spec :as spec] - [clojure.string :as string] + (:require [clojure.string :as string] [trptcolin.versioneer.core :as version])) @@ -96,7 +95,7 @@ (when errors (newline) (doseq [error errors] - (println "Error:" error))) + (println "ERROR:" error))) (newline) (println "Usage: java -jar moccafaux.jar [OPTION...]") @@ -124,11 +123,3 @@ (newline) (print-list padding-watches padding-rest "-" (map name watch-names)))) - - -(defn instrument-specs [] - (let [padding-first (format "%s Specs: " (get-timestamp)) - padding-rest (format "%s " padding)] - (newline) - (print-list padding-first padding-rest "-" - (sort (spec/instrument-specs))))) diff --git a/src/de/mzuther/moccafaux/spec.clj b/src/de/mzuther/moccafaux/spec.clj deleted file mode 100644 index 55607c5..0000000 --- a/src/de/mzuther/moccafaux/spec.clj +++ /dev/null @@ -1,91 +0,0 @@ -;; resolve circular dependency -(ns de.mzuther.moccafaux.core) - -(ns de.mzuther.moccafaux.spec - (:require [de.mzuther.moccafaux.core] - [clojure.spec.alpha :as s] - [clojure.spec.test.alpha :as stest])) - - -(s/def ::enabled - boolean?) - -(s/def ::interval - (s/and - int? - pos?)) - -(s/def ::command - (s/and - string? - #(pos? (count %)))) - -(s/def ::tasks - (s/map-of keyword? boolean?)) - -(s/def ::task-states - (s/coll-of (s/cat :name string? - :state (s/* ::task-state)))) - -(s/def ::exit-code - #{:success :forked :failed}) - -(s/def ::task-state - (s/nilable - #{:enable :disable})) - -(s/def ::task-state-coll - (s/coll-of ::task-state)) - -(s/def ::watch - ;; something seems to be broken in spec instrumentation - map-entry?) - -;; ----------------------------------------------------------------------------- - -(s/def ::shell-exec-fn - (fn [{:keys [args ret]}] - (if (get args :fork?) - (contains? #{:forked :failed} ret) - (contains? #{:success :failed} ret)))) - -(s/fdef de.mzuther.moccafaux.core/shell-exec - :args (s/cat :command ::command - :fork? boolean?) - :ret ::exit-code - :fn ::shell-exec-fn) - -;; ----------------------------------------------------------------------------- - -(s/fdef de.mzuther.moccafaux.core/watch-exec - :args (s/cat :watch ::watch) - :ret ::task-states) - - -(s/fdef de.mzuther.moccafaux.core/update-energy-saving - :args (s/cat :task keyword? - :state ::task-state) - :ret ::exit-code) - - -(s/fdef de.mzuther.moccafaux.core/enable-disable-or-nil? - :args (s/cat :task-states ::task-state-coll) - :ret ::task-state) - - -(s/fdef de.mzuther.moccafaux.core/update-status - :args (s/cat :timestamp-ignored any?) - :ret ::task-states) - - -(s/fdef de.mzuther.moccafaux.core/start-scheduler - :args (s/cat :schedule-fn fn? - :interval ::interval) - :ret (s/coll-of (s/cat :name string? - :state (s/* ::task-state)))) - -;; ----------------------------------------------------------------------------- - -(defn instrument-specs [] - (stest/instrument (stest/enumerate-namespace - 'de.mzuther.moccafaux.core))) From c66965f4a255d56b1483a7526b781b3fee9aae05 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Mon, 6 Jul 2020 17:27:38 +0200 Subject: [PATCH 12/19] use records to improve reading of code (part #2) --- src/de/mzuther/moccafaux/core.clj | 53 +++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 3cc494a..fb79fcd 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -53,13 +53,6 @@ (def task-states (ref {})) -(defrecord TaskStates [id states] - Object - (toString [this] - (let [id (name (get this :id))] - (str id ": " (string/join ", " states))))) - - (defrecord TaskState [id state] Object (toString [this] @@ -70,14 +63,40 @@ (str id "=" state)))) -(defn make-task-states [id states] - (TaskStates. id states)) +(defrecord TaskStates [id states] + Object + (toString [this] + (let [id (name (get this :id))] + (str id ": " (string/join ", " states))))) -(defn make-task-state [id state] +(defn make-task-state + "Create new TaskState object with given task ID and an exit state + (:enabled if exit state was *non-zero*, :disabled if it was *zero* + and nil if the watch was either disabled or not assigned to a + task)." + [id state] (TaskState. id state)) +(defn make-task-states + "Create new TaskStates object with given ID and a TaskState seq." + [id states] + (TaskStates. id states)) + + +(defrecord ProcessObject [process state]) + + +(defn make-process-object + "Create new ProcessObject object consisting of a Java process object + and an exit state (:forked if command has forked, :success if + command has exited with a zero exit code, and :failed in any other + case)." + [process state] + (ProcessObject. process state)) + + (defn- shell-exec "Execute command in a shell compatible to the Bourne shell and fork process if fork? is true. @@ -89,15 +108,15 @@ (let [new-process (popen/popen ["sh" "-c" command])] (if-not fork? (if (zero? (popen/exit-code new-process)) - [:success new-process] - [:failed new-process]) + (make-process-object new-process :success) + (make-process-object new-process :failed)) (do ;; wait for 10 ms to check whether the process is actually ;; created and running (Thread/sleep 10) (if (popen/running? new-process) - [:forked new-process] - [:failed new-process]))))) + (make-process-object new-process :forked) + (make-process-object new-process :failed)))))) (defn- watch-exec @@ -105,7 +124,7 @@ Return a map containing the watch's name and an exit state for every defined task (:enabled if exit state was *non-zero*, :disabled if it - was *zero*). In case a watch has not been enabled or to a given + was *zero*). In case a watch has not been enabled or assigned to a task, set exit state to nil. Background information: energy saving is enabled when a watch is @@ -117,7 +136,7 @@ they do not find any matching lines or processes." [[watch-name {:keys [enabled command tasks]}]] (let [save-energy? (when enabled - (let [[exit-state _] (shell-exec command false)] + (let [{exit-state :state} (shell-exec command false)] (if (= exit-state :failed) :enable :disable)))] @@ -155,7 +174,7 @@ (helpers/printfln "%s Command: %s" helpers/padding command) ;; execute command (finally) - (let [[exit-state _] (shell-exec command fork?)] + (let [{exit-state :state} (shell-exec command fork?)] (helpers/printfln "%s Result: %s" helpers/padding (name exit-state)) exit-state)))) From a49d6a6f11e53f107c275cfe8d6a758d1d1c0147 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Mon, 6 Jul 2020 22:26:08 +0200 Subject: [PATCH 13/19] add pre-conditions to make-... functions --- src/de/mzuther/moccafaux/core.clj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index fb79fcd..6dda4cd 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -76,12 +76,17 @@ and nil if the watch was either disabled or not assigned to a task)." [id state] + {:pre [(keyword? id) + (or (nil? state) + (get #{:enable :disable} state))]} (TaskState. id state)) (defn make-task-states "Create new TaskStates object with given ID and a TaskState seq." [id states] + {:pre [(keyword? id) + (every? #(= (type %) TaskState) states)]} (TaskStates. id states)) @@ -94,6 +99,8 @@ command has exited with a zero exit code, and :failed in any other case)." [process state] + {:pre [(= (type process) java.lang.ProcessImpl) + (get #{:forked :success :failed} state)]} (ProcessObject. process state)) From 6ca2edb84965066a619e43408464b50f60f091f2 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Mon, 6 Jul 2020 22:26:27 +0200 Subject: [PATCH 14/19] fix docstrings --- src/de/mzuther/moccafaux/core.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 6dda4cd..2df3a0f 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -72,7 +72,7 @@ (defn make-task-state "Create new TaskState object with given task ID and an exit state - (:enabled if exit state was *non-zero*, :disabled if it was *zero* + (:enable if exit state was *non-zero*, :disable if it was *zero* and nil if the watch was either disabled or not assigned to a task)." [id state] @@ -130,7 +130,7 @@ "Execute watch and apply exit state of watch to all defined tasks. Return a map containing the watch's name and an exit state for every - defined task (:enabled if exit state was *non-zero*, :disabled if it + defined task (:enable if exit state was *non-zero*, :disable if it was *zero*). In case a watch has not been enabled or assigned to a task, set exit state to nil. From 3de334160c0ed1e0f21bcdb070c8539b5f2ef40e Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Mon, 6 Jul 2020 23:06:57 +0200 Subject: [PATCH 15/19] improve Leiningen project * disable assertions in uberjar * add debug profile --- project.clj | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/project.clj b/project.clj index 27aa715..c8a99ef 100644 --- a/project.clj +++ b/project.clj @@ -2,7 +2,7 @@ :description "Adapt power management to changes in the environment." :url "https://github.com/mzuther/moccafaux" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" - :url "https://www.eclipse.org/legal/epl-2.0/"} + :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.1"] [org.clojure/data.json "1.0.0"] @@ -16,4 +16,14 @@ :target-path "target/%s" :uberjar-name "moccafaux.jar" - :profiles {:uberjar {:aot :all}}) + :profiles {:debug {:debug true + :injections [(newline) + (doseq [s (into {} (System/getProperties))] + (prn s)) + (newline) + (flush)] + :global-vars {*warn-on-reflection* true + *assert* true}} + :uberjar {:aot :all + :global-vars {*warn-on-reflection* true + *assert* false}}}) From dff0beefb355acaa69816e1a61d68a4ceff4ccfa Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Sun, 12 Jul 2020 11:26:50 +0200 Subject: [PATCH 16/19] re-order helper functions --- src/de/mzuther/moccafaux/helpers.clj | 64 ++++++++++++++-------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index 749559b..9d4bf3e 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -7,10 +7,14 @@ (def padding " ") -(defn printfln - "Print formatted output, as per format, followed by (newline)." - [fmt & args] - (println (apply format fmt args))) +(defn get-timestamp + "Get current local time. + + Return a string formatted as \"[HH:mm:ss]\"." + [] + (->> (java.time.LocalTime/now) + (.format (java.time.format.DateTimeFormatter/ofPattern "HH:mm:ss")) + (format "[%s]"))) (defn fill-string @@ -23,22 +27,18 @@ (apply str))) -(defn get-timestamp - "Get current local time. - - Return a string formatted as \"[HH:mm:ss]\"." - [] - (->> (java.time.LocalTime/now) - (.format (java.time.format.DateTimeFormatter/ofPattern "HH:mm:ss")) - (format "[%s]"))) - - (defn add-borders "Create a string consisting of ch followed by s followed by ch." [s ch] (string/join [ch s ch])) +(defn printfln + "Print formatted output, as per format, followed by (newline)." + [fmt & args] + (println (apply format fmt args))) + + (defn print-line "Print a simple line of length \"page-width\" by repeating ch, followed by (newline). If border-ch is given, change the first and @@ -87,6 +87,24 @@ coll))) +(defn print-settings + "Print settings using a nice layout." + [interval task-names watch-names] + (let [padding-tasks (format "%s Tasks: " padding) + padding-watches (format "%s Watches: " padding) + padding-rest (format "%s " padding)] + (newline) + (printfln "%s Probe: every %s seconds" + (get-timestamp) + interval) + (newline) + (print-list padding-tasks padding-rest "-" + (map name task-names)) + (newline) + (print-list padding-watches padding-rest "-" + (map name watch-names)))) + + (defn exit-after-printing-help-and-errors "Print help, command line parsing errors (if any) and exit with given exit-code." @@ -105,21 +123,3 @@ (newline) (flush) (System/exit exit-code))) - - -(defn print-settings - "Print settings using a nice layout." - [interval task-names watch-names] - (let [padding-tasks (format "%s Tasks: " padding) - padding-watches (format "%s Watches: " padding) - padding-rest (format "%s " padding)] - (newline) - (printfln "%s Probe: every %s seconds" - (get-timestamp) - interval) - (newline) - (print-list padding-tasks padding-rest "-" - (map name task-names)) - (newline) - (print-list padding-watches padding-rest "-" - (map name watch-names)))) From cfc53641792900c1ae46191606f8e1e75124f862 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Sun, 12 Jul 2020 11:41:20 +0200 Subject: [PATCH 17/19] place guards around IO operations --- src/de/mzuther/moccafaux/core.clj | 83 ++++++++++++++-------------- src/de/mzuther/moccafaux/helpers.clj | 28 +++++----- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index 2df3a0f..b2f17fd 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -17,19 +17,19 @@ (def preferences (try (let [;; "io/file" takes care of line separators - file-name (io/file (System/getProperty "user.home") - ".config" "moccafaux" "config.json") + file-name (io! (io/file (System/getProperty "user.home") + ".config" "moccafaux" "config.json")) user-prefs (json/read-str (slurp file-name) :key-fn keyword)] (if (map? user-prefs) user-prefs (throw (Exception. "JSON error (not handled by library)")))) (catch Throwable e - (helpers/print-header) - (newline) - (println (str e)) - (newline) - (flush) + (io! (helpers/print-header) + (newline) + (println (str e)) + (newline) + (flush)) (System/exit 1)))) @@ -112,18 +112,18 @@ keyword is :forked if command has forked, :success if command has exited with a zero exit code, and :failed in any other case." [command fork?] - (let [new-process (popen/popen ["sh" "-c" command])] - (if-not fork? - (if (zero? (popen/exit-code new-process)) - (make-process-object new-process :success) - (make-process-object new-process :failed)) - (do - ;; wait for 10 ms to check whether the process is actually - ;; created and running - (Thread/sleep 10) - (if (popen/running? new-process) - (make-process-object new-process :forked) - (make-process-object new-process :failed)))))) + (io! (let [new-process (popen/popen ["sh" "-c" command])] + (if-not fork? + (if (zero? (popen/exit-code new-process)) + (make-process-object new-process :success) + (make-process-object new-process :failed)) + (do + ;; wait for 10 ms to check whether the process is actually + ;; created and running + (Thread/sleep 10) + (if (popen/running? new-process) + (make-process-object new-process :forked) + (make-process-object new-process :failed))))))) (defn- watch-exec @@ -172,19 +172,19 @@ command (sp/select-one [:command] prefs) fork? (sp/select-one [:fork] prefs) message (sp/select-one [:message] prefs)] - (when command - (newline) - (helpers/printfln "%s Task: %s %s" - (helpers/get-timestamp) (name new-state) (name task)) - (helpers/printfln "%s State: %s" - helpers/padding message) - (helpers/printfln "%s Command: %s" - helpers/padding command) - ;; execute command (finally) - (let [{exit-state :state} (shell-exec command fork?)] - (helpers/printfln "%s Result: %s" - helpers/padding (name exit-state)) - exit-state)))) + (io! (when command + (newline) + (helpers/printfln "%s Task: %s %s" + (helpers/get-timestamp) (name new-state) (name task)) + (helpers/printfln "%s State: %s" + helpers/padding message) + (helpers/printfln "%s Command: %s" + helpers/padding command) + ;; execute command (finally) + (let [{exit-state :state} (shell-exec command fork?)] + (helpers/printfln "%s Result: %s" + helpers/padding (name exit-state)) + exit-state))))) (defn enable-disable-or-nil? @@ -230,8 +230,8 @@ (doseq [task task-names] (update-task task)) (when update-needed? - (newline) - (helpers/print-line \-)) + (io! (newline) + (helpers/print-line \-))) (dosync (ref-set task-states new-task-states)))) @@ -253,29 +253,28 @@ (when-not (>= seconds-late interval) (f timestamp)))) ;; display exception and kill scheduler - {:error-handler (fn [e] (println (str e)))})) + {:error-handler (fn [e] (io! (println (str e))))})) (defn -main "Print information on application and schedule watchers." [& unparsed-args] - (helpers/print-header) + (io! (helpers/print-header)) (let [args (cli/parse-opts unparsed-args cli-options)] (cond ;; display errors and exit (sp/select-one [:errors] args) - (helpers/exit-after-printing-help-and-errors args 2) + (io! (helpers/exit-after-printing-help-and-errors args 2)) ;; display help and exit (sp/select-one [:options :help] args) - (helpers/exit-after-printing-help-and-errors args 0)) + (io! (helpers/exit-after-printing-help-and-errors args 0))) ;; display settings and enter main loop (let [interval (sp/select-one [:scheduler :probing-interval] preferences)] - (helpers/print-settings interval task-names watch-names) - - (newline) - (helpers/print-line \-) + (io! (helpers/print-settings interval task-names watch-names) + (newline) + (helpers/print-line \-)) (start-scheduler update-status interval)))) diff --git a/src/de/mzuther/moccafaux/helpers.clj b/src/de/mzuther/moccafaux/helpers.clj index 9d4bf3e..f31de01 100644 --- a/src/de/mzuther/moccafaux/helpers.clj +++ b/src/de/mzuther/moccafaux/helpers.clj @@ -109,17 +109,17 @@ "Print help, command line parsing errors (if any) and exit with given exit-code." [args exit-code] - (let [{:keys [summary errors]} args] - (when errors - (newline) - (doseq [error errors] - (println "ERROR:" error))) - - (newline) - (println "Usage: java -jar moccafaux.jar [OPTION...]") - (newline) - ;; display command line help - (println summary) - (newline) - (flush) - (System/exit exit-code))) + (io! (let [{:keys [summary errors]} args] + (when errors + (newline) + (doseq [error errors] + (println "ERROR:" error))) + + (newline) + (println "Usage: java -jar moccafaux.jar [OPTION...]") + (newline) + ;; display command line help + (println summary) + (newline) + (flush))) + (System/exit exit-code)) From 83ad0c955370ab1b19b24da16f9c90cf83652dab Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Sun, 12 Jul 2020 12:43:57 +0200 Subject: [PATCH 18/19] fix reflection warning --- project.clj | 10 +++++----- src/de/mzuther/moccafaux/core.clj | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/project.clj b/project.clj index c8a99ef..7713a89 100644 --- a/project.clj +++ b/project.clj @@ -17,11 +17,11 @@ :uberjar-name "moccafaux.jar" :profiles {:debug {:debug true - :injections [(newline) - (doseq [s (into {} (System/getProperties))] - (prn s)) - (newline) - (flush)] + ;; :injections [(newline) + ;; (doseq [s (into {} (System/getProperties))] + ;; (prn s)) + ;; (newline) + ;; (flush)] :global-vars {*warn-on-reflection* true *assert* true}} :uberjar {:aot :all diff --git a/src/de/mzuther/moccafaux/core.clj b/src/de/mzuther/moccafaux/core.clj index b2f17fd..b6fde87 100644 --- a/src/de/mzuther/moccafaux/core.clj +++ b/src/de/mzuther/moccafaux/core.clj @@ -245,7 +245,7 @@ (java.time.Duration/ofSeconds interval)) (fn [timestamp] (let [actual-millis (.toEpochMilli (java.time.Instant/now)) - target-millis (.toEpochMilli timestamp) + target-millis (.toEpochMilli ^java.time.Instant timestamp) seconds-late (/ (- actual-millis target-millis) 1000.0)] ;; skip scheduled instants that were actually From 7086ef1c79465709f0861c6c524f520cdeb419a2 Mon Sep 17 00:00:00 2001 From: Martin Zuther Date: Mon, 13 Jul 2020 13:17:02 +0200 Subject: [PATCH 19/19] v1.2.0: add command line help and version display - add Leiningen debug profile - add a first unit test - improve console logging - return process object from "shell-exec" - place guards around IO operations - re-factor code - fix reflection warning --- CHANGELOG.md | 44 +++++++++++++++++++++++++++++++++++--------- project.clj | 2 +- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 504fb51..006e547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,36 +10,61 @@ file. This change log follows the conventions of +## [1.2.0] - 2020-07-13 +### Added + +- add command line help and version display + +- add Leiningen debug profile + +- add a first unit test + +### Changed + +- improve console logging + +- return process object from "shell-exec" + +- place guards around IO operations + +- re-factor code + +## Fixed + +- fix reflection warning + + + ## [1.1.0] - 2020-06-23 ### Added -* let users define tasks and do not limit the number of tasks +- let users define tasks and do not limit the number of tasks -* add complete documentation +- add complete documentation -* display an error message if the settings file cannot be opened or is +- display an error message if the settings file cannot be opened or is broken ### Changed -* completely change the structure of the settings file +- completely change the structure of the settings file -* do not skip the first state update when MoccaFaux is started +- do not skip the first state update when MoccaFaux is started -* drop the default settings +- drop the default settings -* rename the sample settings file +- rename the sample settings file ## Fixed -* kill the scheduler and end MoccaFaux if an exception is thrown +- kill the scheduler and end MoccaFaux if an exception is thrown ## [1.0.0] - 2020-06-20 ### Changed -* This is the first release. +- This is the first release. [keepachangelog.com]: http://keepachangelog.com/ @@ -47,3 +72,4 @@ file. This change log follows the conventions of [1.0.0]: https://github.com/mzuther/moccafaux/commits/v1.0.0 [1.1.0]: https://github.com/mzuther/moccafaux/commits/v1.1.0 +[1.2.0]: https://github.com/mzuther/moccafaux/commits/v1.2.0 diff --git a/project.clj b/project.clj index 7713a89..b3ab042 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject de.mzuther/moccafaux.core "1.1.4-SNAPSHOT" +(defproject de.mzuther/moccafaux.core "1.2.0" :description "Adapt power management to changes in the environment." :url "https://github.com/mzuther/moccafaux" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"