From c0bfeabbc39cd7449f59c8e1fd1fe9e5abba315a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 1 Mar 2015 15:08:37 +0100 Subject: [PATCH] feat(pyratemp): successfully generating make deps After minor modifications to pyratemp, it certainly does the job. What it **does NOT** do: * multiple outputs per template/command invocation * NICE embedding of code (like GSL can) It will do the job nonetheless, but mako might be worth a look --- .gitignore | 1 + Makefile | 16 +-- README.md | 2 +- etc/api/shared.yaml | 20 ++++ etc/api/shared.yml | 12 --- etc/bin/pyratemp.py | 230 +++++++++++++++++++++++++++++++++++++++++--- src/pyra/deps.pyra | 24 +++++ 7 files changed, 269 insertions(+), 36 deletions(-) create mode 100644 etc/api/shared.yaml delete mode 100644 etc/api/shared.yml create mode 100644 src/pyra/deps.pyra diff --git a/.gitignore b/.gitignore index 0a4af99849d..efcadb0302f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target +.api.deps Cargo.lock *.sublime-workspace *.xml diff --git a/Makefile b/Makefile index d715d0ee7d9..4ac33019b18 100644 --- a/Makefile +++ b/Makefile @@ -5,27 +5,29 @@ include Makefile.helpers PYTHON = python2.7 TPL = etc/bin/pyratemp.py +API_DEPS_TPL = src/pyra/deps.pyra +API_MAIN_TPL = src/pyra/main.pyra API_DEPS = .api.deps -API_SHARED_INFO = ./etc/api/shared.yml +API_SHARED_INFO = ./etc/api/shared.yaml API_JSON_FILES = $(shell find ./etc -type f -name '*-api.json') help: $(info Programs) - $(info ----> GSL: '$(GSL)') $(info ----> templat engine: '$(TPL)') $(info ) $(info Targets) $(info help - print this help) $(info api-deps - generate a file to tell make what API file dependencies will be) + $(info help-api - show all api targets to build individually) -json-to-xml: $(API_XML_FILES) -$(API_DEPS): $(API_SHARED_INFO) - $(TPL) -f $(API_SHARED_INFO) -d DEP_FILE=$@ +$(API_DEPS): $(API_SHARED_INFO) $(API_DEPS_TPL) + $(TPL) -f $(API_SHARED_INFO) $(API_DEPS_TPL) > $@ api-deps: $(API_DEPS) -clean: +include $(API_DEPS) + +clean: clean-api -rm $(API_DEPS) - -rm $(API_XML_FILES) diff --git a/README.md b/README.md index c9786d3a8e4..42159cb395a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The license of everything not explicitly under a different license are licensed What follows is a list of other material that is licensed differently. -* **./etc/bin/pyratemp.py** is licensed under MIT, as shown in [the header][pyratemp-header] of the file. +* **./etc/bin/pyratemp.py** is licensed under MIT-like, as shown in [the header][pyratemp-header] of the file. * **./etc/api/\*\*/*.json** are licensed under a [MIT-like google license][google-lic]. diff --git a/etc/api/shared.yaml b/etc/api/shared.yaml new file mode 100644 index 00000000000..7980e44b26c --- /dev/null +++ b/etc/api/shared.yaml @@ -0,0 +1,20 @@ +# Contains values shared among all API implementations +directories: + # directory under which all generated sources should reside + output: ./generated + # how to get from `output` back to common library + common: ../ + # where are all the API meta files + api_base: ./etc/api +api: + list: + - name: youtube + version: v3 + base_path: "etc/api" +cargo: + build_version: "0.0.1" + authors: + - Sebastian Thiel + keywords: [google, protocol] + # All APIs should live in the same repository + repository_url: https://github.com/Byron/youtube-rs diff --git a/etc/api/shared.yml b/etc/api/shared.yml deleted file mode 100644 index fddfa97e94b..00000000000 --- a/etc/api/shared.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Contains values shared among all API implementations -apis: - base_path: "etc/api" - - name: youtube - version: v3 -cargo: - build_version: "0.0.1" - authors: - - Sebastian Thiel - keywords: [google, protocol] - # All APIs should live in the same repository - repository_url: https://github.com/Byron/youtube-rs diff --git a/etc/bin/pyratemp.py b/etc/bin/pyratemp.py index fdf5df22a6c..0951e32cf29 100755 --- a/etc/bin/pyratemp.py +++ b/etc/bin/pyratemp.py @@ -1162,20 +1162,25 @@ def render(self, parsetree, data): output = [] do_else = False # use else/elif-branch? + def to_str(v): + if v is None: + return unicode() + return unicode(v) + if parsetree is None: return "" for elem in parsetree: if "str" == elem[0]: output.append(elem[1]) elif "sub" == elem[0]: - output.append(unicode(_eval(elem[1], data))) + output.append(to_str(_eval(elem[1], data))) elif "esc" == elem[0]: obj = _eval(elem[2], data) #prevent double-escape if isinstance(obj, _dontescape) or isinstance(obj, TemplateBase): - output.append(unicode(obj)) + output.append(to_str(obj)) else: - output.append(self.escapefunc(unicode(obj), elem[1])) + output.append(self.escapefunc(to_str(obj), elem[1])) elif "for" == elem[0]: do_else = True (names, iterable) = elem[1:3] @@ -1279,6 +1284,201 @@ def __init__(self, string=None,filename=None,parsetree=None, encoding='utf-8', d #========================================= + +class DictObject(object): + + """An object which wraps a dictionary to allow object.key access. + If the source dictionary doesn't contain any sub-dictionaries, the input + dict will be referenced. Otherwise it will be copied. + + An attribute error is raised if a value is not accessible. + + Please note that you cannot access dict keys which are not valid attribute names. + """ + + _default_dict = dict() + _unpackable_types = (dict, tuple, list) + + def __init__(self, indict=_default_dict): + """Initialize this instance from an input dictionary. If it contains other dictionaries, those will + trigger their parent dictionaries to be copied, as they will be used as DictObject themselves and + placed in the copy accordingly. + NOTE: other DictObjects are used by reference. Generally, this type tries to perform the least + amount of copying possible.""" + if indict is self._default_dict: + return + # end handle default instantiation, which makes us empty + if isinstance(indict, DictObject): + self.__dict__ = indict.__dict__ + return + # END handle special case, be a reference + dct = indict + for key, val in dct.items(): + if isinstance(val, self._unpackable_types): + dct = None + break + # END for each key-value pair + + if dct is None: + dct = dict(indict) + + def unpack(val): + """unpack helper""" + if isinstance(val, dict): + val = DictObject(val) + elif isinstance(val, (tuple, list)): + val = type(val)(unpack(item) for item in val) + return val + # END unpack + for key, val in dct.items(): + dct[key] = unpack(val) + # END for each k,v pair + # END handle recursive copy + self.__dict__ = dct + + def __str__(self): + return str(self.__dict__) + + def __repr__(self): + return repr(self.__dict__) + + def __getattr__(self, name): + return object.__getattribute__(self, name) + + def __getitem__(self, name): + try: + return getattr(self, name) + except AttributeError: + raise KeyError(name) + # end convert exception + + def __setitem__(self, name, value): + setattr(self, name, value) + + def __contains__(self, name): + return name in self.__dict__ + + def __len__(self): + return len(self.__dict__) + + def __iter__(self): + return iter(self.__dict__) + + def __eq__(self, other): + """Compares a possibly expensive comparison""" + if isinstance(other, DictObject): + # EXPENSIVE ! + return self.to_dict() == other.to_dict() + elif isinstance(other, dict): + return self.to_dict() == other + # end handle type of other + return self is other + + def update(self, other, **kwargs): + """Similar to dict.update""" + items = other + if hasattr(other, 'keys'): + items = other.items() + for item_list in (items, kwargs.items()): + for k, v in item_list: + setattr(self, k, v) + # end for each item list + + def to_dict(self, recursive=False): + """@return ourselves as normal dict + @param recursive if True, a recursive copy will be returned if required.""" + if recursive: + def obtain_needs_copy(value): + """figure out if a copy is required""" + if isinstance(value, DictObject): + return True + if isinstance(value, (tuple, list, set)): + for item in value: + if obtain_needs_copy(item): + return True + # end check needs copy + # end for each item in value + # end if instance is iterable + return False + # end check needs copy + + def unpack(val): + """unpack val recursively and copy it gently""" + if isinstance(val, DictObject): + val = val.to_dict(recursive) + elif isinstance(val, (tuple, list, set)): + val = type(val)(unpack(item) for item in val) + # end handle type resolution + return val + # end unpack + + needs_copy = False + for value in self.__dict__.values(): + if obtain_needs_copy(value): + needs_copy = True + break + # end check value + # END for each value + + if needs_copy: + new_dict = dict() + for key, val in self.__dict__.items(): + new_dict[key] = unpack(val) + # END for each key, value pair + return new_dict + # else: + # just fall through and return ourselves as dictionary + + # END handle recursion + return self.__dict__ + + def copy(self): + """@return a (deep) copy of self""" + return type(self)(self.to_dict()) + + def clone(self): + """@return a deep copy of this dict. This onyl means that the key-sets are independent. However, the + values are still shared, which matters in case of lists for instance""" + return type(self)(deepcopy(self.to_dict(recursive=True))) + + def inversed_dict(self): + """@return new dictionary which uses this dicts keys as values, and values as keys + @note duplicate values will result in just a single key, effectively drupping items. + Use this only if you have unique key-value pairs""" + return dict(list(zip(list(self.__dict__.values()), list(self.__dict__.keys())))) + + def get(self, name, default=None): + """as dict.get""" + return self.__dict__.get(name, default) + + def keys(self): + """as dict.keys""" + return list(self.__dict__.keys()) + + def values(self): + """as dict.values""" + return list(self.__dict__.values()) + + def items(self): + """as dict.items""" + return list(self.__dict__.items()) + + def iteritems(self): + """as dict.iteritems""" + return iter(self.__dict__.items()) + + def pop(self, key, default=re): + """as dict.pop""" + if default is re: + return self.__dict__.pop(key) + else: + return self.__dict__.pop(key, default) + # end assure semantics are kept + + +# end class DictObject + + __version__ = "0.3.2" __author__ = "Roland Koebler " __license__ = """Copyright (c) 2007-2013 by Roland Koebler @@ -1344,6 +1544,7 @@ def load_data(datafiles): mydata = {} for filename, n, namespace in datafiles: + data = None if filename[-5:].lower() == ".json": if not imported_json: try: @@ -1352,14 +1553,9 @@ def load_data(datafiles): import json imported_json = True try: - myjson = json.load(open(filename, 'r')) + data = json.load(open(filename, 'r')) if n != -1: - myjson = myjson[n] - - if namespace is None: - mydata.update(myjson) - else: - mydata.update({namespace: myjson}) + data = data[n] except ValueError as err: raise ValueError("Invalid JSON in file '%s'. (%s)" % (filename, str(err))) elif filename[-5:].lower() == ".yaml": @@ -1368,13 +1564,16 @@ def load_data(datafiles): imported_yaml = True if n == -1: n = 0 - myyaml = yaml.load_all(open(filename, 'r')) - if namespace is not None: - mydata.update({namespace: list(myyaml)[n]}) - else: - mydata.update(list(myyaml)[n]) + data = yaml.load_all(open(filename, 'r')) + data = list(data)[n] else: raise ValueError("Invalid data-file '%s', must be .json or .yaml" % filename) + assert data is not None + data = DictObject(data) + if namespace is None: + mydata.update(data) + else: + mydata.update({namespace: data}) return mydata #----------------------------------------- @@ -1466,4 +1665,3 @@ def load_data(datafiles): sys.exit(30) #----------------------------------------- - diff --git a/src/pyra/deps.pyra b/src/pyra/deps.pyra new file mode 100644 index 00000000000..9dab692a3be --- /dev/null +++ b/src/pyra/deps.pyra @@ -0,0 +1,24 @@ +$!setvar("api_info", "[]")!$#! + + +$!setvar("gen_root", "directories.output + '/' + a.name + '_' + a.version")!$#! +$!setvar("api_name", "a.name+a.version")!$#! +$!setvar("api_clean", "api_name+'-clean'")!$#! +$!gen_root!$: $!directories.api_base!$/$!a.name!$/$!a.version!$/$!a.name!$-api.json +$!api_name!$: $!gen_root!$ +$!api_clean!$: + rm -Rf $!gen_root!$ +$!api_info.append((api_name, api_clean, gen_root))!$#! + + + +.PHONY += $(.PHONY) $!' '.join(a[0] for a in api_info)!$ $!' '.join(a[1] for a in api_info)!$ + +help-api: + + $(info $!a[0]!$ - build the $!a[0]!$ api) + $(info $!a[1]!$ - clean all generated files of the $!a[0]!$ api) + + + +clean-api: $!' '.join(a[1] for a in api_info)!$