diff --git a/shell/modules/completion.py b/shell/modules/completion.py
index 67a69109e1..c8b13371ba 100644
--- a/shell/modules/completion.py
+++ b/shell/modules/completion.py
@@ -1,490 +1,478 @@
 # Copyright (C) 2008 Dejan Muhamedagic <dmuhamedagic@suse.de>
 # 
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public
 # License as published by the Free Software Foundation; either
 # version 2 of the License, or (at your option) any later version.
 # 
 # This software is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
 # You should have received a copy of the GNU General Public
 # License along with this library; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 #
 
 import os
 import time
 import copy
 import readline
 
 from cibconfig import CibFactory
 from cibstatus import CibStatus
 from levels import Levels
 from ra import *
 from vars import Vars
 from utils import *
 from xmlutil import *
 
 class CompletionHelp(object):
     '''
     Print some help on whatever last word in the line.
     '''
     timeout = 60  # don't print again and again
     def __init__(self):
         self.laststamp = 0
         self.lastitem = ''
     def help(self,f,*args):
         words = readline.get_line_buffer().split()
         if not words:
             return
         key = words[-1]
         if key.endswith('='):
             key = key[0:-1]
         if self.lastitem == key and \
                 time.time() - self.laststamp < self.timeout:
             return
         help_s = f(key,*args)
         if help_s:
             print "\n%s" % help_s
             print "%s%s" % (vars.prompt,readline.get_line_buffer()),
             self.laststamp = time.time()
             self.lastitem = key
 
 def attr_cmds(idx,delimiter = False):
     if delimiter:
         return ' '
     return ["delete","set","show"]
 def nodes_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return listnodes()
 def shadows_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return listshadows()
