include cfg.sh,stale_config.sh in fill_config.py

Staleness check now also checks for included files.
fill_config.py figures out last used paths itself and has cmdline doc.
diff --git a/net/README b/net/README
index a7b344f..c24990b 100644
--- a/net/README
+++ b/net/README
@@ -16,7 +16,7 @@
 
 mkdir my_network
 cd my_network
-../cfg.sh ../config_mine ../tmpl_std
+../fill_config.py ../config_mine ../tmpl_std
 
 ./run.sh
 # Launches numerous x-terminals with one component running in each.
@@ -30,13 +30,13 @@
 
 # tweak config?
 $EDITOR ../config_mine
-../cfg.sh
+../fill_config.py
 # picks up same ../config_mine and ../tmpl_std from last time
 
 # own templates?
 cp -r ../tmpl_std ../tmpl_mine
 $EDITOR ../tmpl_mine/*
-../cfg.sh ../tmpl_mine
+../fill_config.py ../tmpl_mine
 # picks up same ../config_mine from last time, and ../tmpl_mine from cmdline
 
 
@@ -44,13 +44,13 @@
 === Config file templates
 
 A *directory* contains template files that are filled with specific values by the
-cfg.sh script (aided by fill_config.py). See e.g. tmpl_std/.
+fill_config.py script. See e.g. tmpl_std/.
 
 A *file* contains local config items that are put into the templates. See e.g.
 config_2g3g.
 
-The cfg.sh script helps to fill the templates with the config values. Simply
-invoke cfg.sh with a dir argument (templates dir) and a file argument (specific
+The fill_config.py script helps to fill the templates with the config values. Simply
+invoke fill_config.py with a dir argument (templates dir) and a file argument (specific
 config values).
 
 If one or both are omitted, the script tries to re-use the most recent paths,
diff --git a/net/cfg.sh b/net/cfg.sh
deleted file mode 100755
index 4dda0af..0000000
--- a/net/cfg.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-config_file=""
-tmpl_dir=""
-
-while test -n "$1"; do
-  arg="$1"
-  shift
-
-  if [ ! -e "$arg" ]; then
-    if [ -e "../$arg"]; then
-      arg="../$arg";
-    fi
-  fi
-
-  if [ -f "$arg" ]; then
-    if [ -n "$config_file" ]; then
-      echo "Error: more than one config file: '$config_file' and '$arg'"
-      exit 2
-    fi
-    config_file="$arg"
-  fi
-
-  if [ -d "$arg" ]; then
-    if [ -n "$tmpl_dir" ]; then
-      echo "Error: more than one template dir: '$tmpl_dir' and '$arg'"
-      exit 2
-    fi
-    tmpl_dir="$arg"
-  fi
-done
-
-if [ -z "$config_file" ]; then
-  config_file="$(cat .last_config)"
-fi
-
-if [ -z "$tmpl_dir" ]; then
-  tmpl_dir="$(cat .last_templates)"
-fi
-
-set -e
-../fill_config.py "$config_file" "$tmpl_dir"
-
-echo "$config_file" > .last_config
-echo "$tmpl_dir" > .last_templates
diff --git a/net/fill_config.py b/net/fill_config.py
index 24ce303..09b515f 100755
--- a/net/fill_config.py
+++ b/net/fill_config.py
@@ -1,21 +1,63 @@
 #!/usr/bin/env python3
+'''Take values from a config file and fill them into a set of templates.
+Write the result to the current directory.'''
 
 import os, sys, re, shutil
+import argparse
 
-def get_arg(nr, default):
-  if len(sys.argv) > nr:
-    return sys.argv[nr]
-  return default
+def file_newer(path_a, than_path_b):
+  return os.path.getmtime(path_a) > os.path.getmtime(than_path_b)
 
-local_config_file = os.path.realpath(get_arg(1, 'local_config'))
-tmpl_dir = get_arg(2, 'tmpl')
+LAST_LOCAL_CONFIG_FILE = '.last_config'
+LAST_TMPL_DIR = '.last_templates'
 
-if not os.path.isdir(tmpl_dir):
+parser = argparse.ArgumentParser(description=__doc__,
+                                 formatter_class=argparse.RawDescriptionHelpFormatter)
+parser.add_argument('sources', metavar='SRC', nargs='*',
+                    help='Pass both a template directory and a config file.')
+parser.add_argument('-s', '--check-stale', dest='check_stale', action='store_true',
+                    help='only verify age of generated files vs. config and templates.'
+                    ' Exit nonzero when any source file is newer. Do not write anything.')
+
+args = parser.parse_args()
+
+local_config_file = None
+tmpl_dir = None
+
+for src in args.sources:
+  if os.path.isdir(src):
+    if tmpl_dir is not None:
+      print('Error: only one template dir permitted. (%r vs. %r)' % (tmpl_dir, src))
+    tmpl_dir = src
+  elif os.path.isfile(src):
+    if local_config_file is not None:
+      print('Error: only one config file permitted. (%r vs. %r)' % (local_config_file, src))
+    local_config_file = src
+
+if local_config_file is None and os.path.isfile(LAST_LOCAL_CONFIG_FILE):
+  local_config_file = open(LAST_LOCAL_CONFIG_FILE).read().strip()
+
+if tmpl_dir is None and os.path.isfile(LAST_TMPL_DIR):
+  tmpl_dir = open(LAST_TMPL_DIR).read().strip()
+
+if not tmpl_dir or not os.path.isdir(tmpl_dir):
   print("Template dir does not exist: %r" % tmpl_dir)
   exit(1)
 
+if not local_config_file or not os.path.isfile(local_config_file):
+  print("No such config file: %r" % tmpl_dir)
+  exit(1)
+
+local_config_file = os.path.realpath(local_config_file)
+tmpl_dir = os.path.realpath(tmpl_dir)
+
 print('using config file %r\non templates %r' % (local_config_file, tmpl_dir))
 
+with open(LAST_LOCAL_CONFIG_FILE, 'w') as last_file:
+  last_file.write(local_config_file)
+with open(LAST_TMPL_DIR, 'w') as last_file:
+  last_file.write(tmpl_dir)
+
 # read in variable values from config file
 local_config = {}
 
@@ -23,6 +65,10 @@
 for line in open(local_config_file):
   line_nr += 1
   line = line.strip('\n')
+
+  if line.startswith('#'):
+    continue
+
   if not '=' in line:
     if line:
       print("Error: %r line %d: %r" % (local_config_file, line_nr, line))
@@ -40,18 +86,30 @@
     print("Error: duplicate identifier in %r line %d: %r" % (local_config_file, line_nr, line))
   local_config[name] = val
 
-print('config:\n\n' + '\n'.join('%s=%r' % (n,v) for n,v in local_config.items()))
-
 # replace variable names with above values recursively
 replace_re = re.compile('\$\{([A-Za-z0-9_]*)\}')
 command_re = re.compile('\$\{([A-Za-z0-9_]*)\(([^)]*)\)\}')
 
 idx = 0
 
+def check_stale(src_path, target_path):
+  if file_newer(src_path, target_path):
+    print('Stale: %r is newer than %r' % (src_path, target_path))
+    exit(1)
+
 for tmpl_name in sorted(os.listdir(tmpl_dir)):
+
+  # omit "hidden" files
+  if tmpl_name.startswith('.'):
+    continue
+
   tmpl_src = os.path.join(tmpl_dir, tmpl_name)
   dst = tmpl_name
 
+  if args.check_stale:
+    check_stale(local_config_file, dst)
+    check_stale(tmpl_src, dst)
+
   local_config['_fname'] = tmpl_name
   local_config['_name'] = os.path.splitext(tmpl_name)[0]
   local_config['_idx0'] = str(idx)
@@ -79,6 +137,8 @@
         except:
           print('Cannot read %r for %r' % (include_path, tmpl_src))
           raise
+        if args.check_stale:
+          check_stale(include_path, dst)
         result = result.replace('${%s(%s)}' % (cmd, arg), incl)
       else:
         print('Error: unknown command: %r in %r' % (cmd, tmpl_src))
@@ -97,9 +157,9 @@
     for var in used_vars:
       result = result.replace('${%s}' % var, local_config.get(var))
 
-  with open(dst, 'w') as dst_file:
-    dst_file.write(result)
-
-  shutil.copymode(tmpl_src, dst)
+  if not args.check_stale:
+    with open(dst, 'w') as dst_file:
+      dst_file.write(result)
+    shutil.copymode(tmpl_src, dst)
 
 # vim: ts=2 sw=2 expandtab
diff --git a/net/stale_config.sh b/net/stale_config.sh
deleted file mode 100755
index f849ce1..0000000
--- a/net/stale_config.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/sh
-
-stale="0"
-for f in *.cfg; do
-  f="$(basename "$f")"
-  for g in $(find . -maxdepth 2 -name "$f" -or -name "common_logging") ; do
-    if [ "$f" -ot "$g" ]; then
-      stale="1"
-      echo "$f older than $g"
-    fi
-  done
-done
-
-if [ "$stale" = "1" ]; then
-  echo "Stale configs. Hit enter to continue anyway."
-  read ok_to_continue
-fi
diff --git a/net/tmpl_std/run.sh b/net/tmpl_std/run.sh
index d9ec53d..8d71804 100755
--- a/net/tmpl_std/run.sh
+++ b/net/tmpl_std/run.sh
@@ -1,5 +1,5 @@
 #!/usr/bin/env bash
-../stale_config.sh
+../fill_config.py --check-stale || ( echo "STALE CONFIGS. Hit enter to continue anyway."; read enter_to_continue )
 
 dev="${ETH_DEV}"
 ip2="${PUBLIC_IP2}"