# heron dives into a module and execute a function just as
# herons dive into water and catch fish.


import json
# import hjson    as hjson

if 'clise' in __file__:
	import clise.jsontool as jt
	import clise.rabbit   as rb
else:
	import jsontool as jt
	import rabbit   as rb

import re
import sys
# import math
# import glb
import hashlib
# import textwrap3  # we modify one of its routines below: len
import inspect

# import numpy  as np

from os		import path, makedirs
from pathlib	import Path
from socket		import gethostname
from collections  import OrderedDict
from datetime     import datetime
from importlib    import import_module
from IPython	import embed

__version__    = '0.2.3'

cc = jt.cc()

def version():
	return __version__

# handle input parameters
def get_func_parameters(func):
	""" check func input parameters and keyword
	and update them with the given cfg dictionary
	"""

	if hasattr(func, '__code__'):
		keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
	else: return [], {}

	sorter = {j: i for i, j in enumerate(keys[::-1])}
	if func.__defaults__ is not None:
		values = func.__defaults__[::-1]
		kwargs = {i: j for i, j in zip(keys, values)}
		sorted_args = tuple(
			sorted([i for i in keys if i not in kwargs], key=sorter.get)
		)
		sorted_kwargs = {
			i: kwargs[i] for i in sorted(kwargs.keys(), key=sorter.get)
		}
	else:
		sorted_args = keys[::-1]
		sorted_kwargs = OrderedDict()

	# now check if there are list and dict for generic inputs
	params = inspect.signature(func).parameters
	collect = False
	for param in params.values():
		if param.kind == inspect.Parameter.VAR_POSITIONAL:
			collect = True
			break
		if param.kind == inspect.Parameter.VAR_KEYWORD:
			collect = True
			break

	return sorted_args, sorted_kwargs, collect

def set_func_parameters(sorted_args, sorted_kwargs, cfg=None):
	""" check func input parameters and keyword
	and update them with the given cfg dictionary
	"""
	updated_args = []
	for par in sorted_args:
		if par in cfg: updated_args.append(cfg[par])
		else:		   updated_args.append(None)

	if sorted_kwargs is not None:
		updated_kwargs = OrderedDict()
		for par in sorted_kwargs:
			if par in cfg: updated_kwargs[par] = cfg[par]
			else:		   updated_kwargs[par] = sorted_kwargs[par]

	# if you meet this, the function doesn't tell us what it needs
	# e.g., math.sin
	if updated_args == []:
		for par in cfg.get('-pars', []):
			# print(par)
			if par in cfg.keys() - updated_kwargs.keys():
				updated_args.append(cfg[par])
	for par in cfg.get('-kwpars', []):
		if par in cfg:
			updated_kwargs[par] = cfg[par]
		else:
			updated_kwargs[par] = None

	# only when -pars are set
	if cfg.get('-collect', False):
		for par in cfg:
			if par in cfg.get('-discard', []): continue
			# if par[0:1] == '-':		 continue
			if not bool(re.search('^[a-zA-Z_]', par)): continue
			# if par in cfg.get('-pars',[]): continue
			if par in sorted_args: continue
			if par not in updated_kwargs.keys():
				updated_kwargs[par] = cfg[par]

	if len(sorted_args) > 0:
		if sorted_args[0] == 'self':
			sorted_args  = sorted_args[1:]
			updated_args = updated_args[1:]

	return updated_args, updated_kwargs

def set_module_parameters(module, cfg, keys=None):
	""" set module global parameters
	"""
	if cfg is None: return
	if keys is None:
		for key in cfg:			setattr(module, key, cfg[key])
	else:
		for key in keys & cfg.keys(): setattr(module, key, cfg[key])

# ----------------------------------------------------------------------------

def global_diagnostic(cfgs):
	if help_text(cfgs[0].get('-help', False), raw=cfgs[0].get('-rawtext', False)): exit()

	userhelp = str(cfgs[0].get('-Help', False))
	if 'True' in userhelp:
		for each in cfgs: Help_text(each)
		exit()
	elif 'False' not in userhelp:
		Help_text(cfgs[0], userhelp)
		exit()

def diagnostic(cfg):

	global show

	if '-main' not in cfg.keys():
		print('no -main: need the main subroutine to execute')
		if '-show' in cfg.keys(): show(cfg, full=True)
		return True

	show = str(cfg.get('-show', None))
	if 'True' in show:
		print(cc.hl + 'job', cfg['-id'] + ':', cfg['-main'], cc.reset)
		jt.show(cfg)
		return True

	return False