-def listtemplates():
-    l = []
-    for f in os.listdir(vars.tmpl_dir):
-        if os.path.isfile("%s/%s" % (vars.tmpl_dir,f)):
-            l.append(f)
-    return l
-def listconfigs():
-    l = []
-    for f in os.listdir(vars.tmpl_conf_dir):
-        if os.path.isfile("%s/%s" % (vars.tmpl_conf_dir,f)):
-            l.append(f)
-    return l
 def templates_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return listtemplates()
 def config_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return listconfigs()
 def config_list_method(idx,delimiter = False):
     if delimiter:
         return ' '
     return listconfigs() + ["replace","update"]
 def shadows_live_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return listshadows() + ['live']
 def rsc_list(idx,delimiter = False):
     if delimiter:
         return ' '
     doc = resources_xml()
     if not doc:
         return []
     nodes = get_interesting_nodes(doc,[])
     return [x.getAttribute("id") for x in nodes if is_resource(x)]
 def null_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return []
 def loop(idx,delimiter = False):
     "just a marker in a list"
     pass
 def id_xml_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_factory.id_list() + ['xml','changed']
 def id_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_factory.id_list()
 def f_prim_id_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_factory.f_prim_id_list()
 def f_children_id_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_factory.f_children_id_list()
 def rsc_id_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_factory.rsc_id_list()
 def node_id_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_factory.node_id_list()
 def node_attr_keyw_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return vars.node_attributes_keyw
 def status_node_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_status.status_node_list()
 def status_rsc_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return cib_status.status_rsc_list()
 def node_states_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return vars.node_states
 def ra_operations_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return vars.ra_operations
 def lrm_exit_codes_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return vars.lrm_exit_codes.keys()
 def lrm_status_codes_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return vars.lrm_status_codes.keys()
 def skills_list(idx,delimiter = False):
     if delimiter:
         return ' '
     return user_prefs.skill_levels.keys()
 def ra_classes_list(idx,delimiter = False):
     if delimiter:
         return ':'
     return ra_classes()
 
 #
 # completion for primitives including help for parameters
 # (help also available for properties)
 #
 def get_primitive_type(words):
     try:
         idx = words.index("primitive") + 2
         type_word = words[idx]
     except: type_word = ''
     return type_word
 def ra_type_list(toks,idx,delimiter):
     if idx == 2:
         if toks[0] == "ocf":
             dchar = ':'
             l = ra_providers_all()
         else:
             dchar = ' '
             l = ra_types(toks[0])
     elif idx == 3:
         dchar = ' '
         if toks[0] == "ocf":
             l = ra_types(toks[0],toks[1])
         else:
             l = ra_types(toks[0])
     if delimiter:
         return dchar
     return l
 def prim_meta_attr_list(idx,delimiter = False):
     if delimiter:
         return '='
     return vars.rsc_meta_attributes
 def op_attr_list(idx,delimiter = False):
     if delimiter:
         return '='
     return vars.op_attributes
 def operations_list():
     return vars.op_cli_names
 def prim_complete_meta(ra,delimiter = False):
     if delimiter:
         return '='
     return prim_meta_attr_list(0,delimiter)
 def prim_complete_op(ra,delimiter):
     words = split_buffer()
     if (readline.get_line_buffer()[-1] == ' ' and words[-1] == "op") \
             or (readline.get_line_buffer()[-1] != ' ' and words[-2] == "op"):
         dchar = ' '
         l = operations_list()
     else:
         if readline.get_line_buffer()[-1] == '=':
             dchar = ' '
             l = []
         else:
             dchar = '='
             l = op_attr_list()
     if delimiter:
         return dchar
     return l
 def prim_complete_params(ra,delimiter):
     if readline.get_line_buffer()[-1] == '=':
         dchar = ' '
         l = []
     else:
         dchar = '='
         l = ra.completion_params()
     if delimiter:
         return dchar
     return l
 def prim_params_info(key,ra):
     return ra.meta_parameter(key)
 def meta_attr_info(key,ra):
     pass
 def op_attr_info(key,ra):
     pass
 def get_lastkeyw(words,keyw):
     revwords = copy.copy(words)
     revwords.reverse()
     for w in revwords:
         if w in keyw:
             return w
 def primitive_complete_complex(idx,delimiter = False):
     '''
     This completer depends on the content of the line, i.e. on
     previous tokens, in particular on the type of the RA.
     '''
     completers_set = {
         "params": (prim_complete_params, prim_params_info),
         "meta": (prim_complete_meta, meta_attr_info),
         "op": (prim_complete_op, op_attr_info),
     }
     # manage the resource type
     words = readline.get_line_buffer().split()
     type_word = get_primitive_type(words)
     toks = type_word.split(':')
     if toks[0] != "ocf":
         idx += 1
     if idx in (2,3):
         return ra_type_list(toks,idx,delimiter)
     # create an ra object
     ra = None
     ra_class,provider,rsc_type = disambiguate_ra_type(type_word)
     if ra_type_validate(type_word,ra_class,provider,rsc_type):
         ra = RAInfo(ra_class,rsc_type,provider)
     keywords = completers_set.keys()
     if idx == 4:
         if delimiter:
             return ' '
         return keywords
     lastkeyw = get_lastkeyw(words,keywords)
     if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ':
         if not delimiter and lastkeyw and \
                 readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1:
             compl_help.help(completers_set[lastkeyw][1],ra)
         if delimiter:
             return ' '
         return ['*']
     else:
         if lastkeyw:
             return completers_set[lastkeyw][0](ra,delimiter)
 def property_complete(idx,delimiter = False):
     '''
     This completer depends on the content of the line, i.e. on
     previous tokens.
     '''
     ra = get_properties_meta()
     words = readline.get_line_buffer().split()
     if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ':
         if not delimiter and \
                 readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1:
             compl_help.help(prim_params_info,ra)
         if delimiter:
             return ' '
         return ['*']
     else:
         return prim_complete_params(ra,delimiter)
 
 #
 # core completer stuff
 #
 def lookup_dynamic(fun_list,idx,f_idx,words):
     if not fun_list:
         return []
     if fun_list[f_idx] == loop:
         f_idx -= 1
     f = fun_list[f_idx]
     w = words[0]
     wordlist = f(idx)
     delimiter = f(idx,1)
     if len(wordlist) == 1 and wordlist[0] == '*':
         return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:])
     elif len(words) == 1:
         return [x+delimiter for x in wordlist if x.startswith(w)]
     return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:])
 def lookup_words(ctab,words):
     if not ctab:
         return []
     if type(ctab) == type(()):
         return lookup_dynamic(ctab,0,0,words)
     if len(words) == 1:
         return [x+' ' for x in ctab if x.startswith(words[0])]
     elif words[0] in ctab.keys():
         return lookup_words(ctab[words[0]],words[1:])
     return []
 def split_buffer():
     p = readline.get_line_buffer()
     p = p.replace(':',' ').replace('=',' ')
     return p.split()
 
 def completer(txt,state):
     levels = Levels.getInstance()
     words = split_buffer()
     if readline.get_begidx() == readline.get_endidx():
         words.append('')
     matched = lookup_words(levels.completion_tab,words)
     matched.append(None)
     return matched[state]
 def setup_readline():
     readline.set_history_length(100)
     readline.parse_and_bind("tab: complete")
     readline.set_completer(completer)
     readline.set_completer_delims(\
         readline.get_completer_delims().replace('-','').replace('/','').replace('=',''))
     try: readline.read_history_file(vars.hist_file)
     except: pass
 
 #
 # a dict of completer functions
 # (feel free to add more completers)
 #
 completer_lists = {
     "options" : {
         "skill-level" : (skills_list,),
         "editor" : None,
         "pager" : None,
         "user" : None,
         "output" : None,
         "colorscheme" : None,
         "check-frequency" : None,
         "check-mode" : None,
         "sort-elements" : None,
         "save" : None,
         "show" : None,
     },
     "cib" : {
         "new" : None,
         "delete" : (shadows_list,),
         "reset" : (shadows_list,),
         "commit" : (shadows_list,),
         "use" : (shadows_live_list,),
         "diff" : None,
         "list" : None,
         "import" : None,
         "cibstatus" : None,
     },
     "template" : {
         "new" : (null_list,templates_list,loop),
         "load" : (config_list,),
         "edit" : (config_list,),
         "delete" : (config_list,),
         "show" : (config_list,),
         "apply" : (config_list_method,config_list),
         "list" : None,
     },
     "resource" : {
         "status" : (rsc_list,),
         "start" : (rsc_list,),
         "stop" : (rsc_list,),
         "restart" : (rsc_list,),
         "promote" : (rsc_list,),
         "demote" : (rsc_list,),
         "manage" : (rsc_list,),
         "unmanage" : (rsc_list,),
         "migrate" : (rsc_list,nodes_list),
         "unmigrate" : (rsc_list,),
         "param" : (rsc_list,attr_cmds),
         "meta" : (rsc_list,attr_cmds),
         "utilization" : (rsc_list,attr_cmds),
         "failcount" : (rsc_list,attr_cmds,nodes_list),
         "cleanup" : (rsc_list,nodes_list),
         "refresh" : (nodes_list,),
         "reprobe" : (nodes_list,),
     },
     "node" : {
         "status" : (nodes_list,),
         "show" : (nodes_list,),
         "standby" : (nodes_list,),
         "online" : (nodes_list,),
         "fence" : (nodes_list,),
         "delete" : (nodes_list,),
         "clearstate" : (nodes_list,),
         "attribute" : (nodes_list,attr_cmds),
         "utilization" : (nodes_list,attr_cmds),
         "status-attr" : (nodes_list,attr_cmds),
     },
     "ra" : {
         "classes" : None,
         "list" : None,
         "providers" : None,
         "meta" : None,
     },
     "cibstatus" : {
         "show" : None,
         "save" : None,
         "load" : None,
         "origin" : None,
         "node" : (status_node_list,node_states_list),
         "op" : (ra_operations_list,status_rsc_list,lrm_exit_codes_list,lrm_status_codes_list,status_node_list),
         "run" : None,
         "simulate" : None,
         "quorum" : None,
     },
     "configure" : {
         "erase" : None,
         "verify" : None,
         "refresh" : None,
         "ptest" : None,
         "commit" : None,
         "upgrade" : None,
         "show" : (id_xml_list,id_list,loop),
         "edit" : (id_xml_list,id_list,loop),
         "filter" : (null_list,id_xml_list,id_list,loop),
         "delete" : (id_list,loop),
         "default-timeouts" : (id_list,loop),
         "rename" : (id_list,id_list),
         "save" : None,
         "load" : None,
         "node" : (node_id_list,node_attr_keyw_list),
         "primitive" : (null_list,ra_classes_list,primitive_complete_complex,loop),
         "group" : (null_list,f_prim_id_list,loop),
         "clone" : (null_list,f_children_id_list),
         "ms" : (null_list,f_children_id_list),
         "location" : (null_list,rsc_id_list),
         "colocation" : (null_list,null_list,rsc_id_list,loop),
         "order" : (null_list,null_list,rsc_id_list,loop),
         "property" : (property_complete,loop),
         "rsc_defaults" : (prim_complete_meta,loop),
         "op_defaults" : (op_attr_list,loop),
         "xml" : None,
         "monitor" : None,
         "ra" : None,
         "cib" : None,
         "cibstatus" : None,
         "template" : None,
         "_test" : None,
         "_regtest" : None,
         "_objects" : None,
     },
 }
 def get_completer_list(level,cmd):
     'Return a list of completer functions.'
     try: return completer_lists[level][cmd]
     except: return None
 
 compl_help = CompletionHelp()
 user_prefs = UserPrefs.getInstance()
 vars = Vars.getInstance()
 cib_status = CibStatus.getInstance()
 cib_factory = CibFactory.getInstance()
 
 # vim:ts=4:sw=4:et:
