Redesign message format
authorSiraaj Khandkar <siraaj@khandkar.net>
Wed, 5 Sep 2018 20:48:23 +0000 (16:48 -0400)
committerSiraaj Khandkar <siraaj@khandkar.net>
Wed, 5 Sep 2018 20:48:23 +0000 (16:48 -0400)
- supports distributed khatus (adds node field)
- separates messages into types (data, error, alert, log)

16 files changed:
Makefile
README.md
bin/khatus
src/awk/exe/actuate_alert_to_notify_send.awk
src/awk/exe/actuate_device_add_to_automount.awk
src/awk/exe/actuate_status_bar_to_xsetroot_name.awk
src/awk/exe/bar.awk
src/awk/exe/monitor_devices.awk
src/awk/exe/monitor_energy.awk
src/awk/exe/monitor_errors.awk
src/awk/exe/parse_metar_d_output.awk
src/awk/lib/cache.awk
src/awk/lib/msg_in.awk
src/awk/lib/msg_out.awk
src/awk/lib/num.awk [moved from src/awk/lib/util.awk with 84% similarity]
src/awk/lib/str.awk [new file with mode: 0755]

index 5dbefc2..1f39234 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -24,9 +24,10 @@ AWK_EXECUTABLES := \
        bin/khatus_parse_upower
 
 define BUILD_AWK_EXE
-       echo '#! $(PATH_TO_AWK) -f' > $@ && \
-       echo 'BEGIN {Module = "$(notdir $@)"}' >> $@ && \
-       cat $^ >> $@ && \
+       echo '#! $(PATH_TO_AWK) -f'                                > $@ && \
+       echo 'BEGIN {Node   = Node ? Node : "$(shell hostname)"}' >> $@ && \
+       echo 'BEGIN {Module = "$(notdir $@)"}'                    >> $@ && \
+       cat $^                                                    >> $@ && \
        chmod +x $@
 endef
 
@@ -46,42 +47,49 @@ clean:
 bin/khatus_bar: \
        src/awk/exe/bar.awk \
        src/awk/lib/cache.awk \
+       src/awk/lib/str.awk \
        src/awk/lib/msg_in.awk \
        src/awk/lib/msg_out.awk \
-       src/awk/lib/util.awk
+       src/awk/lib/num.awk
        $(BUILD_AWK_EXE)
 
 bin/khatus_actuate_alert_to_notify_send: \
        src/awk/exe/actuate_alert_to_notify_send.awk \
+       src/awk/lib/str.awk \
        src/awk/lib/msg_in.awk
        $(BUILD_AWK_EXE)
 
 bin/khatus_actuate_device_add_to_automount: \
        src/awk/exe/actuate_device_add_to_automount.awk \
+       src/awk/lib/str.awk \
        src/awk/lib/msg_in.awk \
        src/awk/lib/msg_out.awk
        $(BUILD_AWK_EXE)
 
 bin/khatus_actuate_status_bar_to_xsetroot_name: \
        src/awk/exe/actuate_status_bar_to_xsetroot_name.awk \
+       src/awk/lib/str.awk \
        src/awk/lib/msg_in.awk
        $(BUILD_AWK_EXE)
 
 bin/khatus_monitor_devices: \
        src/awk/exe/monitor_devices.awk \
+       src/awk/lib/str.awk \
        src/awk/lib/msg_in.awk \
        src/awk/lib/msg_out.awk
        $(BUILD_AWK_EXE)
 
 bin/khatus_monitor_energy: \
        src/awk/exe/monitor_energy.awk \
+       src/awk/lib/str.awk \
        src/awk/lib/msg_in.awk \
        src/awk/lib/msg_out.awk \
-       src/awk/lib/util.awk
+       src/awk/lib/num.awk
        $(BUILD_AWK_EXE)
 
 bin/khatus_monitor_errors: \
        src/awk/exe/monitor_errors.awk \