def skip(cfg):

	# input files exist?
	for each in jt.str_to_list(cfg.get('-checkin', [])):
		target = cfg.get(each, '')
		if target is None or target == '': continue
		target = str(Path(target).expanduser())
		if not path.exists(target):
			print(cc.err + 'missing input', target, 'skipping...' + cc.reset)
			return True

	# output files exist?
	# this needs to be combined with -timecheck or -sizecheck
	if not cfg.get('-clobber', False):
		for each in jt.str_to_list(cfg.get('-checkout', [])):
			target = cfg.get(each, '')
			if target is None or target == '': continue
			if path.exists(target):
				if cfg['-verbose'] == 0: target = path.basename(target)
				print(cc.err + 'output', target, 'exists. skipping...', cc.reset)
				return True
	else:
		for each in jt.str_to_list(cfg.get('-after', [])):
			key, reference = each.split(':')
			target = cfg.get(key, '')
			if target is None or target == '': continue
			mt_target    = Path(target).expanduser().stat().st_mtime
			if reference not in mt_refer:
				mt_refer[reference] = Path(reference).expanduser().stat().st_mtime
			if mt_target < mt_refer[reference]:
				print(cc.err + key, target, 'is modified earlier than', reference + '. skipping...', cc.reset)
				return True

		for each in jt.str_to_list(cfg.get('-before', [])):
			key, reference = each.split(':')
			target = cfg.get(key, '')
			if target is None or target == '': continue
			mt_target = Path(target).expanduser().stat().st_mtime
			if reference not in mt_refer:
				mt_refer[reference] = Path(reference).expanduser().stat().st_mtime
			if mt_target > mt_refer[reference]:
				print(cc.err + key, target, 'is later than', reference + '. skipping...', cc.reset)
				return True

	for each in jt.str_to_list(cfg.get('-include', [])):
		key, phrase = each.split(':')
		target = cfg.get(key, None)
		if target is None or target == '': continue
		mat = re.search(phrase, target)
		if not bool(mat):
			print(cc.err + key, target, 'does not have', phrase + '. skipping...', cc.reset)
			return True

	# exclude
	for each in jt.str_to_list(cfg.get('-exclude', [])):
		key, phrase = each.split(':')
		target = cfg.get(key, None)
		if target is None or target == '': continue
		mat = re.search(phrase, target)
		if bool(mat):
			print(cc.err + key, target, 'has', phrase + '. skipping...', cc.reset)
			return True

	return False

def create_dir(trgdir):
	if not path.isdir(trgdir):
		if trgdir != '':
			makedirs(trgdir, exist_ok=True)

# ----------------------------------------------------------------------------
# load module
def load_routine(name, cfg=None, show_trace=False):

	levels  = name.split('.')
	nlevels = len(levels)

	if nlevels == 1:
		if type(__builtins__).__name__ == 'dict': routine = __builtins__[levels[0]]
		else:							routine = getattr(__builtins__, levels[0])
		if show_trace: print(cc.key + 'loading:', routine, type(routine).__name__, cc.reset)
		return routine, None

	# this shouldn't be necessary, but since I don't understand something, here it is
	# if levels[0] == 'clise':
	# 	levels = levels[1:]
	# 	levels[0] = 'clise.' + levels[0]
	# 	nlevels = nlevels - 1

	if levels[0] not in sys.modules:
		module = __import__(levels[0])
	else:
		module = sys.modules[levels[0]]

	# potential class name
	class_name_ = '.'.join(levels[0:-1])
	global class_obj, class_name
	if "-object" in cfg:
		if cfg["-object"] in class_obj:
			if show_trace: print(cc.key + 'loading the requested object:', cfg["-object"], cc.reset)
			object = class_obj[cfg["-object"]]
			routine = getattr(object, levels[-1])
			return routine, module
	else:
		if class_name_ in class_obj:
			if show_trace: print(cc.key + 'loading the existing object:', class_name_, cc.reset)
			object = class_obj[class_name_]
			routine = getattr(object, levels[-1])
			return routine, module

	routine = module
	if show_trace: print(cc.key + 'loading:', routine, type(routine).__name__, cc.reset)
	for idx, each in enumerate(levels[1:]):

		#  this shouldn't be necessary, but since I don't understand something, here it is
		#  or this must be a more generic issue, requiring a better solution
		#  i.e., why does it load "each" from "routine"? instead, it ignores "routine",
		#  and look just for "each"
		if idx == 0 and routine.__name__ == 'clise':
			routine = import_module('clise.' + each)
			if show_trace:
				print(cc.key + 'loading:', idx, routine, type(routine).__name__, cc.reset)

			continue

		try:
			routine = getattr(routine, each)
			# if show_trace: print(cc.key + 'loading got attr', cc.reset)

		except AttributeError:
			routine = import_module(each, routine)
			# if show_trace: print(cc.key + 'loading a module', each, cc.reset)

		if show_trace: print(cc.key + 'loading:', idx, routine, type(routine).__name__, cc.reset)

		if idx == nlevels - 3:  # -3 indicates the next to the last name

			icfg = cfg.get('-init', OrderedDict()).copy()
			icfg['-collect'] = icfg.get('-collect', True)  # by default, collect everything in "-init"
			init_args, init_kwargs = set_func_parameters([], [], cfg=icfg)

			# print(init_args, init_kwargs)
			# try:
			if type(routine).__name__ == 'type':
				# now we are loading a new class or attempting

				class_obj_  = routine(*init_args, **init_kwargs)
				routine     = getattr(class_obj_, levels[-1])

				# if not specified, then use class_name_ for the key of the object
				# so that the same class name will share the same object
				# if there needs to be more than an object for the same class,
				# one has to specify "-object" to separate them
				objkey	= cfg.get("-object", class_name_)

				# if you are here, then safe to assign these
				class_name[objkey] = class_name_
				class_obj [objkey] = class_obj_
			# except Exception:
			else:
				# an issue with "try: .. except:" is if exception occurs due to an error in a class
				# one ends up here as well, which will fail
				# oops, not class
				routine = getattr(routine, levels[-1])
			break

	return routine, module