diff --git a/shell/modules/utils.py b/shell/modules/utils.py
index 13e4db6744..240c37c000 100644
--- a/shell/modules/utils.py
+++ b/shell/modules/utils.py
@@ -1,416 +1,429 @@
 # Copyright (C) 2008 Dejan Muhamedagic <dmuhamedagic@suse.de>
 # 
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public
 # License as published by the Free Software Foundation; either
 # version 2 of the License, or (at your option) any later version.
 # 
 # This software is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
 # You should have received a copy of the GNU General Public
 # License along with this library; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 #
 
 import os
 import pwd
 from tempfile import mkstemp
 import subprocess
 import re
 import glob
 import time
 
 from userprefs import Options, UserPrefs
 from msg import *
 
 def is_program(prog):
     return subprocess.call("which %s >/dev/null 2>&1"%prog, shell=True) == 0
 
 def ask(msg):
     # if there's no terminal, no use asking and default to "no"
     if not sys.stdin.isatty():
         return False
     print_msg = True
     while True:
         try:
             ans = raw_input(msg + ' ')
         except EOFError:
             ans = 'n'
         if not ans or ans[0].lower() not in ('n','y'):
             if print_msg:
                 print "Please answer with y[es] or n[o]"
                 print_msg = False
         else:
             return ans[0].lower() == 'y'
 
 def verify_boolean(opt):
     return opt.lower() in ("yes","true","on") or \
         opt.lower() in ("no","false","off")
 def is_boolean_true(opt):
     return opt.lower() in ("yes","true","on")
 
 def keyword_cmp(string1, string2):
     return string1.lower() == string2.lower()
 
 from UserDict import DictMixin
 class odict(DictMixin):
     def __init__(self, data=None, **kwdata):
         self._keys = []
         self._data = {}
     def __setitem__(self, key, value):
         if key not in self._data:
             self._keys.append(key)
         self._data[key] = value
     def __getitem__(self, key):
         if key not in self._data:
             return self._data[key.lower()]
         return self._data[key]
     def __delitem__(self, key):
         del self._data[key]
         self._keys.remove(key)
     def keys(self):
         return list(self._keys)
     def copy(self):
         copyDict = odict()
         copyDict._data = self._data.copy()
         copyDict._keys = self._keys[:]
         return copyDict
 
 class olist(list):
     def __init__(self, keys):
         #print "Init %s" % (repr(keys))
         super(olist, self).__init__()
         for key in keys:
             self.append(key)
             self.append(key.upper())
 
 def setup_help_aliases(obj):
     for cmd in obj.cmd_aliases.keys():
         for alias in obj.cmd_aliases[cmd]:
             if obj.help_table:
                 obj.help_table[alias] = obj.help_table[cmd]
 def setup_aliases(obj):
     for cmd in obj.cmd_aliases.keys():
         for alias in obj.cmd_aliases[cmd]:
             obj.cmd_table[alias] = obj.cmd_table[cmd]
 
 def getpwdent():
     try: euid = os.geteuid()
     except Exception, msg:
         common_err(msg)
         return None
     try: pwdent = pwd.getpwuid(euid)
     except Exception, msg:
         common_err(msg)
         return None
     return pwdent
 def getuser():
     user = os.getenv("USER")
     if not user:
         try: return getpwdent()[0]
         except: return None
     else:
         return user
 def gethomedir():
     homedir = os.getenv("HOME")
     if not homedir:
         try: return getpwdent()[5]
         except: return None
     else:
         return homedir
 
 def os_types_list(path):
     l = []
     for f in glob.glob(path):
         if os.access(f,os.X_OK) and os.path.isfile(f):
             a = f.split("/")
             l.append(a[-1])
     return l
 