+       src/awk/lib/str.awk \
        src/awk/lib/msg_in.awk \
        src/awk/lib/msg_out.awk
        $(BUILD_AWK_EXE)
@@ -124,7 +132,7 @@ bin/khatus_parse_loadavg_file: \
 bin/khatus_parse_metar_d_output: \
        src/awk/exe/parse_metar_d_output.awk \
        src/awk/lib/msg_out.awk \
-       src/awk/lib/util.awk
+       src/awk/lib/str.awk
        $(BUILD_AWK_EXE)
 
 bin/khatus_parse_mpd_status_currentsong: \
index 834d6ab..c02c1f8 100644 (file)
--- a/README.md
+++ b/README.md
@@ -258,7 +258,7 @@ Idea grab bag
     - report history and trends on when and how-often each
       device/category is plugged-in, how-long it stays plaugged-in, etc.
 - daemonize `khatus`, so we don't have to re-launch `X11` to re-launch `khatus`
-- interoperate with other khatus instances
+- interoperate with other khatus nodes
     - prefix machine ID to each data source
       (What should that ID be? Hostname? Pub key?)
     - fetch remote data and process locally
index 747fd32..ec8270f 100755 (executable)
@@ -36,18 +36,18 @@ run_producer() {
     2> >(
         while read line
         do
-            echo "ERROR${MSG_FS}${executable_name}${MSG_FS}$line" > "$pipe"
+            echo "${NODE}${MSG_FS}${executable_name}${MSG_FS}error${MSG_FS}$line" > "$pipe"
         done \
     ) \
     | while read line
         do
-            echo "OK${MSG_FS}${executable_name}${MSG_FS}$line" > "$pipe"
+            echo "${NODE}${MSG_FS}${executable_name}${MSG_FS}data${MSG_FS}$line" > "$pipe"
         done
     cmd_exit_code=${PIPESTATUS[0]}
     if [ "$cmd_exit_code" -ne 0 ]
     then
         echo
-            "ERROR${MSG_FS}${executable_name}${MSG_FS}NON_ZERO_EXIT_CODE${MSG_FS}$cmd_exit_code" \
+            "${NODE}${MSG_FS}${executable_name}${MSG_FS}error${MSG_FS}NON_ZERO_EXIT_CODE${MSG_FS}$cmd_exit_code" \
             > "$pipe"
     fi
 }