def execute_routine(routine, args, kwargs, cfg=None, show_out=False):
	out = routine(*args, **kwargs)
	if type(out).__name__ == 'function':
		args,  kwargs, collect = get_func_parameters(out)
		cfg['-collect'] = cfg.get('-collect', collect)
		args,  kwargs = set_func_parameters(args, kwargs, cfg=cfg)
		out = out(*args, **kwargs)
	if show_out: print(out)
	return out

# should we use a class
routines   = OrderedDict()
modules    = OrderedDict()
taskkey    = 0
maxtask    = 1
class_obj  = OrderedDict()
class_name = OrderedDict()
output     = OrderedDict()
mt_refer   = OrderedDict()
show	     = ''

# execute configuration file

# to avoid conflict with expression in eval, most variables start and end with _
def inherit(_cfg_):
	global output
	_output_ = output.copy()
	if "-inherit" in _cfg_:
		for _each_ in _output_:
			locals()[_each_] = _output_[_each_]

		# this is not necessarily a scalar value, but it's just the first
		# element of the dictionary values instead of the whole dictionary.
		if "-scalar" in _cfg_["-inherit"]:
			_selected_ = _cfg_["-inherit"]["-scalar"]
			for _each_ in _selected_:
				locals()[_each_] = eval("list(" + _each_ + ".values())[0]")

		# this returns the dictionary values as a list
		if "-list" in _cfg_["-inherit"]:
			_selected_ = _cfg_["-inherit"]["-list"]
			for _each_ in _selected_:
				locals()[_each_] = eval("list(" + _each_ + ".values())")

		for _key_, _val_ in _cfg_["-inherit"].items():
			if _key_[0] == "-": continue
			_cfg_[_key_] = eval(_val_)
	return _cfg_