+def listtemplates():
+    l = []
+    for f in os.listdir(vars.tmpl_dir):
+        if os.path.isfile("%s/%s" % (vars.tmpl_dir,f)):
+            l.append(f)
+    return l
+def listconfigs():
+    l = []
+    for f in os.listdir(vars.tmpl_conf_dir):
+        if os.path.isfile("%s/%s" % (vars.tmpl_conf_dir,f)):
+            l.append(f)
+    return l
+
 def add_sudo(cmd):
     if user_prefs.crm_user:
         return "sudo -E -u %s %s"%(user_prefs.crm_user,cmd)
     return cmd
 def pipe_string(cmd,s):
     rc = -1 # command failed
     cmd = add_sudo(cmd)
     p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
     try:
         p.communicate(s)
         p.wait()
         rc = p.returncode
     except IOError, msg:
         common_err(msg)
     return rc
 def filter_string(cmd,s,stderr_on = True):
     rc = -1 # command failed
     if stderr_on:
         stderr = None
     else:
         stderr = subprocess.PIPE
     cmd = add_sudo(cmd)
     p = subprocess.Popen(cmd, shell=True, \
         stdin = subprocess.PIPE, \
         stdout = subprocess.PIPE, stderr = stderr)
     try:
         outp = p.communicate(s)[0]
         p.wait()
         rc = p.returncode
     except IOError, msg:
         common_err(msg)
     return rc,outp
 
 def str2tmp(s):
     '''
     Write the given string to a temporary file. Return the name
     of the file.
     '''
     fd,tmp = mkstemp()
     try: f = os.fdopen(fd,"w")
     except IOError, msg:
         common_err(msg)
         return
     f.write(s)
     f.close()
     return tmp
 def str2file(s,fname):
     '''
     Write a string to a file.
     '''
     try: f = open(fname,"w")
     except IOError, msg:
         common_err(msg)
         return False
     f.write(s)
     f.close()
     return True
 
 def is_filename_sane(name):
     if re.search("['`/#*?$\[\]]",name):
         common_err("%s: bad name"%name)
         return False
     return True
 def is_name_sane(name):
     if re.search("[']",name):
         common_err("%s: bad name"%name)
         return False
     return True
 def is_value_sane(name):
     if re.search("[']",name):
         common_err("%s: bad name"%name)
         return False
     return True
 
 def show_dot_graph(dotfile):
     p = subprocess.Popen("%s %s" % (user_prefs.dotty,dotfile), shell=True, bufsize=0, stdin=None, stdout=None, stderr=None, close_fds=True)
     common_info("starting %s to show transition graph"%user_prefs.dotty)
 
 def ext_cmd(cmd):
     if options.regression_tests:
         print ".EXT", cmd
     return subprocess.call(add_sudo(cmd), shell=True)
 
 def get_stdout(cmd, stderr_on = True):
     '''
     Run a cmd, return stdin output.
     stderr_on controls whether to show output which comes on stderr.
     '''
     if stderr_on:
         stderr = None
     else:
         stderr = subprocess.PIPE
     proc = subprocess.Popen(cmd, shell = True, \
         stdout = subprocess.PIPE, stderr = stderr)
     outp = proc.communicate()[0]
     proc.wait()
     outp = outp.strip()
     return outp
 def stdout2list(cmd, stderr_on = True):
     '''
     Run a cmd, fetch output, return it as a list of lines.
     stderr_on controls whether to show output which comes on stderr.
     '''
     s = get_stdout(add_sudo(cmd), stderr_on)
     return s.split('\n')
 
 def wait4dc(what = "", show_progress = True):
     '''
     Wait for the DC to get into the S_IDLE state. This should be
     invoked only after a CIB modification which would exercise
     the PE. Parameter "what" is whatever the caller wants to be
     printed if showing progress.
 
     It is assumed that the DC is already in a different state,
     usually it should be either PENGINE or TRANSITION. This
     assumption may not be true, but there's a high chance that it
     is since crmd should be faster to move through states than
     this shell.
 
     Further, it may also be that crmd already calculated the new
     graph, did transition, and went back to the idle state. This
     may in particular be the case if the transition turned out to
     be empty.
 
     Tricky. Though in practice it shouldn't be an issue.
 
     There's no timeout, as we expect the DC to eventually becomes
     idle.
     '''
     cmd = "crmadmin -D"
     s = get_stdout(add_sudo(cmd))
     if not s.startswith("Designated"):
         common_warn("%s unexpected output: %s" % (cmd,s))
         return False
     dc = s.split()[-1]
     if not dc:
         common_warn("can't find DC in: %s" % s)
         return False
     cmd = "crmadmin -S %s" % dc
     cnt = 0
     output_started = 0
     while True:
         s = get_stdout(add_sudo(cmd))
         if not s.startswith("Status"):
             common_warn("%s unexpected output: %s" % (cmd,s))
             return False
         try: dc_status = s.split()[-2]
         except:
             common_warn("%s unexpected output: %s" % (cmd,s))
             return False
         if dc_status == "S_IDLE":
             if output_started:
                 sys.stderr.write(" done\n")
             return True
         time.sleep(0.1)
         if show_progress:
             cnt += 1
             if cnt % 10 == 0:
                 if not output_started:
                     output_started = 1
                     sys.stderr.write("waiting for %s to finish " % what)
                 sys.stderr.write(".")
 
 def is_id_valid(id):
     """
     Verify that the id follows the definition:
     http://www.w3.org/TR/1999/REC-xml-names-19990114/#ns-qualnames
     """
     if not id:
         return False
     id_re = "^[A-Za-z_][\w._-]*$"
     return re.match(id_re,id)
 def check_filename(fname):
     """
     Verify that the string is a filename.
     """
     fname_re = "^[^/]+$"
     return re.match(fname_re,id)
 
 def is_process(s):
     proc = subprocess.Popen("ps -e -o pid,command | grep -qs '%s'" % s, \
         shell=True, stdout=subprocess.PIPE)
     proc.wait()
     return proc.returncode == 0
 def cluster_stack():
     if is_process("heartbeat:.[m]aster"):
         return "heartbeat"
     elif is_process("[a]isexec"):
         return "openais"
     return ""
 
 def edit_file(fname):
     'Edit a file.'
     if not fname:
         return
     if not user_prefs.editor:
         return
     return ext_cmd("%s %s" % (user_prefs.editor,fname))
 
 def page_string(s):
     'Write string through a pager.'
     if not s:
         return
     w,h = get_winsize()
     if s.count('\n') <= h:
         print s
     elif not user_prefs.pager or not options.interactive:
         print s
     else:
         opts = ""
         if user_prefs.pager == "less":
             opts = "-R"
         pipe_string("%s %s" % (user_prefs.pager,opts), s)
 
 def get_winsize():
     try:
         import curses
         curses.setupterm()
         w = curses.tigetnum('cols')
         h = curses.tigetnum('lines')
     except:
         try:
             w = os.environ['COLS']
             h = os.environ['LINES']
         except:
             w = 80; h = 25
     return w,h
 def multicolumn(l):
     '''
     A ls-like representation of a list of strings.
     A naive approach.
     '''
     min_gap = 2
     w,h = get_winsize()
     max_len = 8
     for s in l:
         if len(s) > max_len:
             max_len = len(s)
     cols = w/(max_len + min_gap)  # approx.
     col_len = w/cols
     for i in range(len(l)/cols + 1):
         s = ''
         for j in range(i*cols,(i+1)*cols):
             if not j < len(l):
                 break
             if not s:
                 s = "%-*s" % (col_len,l[j])
             elif (j+1)%cols == 0:
                 s = "%s%s" % (s,l[j])
             else:
                 s = "%s%-*s" % (s,col_len,l[j])
         if s:
             print s
 
 def find_value(pl,name):
     for n,v in pl:
         if n == name:
             return v
     return None
 
 def lines2cli(s):
     '''
     Convert a string into a list of lines. Replace continuation
     characters. Strip white space, left and right. Drop empty lines.
     '''
     cl = []
     l = s.split('\n')
     cum = []
     for p in l:
         p = p.strip()
         if p.endswith('\\'):
             p = p.rstrip('\\')
             cum.append(p)
         else:
             cum.append(p)
             cl.append(''.join(cum).strip())
             cum = []
     if cum: # in case s ends with backslash
         cl.append(''.join(cum))
     return [x for x in cl if x]
 
 user_prefs = UserPrefs.getInstance()
 options = Options.getInstance()
 # vim:ts=4:sw=4:et: