#!/usr/bin/env python # -*- coding: utf-8 -*- """ Welcom the ei_handler. We will create all the ezfio related stuff from a EZFIO.cfg file. Usage: ei_handler.py [--recursif] [--irpf90] [--ezfio_config] [--ocaml] [--ezfio_default] ei_handler.py ocaml_global By default all the option are executed. Options: -h --help --recursif Folow the dependancy of the module --irpf90 Create the `ezfio_interface.irpf90` which contains all the providers needed (aka all with the `interface: input` parameter) in `${pwd}` --ezfio_config Create the `${module_lower}_ezfio_interface_config` in `${QPACKAGE_ROOT}/EZFIO/config/` This file is needed by *EZFIO* to create the `libezfio.so` --ocaml Create the `Input_module.lower.ml` for the *qp_edit* --ezfio_default Create the `${module_lower}_ezfio_interface_default` in `${QPACKAGE_ROOT}/data/ezfio_defaults` needed by the ocaml ocaml_global Create the qp_edit Format specification : [provider_name] | the name of the provider in irp.f90 doc:{str} | Is the doc Type:{str} | Is a fancy_type supported by the ocaml ezfio_name:{str} | Will be the name of the file for the ezfio (optional by default is the name of the provider) interface:{str} | The provider is a imput or a output default:{str} | The default value if interface == input: size:{str} | the size information (like 1 or =sum(ao_num) or (ao_num,3) ) Example of EZFIO.cfg: ``` [thresh_SCF] doc: Threshold on the convergence of the Hartree Fock energy type: Threshold default: 1.e-10 interface: input size: 1 [energy] type: double precision doc: Calculated HF energy interface: output ``` """ from docopt import docopt import sys import os import os.path import ConfigParser from collections import defaultdict from collections import namedtuple from cache import cache from os import listdir from os.path import isdir, join, exists, islink Type = namedtuple('Type', 'fancy ocaml fortran') def is_bool(str_): """ Take a string, if is a bool return the conversion into fortran and ocaml. """ if "true" in str_.strip().lower(): return Type(None, "true", ".True.") elif "false" in str_.strip().lower(): return Type(None, "false", ".False") else: raise TypeError @cache def get_type_dict(): """ This function makes the correspondance between the type of value read in ezfio.cfg into the f90 and Ocaml Type return fancy_type[fancy_type] = namedtuple('Type', 'ocaml fortran') For example fancy_type['Ndet'].fortran = interger .ocaml = int """ # ~#~#~#~#~ # # P i c l e # # ~#~#~#~#~ # qpackage_root = os.environ['QPACKAGE_ROOT'] # ~#~#~#~ # # I n i t # # ~#~#~#~ # fancy_type = defaultdict(dict) # ~#~#~#~#~#~#~#~ # # R a w _ t y p e # # ~#~#~#~#~#~#~#~ # fancy_type['integer'] = Type(None, "int", "integer") fancy_type['integer*8'] = Type(None, "int", "integer*8") fancy_type['int'] = Type(None, "int", "integer") fancy_type['float'] = Type(None, "float", "double precision") fancy_type['double precision'] = Type(None, "float", "double precision") fancy_type['logical'] = Type(None, "bool", "logical") fancy_type['bool'] = Type(None, "bool", "logical") fancy_type['character*(32)'] = Type(None, "string", "character*(32)") fancy_type['character*(64)'] = Type(None, "string", "character*(68)") fancy_type['character*(256)'] = Type(None, "string", "character*(256)") # ~#~#~#~#~#~#~#~ # # q p _ t y p e s # # ~#~#~#~#~#~#~#~ # # Dict to change ocaml LowLevel type into FortranLowLevel type ocaml_to_fortran = {"int": "integer", "float": "double precision", "logical": "logical", "string": "character*32"} # Read and parse qptype generate src = qpackage_root + "/ocaml/qptypes_generator.ml" with open(src, "r") as f: r = f.read() # Generate l_gen = [i for i in r.splitlines() if i.strip().startswith("*")] # Untouch b = r.find('let untouched = "') e = r.find(';;', b) l_un = [i for i in r[b:e].splitlines() if i.strip().startswith("module")] # ~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~ # # q p _ t y p e s _ g e n e r a t e # # ~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~ # # Read the fancy_type, the ocaml. and convert the ocaml to the fortran for i in l_gen + l_un: str_fancy_type = i.split()[1].strip() str_ocaml_type = i.split()[3] if str_ocaml_type != 'sig': str_fortran_type = ocaml_to_fortran[str_ocaml_type] else: str_fortran_type = 'character*(32)' str_ocaml_type = 'string' fancy_type[str_fancy_type] = Type(str_fancy_type, str_ocaml_type, str_fortran_type) # ~#~#~#~#~#~#~#~ # # F i n a l i z e # # ~#~#~#~#~#~#~#~ # return dict(fancy_type) type_dict = get_type_dict() def get_dict_config_file(config_file_path, module_lower): """ Input: config_file_path is the config file path (for example FULL_PATH/EZFIO.cfg) module_lower is the MODULE name lowered (Ex fullci) Return a dict d[provider_name] = {type, doc, ezfio_name, ezfio_dir, size, interface, default} - Type : Is a Type named tuple who containt fortran and ocaml type - doc : Is the doc - ezfio_name : Will be the name of the file - ezfio_dir : Will be the folder who containt the ezfio_name * /ezfio_dir/ezfio_name * equal to MODULE_lower name by default. - interface : The provider is a imput or a output - default : The default value /!\ stored in a Type named type! if interface == input - size : Is the string read in ezfio.cgf who containt the size information (like 1 or =sum(ao_num)) """ # ~#~#~#~ # # I n i t # # ~#~#~#~ # d = defaultdict(dict) l_info_required = ["doc", "interface"] l_info_optional = ["ezfio_dir", "ezfio_name", "size"] # ~#~#~#~#~#~#~#~#~#~#~ # # L o a d _ C o n f i g # # ~#~#~#~#~#~#~#~#~#~#~ # config_file = ConfigParser.ConfigParser() config_file.readfp(open(config_file_path)) # ~#~#~#~#~#~#~#~#~ # # F i l l _ d i c t # # ~#~#~#~#~#~#~#~#~ # def error(o, p, c): "o option ; p provider_name ;c config_file_path" print "You need a {0} for {1} in {2}".format(o, p, c) for section in config_file.sections(): # pvd = provider pvd = section.lower() # Create the dictionary who containt the value per default d_default = {"ezfio_name": pvd, "ezfio_dir": module_lower, "size": "1"} # Check if type if avalaible type_ = config_file.get(section, "type") if type_ not in type_dict: print "{0} not avalaible. Choose in:".format(type_) print ", ".join(sorted([i for i in type_dict])) sys.exit(1) else: d[pvd]["type"] = type_dict[type_] # Fill the dict with REQUIRED information for option in l_info_required: try: d[pvd][option] = config_file.get(section, option) except ConfigParser.NoOptionError: error(option, pvd, config_file_path) sys.exit(1) # Fill the dict with OPTIONAL information for option in l_info_optional: try: d[pvd][option] = config_file.get(section, option).lower() except ConfigParser.NoOptionError: if option in d_default: d[pvd][option] = d_default[option] # If interface is input we need a default value information if d[pvd]["interface"].lower() == "input": try: default_raw = config_file.get(section, "default") except ConfigParser.NoOptionError: error("default", pvd, config_file_path) sys.exit(1) try: d[pvd]["default"] = is_bool(default_raw) except TypeError: d[pvd]["default"] = Type(None, default_raw, default_raw) return dict(d) def create_ezfio_provider(dict_ezfio_cfg): import re """ From dict d[provider_name] = {type, doc, ezfio_name, ezfio_dir, interface, default size} create the a list who containt all the code for the provider output = output_dict_info['ezfio_dir' return [code, ...] """ from ezfio_generate_provider import EZFIO_Provider dict_code_provider = dict() ez_p = EZFIO_Provider() for provider_name, dict_info in dict_ezfio_cfg.iteritems(): if "input" in dict_info["interface"]: ez_p.set_type(dict_info['type'].fortran) ez_p.set_name(provider_name) ez_p.set_doc(dict_info['doc']) ez_p.set_ezfio_dir(dict_info['ezfio_dir']) ez_p.set_ezfio_name(dict_info['ezfio_name']) ez_p.set_output("output_%s" % dict_info['ezfio_dir']) # (nuclei.nucl_num,pseudo.klocmax) => (nucl_num,klocmax) ez_p.set_size(re.sub(r'\w+\.', "", dict_info['size'])) dict_code_provider[provider_name] = str(ez_p) + "\n" return dict_code_provider def save_ezfio_provider(path_head, dict_code_provider): """ Write in path_head/"ezfio_interface.irp.f" the value of dict_code_provider """ path = "{0}/ezfio_interface.irp.f".format(path_head) try: f = open(path, "r") except IOError: old_output = "" else: old_output = f.read() f.close() l_output = ["! DO NOT MODIFY BY HAND", "! Created by $QPACKAGE_ROOT/scripts/ezfio_interface.py", "! from file {0}/EZFIO.cfg".format(path_head), "\n"] l_output += [code for code in dict_code_provider.values()] output = "\n".join(l_output) if output != old_output: with open(path, "w+") as f: f.write(output) def create_ezfio_stuff(dict_ezfio_cfg, config_or_default="config"): """ From dict_ezfio_cfg[provider_name] = {type, default, ezfio_name,ezfio_dir,doc} Return the string ezfio_interface_config """ def size_format_to_ezfio(size_raw): """ If size_raw == "=" is a formula -> do nothing; return Else convert the born of a multidimential array (12,begin:end) into (12,begin+end+1) for example If the value are between parenthses -> do nothing; return """ size_raw = str(size_raw) if size_raw.startswith('='): size_convert = size_raw else: size_raw = provider_info["size"].translate(None, "()") size_raw = size_raw.replace('.', '_') a_size_raw = [] for dim in size_raw.split(","): try: (begin, end) = map(str.strip, dim.split(":")) except ValueError: a_size_raw.append(dim) else: a_size_raw.append("{0}+{1}+1".format(begin, end)) size_raw = ",".join(a_size_raw) size_convert = "({0})".format(size_raw) return size_convert def create_format_string(size): """ Take a size number and return the string format for being right align with this offset """ return "{{0:<{0}}}".format(size).format # ~#~#~#~#~#~#~#~#~#~#~# # # F o r m a t _ i n f o # # ~#~#~#~#~#~#~#~#~#~#~# # lenmax_name = max([len(i) for i in dict_ezfio_cfg]) lenmax_type = max([len(i["type"].fortran) for i in dict_ezfio_cfg.values()]) str_name_format = create_format_string(lenmax_name + 2) str_type_format = create_format_string(lenmax_type + 2) # ~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~# # # C r e a t e _ t h e _ s t r i n g # # ~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~# # # Checking is many ezfio_dir provided l_ezfio_dir = [d['ezfio_dir'] for d in dict_ezfio_cfg.values()] if not l_ezfio_dir.count(l_ezfio_dir[0]) == len(l_ezfio_dir): print >> sys.stderr, "You have many ezfio_dir. Not supported yet" raise TypeError else: result = [l_ezfio_dir[0]] for provider_name, provider_info in sorted(dict_ezfio_cfg.iteritems()): # Get the value from dict name_raw = provider_info["ezfio_name"].lower() fortran_type_raw = provider_info["type"].fortran if "size" in provider_info and not provider_info["size"] == "1": size_raw = provider_info["size"] else: size_raw = None # It is the last so we don't need to right align it str_size = size_format_to_ezfio(size_raw) if size_raw else "" # Get the string in to good format (left align and co) str_name = str_name_format(name_raw) str_fortran_type = str_type_format(fortran_type_raw) # Return the string if config_or_default == "config": s = " {0} {1} {2}".format(str_name, str_fortran_type, str_size) elif config_or_default == "default": try: str_value = provider_info["default"].ocaml except KeyError: continue else: s = " {0} {1}".format(str_name, str_value) else: raise KeyError # Append result.append(s) return "\n".join(result) def create_ezfio_config(dict_ezfio_cfg): return create_ezfio_stuff(dict_ezfio_cfg, config_or_default="config") def save_ezfio_config(module_lower, str_ezfio_config): """ Write the str_ezfio_config in "$QPACKAGE_ROOT/EZFIO/{0}.ezfio_interface_config".format(module_lower) """ root_ezfio = "{0}/EZFIO".format(os.environ['QPACKAGE_ROOT']) path = "{0}/config/{1}.ezfio_interface_config".format(root_ezfio, module_lower) try: f = open(path, "r") except IOError: old_output = "" else: old_output = f.read() f.close() if str_ezfio_config != old_output: with open(path, "w+") as f: f.write(str_ezfio_config) def create_ezfio_default(dict_ezfio_cfg): return create_ezfio_stuff(dict_ezfio_cfg, config_or_default="default") def save_ezfio_default(module_lower, str_ezfio_default): """ Write the str_ezfio_config in "$QPACKAGE_ROOT/data/ezfio_defaults/{0}.ezfio_interface_default".format(module_lower) """ root_ezfio_default = "{0}/data/ezfio_defaults/".format( os.environ['QPACKAGE_ROOT']) path = "{0}/{1}.ezfio_interface_default".format(root_ezfio_default, module_lower) try: f = open(path, "r") except IOError: old_output = "" else: old_output = f.read() f.close() if str_ezfio_default != old_output: with open(path, "w+") as f: f.write(str_ezfio_default) def create_ocaml_input(dict_ezfio_cfg, module_lower): # ~#~#~#~# # # I n i t # # ~#~#~#~# # from ezfio_generate_ocaml import EZFIO_ocaml l_ezfio_name = [] l_type = [] l_doc = [] for k, v in dict_ezfio_cfg.iteritems(): if v['interface'] == "input": l_ezfio_name.append(v['ezfio_name']) l_type.append(v["type"]) l_doc.append(v["doc"]) if not l_ezfio_name: raise ValueError e_glob = EZFIO_ocaml(l_ezfio_name=l_ezfio_name, l_type=l_type, l_doc=l_doc) # ~#~#~#~#~#~#~#~# # # C r e a t i o n # # ~#~#~#~#~#~#~#~# # template = ['(* =~=~ *)', '(* Init *)', '(* =~=~ *)', ""] template += ["open Qptypes;;", "open Qputils;;", "open Core.Std;;", "", "module {0} : sig".format(module_lower.capitalize())] template += [e_glob.create_type()] template += [" val read : unit -> t option", " val write : t-> unit", " val to_string : t -> string", " val to_rst : t -> Rst_string.t", " val of_rst : Rst_string.t -> t option", "end = struct"] template += [e_glob.create_type()] template += ['', ' let get_default = Qpackage.get_ezfio_default "{0}";;'.format(module_lower), ''] template += ['(* =~=~=~=~=~=~==~=~=~=~=~=~ *)', '(* Generate Special Function *)', '(* =~=~=~==~=~~=~=~=~=~=~=~=~ *)', ""] for provider_name, d_val in sorted(dict_ezfio_cfg.iteritems()): if 'default' not in d_val: continue ezfio_dir = d_val["ezfio_dir"] ezfio_name = d_val["ezfio_name"] e = EZFIO_ocaml(ezfio_dir=ezfio_dir, ezfio_name=ezfio_name, type=d_val["type"]) template += [e.create_read(), e.create_write(), ""] template += ['(* =~=~=~=~=~=~=~=~=~=~=~=~ *)', '(* Generate Global Function *)', '(* =~=~=~=~=~=~=~=~=~=~=~=~ *)', ""] template += [e_glob.create_read_global(), e_glob.create_write_global(), e_glob.create_to_string(), e_glob.create_to_rst()] template += [" include Generic_input_of_rst;;", " let of_rst = of_rst t_of_sexp;;", "", "end"] return "\n".join(template) def save_ocaml_input(module_lower, str_ocaml_input): """ Write the str_ocaml_input in $QPACKAGE_ROOT/ocaml/Input_{0}.ml".format(module_lower) """ path = "{0}/ocaml/Input_{1}.ml".format(os.environ['QPACKAGE_ROOT'], module_lower) try: f = open(path, "r") except IOError: old_output = "" else: old_output = f.read() f.close() if str_ocaml_input != old_output: with open(path, "w+") as f: f.write(str_ocaml_input) def get_l_module_lower(): """ Get all module who have EZFIO.cfg with input data (NB `search` in all the ligne and `match` only in one) """ # ~#~#~#~ # # I n i t # # ~#~#~#~ # mypath = "{0}/src".format(os.environ['QPACKAGE_ROOT']) # ~#~#~#~#~#~#~#~ # # L _ f o l d e r # # ~#~#~#~#~#~#~#~ # l_folder = [f for f in listdir(mypath) if isdir(join(mypath, f))] # ~#~#~#~#~#~#~#~#~#~#~#~#~#~ # # L _ m o d u l e _ l o w e r # # ~#~#~#~#~#~#~#~#~#~#~#~#~#~ # l_module_lower = [] import re p = re.compile(ur'interface:\s+input') for f in l_folder: path = "{0}/{1}/EZFIO.cfg".format(mypath, f) if exists(path): with open(path, 'r') as file_: if p.search(file_.read()): l_module_lower.append(f.lower()) # ~#~#~#~#~#~ # # R e t u r n # # ~#~#~#~#~#~ # return l_module_lower def create_ocaml_input_global(): """ Check for all the EZFIO.cfg get the module lower then create incule {module_lower}.ml """ # ~#~#~#~# # # I n i t # # ~#~#~#~# # l_module_lower = get_l_module_lower() # ~#~#~#~#~#~#~#~# # # C r e a t i o n # # ~#~#~#~#~#~#~#~# # from ezfio_generate_ocaml import EZFIO_ocaml qpackage_root = os.environ['QPACKAGE_ROOT'] path = qpackage_root + "/scripts/ezfio_interface/qp_edit_template" with open(path, "r") as f: template_raw = f.read() e = EZFIO_ocaml(l_module_lower=l_module_lower) template = template_raw.format(keywords=e.create_qp_keywords(), keywords_to_string=e.create_qp_keywords_to_string(), section_to_rst=e.create_qp_section_to_rst(), write=e.create_qp_write(), tasks=e.create_qp_tasks()) input_auto = e.create_input_auto_generated() return (template, input_auto) def save_ocaml_input_auto(str_ocaml_input_global): """ Write the str_ocaml_input in $QPACKAGE_ROOT/ocaml/Input_auto_generated.ml """ path = "{0}/ocaml/Input_auto_generated.ml".format(os.environ['QPACKAGE_ROOT']) try: f = open(path, "r") except IOError: old_output = "" else: old_output = f.read() f.close() if str_ocaml_input_global != old_output: with open(path, "w+") as f: f.write(str_ocaml_input_global) def save_ocaml_qp_edit(str_ocaml_qp_edit): """ Write the str_ocaml_qp_edit in $QPACKAGE_ROOT/ocaml/qp_edit.ml """ path = "{0}/ocaml/qp_edit.ml".format(os.environ['QPACKAGE_ROOT']) try: f = open(path, "r") except IOError: old_output = "" else: old_output = f.read() f.close() if str_ocaml_qp_edit != old_output: with open(path, "w+") as f: f.write(str_ocaml_qp_edit) def code_generation(arguments, dict_ezfio_cfg, m): module_lower = m.lower path_dirname = m.path.replace("/EZFIO.cfg", "") # ~#~#~#~#~#~#~#~#~#~ # # W h a t _ t o _ d o # # ~#~#~#~#~#~#~#~#~#~ # if any([arguments[i] for i in ["--irpf90", "--ezfio_config", "--ocaml", "--ezfio_default"]]): # User changer somme argument, do what he want do_all = False else: # Do all the stuff do_all = True # ~#~#~#~#~#~#~ # # I R P . f 9 0 # # ~#~#~#~#~#~#~ # if do_all or arguments["--irpf90"]: l_str_code = create_ezfio_provider(dict_ezfio_cfg) save_ezfio_provider(path_dirname, l_str_code) # ~#~#~#~#~#~#~#~#~#~#~#~ # # e z f i o _ c o n f i g # # ~#~#~#~#~#~#~#~#~#~#~#~ # if do_all or arguments["--ezfio_config"]: str_ezfio_config = create_ezfio_config(dict_ezfio_cfg) save_ezfio_config(module_lower, str_ezfio_config) # ~#~#~#~#~#~# # O c a m l # # ~#~#~#~#~#~# if do_all or arguments["--ocaml"]: try: str_ocaml_input = create_ocaml_input(dict_ezfio_cfg, module_lower) except ValueError: pass else: save_ocaml_input(module_lower, str_ocaml_input) # ~#~#~#~#~#~#~#~#~#~#~#~#~ # # e z f i o _ d e f a u l t # # ~#~#~#~#~#~#~#~#~#~#~#~#~ # if do_all or arguments["--ezfio_default"]: str_ezfio_default = create_ezfio_default(dict_ezfio_cfg) save_ezfio_default(module_lower, str_ezfio_default) if __name__ == "__main__": arguments = docopt(__doc__) # ___ # | ._ o _|_ # _|_ | | | |_ # if arguments["ocaml_global"]: str_ocaml_qp_edit, str_ocaml_input_auto = create_ocaml_input_global() save_ocaml_input_auto(str_ocaml_input_auto) save_ocaml_qp_edit(str_ocaml_qp_edit) sys.exit(0) # ~#~#~#~#~#~#~#~#~#~#~#~#~#~# # # G e t _ m o d u l e _ d i r # # ~#~#~#~#~#~#~#~#~#~#~#~#~#~# # path_dirname = os.getcwd() root_module = [i for i in path_dirname.split("/") if i][-1] # ~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~# # # G e t _ l _ d i c t _ e z f i o _ c f g # # ~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~# # if arguments["--recursif"]: def true_link(f): return islink(join(path_dirname, f)) l_symlink = [f for f in listdir(path_dirname) if true_link(f)] else: l_symlink = [] l_module = [root_module] + l_symlink def check_ezfio(f): return exists() qpackage_root_src = join(os.environ['QPACKAGE_ROOT'], "src") l_module_with_ezfio = [] Module = namedtuple('Module', 'path lower') for f in l_module: path = join(qpackage_root_src, f, "EZFIO.cfg") if exists(path): l_module_with_ezfio.append(Module(path, f.lower())) l_dict_ezfio_cfg = [(get_dict_config_file(m.path, m.lower), m) for m in l_module_with_ezfio] # _ # / _ _| _ _ _ ._ _ ._ _. _|_ o _ ._ # \_ (_) (_| (/_ (_| (/_ | | (/_ | (_| |_ | (_) | | # _| for (dict_ezfio_cfg, m) in l_dict_ezfio_cfg: code_generation(arguments, dict_ezfio_cfg, m)