def execute(cfg):
	""" execute the main routine called in cfg
	"""

	global show

	# rabbit=cfg.get('-rabbit', False) >= True
	block = 'rabbit' in cfg.get('-block', '')
	if diagnostic(cfg):
		if not block: return True
		return False

	job_str = ['job', f"{cfg['-jobc']}/{cfg['-maxjob']}",
		"%-8s: %-25s:" % (cfg['-nid'], cfg['-id']), cfg['-main']]
	job_str = " ".join(job_str)
	if 'job' in show:
		# idf = cfg['-id'].split(':')
		print(cc.hl + job_str, cc.reset)

	if 'input' in show:
		jt.show(cfg, full=False)
		if not block: return True

	if 'hidden' in show:
		jt.show(cfg, full=True)
		if not block: return True

	if 'file' in show:
		jt.show_files(cfg)

	if cfg.get('-dump', '') != '':
		try:
			cfg_str = json.dumps(cfg.copy())
			hfile = open(path.expanduser(cfg['-dump']), "a")
			hfile.write('// ' + job_str + '\n')
			hfile.write(cfg_str.replace(',', ',\n\t'))
			hfile.write(',\n')
			hfile.close()
		except Exception:
			print(cc.err + "cannot dump cfg since serializing cfg failed" + cc.reset)

	# anything not at the base level, assume it's a function inside a class
	# cfg['-init'] = cfg.get('-init',len(cfg['-main'].split('.'))>1)

	global taskkey
	routine, module = load_routine(cfg['-main'], cfg=cfg, show_trace='trace' in show)

	routines[taskkey] = routine
	modules [taskkey] = module

	routine_args,  routine_kwargs, collect = get_func_parameters(routine)
	cfg['-collect'] = cfg.get('-collect', collect)

	if 'func' in show:
		jt.show_feed(routine_args, routine_kwargs, cfg['-main'])
		return True

	# -------------------------------------------------------------------------
	# variable inherit
	cfg = inherit(cfg)
	# -------------------------------------------------------------------------
	# when the routine has clear input parameters exposed, take advantage of it
	# if hasattr(routine,'__code__'):
	# 	routine_keys = routine.__code__.co_varnames[:routine.__code__.co_argcount][::-1]
	# else: routine_keys = []
	#
	# cfg['-pars'] = cfg.get('-pars', routine_keys[::-1])
	# actualy this part requires switching the unique parameter sets
	# default=OrderedDict()
	# for each in cfg['-pars']:
	# 	if each in cfg: default[each] = cfg[each]
	#
	# if len(default)==0: default=copy(cfg)
	# -------------------------------------------------------------------------
	# set module level global parameters
	if '-global' in cfg.keys():
		gvar = cfg['-global']
		# doing this here for safety
		if type(gvar) is list:
			gvar = OrderedDict()
			for each in cfg['-global']:
				if each in cfg.keys(): gvar[each] = cfg[each]
		set_module_parameters(module, gvar)

	if 'time' in show: start_time = datetime.now()

	# if this is a native routine, feed the whole thing either by class itself or cfg
	sorted_args   = OrderedDict()
	sorted_kwargs = OrderedDict()
	sorted_args, sorted_kwargs = set_func_parameters(routine_args, routine_kwargs, cfg=cfg)

	# -------------------------------------------------------------------------
	# feed all the configuration parameters if the input keyword is correct
	roundup = cfg.get('-roundup', '_heron_')
	if   roundup in routine_args : sorted_args  [routine_args.index(roundup)] = cfg
	elif roundup in sorted_kwargs: sorted_kwargs[roundup] = cfg

	# a bit too risky, so not now
	# roundup=cfg.get('-roundupall', '__heron__')
	# if   roundup in routine_args : sorted_args  [routine_args.index(roundup)] = all
	# elif roundup in sorted_kwargs: sorted_kwargs[roundup] = all

	# -------------------------------------------------------------------------
	if 'feed' in show:
		jt.show_feed(routine_args, routine_kwargs, cfg['-main'],
					inargs=sorted_args, inkwargs=sorted_kwargs)

	for each in cfg.get('-mkoutdir', []):
		target = path.dirname(cfg.get(each, ''))
		if not path.exists(target): create_dir(target)

	if skip(cfg): return -1

	# -------------------------------------------------------------------------
	output_ = execute_routine(routine, sorted_args, sorted_kwargs, cfg=cfg,
				show_out='output' in show)

	if 'time' in show:
		runtime(start_time, datetime.now(), cfg['-main'], jobid='job ' + str(taskkey), cc=cc)

	if '-return' in cfg:
		global output
		if type(output_) is not tuple: output_ = [output_]
		for idx, each in enumerate(cfg.get('-return', '').replace(' ', '').split(',')):
			if each not in output:
				output[each] = OrderedDict()
			# this needs a mod for rabbit's task generation
			output[each][cfg['-nid']] = output_[idx]
		return output
	else:
		return output_

	""" """