@@ -87,6 +87,7 @@ fork_poller() {
 
 main() {
     declare -A opts=(
+        ["--node"]=$(hostname)
         ["--dir_bin"]="$HOME/bin"
         ["--dir_perf_logs"]=''
         ["--file_pipe"]=$(mktemp)
@@ -160,6 +161,8 @@ main() {
         echo '----------------------------------------------'
     ) >&2
 
+    NODE="${opts['--node']}"
+
     screen_brightness_device_path='/sys/class/backlight'
     screen_brightness_device_path+="/${opts['--screen_brightness_device_name']}"
 
index 2cebbff..a49a723 100755 (executable)
@@ -6,31 +6,22 @@ BEGIN {
     Display = Display ? Display : ":0"
 }
 
-$1 == "OK" && \
-$3 ~ /^alert/ {
-    src      = $2
-    key      = $3
-    split(key, key_parts, Kfs)
-    priority = key_parts[2]
-    subject  = key_parts[3]
 
-    # Not just using $6 for body - because body might contain a character
-    # identical to FS
-    len_line = length($0)
-    len_head = length($1 FS $2 FS $3 FS $4 FS $5 FS)
-    len_body = len_line - len_head
-    body = substr($0, len_head + 1, len_body)
 
+$3 == "alert" {
+    delete msg
+    msg_in_parse(msg, $0)
+    body = msg["body"]
     sep = body ? "\n" : ""
-    body = body sep "--" src
-    urgency = priority
+    body = body sep "--" msg["node"] ":" msg["module"]
+    urgency = msg["priority"]
     sub("hi" , "critical", urgency)
     sub("med", "normal"  , urgency)
 
     cmd = \
         sprintf(\
             "DISPLAY=%s notify-send -u %s %s \" %s\"",
-            Display, urgency, subject, body \
+            Display, urgency, msg["subject"], body \
         )
     system(cmd)
     next
index d1a56aa..9510720 100755 (executable)
@@ -3,11 +3,12 @@ BEGIN {
     Execute_On_Mount = Execute_On_Mount ? Execute_On_Mount : ""
 }
 
-$1 == "OK" && \
+$1 == Node && \
 $2 == "khatus_sensor_devices" && \
-$3 == "add" && \
-$4 ~ /[0-9]$/ {
-    mount_device($4)
+$3 == "data" && \
+$4 == "add" && \
+$5 ~ /[0-9]$/ {
+    mount_device($5)
 }
 
 function mount_device(path,    cmd, line, lines, line_count, status, i,
@@ -28,15 +29,15 @@ function mount_device(path,    cmd, line, lines, line_count, status, i,
                 path_mnt=line
                 sub("^Mounted " path_dev " at ", "", path_mnt)
                 sub("\.$", "", path_mnt)
-                msg_out_ok_alert("low", "successfully-mounted", path_dev " to " path_mnt)
+                msg_out_alert_low("successfully-mounted", path_dev " to " path_mnt)
                 if (Execute_On_Mount) {
                     system(Execute_On_Mount " '" path_mnt "'")
                 }
             } else {
-                msg_out_ok_alert("hi", "unexpected-success-line", line)
+                msg_out_alert_hi("unexpected-success-line", line)
             }
         }
     } else {
-        msg_out_ok_alert("hi", "failed-to-mount-device", path)
+        msg_out_alert_hi("failed-to-mount-device", path)
     }
 }
index b97538b..6f87344 100755 (executable)
@@ -1,12 +1,8 @@
-$1 == "OK" && \
+$1 == Node && \
 $2 == "khatus_bar" && \
 $3 == "status_bar" {
-    # Not just using $4 for val - because val might contain a character
-    # identical to FS
-    len_line = length($0)
-    len_head = length($1 FS $2 FS $3 FS)
-    len_val  = len_line - len_head
-    val = substr($0, len_head + 1, len_val)
-    system("xsetroot -name \"" val "\"")
+    delete msg
+    msg_in_parse(msg, $0)
+    system("xsetroot -name \"" msg["status_bar"] "\"")
     next
 }
index 9be1adb..43126ff 100755 (executable)
 # -----------------------------------------------------------------------------
 # Input
 # -----------------------------------------------------------------------------
-$1 == "OK" {
-    cache_update()
+$3 == "data" {
+    delete msg
+    msg_in_parse(msg, $0)
+    cache_update(msg["node"], msg["module"], msg["key"], msg["val"])
 }
 
-$1 == "OK" && \
-$2 == "khatus_sensor_datetime" {
+$1 == Node && \
+$2 == "khatus_sensor_datetime" && \
+$3 == "data" {
     # Code for bar_make_status is expected to be passed as an
     # additional source file, using  -f  flag.
-    msg_out_ok("status_bar", bar_make_status())
+    msg_out_status_bar(bar_make_status())
 }
 
-
 # -----------------------------------------------------------------------------
 # Energy
 # -----------------------------------------------------------------------------
@@ -54,7 +56,7 @@ function bar_make_status_mem_percent(    total, used, percent, percent_str) {
         !used["is_expired"] && \
         total["value"] \
         ) {
-        percent = util_round((used["value"] / total["value"]) * 100)
+        percent = num_round((used["value"] / total["value"]) * 100)
         percent_str = sprintf("%d", percent)
     } else {
         percent_str = "__"
@@ -198,7 +200,7 @@ function bar_make_status_volume_pulseaudio_sink(sink,    mu, vl, vr, show) {
              if (mu["value"] == "yes") {show = "X"}
         else if (mu["value"] == "no")  {show = vl["value"] " " vr["value"]}
         else {
-            msg_out_error(\
+            msg_out_log_error(\
                 "bar_make_status_volume_pulseaudio_sink: " sink ". ", \
                 "Unexpected value for 'mute' field: " mu["value"] \
             )
@@ -221,7 +223,7 @@ function bar_make_status_mpd(    state, status) {
         } else if (state["value"] == "stop") {
             status = bar_make_status_mpd_state_known("⬛")
         } else {
-            msg_out_error(\
+            msg_out_log_error(\
                 "bar_make_status_mpd", \
                 "Unexpected value for 'state' field: " state["value"] \
             )
index d43f67c..efb7b79 100755 (executable)
@@ -1,5 +1,10 @@
-$1 == "OK" && \
-$2 == "khatus_sensor_devices" \
+$2 == "khatus_sensor_devices" && \
+$3 == "data" \
 {
-    msg_out_ok_alert("low", "BlockDeviceEvent", $3 " " $4)
+    delete msg
+    msg_in_parse(msg, $0)
+    msg_out_alert_low( \
+        "BlockDeviceEvent",
+        msg["key"] " " msg["val"] " on " msg["node"]\
+    )
 }
index 0d3f339..518d687 100755 (executable)
@@ -8,37 +8,40 @@ BEGIN {
     bat_alert_spec[5]  = "hi|Energy_CRITICALLY_Low|CHARGE NOW!!! GO GO GO!!!"
 }
 
-$1 == "OK" && \
+$1 == Node && \
 $2 == "khatus_sensor_energy" && \
-$3 == "line_power" {
+$3 == "data" && \
+$4 == "line_power" {
     line_power_prev = line_power_curr
-    line_power_curr = $4
+    line_power_curr = $5
     if (line_power_curr == "no" && line_power_prev != "no") {
-        msg_out_ok_alert("low", "PowerUnplugged", "")
+        msg_out_alert_low("PowerUnplugged", "")
     }
 }
 
-$1 == "OK" && \
+$1 == Node && \
 $2 == "khatus_sensor_energy" && \
-$3 == "battery_state" {
+$3 == "data" && \
+$4 == "battery_state" {
     battery_state_prev = battery_state_curr
-    battery_state_curr = $4
+    battery_state_curr = $5
 }
 
-$1 == "OK" && \
+$1 == Node && \
 $2 == "khatus_sensor_energy" && \
-$3 == "battery_percentage" {
+$3 == "data" && \
+$4 == "battery_percentage" {
     # TODO: Re-think the spec - can't rely on order of keys
-    battery_percentage = util_ensure_numeric($4)
+    battery_percentage = num_ensure_numeric($5)
     if (battery_state_curr == "discharging") {
         for (threshold in bat_alert_spec) {
-            threshold = util_ensure_numeric(threshold)
+            threshold = num_ensure_numeric(threshold)
             if (battery_percentage <= threshold && !_alerted[threshold]) {
-                split(bat_alert_spec[threshold], msg, "|")
-                priority = msg[1]
-                subject = msg[2]
-                body = sprintf("%d%% %s", battery_percentage, msg[3])
-                msg_out_ok_alert(priority, subject, body)
+                split(bat_alert_spec[threshold], alert, "|")
+                priority = alert[1]
+                subject = alert[2]
+                body = sprintf("%d%% %s", battery_percentage, alert[3])
+                msg_out_alert(priority, subject, body)
                 _alerted[threshold]++
             }
         }
index 13130cc..36fd534 100755 (executable)
@@ -1,10 +1,6 @@
-/^ERROR/ {
-    src = $2
-    # Not just using $3 for body - because body might contain a character
-    # identical to FS
-    len_line = length($0)
-    len_head = length($1 FS $2 FS)
-    len_body = len_line - len_head
-    body = substr($0, len_head + 1, len_body)
-    msg_out_ok_alert("hi", "ERROR_IN_" src, body)
+$3 == "error" {
+    delete msg
+    msg_in_parse(msg, $0)
+    subject = "ERROR_IN_" msg["node"] ":" msg["module"]
+    msg_out_alert_hi(subject, msg["line"])
 }
index fa354da..82d2e29 100755 (executable)
@@ -8,15 +8,15 @@
 
 /[A-z][a-z]+ *: / {
     split($0, line, ":")
-    key = util_strip(line[1])
-    val = util_strip(line[2])
+    key = str_strip(line[1])
+    val = str_strip(line[2])
     values[NR] = val
     first[key] = NR
     last[key] = NR
 }
 
 /^ +/ {
-    values[NR] = util_strip($0)
+    values[NR] = str_strip($0)
     last[key] = NR
 }
 
index fbdd122..a2dd879 100755 (executable)
@@ -31,86 +31,79 @@ BEGIN {
     _scale["khatus_sensor_net_addr_io", "bytes_read"   ] = 1 / _bytes_per_mb
 }
 
-function cache_update(    src, key, val, len_line, len_head, len_val, time) {
-    src = $2
-    key = $3
-    # Not just using $4 for val - because an unstructured value (like name of a
-    # song) might contain a character identical to FS
-    len_line = length($0)
-    len_head = length($1 FS $2 FS $3 FS)
-    len_val  = len_line - len_head
-    val = substr($0, len_head + 1, len_val)
-    val = cache_maybe_total_to_diff(src, key, val)
-    val = cache_maybe_scale(src, key, val)
-    _cache[src, key] = val
+function cache_update(node, module, key, val,    time) {
+    # TODO: Use node value
+    val = cache_maybe_total_to_diff(module, key, val)
+    val = cache_maybe_scale(module, key, val)
+    _cache[module, key] = val
     time = cache_get_time()
-    _cache_mtime[src, key] = time
+    _cache_mtime[module, key] = time
     if (time % GC_Interval == 0) {
         cache_gc()
     }
 }
 
-function cache_get(result, src, key, ttl,    time, age, is_expired) {
+function cache_get(result, module, key, ttl,    time, age, is_expired) {
     time = cache_get_time()
-    _cache_atime[src, key] = time
-    age = time - _cache_mtime[src, key]
+    _cache_atime[module, key] = time
+    age = time - _cache_mtime[module, key]
     result["is_expired"] = ttl && age > ttl  # ttl = 0 => forever
-    result["value"] = _cache[src, key]
+    result["value"] = _cache[module, key]
 }
 
 function cache_res_fmt_or_def(result, format, default) {
     return result["is_expired"] ? default : sprintf(format, result["value"])
 }
 
-function cache_get_fmt_def(src, key, ttl, format, default,    result) {
+function cache_get_fmt_def(module, key, ttl, format, default,    result) {
     default = default ? default : "--"
-    cache_get(result, src, key, ttl)
+    cache_get(result, module, key, ttl)
     return cache_res_fmt_or_def(result, format, default)
 }
 
-function cache_get_time(    src, key, time) {
-    src = "khatus_sensor_datetime"
+function cache_get_time(    module, key, time) {
+    module = "khatus_sensor_datetime"
     key = "epoch"
-    time = _cache[src, key]
-    _cache_atime[src, key] = time
+    time = _cache[module, key]
+    _cache_atime[module, key] = time
     return time
 }
 
-function cache_gc(    src_and_key, parts, src, key, unused_for) {
-    for (src_and_key in _cache) {
-        split(src_and_key, parts, SUBSEP)
-        src = parts[1]
+function cache_gc(    module_and_key, parts, module, key, unused_for) {
+    for (module_and_key in _cache) {
+        split(module_and_key, parts, SUBSEP)
+        module = parts[1]
         key = parts[2]
-        val = _cache[src, key]
-        unused_for = cache_get_time() - _cache_atime[src, key]
+        val = _cache[module, key]
+        unused_for = cache_get_time() - _cache_atime[module, key]
         if (unused_for > GC_Interval) {
-            msg_out_info(\
+            msg_out_log_info(\
                 "cache_gc",
                 sprintf(\
-                    "Deleting unused data SRC=%s KEY=%s VAL=%s",
-                    src, key, val\
+                    "Deleting unused data MODULE=%s KEY=%s VAL=%s",
+                    module, key, val\
                 ) \
             )
-            delete _cache[src, key]
+            delete _cache[module, key]
         }
     }
 }
 
-function cache_maybe_total_to_diff(src, key, val,    key_parts) {
+function cache_maybe_total_to_diff(module, key, val,    key_parts) {
     split(key, key_parts, Kfs)
-    if (_total_to_diff[src, key_parts[1]]) {
-        _prev[src, key] = _curr[src, key]
-        _curr[src, key] = val
-        return (_curr[src, key] - _prev[src, key])
+    if (_total_to_diff[module, key_parts[1]]) {
+        _prev[module, key] = _curr[module, key]
+        _curr[module, key] = val
+        return (_curr[module, key] - _prev[module, key])
     } else {
         return val
     }
 }
 
-function cache_maybe_scale(src, key, val,    key_parts) {
+function cache_maybe_scale(module, key, val,    key_parts) {
     split(key, key_parts, Kfs)
-    if ((src SUBSEP key_parts[1]) in _scale) {
-        return val * _scale[src, key_parts[1]]
+    if ((module SUBSEP key_parts[1]) in _scale) {
+        return val * _scale[module, key_parts[1]]
     } else {
         return val
     }
index d1f62d6..cda89e8 100755 (executable)
@@ -1,4 +1,38 @@
 BEGIN {
-     FS = Msg_fs ? Msg_fs : "|"
-    Kfs = Key_fs ? Key_fs : ":"
+    FS1 = "|"  # Fiels separator, level 1 (record to fields)
+    FS2 = ":"  # Fiels separator, level 2 (field to subfields)
+
+     FS = FS1
+    Kfs = FS2
+}
+
+function msg_in_parse(msg, line,    fields, type) {
+    split(line, fields, FS1)
+    msg["node"]   = fields[1]
+    msg["module"] = fields[2]
+    type          = fields[3]
+    msg["type"]   = type
+
+    if (type == "data") {
+        msg["key"] = fields[4]
+        msg["val"] = str_tail(str_join(fields, 1, 4, FS1) FS1, line)
+    } else if (type == "error") {
+        msg["line"] = str_tail(str_join(fields, 1, 3, FS1) FS1, line)
+    } else if (type == "alert") {
+        msg["priority"] = fields[4]
+        msg["subject"]  = fields[5]
+        msg["body"]     = str_tail(str_join(fields, 1, 5, FS1) FS1, line)
+    } else if (type == "log") {
+        msg["location"] = fields[4]
+        msg["level"]    = fields[5]
+        msg["msg"]      = str_tail(str_join(fields, 1, 5, FS1) FS1, line)
+    } else if (type == "status_bar") {
+        msg["status_bar"] = str_tail(str_join(fields, 1, 3, FS1) FS1, line)
+    } else {
+        msg_out_log_error(\
+            "msg_in_parse",
+            "Unexpected msg type: " type " in given input line: " line \
+        )
+        exit 1
+    }
 }
index 6f5f945..24393c6 100755 (executable)
@@ -1,29 +1,58 @@
 BEGIN {
-    OFS = Msg_fs ? Msg_fs : "|"
-    Kfs = Key_fs ? Key_fs : ":"
+    FS1 = "|"  # Fiels separator, level 1 (record to fields)
+    FS2 = ":"  # Fiels separator, level 2 (field to subfields
+
+    OFS = FS1
+    Kfs = FS2
+}
+
+# -----------------------------------------------------------------------------
+# alert
+# -----------------------------------------------------------------------------
+function msg_out_alert_low(subject, body) {
+    msg_out_alert("low", subject, body)
+}
+
+function msg_out_alert_med(subject, body) {
+    msg_out_alert("med", subject, body)
 }
 
-function msg_out_ok_alert(priority, subject, body,    key, val) {
+function msg_out_alert_hi(subject, body) {
+    msg_out_alert("hi", subject, body)
+}
+
+function msg_out_alert(priority, subject, body) {
     # priority : "low" | "med" | "hi"
     # subject  : string without spaces
     # body     : anything
-    key = "alert" Kfs priority Kfs subject
-    val = body
-    msg_out_ok(key, val)
+    print(Node, Module, "alert", priority, subject, body)
+}
+
+# -----------------------------------------------------------------------------
+# log
+# -----------------------------------------------------------------------------
+function msg_out_log_info(location, msg) {
+    msg_out_log("info", location, msg)
 }
 
-function msg_out_ok(key, val) {
-    msg_out("OK", key, val, "/dev/stdout")
+function msg_out_log_error(location, msg) {
+    msg_out_log("error", location, msg)
 }
 
-function msg_out_info(location, msg) {
-    msg_out("INFO", location, msg, "/dev/stderr")
+function msg_out_log(level, location, msg) {
+    print(Node, Module, "log", location, level, msg) > "/dev/stderr"
 }
 
-function msg_out_error(location, msg) {
-    msg_out("ERROR", location, msg, "/dev/stderr")
+# -----------------------------------------------------------------------------
+# status_bar
+# -----------------------------------------------------------------------------
+function msg_out_status_bar(bar) {
+    print(Node, Module, "status_bar", bar)
 }
 
-function msg_out(status, key, val, channel) {
-    print(status, Module, key, val) > channel
+# -----------------------------------------------------------------------------
+# data
+# -----------------------------------------------------------------------------
+function msg_out_data(key, val) {
+    print(Node, Module, "data", key, val)
 }
similarity index 84%
rename from src/awk/lib/util.awk
rename to src/awk/lib/num.awk
index b7a92e6..4c22f9d 100755 (executable)
@@ -1,20 +1,14 @@
-function util_strip(s) {
-    sub("^ *", "", s)
-    sub(" *$", "", s)
-    return s
-}
-
-function util_round(n) {
+function num_round(n) {
     return int(n + 0.5)
 }
 
-function util_ensure_numeric(n) {
+function num_ensure_numeric(n) {
     return n + 0
 }
 
-#------------------------------------
-# Why do we need util_ensure_numeric?
-#------------------------------------
+#-----------------------------------
+# Why do we need num_ensure_numeric?
+#-----------------------------------
 # awk appears to be guessing the type of an inputted scalar based on usage, so
 # if we read-in a number, but did not use it in any numeric operations, but did
 # use as a string (even in just a format string!) - it will be treated as a
diff --git a/src/awk/lib/str.awk b/src/awk/lib/str.awk
new file mode 100755 (executable)
index 0000000..422ff66
--- /dev/null
@@ -0,0 +1,23 @@
+function str_join(array, from, to, sep_given,    str, sep, i) {
+    str = ""
+    sep = ""
+    for (i=from; i<=to; i++) {
+        str = str sep array[i]
+        sep = sep_given
+    }
+    return str
+}
+
+function str_tail(head, full,    tail, len_tail, len_head, len_full) {
+    len_full = length(full)
+    len_head = length(head)
+    len_tail = len_full - len_head
+    tail = substr(full, len_head + 1, len_tail)
+    return tail
+}
+
+function str_strip(s) {
+    sub("^ *", "", s)
+    sub(" *$", "", s)
+    return s
+}
This page took 0.045054 seconds and 4 git commands to generate.