def go(cfgs=None, befores=None, afters=None, ovw=None, cli=sys.argv[1:], level=-1):
	global taskkey, mt_refer, maxtask, show

	if cfgs is None: cfgs, befores, afters, ovw = jt.load_cli(cli)
	if type(cfgs) is not list: cfgs = [cfgs]

	global_diagnostic(cfgs)

	# search for self containing rabbit and reconfig?
	# cfgs=jt.reconfig_to_alltaskgen(cfgs)

	level = level + 1
	if level == 0:
		taskkey  = 0
		mt_refer = OrderedDict()
		maxtask  = len(cfgs)

	start_time = datetime.now()

	for idx, each in enumerate(cfgs):
		taskkey = taskkey + 1

		each['-level' ] = level
		each['-jobc'  ] = taskkey
		each['-maxjob'] = maxtask

		# this doesn't work
		# need to set this in jsontool 
		# and then execute here? or execute as a special job
		# or use another script?
		# if "-restart" in each:
		# 	# clean up existing class object
		# 	print("cleaning up existing class object")
		# 	global class_obj, class_name
		# 	class_name = OrderedDict()
		# 	import plottool as pt
		# 	for key, obj in class_obj.items():
		# 		print(key, obj)
		# 		# pt.embed()
		# 		del obj
		# 		# pt.embed()
		# 	class_obj = OrderedDict()

		out = execute(each)

		if type(befores) is list: before = befores[idx]
		else:				  before = befores
		if type(afters ) is list: after  = afters [idx]
		else:				  after  = afters

		_, after_ = rb.get_common_vs_match(after)
		after_    = jt.merge(after_, ovw)

		# if each.get('-rabbit', False) >=True and type(out) is not bool:
		if 'rabbit' in each.get('-block', '') and type(out) is not bool:
			# id augmentation by level depth
			# a bit adhoc: too intertwined with internal steps of jt.load_cli
			# need a more clean break btw the two
			if len(out) == 0: continue

			maxtask = maxtask + len(out)

			for idx2, each2 in enumerate(out):
				# update the number and full id
				each2['-id'], each2['-nid'], each2['-fid'] \
						= jt.set_id(each2.get('-id', ''), idx2 + 1, pref=each['-nid'] + '.')

				# only applied selected cases in after?
				# other wise this supercede
				# what if one needs to apply globally?
				out[idx2] = jt.get_task_ready(each2, before, after_, doprocess=level + 2)

			go(out, befores=before, afters=after, level=level)

	global show
	end_time = datetime.now()
	if 'time' in show:
		runtime(start_time, end_time, '', jobid='job all', cc=cc)
	log(cfgs, start_time, end_time)

# ----------------------------------------------------------------------------
def log(cfg, start_time, end_time):
	""" write a log
	"""
	# called at the end of the program
	# requires the directory name: set by _hisdir
	# the filename is automatically assigned by date_time,
	# but can be set manually by _hisfile

	if cfg[0].get('-dryrun', False):     return
	if cfg[0].get('-logfile', '') == "": return

	if not cfg[0].get('-save_cli_only_run', False):
		if jt.json_counter == 0: return

	hostname = gethostname()

	try:
		cfg_str = json.dumps(cfg)
	except Exception:
		print(cc.err + "cannot log this since serializing cfg failed" + cc.reset)
		return
	hashres = hashlib.sha1(cfg_str.encode())

	# ------
	for each in cfg:
		if '-main' in each:
			main_name = each['-main'][0]
			break
	lmain     = len(cfg)
	if lmain > 0: main_name = main_name + '+' + str(lmain)
	loginfo = ['{}'.format(start_time)[2:19], '{}'.format(end_time - start_time),
			'SHA1:' + hashres.hexdigest(),
			main_name, '', hostname,
			'']
	loginfo = ' '.join(loginfo)

	logfile = path.expanduser(cfg[0]['-logfile'])
	if path.isfile(logfile):
		lfile = open(logfile, "a")
		lfile.write('\n' + loginfo)
	else:
		lfile = open(logfile, "w")
		lfile.write(loginfo)
	lfile.close()

	# ------
	hisfile = cfg[0].get('-hisfile', None)
	if hisfile is None: return
	if hisfile == '_auto_':
		filename = start_time.strftime("%y%m%d_%H%M%S") + '_' + main_name + '.json5'
		hisfile = (Path(logfile).parent / Path(filename)).expanduser()

	hfile = open(path.expanduser(hisfile), "w")
	hfile.write('//' + loginfo + '\n')
	hfile.write(cfg_str.replace(',', ',\n\t'))
	hfile.close()

	""" """

def runtime(start_time, end_time, label, jobid='', cc=cc):
	print(cc.key + jobid + ':', '{}'.format(end_time - start_time),
			cc.type + '{}'.format(start_time),
			cc.key + label, cc.reset)

# ----------------------------------------------------------------------------
def help_text(display=False, raw=False):
	""" print out the help text of cjpy and cjson
	"""

	display  = str(display)
	if 'True' in display:
		print("""Usage: clise JSON_input_file1 -options_for_file1 ...
				[JSON_input_file2 -options_for_file2 ...]
				[--with common_JSON_files -common_options ...]
				[--WITH common_JSON_files -common_options ...]

		clise --help [keys]
		clise [JSON_files ...] --Help
		clise --main module.routine --Help """.replace("\n\t\t", "\n"))
		return True
	if 'False' in display: return False

	contents = OrderedDict()
	contents['heading'    ] = '### CLISe:'
	contents['contents'   ] = '### Table of Contents'
	contents['overview'   ] = '### Quick overview of the basic concept'
	contents['install'    ] = '### Installation and startup'
	contents['features'   ] = '### Features and limitation'
	contents['features'   ] = '### Features in parameter setting with JSON files'
	contents['iteration'  ] = '### Iterative calling and file search'
	contents['Sequential' ] = '### Sequential calling'
	contents['cli'	    ] = '### Options for command line parameters'
	contents['parameters' ] = '### Parameter list'
	contents['logging'    ] = '### Logging'
	contents['more'       ] = '### More'
	contents['changes'    ] = '### Changes'
	contents['end'        ] = ''
	keys = list(contents.keys())

	dir = path.dirname(__file__)
	rf = open(dir + '/doc/heron.md', "r")
	readme = rf.read()
	rf.close()

	if display == "all": display = keys[:-1]
	else:		         display = display.split(',')

	from rich.console import Console
	from rich.markdown import Markdown
	console = Console()

	for each in display:
		if each == "keys":
			print(keys[:-1] + ['all'])
			continue
		if each in keys:
			idx = keys.index(each)
			phrase = '(' + contents[each] + '.+)' + contents[keys[idx + 1]]
			mat = re.search(phrase, readme, re.DOTALL)
			if not bool(mat): continue
			selected = mat.group(1)
			if raw: print(selected)
			else:   console.print(Markdown(selected))

	return True
	""" """

# ----------------------------------------------------------------------------
def Help_text(cfg, what=None, keyword='-main', cc=cc):
	"""
	print out the Help text from the called routines
	No argument:
		if a single routine is called, then print out __doc__ of the routine
		if multiple routines are called, list the modules and functions called
	with an argument for a routine:
		the print out __doc__ of the routine whose name is the same as argument
		this enables printing out any doc function
	with an argument for a module:
		list all the functions of the module
		this argument ends with .
	"""

	mdict, mlist = jt.extract(cfg, key=keyword)
	jt.show(mdict, full=True)
	if len(mlist) == 1:
		routine, module = load_routine(mlist[0], cfg=cfg)
		if routine.__doc__ is None: print('hmm, does not have any doc in', mlist[0])
		else:				    print(routine.__doc__.replace("\n\t", "\n"))
	else:
		jt.show(mdict, full=True)

	if what is None: return True

	# forgot what's the point of all these.....
	if what in mlist:
		print(cc.key + what + cc.reset)
		routine, module = load_routine(what, cfg=cfg)
		if routine.__doc__ is None: print('hmm, does not have any doc in', what)
		else:				    print(routine.__doc__.replace("\n\t", "\n"))
	else:
		if what[-1] == '.':
			what = what[:-1]
			print('searching functions in module:', cc.key + what + cc.reset)

			from inspect import isfunction
			whats = what.split('.')
			if len(whats) > 1:
				routine, module = load_routine(what, cfg=cfg)
			else:
				routine = __import__(what)
			members = dir(routine)
			for each in members:
				if isfunction(getattr(routine, each)): print('	', each)
		else:
			print(cc.err + what, 'is not called yet' + cc.reset)
			try:
				routine, module = load_routine(what, cfg=cfg)
				if routine.__doc__ is None: print('hmm, does not have any doc in', what)
				else:				    print(routine.__doc__.replace("\n\t", "\n"))
			except Exception:
				print(cc.err + "cannot load", what + cc.reset)

	return True
	""" """

def main():
	go()
# ----------------------------------------------------------------------------
if __name__ == "__main__":
	"""
	in bash, use this program in this way
	alias heron='python heron.py'

	and then
	heron json_file [json_files ...] [-other parameters]

	to run in ipython

	import heron as hr
	cfg=jt.load_cli('cli_text')
	out=hr.go(cfg)

	"""

	go()
	""" """
