From c2b46ccc637945c33d6742b06a2f3984ebd5bf6b Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:07:24 +0100 Subject: [PATCH 01/20] Create Python reference documentation --- bindings/python/dlite-collection-python.i | 10 +- bindings/python/dlite-entity-python.i | 2 +- bindings/python/dlite-misc-python.i | 6 +- bindings/python/triplestore/README.md | 148 -------- bindings/python/triplestore/__init__.py | 13 - bindings/python/triplestore/test_units.py | 31 -- bindings/python/triplestore/units.py | 364 -------------------- bindings/python/triplestore/units_README.md | 61 ---- doc/CMakeLists.txt | 13 +- doc/Doxyfile.in | 8 +- doc/conf.py.in | 28 +- doc/getting_started/build/patch_activate.md | 2 + requirements_doc.txt | 46 ++- src/getuuid.h | 2 +- 14 files changed, 73 insertions(+), 661 deletions(-) delete mode 100644 bindings/python/triplestore/README.md delete mode 100644 bindings/python/triplestore/__init__.py delete mode 100644 bindings/python/triplestore/test_units.py delete mode 100644 bindings/python/triplestore/units.py delete mode 100644 bindings/python/triplestore/units_README.md diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i index de53c2309..62839d0e7 100644 --- a/bindings/python/dlite-collection-python.i +++ b/bindings/python/dlite-collection-python.i @@ -64,10 +64,10 @@ class Collection(Instance): Relations are (s, p, o, d=None)-triples with an optional fourth field `d`, specifying the datatype of the object. The datatype may have the following values: - - None: object is an IRI. - - Starts with '@': object is a language-tagged plain literal. - The language identifier follows the '@'-sign. - - Otherwise: object is a literal with datatype `d`. + - None: object is an IRI. + - Starts with '@': object is a language-tagged plain literal. + The language identifier follows the '@'-sign. + - Otherwise: object is a literal with datatype `d`. """ def __new__(cls, id=None): """Creates an empty collection.""" @@ -85,7 +85,7 @@ class Collection(Instance): Arguments: src: Storage instance | url | driver - location: str + location: File path to load from when `src` is a driver. options: str Options passed to the storage plugin when `src` is a driver. diff --git a/bindings/python/dlite-entity-python.i b/bindings/python/dlite-entity-python.i index 7a572c2ef..c65c9ede2 100644 --- a/bindings/python/dlite-entity-python.i +++ b/bindings/python/dlite-entity-python.i @@ -459,7 +459,7 @@ def get_instance( protocol+driver://location?options#id protocol://location?driver=;options#id - where `protocol`, `driver`, `location`, `options` and `id are + where `protocol`, `driver`, `location`, `options` and `id` are documented in the load() method. If `metaid` is provided, the instance is tried mapped to this diff --git a/bindings/python/dlite-misc-python.i b/bindings/python/dlite-misc-python.i index 36b2e2508..544989b0c 100644 --- a/bindings/python/dlite-misc-python.i +++ b/bindings/python/dlite-misc-python.i @@ -28,9 +28,9 @@ class errctl(): shown/hidden. filename: Filename to redirect errors to. The following values are handled specially: - - "None" or empty: No output is written. - - "": Write errors to stderr (default). - - "": Write errors to stdout. + - "None" or empty: No output is written. + - "": Write errors to stderr (default). + - "": Write errors to stdout. """ def __init__(self, hide=(), show=(), filename=""): diff --git a/bindings/python/triplestore/README.md b/bindings/python/triplestore/README.md deleted file mode 100644 index 53d3baf25..000000000 --- a/bindings/python/triplestore/README.md +++ /dev/null @@ -1,148 +0,0 @@ -Triplestore -=========== -> A Python package encapsulating different triplestores using the strategy -> design pattern. - -This package has by itself no dependencies outside the standard library, -but the triplestore backends may have. - -The main class is Triplestore, who's `__init__()` method takes the name of the -backend to encapsulate as first argument. Its interface is strongly inspired -by rdflib.Graph, but simplified when possible to make it easy to use. Some -important differences: -- all IRIs are represented by Python strings -- blank nodes are strings starting with "_:" -- literals are constructed with `Literal()` - -```python -from triplestore import Triplestore -ts = Triplestore(backend="rdflib") -``` - -The module already provides a set of pre-defined namespaces that simplifies -writing IRIs. For example: - -```python -from triplestore import RDFS, OWL -RDFS.subClassOf -# -> 'http://www.w3.org/2000/01/rdf-schema#subClassOf' -``` - -New namespaces can be created using the Namespace class, but are usually -added with the `bind()` method: - -```python -ONTO = ts.bind("onto", "http://example.com/onto#") -ONTO.MyConcept -# -> 'http://example.com/onto#MyConcept' -``` - -Namespace also support access by label and IRI checking. Both of these features -requires loading an ontology. The following example shows how to create an EMMO -namespace with IRI checking. The keyword argument `label_annotations=True` enables -access by `skos:prefLabel`, `rdfs:label` or `skos:altLabel`. The `check=True` -enables checking for existing IRIs. The `triplestore_url=...` is a resolvable URL -that can be read by the 'rdflib' backend. It is needed, because the 'rdflib' -backend is currently not able to load EMMO from the "http://emmo.info/emmo#" -namespace. - -```python -EMMO = ts.bind( - "emmo", "http://emmo.info/emmo#", - label_annotations=True, - check=True, - triplestore_url="https://emmo-repo.github.io/versions/1.0.0-beta4/emmo-inferred.ttl", -) -EMMO.Atom -# -> 'http://emmo.info/emmo#EMMO_eb77076b_a104_42ac_a065_798b2d2809ad' -EMMO.invalid_name -# -> NoSuchIRIError: http://emmo.info/emmo#invalid_name -``` - -New triples can be added either with the `parse()` method (for -backends that support it) or the `add()` and `add_triples()` methods: - -```python -# en(msg) is a convenient function for adding english literals. -# It is equivalent to ``triplestore.Literal(msg, lang="en")``. -from triplestore import en -ts.parse("onto.ttl", format="turtle") -ts.add_triples([ - (ONTO.MyConcept, RDFS.subClassOf, OWL.Thing), - (ONTO.MyConcept, RDFS.label, en("My briliant ontological concept.")), -]) -``` - -For backends that support it the triplestore can be serialised using -`serialize()`: - -```python -ts.serialize("onto2.ttl") -``` - -A set of convenient functions exists for simple queries, including -`triples()`, `subjects()`, `predicates()`, `objects()`, `subject_predicates()`, -`subject_objects()`, `predicate_objects()` and `value()`. Except for `value()`, -they return the result as generators. For example: - -```python -ts.objects(subject=ONTO.MyConcept, predicate=RDFS.subClassOf) -# -> -list(ts.objects(subject=ONTO.MyConcept, predicate=RDFS.subClassOf)) -# -> ['http://www.w3.org/2002/07/owl#Thing'] -``` - -The `query()` and `update()` methods can be used to query and update the -triplestore using SPARQL. - -Finally Triplestore has two specialised methods `add_mapsTo()` and -`add_function()` that simplify working with mappings. `add_mapsTo()` is -convinient for defining new mappings: - -```python -from triplestore import Namespace -META = Namespace("http://onto-ns.com/meta/0.1/MyEntity#") -ts.add_mapsTo(ONTO.MyConcept, META.my_property) -``` - -It can also be used with DLite and SOFT7 data models. Here we repeat -the above with DLite: - -```python -import dlite -meta = dlite.get_instance("http://onto-ns.com/meta/0.1/MyEntity") -ts.add_mapsTo(ONTO.MyConcept, meta, "my_property") -``` - -The `add_function()` describes a function and adds mappings for its -arguments and return value(s). Currently it only supports the [Function -Ontology (FnO)](https://fno.io/). - -```python -def mean(x, y): - """Returns the mean value of `x` and `y`.""" - return (x + y)/2 - -ts.add_function( - mean, - expects=(ONTO.RightArmLength, ONTO.LeftArmLength), - returns=ONTO.AverageArmLength, -) -``` - - -Further development -------------------- -* Update the `query()` method to return the SPARQL result in a backend- - independent way. -* Add additional backends. Candidates include: - - list of tuples - - owlready2/EMMOntoPy - - OntoRec/OntoFlowKB - - Stardog - - DLite triplestore (based on Redland librdf) - - Redland librdf - - Apache Jena Fuseki - - Allegrograph - - Wikidata -* Add ontological validation of physical dimension to Triplestore.mapsTo(). diff --git a/bindings/python/triplestore/__init__.py b/bindings/python/triplestore/__init__.py deleted file mode 100644 index cb99656fe..000000000 --- a/bindings/python/triplestore/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""A package encapsulating different triplestores using the strategy design -pattern. - -See the README.md file for a description for how to use this package. -""" -import warnings - -warnings.warn( - "dlite.triplestore is deprecated.\n" - "Use tripper (https://github.com/EMMC-ASBL/tripper) instead.", - DeprecationWarning, - stacklevel=2, -) diff --git a/bindings/python/triplestore/test_units.py b/bindings/python/triplestore/test_units.py deleted file mode 100644 index 59857446f..000000000 --- a/bindings/python/triplestore/test_units.py +++ /dev/null @@ -1,31 +0,0 @@ -from units import get_pint_registry - - -ureg = get_pint_registry(force_recreate=True) - -# Test the registry. -test_quantity1 = 1234 * ureg.M -print(str(test_quantity1.to_base_units())) -assert str(test_quantity1) == "1234 m" - -test_quantity2 = 2345.6 * ureg.Watt_per_Kelvin -print(str(test_quantity2)) -assert str(test_quantity2) == "2345.6 w/K" - -test_quantity3 = test_quantity1 * test_quantity2 -print("".join([str(test_quantity3), " = ", - str(test_quantity3.to_base_units()), - " = ", - "{:~}".format(test_quantity3.to_base_units())])) - -test_quantity4 = 16 * ureg.S -print(f'{str(test_quantity4)} = {test_quantity4.to_base_units()}') -assert str(test_quantity4) == "16 S" - -test_quantity5 = 25 * ureg.s -print(f'{str(test_quantity5)} = {test_quantity5.to_base_units()}') -assert str(test_quantity5) == "25 s" - -test_quantity6 = 36 * ureg.exaAmpere -print(f'{str(test_quantity6)} = {test_quantity6.to_base_units()}') -assert str(test_quantity6) == "36 EA" \ No newline at end of file diff --git a/bindings/python/triplestore/units.py b/bindings/python/triplestore/units.py deleted file mode 100644 index dc8d97af5..000000000 --- a/bindings/python/triplestore/units.py +++ /dev/null @@ -1,364 +0,0 @@ -"""Populates a pint unit registry from an ontology. -""" -import os -import re -import logging -from pint import UnitRegistry -from tripper import Triplestore, RDFS -from appdirs import user_cache_dir - - -def load_qudt(): - """Returns a Triplestore instance with QUDT pre-loaded.""" - ts = Triplestore(backend="rdflib") - ts.parse(source="http://qudt.org/2.1/vocab/unit") - ts.parse(source="http://qudt.org/2.1/schema/qudt") - return ts - - -def parse_qudt_dimension_vector(dimension_vector: str) -> dict: - """Split the dimension vector string into separate dimensions.""" - dimensions = re.findall(r'[AELIMHTD]-?[0-9]+', dimension_vector) - - result = {} - for dimension in dimensions: - result[dimension[0]] = dimension[1:] - - for letter in "AELIMHTD": - if letter not in result.keys(): - raise Exception( - f'Missing dimension "{letter}" in dimension vector ' - f'"{dimension_vector}"' - ) - return result - - -def pint_definition_string(dimension_dict: dict) -> str: - # Base units defined by: - # https://qudt.org/schema/qudt/QuantityKindDimensionVector - # https://qudt.org/vocab/sou/SI - base_units = { - "A": "mol", - "E": "A", - "L": "m", - "I": "cd", - "M": "kg", - "H": "K", - "T": "s", - "D": "1", - } - - # Build the unit definition, dimension by dimension. - result = [] - for letter, unit in base_units.items(): - exponent = dimension_dict[letter] - if int(dimension_dict[letter]) < 0: - result.append(f"/ {unit}**{exponent[1:]} ") - elif int(dimension_dict[letter]) > 0: - result.append(f"* {unit}**{exponent} ") - return "".join(result) - - -def prepare_cache_file_path(filename: str) -> str: - """Return cache file name.""" - cache_directory = user_cache_dir("dlite") - if not os.path.exists(cache_directory): - os.makedirs(cache_directory) - return os.path.join(cache_directory, filename) - - -def get_pint_registry(sources=('qudt', ), force_recreate=False) -> UnitRegistry: - """Load units from one or more unit sources into a Pint unit registry. - - Arguments: - sources: Sequence of unit sources to load. The sources are loaded - in the provided order. In case of conflicts, the source listed - first has precedence. - force_recreate: Whether to recreate the unit registry cache. - - Returns: - Pint unit registry. - """ - registry_file_path = prepare_cache_file_path("pint_unit_registry.txt") - if force_recreate or not os.path.exists(registry_file_path): - with open(registry_file_path, "w", encoding="utf8") as f: - f.write("\n".join(pint_prefix_lines()) + "\n") - for source in sources: - pint_registry_lines = pint_registry_lines_from_qudt() - with open(registry_file_path, "a", encoding="utf8") as f: - f.write("\n".join(pint_registry_lines) + "\n") - - ureg = UnitRegistry(registry_file_path) - #ureg.default_format = "~P" #symbols, pretty print - ureg.default_format = "~" #symbols, standard print (preferred) - #ureg.default_format = "~C" #symbols, compact print - return ureg - - -def pint_prefix_lines(): - # Decimal prefixes from pint's default_en.txt registry. - prefixes = ['quecto- = 1e-30 = q-', - 'ronto- = 1e-27 = r-', - 'yocto- = 1e-24 = y-', - 'zepto- = 1e-21 = z-', - 'atto- = 1e-18 = a-', - 'femto- = 1e-15 = f-', - 'pico- = 1e-12 = p-', - 'nano- = 1e-9 = n-', - 'micro- = 1e-6 = µ- = μ- = u-', - 'milli- = 1e-3 = m-', - 'centi- = 1e-2 = c-', - 'deci- = 1e-1 = d-', - 'deca- = 1e+1 = da- = deka-', - 'hecto- = 1e2 = h-', - 'kilo- = 1e3 = k-', - 'mega- = 1e6 = M-', - 'giga- = 1e9 = G-', - 'tera- = 1e12 = T-', - 'peta- = 1e15 = P-', - 'exa- = 1e18 = E-', - 'zetta- = 1e21 = Z-', - 'yotta- = 1e24 = Y-', - 'ronna- = 1e27 = R-', - 'quetta- = 1e30 = Q-', - ] - return prefixes - - -def prefix_names(): - lines = pint_prefix_lines() - for i in range(len(lines)): - lines[i] = lines[i].split(" ")[0].replace("-", "") - return lines - - -def pint_registry_lines_from_qudt(): - ts = load_qudt() - - QUDTU = ts.bind("unit", "http://qudt.org/vocab/unit/", check=True) - QUDT = ts.bind("unit", "http://qudt.org/schema/qudt/", check=True) - DCTERMS = ts.bind("dcterms", "http://purl.org/dc/terms/") - - pint_registry_lines = [] - pint_definitions = {} - identifiers = PintIdentifiers() - - # Explicit definition of which QUDT units that will serve as base units for - # the pint unit registry. (i.e. the QUDT names for the SI units and the - # name of their physical dimension) - #base_unit_dimensions ={ - # "M": "length", - # "SEC": "time", - # "A": "current", - # "CD": "luminosity", - # "KiloGM": "mass", - # "MOL": "substance", - # "K": "temperature", - #} - - # Base units defined by rdfs:label (instead of QUDT name): - base_unit_dimensions ={ - "Meter": "length", - "Second": "time", - "Ampere": "current", - "Candela": "luminosity", - "Kilogram": "mass", - "Mole": "substance", - "Kelvin": "temperature", - } - - # Read info from all units. - for s, p, o in ts.triples([None, QUDT.hasDimensionVector, None]): - - # Check if this unit has been replaced; then skip it. - replaced_by = next( - ts.objects(subject=s, predicate=DCTERMS.isReplacedBy), None) - if replaced_by is not None: - continue - - unit = s.split("/")[-1] - unit_name = unit.replace("-", "_") - - omit_prefixed_unit = False - for prefix in prefix_names(): - if unit_name.startswith('KiloGM'): - pass - elif unit_name.lower().startswith(prefix): - omit_prefixed_unit = True - break - - if omit_prefixed_unit: - continue - - # Extract and parse the dimension vector. - dimension_vector = o.split("/")[-1] - pint_definition = pint_definition_string(parse_qudt_dimension_vector( - dimension_vector)) - - # Extract multiplier and offset. - multiplier = next( - ts.objects(subject=s, predicate=QUDT.conversionMultiplier), "1") - offset = next(ts.objects(subject=s, predicate=QUDT.conversionOffset), - None) - - pint_definitions[s] = { - "unit_in_SI": pint_definition, - "multiplier": multiplier, - "offset": offset, - } - - # Extract identifiers. - pint_name_is_set = False - prio_downgrade = 2 - for label in ts.objects(subject=s, predicate=RDFS.label): - label = label.replace(" ", "_") - label = label.replace("-", "_") - if pint_name_is_set: - identifiers.add_identifier( - URI=s, label_name="label", prio=5, identifier=label) - else: - if label in base_unit_dimensions.keys(): - prio_downgrade = 0 - identifiers.add_identifier( - URI=s, label_name="unit_name", prio=1+prio_downgrade, identifier=label) - pint_name_is_set = True - - identifiers.add_identifier( - URI=s, label_name="label", prio=6, identifier=unit_name) - # Can there be more than one symbol in QUDT? - symbol = next(ts.objects(subject=s, predicate=QUDT.symbol), None) - if symbol is not None: - symbol = symbol.replace(" ", "_") - identifiers.add_identifier( - URI=s, label_name="symbol", prio=2+prio_downgrade, identifier=symbol) - udunits_code = next( - ts.objects(subject=s, predicate=QUDT.udunitsCode), None) - if udunits_code is not None: - udunits_code = udunits_code.replace(" ", "_") - identifiers.add_identifier( - URI=s, label_name="udunits_code", prio=7, identifier=udunits_code) - - identifiers.remove_ambiguities() - - # Build the pint unit registry lines. - for URIb, definition in pint_definitions.items(): - - unit_identifiers = identifiers.get_identifiers(URI=URIb) - - # Start constructing the pint definition line. - unit_name = unit_identifiers["unit_name"] - if unit_name is None: - logging.warning(f'Omitting UNIT {URIb} due to name conflict.') - continue - if unit_name in base_unit_dimensions.keys(): - pint_definition_line = ( - f'{unit_name} = [{base_unit_dimensions[unit_name]}]' - ) - else: - pint_definition_line = ( - f'{unit_name} = {definition["multiplier"]} ' - f'{definition["unit_in_SI"]}' - ) - - # Add offset. - if definition["offset"] is not None: - pint_definition_line += f'; offset: {definition["offset"]}' - - # Add symbol. - symbol = unit_identifiers["symbol"] - if symbol is None: - symbol = "_" - pint_definition_line += f' = {symbol}' - - # Add any labels. - for label in unit_identifiers["labels"]: - if label is not None: - pint_definition_line += f' = {label}' - - # Add URI. - pint_definition_line += f' = {URIb}' - - # Add udunits code. - udunits_code = unit_identifiers["udunits_code"] - if udunits_code is not None: - pint_definition_line += f' = {udunits_code}' - - pint_registry_lines.append(pint_definition_line) - return pint_registry_lines - - -class PintIdentifiers: - """Class for handling the various identifiers, with the functionality - to remove any ambiguous definitions. - """ - def __init__(self): - self.URIs = [] - self.label_names = [] - self.prios = [] - self.identifiers = [] - - def add_identifier(self, URI: str, label_name: str, prio: int, - identifier:str): - self.URIs.append(URI) - self.label_names.append(label_name) - self.prios.append(prio) - self.identifiers.append(identifier) - - def remove_ambiguities(self): - """Remove ambiguities. - - Set ambiguous identifiers to None. - Keep the first occurence within each priority level. - """ - # Store used identifiers along with their URI. - used_identifiers = {} - - # For each priority level, remove any ambiguities. - for prio in sorted(list(set(self.prios))): - inds_prio = [i for i,value in enumerate(self.prios) if value==prio] - for i in inds_prio: - if self.identifiers[i] is not None: - # Check if the identifier has already been used. - if self.identifiers[i] in used_identifiers.keys(): - # Warn if this identifier belongs to another URI. - URI_of_identifier = used_identifiers[self.identifiers[i]] - if self.URIs[i] is not URI_of_identifier: - logging.warning( - f'Omitting {self.label_names[i]} ' - f'"{self.identifiers[i]}" from {self.URIs[i]} ' - f'(the identifier is used for ' - f'{URI_of_identifier})' - ) - self.identifiers[i] = None - else: - used_identifiers[self.identifiers[i]] = self.URIs[i] - - - def is_valid_identifier(self, identifier: str, URI: str, - label_name: str) -> bool: - """Check if an identifier is valid for use as a particular - label_name for a particular unit. - """ - identifier_index = self.identifiers.index(identifier) - return (self.URIs[identifier_index] == URI and - self.label_names[identifier_index] == label_name) - - def get_identifiers(self, URI:str) -> dict: - """Returns a dict containing all identifiers for a given URI.""" - identifiers = {} - identifiers["labels"] = [] - - inds = [i for i,value in enumerate(self.URIs) if value==URI] - for i in inds: - label_name = self.label_names[i] - identifier = self.identifiers[i] - if label_name == "unit_name": - identifiers["unit_name"] = identifier - elif label_name == "symbol": - identifiers["symbol"] = identifier - elif label_name == "label": - identifiers["labels"].append(identifier) - elif label_name == "udunits_code": - identifiers["udunits_code"] = identifier - - return identifiers diff --git a/bindings/python/triplestore/units_README.md b/bindings/python/triplestore/units_README.md deleted file mode 100644 index 8680a8e54..000000000 --- a/bindings/python/triplestore/units_README.md +++ /dev/null @@ -1,61 +0,0 @@ -Pint unit registry generator -============================ - -Introduction and usage ----------------------- - -The units.py file contains the get_pint_registry() function, which downloads -the [QUDT UNITS](https://www.qudt.org/doc/DOC_VOCAB-UNITS.html) vocabulary -and uses its contents to generate a unit registry file for -[Pint](https://pint.readthedocs.io). The function then uses the generated -registry file to generate and return a Pint UnitRegistry object. - -The unit registry file is cached, and the default behavior is to not -re-recreate it if it already exists in the cache directory. - -Any unit identifiers in QUDT UNITS that use the "-" or " " characters will -have these replaced with "_" (in order to be Pint compatible). - -The usage of the get_pint_registry() is demonstrated in test_units.py. - - -Technical details ------------------ -* Unit registry filename: `pint_unit_registry.txt` -* Cache directory in Unix/Linux: typically `~/.cache/dlite` -* Cache directory in Windows 10: typically `C:\Users\\AppData\Local\ -Packages\PythonSoftwareFoundation.Python.\LocalCache\Local\dlite\ -dlite\Cache` -* Cache directory in Mac OS X: presumably `~/Library/Caches/dlite` (not tested) - -For the units, all identifiers, alternative labels and definitions are -read from QUDT. The program resolves naming conflicts by omitting the -conflicting labels, following a certain prioritization. Highest priority is -given to the rdfs:label identifiers, which are used as the primary -identifier ("canonical name") in the Pint unit registry. - -Prefix definitions are hard-coded and not read from QUDT. Units in QUDT UNITS -that start with a prefix are omitted, since Pint performs reasoning based on -the prefix definitions in its unit registry. The "KiloGM" SI unit is -excepted from this rule. - - -Known problems --------------- -* The program does not yet work on Windows, see -[issue #497](https://github.com/SINTEF/dlite/issues/497). - -* The program provides warnings (at registry creation time) for **omitted -units and/or labels**, with details about any label conflicts. This output -can be used to identify duplications and inconsistencies within QUDT UNITS. - -* Since the QUDT UNITS vocabulary is subject to change, so are the omitted -units and labels. When a unit is assigned a new label in QUDT UNITS, this -may take precedence over currently existing labels on other units (depending -on the prioritization of the various label types). Since QUDT UNITS seems -not to be consistency-checked before release, this can result in (possibly -undetected) changed references to the units of interest. - -* Units that contain a prefix inside its name are currently included in the -generated Pint unit registry. - diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 4c1b4b934..9e5667cdd 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -14,7 +14,14 @@ set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html") set(SPHINX_INDEX_FILE "${SPHINX_HTML_DIR}/index.html") # Doxygen intput source directory -set(DOXYGEN_INPUT_DIR ${dlite_SOURCE_DIR}/src) +set(DOXYGEN_INPUT_DIRS + ${dlite_SOURCE_DIR}/src/utils + ${dlite_SOURCE_DIR}/src + ${dlite_SOURCE_DIR}/src/pyembed + #${dlite_BINARY_DIR}/bindings/python/dlite +) +string(REPLACE ";" " " DOXYGEN_INPUT "${DOXYGEN_INPUT_DIRS}") + # Doxygen output directory set(DOXYGEN_OUTPUT_XML_DIR "${CMAKE_CURRENT_BINARY_DIR}/xml") @@ -53,8 +60,8 @@ configure_file( # Doxygen command add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} DEPENDS ${DLITE_PUBLIC_HEADERS} - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_IN} ${DOXYFILE_OUT} - MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + MAIN_DEPENDENCY ${DOXYFILE_OUT} WORKING_DIRECTORY ${BINARY_BUILD_DIR} COMMENT "Generating docs" VERBATIM) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 202ecaf1e..0a9715e4a 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -775,7 +775,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = "@DOXYGEN_INPUT_DIR@" +INPUT = @DOXYGEN_INPUT@ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -831,7 +831,8 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = */tests/* \ + */old/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -848,7 +849,8 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = @dlite_SOURCE_DIR@/src/utils/tests +EXAMPLE_PATH = @dlite_SOURCE_DIR@/examples \ + @dlite_SOURCE_DIR@/src/utils/tests/tgen_example.c # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and diff --git a/doc/conf.py.in b/doc/conf.py.in index f9bada93e..8f7ef6bc4 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -51,15 +51,7 @@ exclude_patterns = [ ] extensions = [ - # Temporary disable autoapi since it leads to the following error during - # build of HTML documentation - # - # Extension error (autoapi.extension): - # Handler for event - # 'builder-inited' threw an exception (exception: 'Module' object has no - # attribute 'doc') - # - #"autoapi.extension", + "autoapi.extension", "breathe", # Doxygen bridge "myst_nb", # markdown source support & support for Jupyter notebooks "sphinx.ext.graphviz", # Graphviz @@ -84,12 +76,13 @@ dlite_share_plugins = [ if plugin_dir.is_dir() ] -autoapi_dirs = [ - "@CMAKE_BINARY_DIR@/bindings/python/dlite" -] + [ - f"@CMAKE_BINARY_DIR@/bindings/python/dlite/share/dlite/{plugin_dir}" - for plugin_dir in dlite_share_plugins -] +#autoapi_dirs = [ +# "@CMAKE_BINARY_DIR@/bindings/python/dlite" +#] + [ +# f"@CMAKE_BINARY_DIR@/bindings/python/dlite/share/dlite/{plugin_dir}" +# for plugin_dir in dlite_share_plugins +#] +autoapi_dirs = ["@CMAKE_BINARY_DIR@/bindings/python/dlite"] autoapi_type = "python" autoapi_file_patterns = ["*.py", "*.pyi"] autoapi_template_dir = "_templates/autoapi" @@ -103,7 +96,10 @@ autoapi_options = [ "imported-members", ] autoapi_keep_files = True # Should be False in production +#autoapi_keep_files = False # Should be False in production autoapi_python_use_implicit_namespaces = True # True to avoid namespace being `python.dlite` +#autoapi_ignore = ["@CMAKE_BINARY_DIR@/doc/_build/autoapi/dlite/dlite"] + autodoc_typehints = "description" autodoc_typehints_format = "short" @@ -127,7 +123,7 @@ html_theme_options = { "use_issues_button": True, "use_fullscreen_button": True, "use_repository_button": True, - "logo_only": True, + #"logo_only": True, "show_navbar_depth": 1, "announcement": "This documentation is under development!", } diff --git a/doc/getting_started/build/patch_activate.md b/doc/getting_started/build/patch_activate.md index 1186ad031..668113733 100644 --- a/doc/getting_started/build/patch_activate.md +++ b/doc/getting_started/build/patch_activate.md @@ -1,3 +1,5 @@ +Patch activate +============== By default, [virtualenv] does not set `LD_LIBRARY_PATH`. This will result in errors when running applications that links to libdlite, like for example, `dlite-codegen`. To fix this, `$VIRTUAL_ENV/lib/` needs to be appended/prepended to `LD_LIBRARY_PATH`. diff --git a/requirements_doc.txt b/requirements_doc.txt index 638e6329c..b3b407834 100644 --- a/requirements_doc.txt +++ b/requirements_doc.txt @@ -1,17 +1,39 @@ importlib-metadata==6.8.0; python_version<'3.8' -beautifulsoup4==4.12.3 -lxml==5.2.2 +# beautifulsoup4==4.12.3 +# lxml==5.2.2 +# +# Sphinx==7.3.7 +# sphinx-autoapi==3.1.2 +# sphinx-autobuild==2024.4.16 +# sphinx-book-theme==1.1.3 +# sphinx-design==0.6.0 +# sphinx-toggleprompt==0.5.2 +# sphinx-copybutton==0.5.2 # does not work well with toggleprompt +# sphinxcontrib-plantuml==0.30 +# +# breathe==4.35.0 +# docutils==0.21.2 +# +# # Documentation of notebooks +# #myst-nb==1.1.1 # 1.1.2 +# #nbclient==0.10.0 # -Sphinx==7.3.7 -sphinx-autoapi==3.1.2 -sphinx-autobuild==2024.4.16 -sphinx-book-theme==1.1.3 -sphinx-design==0.6.0 -sphinxcontrib-plantuml==0.30 -sphinx-toggleprompt==0.5.2 -sphinx-copybutton==0.5.2 # does not work well with toggleprompt +beautifulsoup4==4.12.3 breathe==4.35.0 -myst-nb==1.1.1 -nbclient==0.10.0 docutils==0.21.2 +lxml==5.3.0 +Sphinx==7.4.7 +sphinx-autoapi==3.3.3 +sphinx-autobuild==2024.10.3 +sphinx-book-theme==1.1.3 +sphinx-copybutton==0.5.2 +sphinx-toggleprompt==0.5.2 +sphinx_design==0.6.1 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-plantuml==0.30 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 diff --git a/src/getuuid.h b/src/getuuid.h index f9365bd54..cd1eb61d8 100644 --- a/src/getuuid.h +++ b/src/getuuid.h @@ -55,7 +55,7 @@ int isuuid(const char *s); /** - Returns non-zero if `s` matches /. `len` is the length of `s`. + Returns non-zero if `s` matches `[URI]/[UUID]. `len` is the length of `s`. An optional final hash or slash will be ignored. */ int isinstanceuri(const char *s, int len); From 3eb6ed44221a42590928197af2d20c426e686678 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:20:51 +0100 Subject: [PATCH 02/20] Updated path to Python API reference documentation --- doc/index.rst | 2 +- requirements_doc.txt | 21 ++------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 5ad7fe4df..e1fa9b743 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -54,7 +54,7 @@ DLite :glob: :hidden: - Python API + Python API C-API diff --git a/requirements_doc.txt b/requirements_doc.txt index b3b407834..1c047492d 100644 --- a/requirements_doc.txt +++ b/requirements_doc.txt @@ -1,28 +1,11 @@ importlib-metadata==6.8.0; python_version<'3.8' -# beautifulsoup4==4.12.3 -# lxml==5.2.2 -# -# Sphinx==7.3.7 -# sphinx-autoapi==3.1.2 -# sphinx-autobuild==2024.4.16 -# sphinx-book-theme==1.1.3 -# sphinx-design==0.6.0 -# sphinx-toggleprompt==0.5.2 -# sphinx-copybutton==0.5.2 # does not work well with toggleprompt -# sphinxcontrib-plantuml==0.30 -# -# breathe==4.35.0 -# docutils==0.21.2 -# -# # Documentation of notebooks -# #myst-nb==1.1.1 # 1.1.2 -# #nbclient==0.10.0 # - beautifulsoup4==4.12.3 breathe==4.35.0 docutils==0.21.2 lxml==5.3.0 +myst-nb==1.1.2 +nbclient==0.10.0 Sphinx==7.4.7 sphinx-autoapi==3.3.3 sphinx-autobuild==2024.10.3 From 249ad75598c5eba395718daab40df0f1c1443d94 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:30:50 +0100 Subject: [PATCH 03/20] Correct link to Python reference API --- doc/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.rst b/doc/api.rst index 0ebc29681..d2323c8a9 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -5,5 +5,5 @@ API Reference :maxdepth: 4 :caption: API Reference - Python API + Python API C-API From f19caa814d1e3095a33d27badb7b3fbcd7432182 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:40:54 +0100 Subject: [PATCH 04/20] Corrected index.rst files --- doc/api.rst | 4 ++-- doc/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index d2323c8a9..36c804900 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -2,8 +2,8 @@ API Reference ============= .. toctree:: - :maxdepth: 4 + :maxdepth: 2 :caption: API Reference - Python API + Python API C-API diff --git a/doc/index.rst b/doc/index.rst index e1fa9b743..ca1a43d17 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -49,12 +49,12 @@ DLite .. toctree:: - :maxdepth: 3 + :maxdepth: 2 :caption: API Reference :glob: :hidden: - Python API + Python API C-API From f87888fd42cd905e5b06f52c5a27c2de6781a5f7 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 12:03:14 +0100 Subject: [PATCH 05/20] Added a section about how to generate documentation --- bindings/python/dlite-collection-python.i | 9 ++-- bindings/python/quantity.py | 46 +++++++++---------- .../documentation_contributors.md | 39 ++++++++++++++++ 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i index 62839d0e7..2a28c9e55 100644 --- a/bindings/python/dlite-collection-python.i +++ b/bindings/python/dlite-collection-python.i @@ -64,10 +64,11 @@ class Collection(Instance): Relations are (s, p, o, d=None)-triples with an optional fourth field `d`, specifying the datatype of the object. The datatype may have the following values: - - None: object is an IRI. - - Starts with '@': object is a language-tagged plain literal. - The language identifier follows the '@'-sign. - - Otherwise: object is a literal with datatype `d`. + + - None: object is an IRI. + - Starts with '@': object is a language-tagged plain literal. + The language identifier follows the '@'-sign. + - Otherwise: object is a literal with datatype `d`. """ def __new__(cls, id=None): """Creates an empty collection.""" diff --git a/bindings/python/quantity.py b/bindings/python/quantity.py index 8ce31cd1c..fc9a4dc73 100644 --- a/bindings/python/quantity.py +++ b/bindings/python/quantity.py @@ -1,15 +1,13 @@ - -""" Define the singleton QuantityHelper to work with pint Quantity and dlite - instance properties. +"""Define the singleton QuantityHelper to work with pint Quantity and dlite +instance properties. """ from typing import Any import numpy as np -HAS_PINT = True -try: - import pint -except Exception: - HAS_PINT = False +from dlite.testutils import importcheck + +pint = importcheck("pint") + DLITE_QUANTITY_TYPES = [ 'int', 'float', 'double', 'uint', @@ -21,8 +19,16 @@ class QuantityHelper: + """Singleton class for working with pint Quantity and dlite instance + properties. + """ def __init__(self): + if not pint: + raise RuntimeError( + 'you must install "pint" to work with quantities, ' + 'try: pip install pint' + ) self.__dict__['_instance'] = None self.__dict__['_registry'] = None @@ -46,7 +52,7 @@ def _get_unit_as_string(self, unit: Any) -> str: unit_string = str(unit) return unit_string.replace('%', 'percent') - def __getitem__(self, name: str) -> pint.Quantity: + def __getitem__(self, name: str) -> "pint.Quantity": p = self._get_property(name) u = self._get_unit_as_string(p.unit) return self.quantity(self._instance[name], u) @@ -105,15 +111,15 @@ def get(self, *names): return None @property - def unit_registry(self) -> pint.UnitRegistry: + def unit_registry(self) -> "pint.UnitRegistry": """ Returns the current pint UnitRegistry object """ return self._registry.get() - def quantity(self, magnitude, unit) -> pint.Quantity: + def quantity(self, magnitude, unit) -> "pint.Quantity": """ Return a pint.Quantity object """ return self._registry.Quantity(magnitude, unit) - def parse(self, value: Any) -> pint.Quantity: + def parse(self, value: Any) -> "pint.Quantity": """ Parse the given value and return a pint.Quantity object """ if isinstance(value, (pint.Quantity, self._registry.Quantity)): return value @@ -129,8 +135,8 @@ def parse(self, value: Any) -> pint.Quantity: else: return self.quantity(value, '') - def parse_expression(self, value: str) -> pint.Quantity: - """ Parse an expression (str) and return a pint.Quantity object """ + def parse_expression(self, value: str) -> "pint.Quantity": + """Parse an expression (str) and return a pint.Quantity object """ result = None if value: value_str = self._get_unit_as_string(value) @@ -171,12 +177,6 @@ def to_dict(self, names=None, value_type='quantity', fmt=''): def get_quantity_helper(instance): global quantity_helper - if HAS_PINT: - if quantity_helper is None: - quantity_helper = QuantityHelper() - return quantity_helper(instance) - else: - raise RuntimeError( - 'you must install "pint" to work with quantities, ' - 'try: pip install pint' - ) + if quantity_helper is None: + quantity_helper = QuantityHelper() + return quantity_helper(instance) diff --git a/doc/contributors_guide/documentation_contributors.md b/doc/contributors_guide/documentation_contributors.md index 95f6c74d4..7fd652347 100644 --- a/doc/contributors_guide/documentation_contributors.md +++ b/doc/contributors_guide/documentation_contributors.md @@ -4,7 +4,45 @@ Guideline for contributing documentation The DLite documentation is written in [Markdown]. This include both the README files and documentation found in the `doc/` subdirectory. + +Generate documentation locally +------------------------------ +When writing documentation it is practically to build and check the documentation locally before submitting a pull request. + +The following steps are needed for building the documentation: + +1. Install dependencies. + + First you need [doxygen]. In Ubuntu it can be installed with + + sudo apt-install doxygen + + Python requirements can be installed with + + pip install --update -r requirements_doc.txt + +2. Ask cmake to build documentation + + ``` + cd + cmake -DWITH_DOC=YES . + ``` + + If you haven't build dlite before, you should replace the final dot with the + path to the root of the DLite source directory. + +3. Build the documentation + + cmake --build . + + Check and fix possible error and warning messages from doxygen and sphinx. + The generated documentation can be found in `/doc/html/index.html`. + + +Style recommendations and guidelines +------------------------------------ Common to both is that the text should be as easy and natural as possible to read and write both from the terminal, in an editor and rendered in a web browser. + Hence, the following recommendations: * Write one sentence per line, in order to get an easier to read output from `git diff`. @@ -97,6 +135,7 @@ If you click that button, it will toggle the prompt and output on or off, making +[doxygen]: https://www.doxygen.nl/ [Markdown]: https://en.wikipedia.org/wiki/Markdown [setext]: https://github.com/DavidAnson/markdownlint/blob/main/doc/md003.md [CommonMark]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet From 06342a91f9fdd595c1293d1f9efccfd90ce02fd1 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 19:19:44 +0100 Subject: [PATCH 06/20] Fix a lot of warnings from doxygen and sphinx --- .../python/python-protocol-plugins/file.py | 1 + .../python/python-protocol-plugins/sftp.py | 18 +- doc/CMakeLists.txt | 3 +- doc/Doxyfile.in | 10 +- doc/_static/datamodel.png | Bin 0 -> 49945 bytes doc/_static/storage-protocol.png | Bin 0 -> 26433 bytes doc/getting_started/about.md | 6 +- doc/getting_started/build/build.md | 1 - doc/user_guide/protocol_plugins.md | 2 +- src/pyembed/dlite-python-storage.h | 8 - src/utils/CMakeLists.txt | 12 +- src/utils/LICENSES.txt | 8 +- src/utils/README.md | 2 +- src/utils/compat.h | 10 +- src/utils/compat/config_snprintf.h.in | 77 - src/utils/compat/dirent.h | 1234 ---------- src/utils/compat/getopt.c | 224 -- src/utils/compat/getopt.h | 51 - src/utils/compat/realpath.c | 226 -- src/utils/compat/snprintf.c | 2108 ----------------- src/utils/config.h.in | 2 +- src/utils/fileutils.h | 2 +- .../python/python-storage-plugins/http.py | 6 +- .../python/python-storage-plugins/image.py | 2 + .../python/python-storage-plugins/mongodb.py | 7 +- .../python-storage-plugins/postgresql.py | 10 +- .../python/python-storage-plugins/pyrdf.py | 5 +- .../python/python-storage-plugins/template.py | 10 +- tools/dlite-codegen.c | 2 +- tools/dlite-env.c | 2 +- tools/dlite-getuuid.c | 2 +- 31 files changed, 70 insertions(+), 3981 deletions(-) create mode 100644 doc/_static/datamodel.png create mode 100644 doc/_static/storage-protocol.png delete mode 100644 src/utils/compat/config_snprintf.h.in delete mode 100644 src/utils/compat/dirent.h delete mode 100644 src/utils/compat/getopt.c delete mode 100644 src/utils/compat/getopt.h delete mode 100644 src/utils/compat/realpath.c delete mode 100644 src/utils/compat/snprintf.c diff --git a/bindings/python/python-protocol-plugins/file.py b/bindings/python/python-protocol-plugins/file.py index d8608591b..49005d903 100644 --- a/bindings/python/python-protocol-plugins/file.py +++ b/bindings/python/python-protocol-plugins/file.py @@ -17,6 +17,7 @@ def open(self, location, options=None): Arguments: location: A URL or path to a file or directory. options: Supported options: + - `mode`: Combination of "r" (read), "w" (write) or "a" (append) Defaults to "r" if `location` exists and "w" otherwise. This default avoids accidentially overwriting an existing diff --git a/bindings/python/python-protocol-plugins/sftp.py b/bindings/python/python-protocol-plugins/sftp.py index e7a9c1ceb..9f94f9a17 100644 --- a/bindings/python/python-protocol-plugins/sftp.py +++ b/bindings/python/python-protocol-plugins/sftp.py @@ -27,6 +27,7 @@ def open(self, location, options=None): qualified as `username:password@host:port`. In the latter case the port/username/password takes precedence over `options`. options: Supported options: + - `username`: User name. - `password`: Password. - `hostname`: Host name. @@ -47,14 +48,15 @@ def open(self, location, options=None): For compresison "bzip2"; 1 to 9 are valid. Example: - - # For key-based authorisation, you may get the `key_type` and - # `key_bytes` arguments as follows: - pkey = paramiko.Ed25519Key.from_private_key_file( - "/home/john/.ssh/id_ed25519" - ) - key_type = pkey.name - key_bytes = pkey.asbytes().hex() + Here is an example of how you can use a private ssh-key + + >>> # For key-based authorisation, you may get the `key_type` + >>> # and `key_bytes` arguments as follows: + >>> pkey = paramiko.Ed25519Key.from_private_key_file( + ... "/home/john/.ssh/id_ed25519" + ... ) + >>> key_type = pkey.name + >>> key_bytes = pkey.asbytes().hex() """ options = Options(options, "port=22;compression=lzma") diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 9e5667cdd..f93ec7c21 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -16,9 +16,10 @@ set(SPHINX_INDEX_FILE "${SPHINX_HTML_DIR}/index.html") # Doxygen intput source directory set(DOXYGEN_INPUT_DIRS ${dlite_SOURCE_DIR}/src/utils + ${dlite_SOURCE_DIR}/src/utils/compat-src ${dlite_SOURCE_DIR}/src ${dlite_SOURCE_DIR}/src/pyembed - #${dlite_BINARY_DIR}/bindings/python/dlite + ${dlite_SOURCE_DIR}/src/tests/minunit ) string(REPLACE ";" " " DOXYGEN_INPUT "${DOXYGEN_INPUT_DIRS}") diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 0a9715e4a..a3da100e1 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -815,7 +815,8 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = @dlite_SOURCE_DIR@/src/tests \ + @dlite_SOURCE_DIR@/src/utils/jsmn.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -831,7 +832,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = */tests/* \ +EXCLUDE_PATTERNS = */tests/test_* \ */old/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names @@ -843,7 +844,10 @@ EXCLUDE_PATTERNS = */tests/* \ # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = _WDIR _wdirent __attribute__ + + + # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include diff --git a/doc/_static/datamodel.png b/doc/_static/datamodel.png new file mode 100644 index 0000000000000000000000000000000000000000..d99ac5d9bf6778994d5b353452436e9cabb8a3dc GIT binary patch literal 49945 zcmcG$WmuHm_ckmcB|VffbazOMgmj~nNH@|TLr6CW0|-bXAt6YIbk__ir65Q%v~)`I zTt%*an)3izE?gB-n@5@_MVcg)C*7J?aceSFNS8q142_3)271AJxo0~ zD5mD9rZT6fQdzSj-c@8yg+C@Vk^%y#`&Hwx9 zW7lYN;9my}n-_(-13fh`6w9~i34uwLLuW;tXao#DMSZA&U5Vb*68G_uFYm|>dWktV z@3TxcrTPOo)j({;^gyDhkQ&hl`(E_admvs$)FqRw#fgI-5jUhbgBi~i9rZ}$d)RQX zgN$ngnVBCgEo3da?xTbT;K}~bPk%3DAl_<2DIAPV!H+_QDYNV6BnmP`Rh2YHNA*Z~ zt5^KBlIw|#x#03IR+Yu>Ztb(PuasUHsHRqCpB;pJX@EzGX*-a#LC+W2|C)bKaxg)9 zuZiyU$xbwyB!by!?9+2?Rf=hubuVyy*zEL7N->lD)t4FPogaQv zx_Y!ar=|VFDl-qS&OGmr@SG@gI!TyWIuz{uay|#V!>SaBkWzB?rM#O|Aa-GL2iot% zK0PR@rU9vrV~<_mGiLmtl;2%&IQr5OQ3Lchop)>ojC=SQZ@@Qo!NlTbJ$Z%jdy~os`(gBP72thaeV=&TE!jPgB7M;1R&-o*B)O*E6w3krjuA5`JYqe? z)Xt8+LNSFd;b2z=3*X6@-Cu#9&*4zou+@uzxre!MNgczfx|iNn-u~cXdZ?N2bw0Uq zi#M9R$^$otpM>QWn1Eqt75U04lb5EQ#m_u%`Oxzt$>3ko^32{>lD_1Tr2kGmV zNk0v!%Xf;Ny8!llk_K#J7w@eB%u#3#&)H%@Hc)qx=(yO-Qi z_Y=aGdPjI`cE$BM#Sis`@CdTdbDU3pKx*yr*pt{|dV_dI8@>~&QvM|0$$WH0haY}5 zoafjMovM0M9v9ZgA-1@CL=$}VyU0>6k@+okR0uPx>qAw^R&-tP_+W~w^_|msgBXcN z<)D>{AD`F6B(sNIY6?W!QC0@415W?tEBL%2&Bo0qe8fki6`G< zGXgOcq=5BzxfO#0xg)!mkJ`-jG+w+A2sUo(IYw;7vA`iqK>m$iH;xd!1kxw)MUYr#!Ex-xgHG&%k+|k=L%*b>@6_e6J$-;w6MuA zUlksRt4itvbNw_yV@UaD_jcY*HKMt92~kqtr&q@}d{!>=L-iqClX0>Njki#Na4UTL4Iayg8l+D>DK9?!0WZ7G$m)K4$4M{>VHIYm-Op-$UaIdml26)7SSgC=MO?jglLhPVisc2Zs6JRzfyBZe6x;L+(X| zm>oZ^kH7mHtrzgd@q4+J+Hvrl3Eai!>R-Mn?ORDmNQ@V1$oSoN9#-w>z^;T)oZVYo zdwgf?KLC)Eih;3(FQ*!8oAP`7oH5qK6IFKoHvGWgdnWDzu0XWP_-GFJl z^#v>j{tBvY@Eh8rDoF@D6!f2mvL7JTdmAMNk}G?+4?-9_Ih~jaICVoTKJzSXXPv~C z&_so-G=*}&(MbTuW%Kg41^u1axD;(UpWlIqVI!2BF2}^o!ZAocY0zvqRH`qT zNh|ln3c4tc55p&J3&dsw{3XM`cu;l+HYm*5BB%#2nF^xE`y6863RHYLqiKP|F8{aJ z!H)eHyh1`<7hwtUjQG|(CJK`|TKi`$72lFx%7RI_c+Q4INs6a&T17>2Oj2G}*41<* zbfi!`eMKkT?u!^&eww`s!Db@EB*V(u{luZ#v}=@lW45V^m4%)C5yNMmo;9Oz zLMp0vZJ7p0d1W33P{jUzeYhO*1u8 z>=qGWOex{RMEw*`y3O!s)m9*l^wWUOhR!(+)&M8fj4ftQ9X1oDH7kgX=){mV*WE)-R_ga-lpq|9gJM1xngsdZ6Tjh z!tiBlp1fBh8dF>biDc*@Y6l0WZU_w7watq zBgqEBTNQ@09owrKeC#l{57*!yo;CCSaDNixWQ2W*>Q(7{V)fOyjR>u&jtm($bsd@P z@q0;&q-27ux(5#)m*t3ni?s<@t?`2on*+I=9>1R#<7&doj7(%3l5JK3&x*atE^!4# zQ%`Bja@y7XVxVxhn_$(Dce@2ZB-AF(x8d?Cye-dYY`zHP2Ty8f#X>uS31UrmSLD$S zc#CC#_RX~(IR02@F)B6i6R=qon|vBv{)B5c9pOg^Zf4Cr9fH@=b((n4qtMvYQ7wTS z;Dk0d6c3~q)VG+(fJ%eG&5g9L%N=wfuNuG~2FK+*ACYjLO2ISxz7rRYn6%MpVc4Ia zybQDJavavJaWp1ypE*{T2bRiv7jhsXN9CX*D5YL3>Rx~oqZm(Mx6}xR7nFQwPJ zAKa+fr?|4#g5eZn*Z%9}SQ^>KhbN*dtlzN+Ms}ow#GWAb9@Wr<#>;KT!-42Nd@Wi| z3sR2&a9g_J%={lbAP)+KFD=53vb7SkOn%0i*G`-6hIX14X&ORpW?I^&J`$u-G7SOZNY$`7xGq<4ILg#enJXRzrpGx_!Xy6WHu{%PG# zPPxc-di=2BI+9>sD^ne)-{|0EpH|MpS@P$^?G?YKKw&$F11anPqli3IWL5<1l_-ZW zzPXNfHz0iY@P0}g)h(@zquOg7Je7i2$&u5TN>*K}2h0SZ+XC53in=|~w_|8c5ku41 zFd6e`64G$^G#uIS+Lbxm1fT%NB@&H-e@xC~ii?A&=k*Nf(=p%9%?Ng1Dapu*S}1@w z81v+*FuQ#assa@zR0JQNDtzabS^D;w0mIGp?fU68g&36DDRT7ZPaQU3@KkGuX&#T%A88{8{Gr%W=(Tvt@7ISXr57v!FKGeAF%y;JV2_Up#iD1 zEt*;e>}Kpy)PO2RqeO)aw^21BRcVdy2p#W#2;FXluoQPV9+0xhj)zm z7|H4`cX_~f@L4Mk8HskS%0f3}u*npIH=)oC()TwvGATL;T=RMIhUT~I$Ktubl?{$DOFh01bf7?u5E&c zIWmrdK@LI3hcV8q%*Qw8>6|U*B5EYVo~~*4@b!4FzyB2&v$kVZHLc_Jj&$VwhfnOg zrBgW^)`ZBNTE!O<=t#Sx`}=tXT3d@`OMX`4RkF=*indsV1B9g%AS`D9c%Ttr{oPk_ z=s~|z6qeUZKz({OI!z#puzWJfhYxDuB9}L)9tuhf}fp0Ic0N{gVjs zq9_qYT%F?wiAHwte`esHI!ThvY#{Du**{d-uv7YG`D}xC| z>by>q?@@fuT|X@(-K*n8hI#UaZGHIeqb>e$Bf$NEWLOTfH;?8Fcg+tg#ZvjVfAf`O z#47+)#_Q_Li8+o06%19xVQPE(m%pV3;$U0y1dw&U_NQ}a2emY5-cR0qrk&bsW`#hS zrd|)1{on+s?9k!bx4#wwWK_K-XC(C!D>g#DII)LfPJ>IE;eF8Ia0_tmDTusdQ& z*L6pk?(&!rbjvXez4u^ubv$1(%n9ce!&FJ>JKFF2PelPVN*FZtzaIhOA@)zbRAJ|*Z4`ivPP(^yyw&;HbNxES5<>J|XPc@bOn33AFkp_uzPtxI?_s3HBFy4c8 zS~0-F$%dWwPkli>5I|Pc$}`QsL28ORK$vaQ9oJ95SE_@LyYpVFFGyo}x#3GYcz{HO zz}v5@g=ORCaB?30z rFf&5h-eI!>gi;Dc*O%?iF?Z|r3M-BR@?+#oL4d9f(sy_) zer0Ufs{{oiBJ%OFXlh_C;3pEMp2^BOj0a3=l-7Ye z@?TFx17_Owq5>~oE>5bnPwxZbK(P${dav%}=>C>l$dev(*I~4}?+)lF% zL8m)2EK1<6?rt+%TUa;&4ZY9lcE5T5UE0QpUySH&HGKZ_YV)_g_l8Y%%>xre2h+}^CDEi@J3=qq6I4~JLzY8}p z?_)(-;ptlYaId2^*cM0`m*?+sM>5V_s}K2`H*e_mCA%ce&8)26o*%7;B-75d2bgfZjc*ey4k@Wj=M4*%m*KWX3(QWYl~V={&dP44zDiZt&(Amb$Tnx2_i zMnsX*BloY)U8`{I=9=7dsTm1g{Qg;O9d)S(77(7*1q<|kP~kTOFp77!Z9Vx<;4?}E z!p%;8O=M#Q!eFpMt)1ywjW%B|z91I1v#ay!vs07gjdVZa2gDJ~&pu#1q}9uQeaZIo91V%bj*54t%zK8DxYPp-Ur@22j?z{t45 z0|mf?Sc9<1q%I(so`sRVEeT~&X1yCVoKd-2g}go4GlU}0V&_x8}T0%COo+$H#avnKT6Zi{6aABMr&-f1YXY@ zD#|)Qr`iMBVacS3>SXQ$~qDeHC1gL z8;7k-;+JiM0z+e(uJOG(gMy#>>dF#=!C?4F6{54N3$?DUj{ZZX!%zMywV-4{jd5u* zC@nO5Rnbk%!Iy1UlWF>~)L@~npPyV}D04*(W8|R~baeX+a1ZBe?jhbg&sep)mi+Nd zJY8HWgoOQtA{MHnd;tjw3F9egLvwS~!gtLa+=u*q!VH7VziVzgW@il@bPMv88(r7Q z;w33_*@~SHSCp!(>FXezdS7I>)w0EL3knKy#o`H+-97-kdSj-Z2Newsx-m)z#p5!{ zjX;MzhzhX^rvP5OTy5V1<={@<%S?lzKUEprAUHhexp=NPu3N^h5}gV-b(qiZ-5j-# zPt6|-VoQx=i6fL0YsS);w;AN42?x_QLJOvlk}A21BsORQj?o1q2H>auSFFZ)S|2u6 z3icM-^MK)snIg3iahPT3b5jxL#lSc;V!~;PXI^92>LHi}J!J;EIR_t zv8AAus3s8x5BoKrBZCRe;N<%FLBKX1!krxB>^NRX)Y95|+n)>=)gAMH1957gEEWLr zbqoMd4<@);+?^d5|DGL+p?7D8Xz+iAWm{VW3Z0IHyrrk7Ur9eZE``m4?+(YndEiV^ zcbxq4pX1`#0k_Zqtenir_Ty|ob0CooQ6MGa?L3+-rTqPq1!&Vut-oJpv+G>`&+z}t z25%t@0XqDN5*_k%bhhiW0~v>VpYXz>$he3<$FRoJGcd?mu5ACTMqk-V zqF>X6)iOMugS-W(Sh$UJz zQ-qXK$brzh=({bwTPR&-pfoH-+4tg@4cOEy)B`oH>(6VP=Jdr%VbpOouNR))mG(xw z-==rp#i?QjP~SpElQaUpCDQlh0;$K`qvb%IsR>q6Tu+&-Frir; zNb0^g*+O_NAIC9@ShV%i7m- zHE4Fevd8Xh=jQj%VYs6siJ0PwwtMrfNY&$JkG-zp;pgCIimTthFtR_k=!rH@BNi`9 zV!)55kT=8z7NtfOATG6wdjEvQZ8XFe?;QWOyMBNBR3$WAj+adk#49KmJ79$WVmYnQ-R`~XOA z@IB|})Ou-x00Z?|?0~f%1pT(Em$bf1!V6_n0}~ z3iUmah9}h-zSQsFu}?a9E3B#7MZ~X=k_jG>LhAGn0goNiGAaXSSK#N*pHmyOa+Ul6 ze>iVUy)1}=HlV!<0JB+WbKl=Ex5Ar0*Obn z2?)Hv!T~%=PhArm1P_vZyCR#c3r_sy)OHuH;tT*moW=(Vvwvs|oCCkN-Z0|)>V|S) zoO>WtKy|Tm!Wzqq9LGB4t#T50VQpgyxwyK@luUGfae>2^O`vUUZCzC#2$xE}y1s_) zFG`)AozZe~;vQGOd-pB^@}k`EAwW++De;n)BUwh&PqG)r4)dbOdCUO>?E5Iu-Vl>Z zz(~dq&1O=sq5FW4Dsoy&2jX&63jl!P9bJGI9&Wv2%nKkAp$gy$HydMkc>+8Xh)#rp)O)#rB)@PG8P z8ch{Ja5DQ{6n@$AmI9ycumKSUfk6$=*M1|ncx{&=jgQ$S#vZtS0{D&h- z1zSiGL9^cte_@eUsbn)``kUwcllH_rvqi}e(;hIX(x#!;kU=A&qb1vjpHzAGdpk_k z;CEJpnAw>}Zh{OoCUTl=ezanG@VcK`&Nlggn0tB(b>~sa6V_-$p2_#^nh1>mJElTm z^vJp|O`M!;L@x%xLB6OQ*bb44D6h~ftLxhvnh{Q25dkXr8B)cGY#McLUaAj8(dzUZ z5?~tVGawMAH+4vOIOdz3=`bdhYg&9*qxYz}>onr!#I4kpGwg;XwMZ9gE>AXt)hNrK7jhp*Frp-s3i@-!){1ZZ_f zqTa^|%v${VkCfr~uGVOG8PD+dkPSM+vM~Y+m7TojdDi5$j`2Z6xT7qJ0KPK6cML7n zZ6=Z83BK!RRGOGoD!vXnQn+44O2(@f`Mx?hDpcY|_V=o+mlH~0&Nkrmo<0kW086;P zkmW?G*e5$lUQgrI@JlO##bS7R^S9P(HQTsJ1{P>H|1=Q;?Xdr+qC&@UEhGH`o8n>j zS23EeuA-gAsSi}OtTlboIPvWizTW3Wxihq4CXzj=7*-y^g-@6BQN7t#Fq&Ef-$?YB zkbw7S_6t$TtPao*HxS;HMY!O*K+jG%RAuRqpl~UM%jJU;+R^Z+7sN1`YifLmU%#3x z<;FuSD=RB3g4^rxczXO-gUJXWTm7W@>fMh#tDz5I?;osC?r3bA6v?v`7EkvU!Q6nb zF8(x`ve4>5J&#vVfWVAt zH6X}w&Y_=b83Kv^cIaKUj)m?Vb5&smpF$V~k&=_&Z(u+!XmPOpO7XQ5U9yzp-0HjP zaGt1$w?dlIpV<*f5rKjC4g{=al&``&;=I#m>^9JCyfinAgjdSi=0-I(j4ngadOBF2 zSxxg-=>29Ru@g5-Ow>})A+Th$-D98Sc(l9DVT!rXm8TZA+% zEIlI#$~hIQpCQB+b5?_8km}IYYC~ll+KlU;h=_m*2t;Vrq9_$@%d&1(+sVDfLmQyP zbO08w#sJl+1~g!FGA1ke2)cmc;*AKyN;xv@1=;B%&ZMYsBtid83&JILv$cmv<+E^J z9UujZzER~`btzEohyxA?o5p%Qur9bp4875lnf9){7{7^A`$E|#>(ZHG;{VIz?*AkyE*YW@%5>L4aGoz;I!W@OrJfM3#wkg z$Dj~{-rv2{9S&XUx*sB%D*MpNH`a`hSO2lMz3R^(Jeq!V4>uJVQ!SF0OJ0yhIX<*ARYV>~Na`%!j$ zoLyYHt}lPTe{B6+%6c%F3)!dy_ye9J6FypHp>RhEY`+&w(2H|_z*Yzm;W*7iZMwer~D*bw{#xDF2$Q>a= zwSL2D&_!L{OQ7j~q#eJIc;DW6` z7(Hr9>l-um-$F3s%ZdjiM3}Dzn%#DU3V4jeIfQrV8IL&}O1acaT%LSQ<#ZtLu zo)h0cK%7;uZCUsF|DwEjZ}BMK0)`Ie5uuCG)1ylGU!BP?6YpRFT7nI>APKmX+DzZj zH#1;oh|GE!x-)0f0%g^l$%uz%q|`AwDB*G3j*q-zB)a`O>nza#8I##{my!~3cy&re z-qt9ar|-N@=ZEV(7dg1p*kSNOi%d>&*=Tbf=)&FhJ|OK9!}f(OX_3(%59-W{?V??% zsL<`V6XLonGtsiBGws3$V<~&Q6SdBL>QO`k5<}oQh zo?4QZ-!Pq{(luAz(vmf zy9R;jL*UJp|Eqx+_eJ#SQ_Ap4>-Ae*S2ahm6%T4EeE|}8f=kjEvjlR}nE(YMQmS{} z6CCENrF?y+j@ZQETIA(jMP|<$xZN?aUoKB;FIHKyYkuM--MJuV@OHe~PVzhxz&dw% z)mx6{k9B@^e;LC=A?K=*4_cnSXq4}({NIV0Ka z%w!(AMNeLU|E19s`mtNOK6+rc z((Z;PRoaK>z|js^ZSMIIf2wJ{k7nz1tDZ7~*}WvPziMW?N*WyVu{AnbT*0iOmmZSH zg>t*)Fqp3#|K`x2p9@#hD1B-0^^q{4jCUO= z=;-WQYTBT4-yUjR!;q}^QO$;s8@pzK0BDC9>U=a6R1QcPy-jt9stSJ=$C{28Y3Ip4 z9J>azX2iKthf9EX?;_@noX=+r-F8#rHviigU($4o#x0u~*V2!yAyX$Q!H`kva1=C* z#s8L@u$*ju#{_6)P-bQ(o4l0D#UMI`R4}o$;{g9TB^RydsCGd$|N@n?>Cdf>`cS9Y>0xg zk0JZ_ZC;~atAcJPj71L)e@UgLQbbMKi~g25kCzLLW9*68U1&L?rOsU78cEmvI9-b| z!JbUkUNZJY*iIx7VVN=y>J$mjwXnJ@)niKuoB+gId(6p+7gwRX`cs*)zU&< z<`XM_Gy4RPQ4He{7~(gCWNxj|tH8Wnpsf1eA9_%WU6;5EY_|D-z2t$?+EPeZGsR>z zMU3(+NX}r+2jC(u?t{i^pJ`3$*fS+_y`vg`mi1ZY`)=XOszkzY_--r*KFo-RBNTmg zy6h6-$9J!H*s$y1@eOh&K%e@U?f3ydSy{-D&zwfl*FCKObCwThG*7%VTDVo7i4c%( zlc~8%0g4X)c2}KHR(0?53o4gH+tBd!H#w473nNc#M*Rtu$*NR{0YwL_?Pr6&s-zNt zkjlZ!GXgTeYCq0sBw?n#kBSZJ?gXt%eP$JJ|3ZVXtL>b{4nb(jvDedIy3P?rG62ikL2uS*C}og^gF zE~pL`dcq3Tn+gjVsOt3_4Pl-Lx?N}~6hTKP&j@f{lg|FkfJihsTFYqjC>e8HK)|>^ zj1>8QSrDKdKPta!xcb3#JaE|D1dR*9Cn$I>1K!wpc`D9A`j-{r_^avB8 zaA|D=94U&&`@cV&WT#ZT|1ABufhU8}-(;eS1rRqT0W2jVf@Jl*D$wMA6JQ8PPcE3Y z>SzHCCbysH&6v#i>G7M1O3MqZV~0JgsNjhz(?i~a*UB}qW|wUi6yn)YQ>B^z>H|uj z0eQP$HMBN0Fdm4YMD*Tz+qXa}Wd(6%Qi5QcR#azwj&c1&Y6)4e$K*G>IK%4lMbdefPjOFpo(qDE$k;MP~P$)(oN=EI*`+vk<~k<+tNNC+8>MNYBoWgDJ#C z>H{Vw4u?~)vE#$|OrddIqETBN=r4e_vy?mM6Bfa{4kFr%;dpGtL`iex{>D$&DQzb@ z<{LL0psJt!ag*_r7e_0+DkqI!7q)Q^sO*o#3-3LReAmi|?Ix|>dDS@THELPam>NTw zpAa6C)rK{hPYHL)&!PfLrB~PQiJ#u~R@HTdumtrbiI<%9+^)4MV9zh)YH)=OY%SCZ zzM(np{&w=T!S5Ek&G$NnGtB{BRYgLYvw_JZVy^wNqHu$CyzHeExK}y71qAwc9@#?u z@paF&<2Rhw?f6hK?@A^Kb5@WfD=2S9+gdVzPy@0<_BaYyJO2}w`W{|vEa=&zL#mtb z*GoTlQm_c3cvXoXIS7_@)((Bd&bKF*`XTmEQ1{C&DFT`8lW!>JS!FooE!Tab(Xr2jX{VY zgcRv=#E~iL#a#+O<9)E;c=v%ft3MuMptKT(uk(ZerAgr_a?W_03P9atdL*RgwlgYY zSkl)Dvnq$*zsrS`aVguvus4{1TL91E2F=hk4VZ8A%2VzgH)#AXQH(8{Lg7Sk(&JaC z0feKrK%FcyvshGas_naVzu{n$b zfj%z1xG5w$wPK4IwLLT*FJWCQj@sTP)=~YKNe1YO=|e%swW3Sxr71xOX#$bxDfRe0861_!a#?5;qUK zB$F3)Y0l9NM29N z0Q6R@&`V`C3d(d-WOUV3fG3YogCjALH{d7uEb}RTy_~&SI)d!3lwmoE4RJlA=eOz$ zbrDp~_4%B&F_BLIAW7s{f?-@IGn&=2wD1J$YhGtrlT!T}GQefM#U~tI_s!Ebmh5sNQA-O(s#p!yN= zK%O_)m7HZ|D1qjvfaissE`kN0UvK~Pv z6hVC-_Xr#EusB^s=tGQ55Ke0)Oe%{BLX#% zcZIy*SBIuIUlBI*eM*a-XJ;tCgep*CC+4NWk=mRQVb#gGPD2+ihVKj}Wd^Ov5I4c* zNjPR-D-Slt>!Lg}4+VsT$ZmH93n@#>9C@vO`5qjNRDL{red{z{GDk=$ToJyWjMw0O zk$iPAl@M_8yS!D@`6rL0@ou6X2)cagzCGU}ECtnVhLQ?77{|IQaECk%WIf-V340F; zU&^|*FvQ+hNH6CKb6XiW@aZ4@GNIypHLh{yy{BcT+XtsR>vkp5i|dyL1RS!Kp}Bnb zaY#Dmnmv*ROeX($WMZW6NvCsT})`OLi z163go@d}K z>Wjr==H?i3(EcxNyIkmX0jqXPO7(w2$UTO=X8ZXiml<7R^1<%*JirbnvDAck8@@&*;i? zT0C~#1jwb+?!q$a|OD7rwG~~AvmBjebgrQ6BWMiqhYZ)p{_pd;BKbnQmW5@ z_r(qn8s(rpQDG(w2V!Rbmpd=}1lwgeBs44x#tLX3viqAI)MR{DY;RGN0vFPj&C|Yb zD@&2j#<_Z*}R>+b~a;U=_bouO`gWhFD6fB$0$z;MyEFRd28)q zoO`m5A&vfo52c7`s^MC&sc{yuV&=vBO4s<^%h;zn6^4z${I3RVY7E`Udyv^qT^SM} zb(bRIpsm?vDrt#u^Pz8e5mLC!Llohl&oTXK+8^58gx=iLqiv=5WSa&_L!cKF7!Ajr z0Z%c_lDlwE491BmKif*cM?OmM0d18QuOl2|dp}u{+`Y;y-KPPh1Ig?J1g|}+Dwek2 z4@z?rgD{)?72AP@Zxp7M@CmImGBAfP@|jD7O??u#M~ykV==|12tTIQ|FZ}Yml7{xL z>v{0AOJ3u4zp&kF(LCv&k*QB?W2V4mF%dEj&z1}_uA&p zPOmu*R|YtkSMYub5W_w8=CJjpWeH4GlK4s46H7wbEUY6ysjU5N*SCtjRnz3%Pn_cmmDOw`T0FmBLQ(pFT&Q}X#b$a@Au1$P`9n&A&D3Z@qkTE ztKpAtd(~)Q5sKe2hWLCJ6{~y|zat5C=r?Ne9|_t~j7MnKLx2vkRt#>R7Gtv%R4}y1 z-I4hT0?}xd)f>7`-iLyL;Bn@#`;5zw_&SmDc=Q_jk?p zblW-ts;be1q@-w%A2UQ##ThX8PyyQZfKup#w7@2S5kG7dEO3}ZwqWQQg@XEo`RR$n zTZglUBHmUGciC4i%OF&eYIu213Kapp$}d%Mn@G;-IH7mg^2hzKqNps`vaXKT{pFo- z=TAzG(WbY>b`oX)^(Vjp2?t@@zyinGYW?}-<$GB5`=6_5^Uj*1?cHbC-+kbgwPDjh zNNoEAkXXN40TMAqeDOA&By`qkUduxn+^r-u8J`sr7t_QeNclETI6vZBR4V~!O!njot7rbSC#qfd90BkAiOVCa}S@gIHQkcNK zK7@Xp9;gAdrM^aCRzS@b+#%)ec=Jnh7IviFScX56VaNX z21KBBo1<#p2{q=aVbRfnK$q!+EEh3LN)+DFA-OV~X%JPrnv3@?FcEI{HI=WyWfcby z@)cCi0VD1%cTUpXRh!54-s)A2cE+Mn22@r+S;}{jVSR3G^OYhXn+-OTj`^$pE?7!S0+WisxSqZk2iExgB$y zD)4!juRds&8$MYX`AE|nOVc};!UJeT3si;Jo5K-OuUMZjFz?^pm=uFB-jxcgc9m4Y zto1-A-h5zcbK^(V=e}7&AqC6;Pj31L{CbM->F?9i+oUD4;gGNf0$i0H<3oiNDmyJL z6Evdr1seoH0kmj(0iEapprj=Vqpf1rh|9O1EYC4VL`qXclJmsmtE6=O{Hb^UMdAn` zqw;#=%K}nlM~{~y;|Qfgb*`!c@VmJ;f0VaKCqi_ITxN~ z(<+FlJrU3l{KTjYwOMFuB?Q_CLJ_Q8SAtUBw^xAU!zoqf{@X&T)nwFibfnbfI2J8w`m5=%?i!fJ=gOqP`kEF{jc~)6?ch zYeRq-^`X;38&@uVEVa0Fo9}r%g7JxPHH0yodSMVfULG*pHsg$)>#o>|E9R$Sqaz~x z#_@*WmGX`8%f)Jo$MR~_W|hcMI~zaCuTEULqm17sCv~`qfEE>?q3z0*i{f?LP)C?@ zKn2wtkl_W`(7XPfudlD3rBpsBwy}-z7XXT9i_`6&{pRr*>F3u; zU~gX@km94eMTa6w{%F>q`$=0#3Q+}Ri~KeuPs4nrV}p#GcP>cD!Xf{4rWZk+@9!?c zhT0KJ<3(CPKQOqYgv0;l3fA+EGRlvwh9<1-AWOu(SXV@ba@zI31m5)Bp+8mz>G6%4 z-^f{7GPbm|tR!WhoC9KK*;en9I1U}|Mo5OJ$8*m1Mz14gMF>E$?^=41uUzl{qLN0PMZDKbA#)7z3MJ zG+IbhE$a!jTd-#GSH00%8G>V)c}g{>wm_gZ(aEJ9Nd-M4fA+!fo2`nV-MBiq0o7w4 zzP5l|TwBBVFbl+oJ||ml-@WUYWTG*6tx3DTd72I&Ot13TGp>3OAyhl_^C!%vvmI~- zJZLv(s2S*Oz31V}WwJZoM%8ai{_nbQ#9=MacaGRHsCRnDu3c=}8GPS7v%R&oHI-Hi zz640j%^#9bJySlR^~!GgF1+U!Agxr4lypg^lfh^_>zh_}^t-+gWPec@n82cj^D{ml z5amX_3ShJXM6#NTMmbF?z=151oB;xzt&>w%eFP7srUs4!c&AvV;8yZ9QtaMy5ZChE zQr`*6k)hQ3Pvv3Bt%*D)#;S`-I8Os^INPpMm@eL=!-LW3a|F-%p`NGPX{n=tCjO6p zJTOZ_(mb9?g^UjW_~Yl#EF0srdi0yw8}f>ZeI|f*c+MvG+3&&Ad+IBpvZ2Yx8y_I* zp+VH4fC5{M$epfex&aDEjVb7aWWL*~5KxOncrf^c<5R%}6=J|+@R`jD{X{>dw7+{=Kg5}}&jAm;S-k=wmvj(8Mr`Fqb+lpX>10(gY# z6R<|tQJ<*;jW0lNj$PIt%Y_fX6FH?nAi@k(N#TaMUovt~iM~mU>h1_as{)|YvqTvw zNfa?fgxM5FPsB+1dZCT9EJCTz~eA19I8R(^jxJW z%R#t#eQa{NM$PKMaxcAI-X?IL$rfptMF$i#dEv&BIYQNt4Rvd!%3`3{y`AV;O!7yz zD5>JlZQR28V=6&aJ$D05?g1cgzqWto?*Of=tRw`PL^5+4)TwT3W#`GW+0jKofFk}k z@CV7>6ePEHZvK;+=t!YCt;Eo$)iz2bt7R%c(UgH{N&gFELx#tN^3!tw=6{J>E}mFG zdG}G`zODgq2FiZxq^1hSA!TPo4-#U~!ok5IzQmcGn?vk@^b=k?IdKc^eA8so50?2W zK$qM)wjBX-Q{aS!oxRCCn@+tYBexu9?C8*EsT+ahg^&J(vIt zp3L*o;L6t;R4mm)%*9)s$o>H%f`#+%zsza2gB zaa+{Hv?Lj3EmB)=?XLvg{+I2Jm>iAGJMkS4;8Lh9pgzovdHu{E&`QIz?jl3KS}m8) zZk%=W%clmUq{PQ3t{8v_gk;1cn?YgIMmJJc9Uc_(HveDQ`SbK(TR(~asXBk}Y1P*b ze1*rr+qk}n|B{OVETrKN3sLfjUE%0O(w=kkZ{Ps3@w&_ZQaFlXxWxc8Z`eKU6Z=;r zZPD)H0Q^obGffA4+kt*o2Jp=<&_%v%S9BneFOk3K$WgM@{XcxYbySqm-!H0!0|Fxr z%FrnwA>G~GDJ4h(nty1&F_8Bd(OG*uKO2jxm?f8?EUQc ze(L){zRJP6u^Sa9G%UKyFN$`|T^rGK-~%_=Veb8nxL|FJ>sz@CD# z;qt(1;2j-N?Sk1952yZWAgbr}6^8lWtNh#ZWa+eddF)146UwkpvGO2BLVW@6-@WSp z7>`DI5J3GBaa*88bZ^SigIE>IAInF60{jQbm9~`PJ@sc23=1;P z(Uh#IrigW`(1Dz<#dq28Kbm|q*{Uj|M9PAa`fY5`GYt_i^N)c#XR8g zC8xJ?U-=Uf7J_J{>=>@z3VM1(74HmenD$*-`8{b%VVp1x^h#DOPW{F-{)Ihdrh8|o z$dh<5hBN7z6LSWY%u9%R$VT+{YEZzO+H5dejUtsWY_|<8?#U35i-0fNYi@b{F ze;MA%`wpM*Wjb8~-g3SK0>JjKPS0=*JUxA@jL9ujN+G8o7#Xch9X*GFQb&$Da^?Tz z>6Ygi{2cps`_|kI9=1&X}agamLwnzHA0w517 zE+=aexUW@_wzeK_@mYZZ4FKDtmcRbpk9%KM@CYD&_+qInXz2AvEBIp$HiC;k^V-S6 zlk-YmEY97xJr;{se^{wY=PA{oL`k@vX;hiQvoVI;hYlov`q}*?f*-tmX$2s69iaMp zxSfsF9l#+H+xyeIP>KpH04Z~|W?^0%y67_92g#U|HHB!s?J$eBi&(U&&# zl8QH46VF_h%NWL5=EcFy+=UEEn7F(Usi3eiPNGS0X6P;~3+hySDBRI;f<*-A?$F1+etZ%_PZ0AI{P9UA zP<8kP$7?7&A9{mtlT*2RnGy~u_MfZarbChMXEKM!N{77uAM!`ssxr;$Se5#x-Igj~ z*=adHua+|uTByR-B#F=8%!7UIP6a3|?gW+1G3uG1adB7!S`@UJ%sMd`>C@y_PAu{Y z@~mF|45hs41U4O)E6^{$vLf|dY`c4B3Gs&bI-SO0rqzQeb9X~qO~yT27bx!c=sb1X z-D^BUHLjZok0?0+{2JRoe4VH0;Idsdir)tkNxNqys$azC#y45b4$x;m)M(J|Bb_PA z!z}*14vKU6->U16PE(RyjL`XrZuNi>jv;{p?4u{A?ef4Nl6 zVdF;}k=l2~keV9SFF!N=`7S>hFE{k;ZQYIhTgQQ=`P@}Z^8=|x}q{G`WF<$!HWvQReZ{DUo+X7(jSM(Fqs#A!zRkgR>7%7boOQ2|-{3Ap_zzV2`6y&UtdU)K->sW7(n%Uhc~$DOIxI{$E;9 za_eR(238Ejoi0N#qX5?f_xN!-K(^I?rXwCJMB$^?hFrp@r6#{GzcB3f*AQ8oF8RD* zfU%MojUWRiG|B@lLdJ2uM|i!7JC;hF-caWszg0Y1cXxNSFYc9WT8?Sv-KNh{^sOm3}9< zm=OYu^($Hf#J%Dn9}DEgUYqt{<>(1;e48F1{IGRxl(m4W*{5&xS@(|i58cf@IJyFc zvH&gKqW^ZS+HFx&y+mDM6!;_mZ{_zBFT|rJGfK^-sw**A!>8Y2;hV6Hwc~v)Lb~Tz zAjRd?9BE7yBrJiTAYK}_cWKwTE9XryZwI`dscC8C{QoXVWy66sOb;J;mQ+iBaD1Fb z0=w9bpwGWxN)iBFNO9Za)`J7tp(ZgZ;S*Mlt!hV*r_}Mpx2wO`kUd8`VQ33uyj5Tx z4v|xMKvfkpuy5V3B)nFmd35kNYz2pi=$Q??)@gJ0kLh=j$v-~ z@NVE_vV^f|hG-n))x0>$sE5R&59GZ`9@hOIR6z)-@YRg4X~oxlJ$PR#gM!GfW0 zasAN|eiOiyfP#aAQ#0rR3Qj^^$Kvj($%_}N3gWN^W`3l)$q#fIZPneL*Uv1mU0wgY z%vy8{o9-b(!Ag`>J-;gkN8ZHzjzh5E3&VELFqs6JRZy)oI&4RCuN%gR4>7zZm6Ma> z_q@Uc$iw~ItHu`gVO6tW8=0PnTDorCl*+qfJ3K+bGf9%7C#gEKB^7(&?H!YvSOPz^ z<0fD_uzUQFClCAD&BkHc9A`9F7?sx$qd)N+behfFzgsoC*@H@cMdA-e^yJgN7_EKp zaIol(5`>1=wPlkv+hooMh>6ILMYgCd`9H^GfY0cn-iJb?bP||PUXG{hg_(Mc4f#W5K4&%lI+TTY%xiYZU!*6u2Pdob0 z0KnFF0qk`6*>E5Se)Q(Wg2MX*8Ce2m>QQ1E>+kR1TQfqdlqC+TXW)UyKqulv*QzqW z^A~ZlcW@8`*29ytZ6y_2;NW_MHGcj2WwQQeD#%;t!!Y5=Z2^sL7OwjYhFh7f(%u`- z%xFv$C?R4#dz!J{y3cZFY4hXws=sR&+>#3UI%*kR@9uZM47eU2&V;^ITX>__I+XKn z`vOu&0AdUCoaNA8abRZNI!>n1 zL*MST_*~IW{Qg*sa=y5L7=64WuC92IQlN{%RdeI0s0=sW1z@ARRia&Gx0*x&ZcC94@4H8V{iA>#z(3g8 zXjD(n-#i4O;#33oC%@0VK1=P_uXwbz7^H$QkRc%XXg?0jrnv+`Nnpq+fGd*Q`yaQ} zR4H{b<5+>;p0KF!_Ji6S;1bvL*yF>%WQ6m-?gox+bPo@YRsi>`h9XK3jhk1Z?(=uN z7+xt5Lh|-(_1{jp$v66z&GMF0O|Azl^O8br_taegWsOznagEj1TTX(SRjZ_*eSGmg>Q#BbUOTW5{*WCj`}oUJHg{ zhlWRfCm&I>bGAfDFHepyF3dZU!iLPdRNE|Ys`z{^Ry%iGZucrS>=11CzN1fm8_y0( z{U;LC05^JbSXW!y4RF37>w&|RKg(fCFi}y_K-1AMK7ca$H#PAH0%s)+8VFRtcT*M* zpnzT;EfWZ-rSG@^b2Yxu#8DDn2;}f}T-_RdK?6Ng*!`MpakQ|bBR2_&qi_~JSM0d1 zz+KtSt(;TE#6!mXH(Ta*>l`}DKRpHFD#NqbU2Cz9@Y!19h}jqLzlSd~U-AQVVwe6= zZ6vz5w!&$GR?5zR7J)vl17VKKEg_gvzPy!Fkp?G_sa%r&yLX!s0A_{)4=DEX6}UmX z03|x)&HDn2Ct=76=h*@tG|5C2?^-nFcg{A4LI8FdDs+lRua4<9*y~r|i~UC-_Ki#4 z&!4sXP*A=@&0SA7!UnQwpj1@#e|2d7gVkWgZG`vSIY|z@>q9GHJiwC}n%RHyfB8PX zYMkE{Cd*o|s5#l%Daoo8L(k`ojg6nCDA*{MN$Y7Bsyv|T%9QAd&RAGEIC{z@6&nyO za&|jJ5hx7kI}1ewd{?H`k8P&q1l!1uHL^Ir()|DWrHo+DSYLn<>%mU9M`D0? z8BHWd==XVR3WY@YK%d{9lnT=4e`>hKw}1bTp~B(txClfE6pJRTsi?n#%7V4xwaSfl z#_*9Gk$_&VDB!{D1;&C0Ob9PVC2&sz3lkItu>~B%Ux}G+`BiKxPqrn8l6KJ0!tGS! zGw&_T&1oqUVGTy2Jk}JM}#{ z^N`N)dZaDZqv0+@hA9CP(@_DohO=BE3!=nQmYA+!H^}=96;tAuB?ajCkR&5{Z<@WV zSvp=78Se=7ba0)sa(kuwY0ul|^$)eN%lD#d${=yU^ygg#Y-%RDyuq{Q zYI!VFKMZr?%hZEp@Yn42r{6d~$xKNh7|JAD6WIuC#TA!zbaF~7qG;;(!>)Vn_PG_N zjz!-Dq0HCm7fS;6zc!!kUXhx=YTxe_gLFb+qY#uZb8;RX>B=}>EJ(b^@0^q4%ZvWR zYc=I|6CTf-D^xzNKL~BJq%M5H-4gMF%?At3FNjhe0#Mm#bBybr9ptRm(f;aoyA-i@ zu9ZqimNAbjl!Q@jYVTg!sw~?rtaBb{*?cfHTR+6`&o0Uvcu7kQHFs{LwX)n4F1+cU z($p+i`8=t$GGT;t)7F!7{SHY@?j>4;QTKz;$=f<}?Ewmc5qK;z^+lUxLKtGuvsfu# z**XwPTO0lnw^n>gX#j0?Z|{e}@oS#Xr7SA;Y)M*$;vd-9*wR7~b+UdmsWxfI2Ew@Q z$S6t0Y4Y;(b&v}ad;QijOH3aVV~pRw@A;Jz_Z7ZI#-O_AMobG0|C;{xugfVXriyd~ zV=>#KVexZjiQV4~9_R0(?f=|Vs^9|hp9N6+K?2yYUF`RdthLUroTXdE>xXm|B zczktkwmxLSS;09eq^tYaFa?iE$v$^8WHFiljD*7)8CCsUo~;utbgo-^Fma<-r0&hH zumF=k3=Mbh7Wu)K4&J(?p@xE7m$0LMVk>T2PX$B7E!wvz##mzfNUXkAQqftGY4kNz z51@6>1k+y-Eum?Ymzqo`Hfh&DRn7p*(SA_Mx z%{NU5zzKP!S zp%g>)+CfTmTIgv`;mt=48CdDpy$~rS!O^qB{JI2aGt% zl{24u>SI|;wRHy^y?#V&P(yWynbkypxFwYL`auZ_s=3|Ln~z-fDw)Akb(PyHm6EuN zQ=aSV>R>8g2B}X7Kx=1X4HMjuHprolOn!{;*0dDKI{{yvv3JQH8VcZO?O}0Oc45r= zaF*UE$94GqdnQDs-cBSfOsve{J!^pr1brvVu|23&!C)SQB{nQg{g92Uw_uJt2j79J zYt(ZYjoa(UEgBKW=S`%MQSze=8hrBE?CBaUap&LWKM73FRej%PHl{O}L?_bvBza=; zAv7W;V6n81+vCL0Gb&QJCa;gxIPp!h(_R<0VL}MKN?0>Ty5?HO{>wnAz@EH%Pd;WVO_=(L##rK+Idj*d-R#c#&yYVLIvVyA7Zj@TT zGAvZ9c=cb{d>8B3PHnX#ZY326a)#Qle^kcN(;Svu^_LlWa_0qtXI=lk#njxCdvE$s zd9WO>TzGysGVCh4@b>Hd1O*hkTNpy1dU^*)Q66|~m#~SR5;yp3VdaTR3$?Q5cM~UA z=%$D{aI9#?fo$jB*4W>v!e-r&%;fZp`rB=0YaMW&T@c?u;EjKr(C$JmzYmb-&)NFf zB969tQ6|5rS?EGVMn^MRDQ>4zX+=tA){P3S!m==ZN5E#YouhQ=eT9o3&YB=qn-S>7 zhA%R1SoO)P+VtMuLIkbtnTy1(su>6i;ElD`bDm#6URtNqAHe1MO}Tst9$fVkL= z-mXsT9}2PqiNARD!(+e;aA;ezj$)IUfSwi$>`%#7)vfR_5p^ zAEozH?|aVlrCTq>vDa#Y{j_u70lmesU;_e?k+MPfeB=-xL3mtNeuLp?g6*T zv9BMs6k(<;@w3x7kAFYEGVBfuctxZ{GWNI+Qnmm&-d#As727U!L#HBbLn=Jy zRxf|=CsCJ@qctNqUh}!Zrn41?iU=!&;)_I$age}mrmTH(g(h|jF>I{S8%Mp;@M#^* zNMEO5z}!(hd?WM4sR1)Nw9D8w&0ItjzqsDQ+M4mqTU%SZ8Yd-bZ1QnlC^c{(3vpg2 zy$^UxK37QQA%*gbV{9qpG3_LbvPqqq{p*q3{KsC=uY4~0Q;M{VQiVucm?+*p|Bk1( z*1qqpUZH*XVh!^zwf{waQM!6ltY~(_0bj5=Nk4pIpZt{*N%W;4fpS4L>aEvV7A2vO zOHv|tfs4?)?D%3+$F|W1`3fIYIJf&!Oc0d}Ke{;Kj<}&1j*Q77#>Sa;+)Px1w9IR|c-% zD&=clJSfh>tS3MV0^}O!IY~*Fl}1&-Zo&td&6;wJXGEHxf}Msqxm^#Q;4`HUp~z22 z{v2CsrYzMe$+c*F*og`45#;jt?Pe!;TyynAM`~pk+SwY1Ng#jgfWfIknfCh4Ao0Pb0dPrX!rzkwOwJE3ejF`Ss1q{S7-?GUJyq~H-c!J& z{3YZgk?$+wjkI(}5Tev#G8z~Z>b?H@Pkel4#cgy7cF6f*Q995vMOLQZkOb+sTN%88 zHR^#V6faY9nL*S5(FMdvh=`%d)WgHW1Nnr5(cXp*hqau!x@a88&xR^>zM40T zr`=4BSJH<$-Tv5+=hS&A_Zz*r4%yDdhDFAodsm}uLvd*+pzoz3rLx&Wd&+!MER*e% zTww+R?hs;|Org*K)7TItl9keaybQr@|D9Bee;!u|=NIRNxw4j6AtL!VdCFbg+I5bI zlu%p>4jYrbUcc`*udeY9XBJluSHGXq)AfdT*9KaP!^%UrIxqMRhZlrtVcP&0NURfy z;(y_^Kdm8~iXEj@ruha$M`=hWO*J23;Z2uja0B%ameazE@398N#`>L4%3v^^E3CL+ z@cow?n?j-I@@MOkrv%qpej>%Q^|Z-9Ng&Td@_s`=IJVBRI1Cn()i+@2+<_Hxru8Xz z(KjrT8!064i-(WwTI`r>>1V19_~=JFtiK*itTAH1?ETVBI!jNyH}`#iG`GiJ>o0v9 zz2p<4X1kItb@^k%wX~6^RBpp=6ENUO!-1bJ(L`GFztDAz4%jOG&LI=#3l5UpH}sA? z(lFnLJE-`Q$&ThwSYq?DL94L*osToPi@mdTl=xdxa zMd|tbp6JFHrJRBkWEGVglpRoP_pyV)bG8;pI_B4UWjVC@fEaDOayXtVdD7E+`5x}Mvn z-3I6Rt!QXIIKJx!D@I6g`i3w6m=IgM^OWvbUE6vTCsS`q2Os1L7!_^Kp+BUd zG}U%r$QvS#uXg1mVTVHA$%l82!WsPQC40h6lcn@neq^V@RHAEsAk4|`OME`Em#f5V z6F3{c6#;TQf%H|%5xr=AqqL65N>}#8=OI;zucT#F9{kT9#b2o3w;{4Dk&n^DG${KikkI^!wSv zwibSVui)c4zUz0zqEX43bgFwDjke^I zSnJ&sfmvyP5^zF>DT+ug;cGc0&hgSEKVXmxQCWe)q#*9%q7S8Uy5+aG+qR^uCu@vZ zNXrrwuCFSrK7^P^zA-Zilf<`4D-s#6r|9sMw!j9dl*2q`)K~ ziiz!1ZgqPV38hT$D)&@VF5R@;Z&QwB5eoMRrML% z0}ex%|JIT_UT7a;io$ zPv2#%<4dTps|Av~f(-*9yNRJTlA8YaXmF>;qT^_b#DQGAh0#Ly%$VH@!ABYNPNs9y zY3ePdYh#Yi^ITe(OnpzLGyTAbpH>?M>+lxu`7`?FJbg-}8gYOdF73k$#%C^u=kH`aop%QKc?U4c}5-2$^XuB%0Z~B6n88f(1INhdl-sfgB zyOFPqP=Ye{*Gq-wEF4ImZbB7pcwfRL4G{tusNe)6Ez~=c9s+zt7*cwx)o9J=NJ=Z_ z@pc>z#BDp0=CSfLsTiEW>02B0NWgfif(7Aa-0#GaLf;kA=iKF3q0cq6zUq#@r<&uv z!+hmf{Itjan!|__ju%fEn;IMH;8B&gy}`-yT2mNz<#?MFajja8h??lYFDo~R?w~J7 z83qD5QICbRAY1Q_#Adj6SEQF}gP#~G)drJx@HwD_hI6|vc0`mo(5&7dj|^Mp2fGZ4 z!9WnHG0=~tBzqdmkTPxKnHw485{9SXR8m#yKU*hYK=&IMSzdwvKE^8!$C^-kxPj!sl(ig@uU$ zZ(MaF7VC#1xe=c`UOd*&nl{?ee216DCjXYnEdli`0(^nT9B&b9m)b-Iv9}uc;K*Rd z?rt_CV$Ar8#N)7K8Vcieexm1W^F3q~GZl})Z#6(N*EZ%pO-jy}kBtHfl!%>xvA0|6 zMYaCji1n`7F;4Dzdv(E!nLl+ej(qOh=uT10&S)71 zs80P)@@ZDLY+lUHMt2_mj4sxxz$X!K^998D`^8hA(b74?(oWtfRy9f}cJ0c+LFDMD zvh#9*Oaf56JZswALX*3Rz5DkUEkrXrAWZ@*u}nqwljk+YnmE}`AB6whHC!=YFGqW> zI}a;fC}uaE;?SH9Ax5XGTAUy?k?75<7Pv@laOX@;9g15cCiT3bpEuSC=5@Jc;Z4i6 zd!9RysT8WX9BhE_BWFe6!jv4h&%d{IKW~11v|AX$3ArTc4|kG-uPwiC+&`rJx{$Yd zc5*z3n8AO{4E(#{*nM)3X3Fv9Kbmj47jn2}??*_+W_3kj{yj@?NBjdBV`lxik<=hNvT_>yO>BjHJ z>f~&}5q|x|o?g)`*&SHd zdt|YxWD5#bt{Rh7tpi233K-NW?>6}RbbA`-tq(0@iQlGG;hwp#UH`y~2{@uS&_h|n zSu>puGo*teS$ulAM-W(O=`_i(_{z zZxZmMT$evAA!n&s%xL(46lNF6768{p74GXfrieYyb&3DGB-|UDALcT>o z^qN`(|6}BA_vz*k8%O$QE?X!l=R=qT1)pN&#z}|H{7%7}8&j@gu*@AYPn2R08!gGD z-HI}ZoK}<%6_M{{qoU~zS7~J&QvaretM9TABqCVBcuB1UsW0N@;prM5SIt`3XwhVV z*l%%chmD@*(#%Ym=qq)1_;jlr*<8K6B`%aNH%gqr)#a(*9FH(8cSjm{Pk4QXw&e9! zYO=}>=%HbSXqu+}VOiW6++LAEj7~?BI7j}=COywRXt=RL@|i+boi3i*Z%999NUL*w zhOmURvT~SD$N{IT|LRn2b_Xp%76d@rd&>8gr}cXZhy}y&(9lrdh=_1DM9%9 z1oK0EwcQ%oGdYKs+z2a!k)GJnShF7NNMc1d#;Y5u+U!mBs&Tnqt4nPj#X5CgRQopr zUgJMy#SZy1^CwnLiqz?cHiM?lQu+_fV~qBw6jCpi(i2a@?Kg}}YmbgzMt#8-l}v{$ z>@0!pS%ThoH$k5j%2+xZY-YnGFVdyMdIYx@Q`tW}FH1guyMla!z8h_cV{UAe-IYVH z5FquU^UD;V88)i-S=cVd<+6QTW!JR7nBMeIJwkAU zk=zQ1e*G~8Kc4l*Pg!d6;u=n`!Q`EnL90F+yL+sFj)ahUdIylaJfNLOM0yQw5rt;z z(Ha!AaVVt?ljS;WP2zwoVQReUgFO|&!d#GKF0O4oW5*)14ft7Z9vY_L`{@vt%F|Gw z+6D!ey6+pf3+|#z&(shd5;-;@|17_w-zr5YsfZlAI%C%@%tb_S7a*S-rF$0}e5K)A z5E&2U=0Ej9*?uQAIWJg?9WqQm7$){1Zv7IDgd5{K%jw?talj)H`DxfU5&MeT$B!5= zp*}IBY`8F;Yl2&-wlhSYo+4`W33+TJ#A5$`*GkM%I_L?<2?4m-$}!wrGx*{rXo1l) z31+pg5IRl6yjQzcHxQ&30{r6{Y%5QRf zy~%$*N&R?2gwWr1-xd1Zp;r!8T&i5?zNuVnWQ^aS-l5pDjJrYtnft&F(45uYsx=fk z#HTej=RZc%VovxFUbkVC9`o9*<-Dv@{SV^vnYC(sT=X1p7po6Sd?clb?-+kxY@i&M z%FcN~3x;j3@G_s+*u3fOr=`$hdNWL7UYV%>pRYw@4<6#IPVlCn*Z6k0C+w@PmLELs zq1$&-0@W#njfoRVkjX#lp{qxj_B=r7lDqhparN{gHiF5*4hO29)2PIn%|L!tlqDy1 zwX4??hW_TM*My`!COK6ph+PIz$W_(7VFA3u$&qGY^120}FBjx9t75T3*!X_qe66?7 zQN6R@E059rL2|`T=;wXJV&P5DwE^A=77Mq2*wd!}ycORN{sqfZ3NHoApk=>5m1diT zGItI5X`*ud=F=nMVAzJ-3EEyTMDFOR+f^abLen_&i$6VnT3A5wmyqtDxc^U9NRaU8 z*ac}|0`vTo(o-NCbh()?)~+fy!USv;VSVfSXy4x`p*Hy`dQZK@zF?7=jP^CJ*e(SE zc4)k2s88vDmmFMBMf1v9+xr@_z(nvbl$lVyhD!s3pyxIz!U)@oV;(9|yjs&C)-%GL zT+Mx1|M+JJ$vBX?qM3j)rIWRLqClU2;a68kbG92=;RsJZ?_Woq-_7o5G(qypm3do14*>3+JR4h8Eei2X4Mlt-2pmXv z3cp2zp643(1Zy|hQ-dJww>HC|Tfn1E_A%o5w-@Z4%hRTF`&{>laQo#UTTEJnks}CM zCl`u9QN6LaGNZyvqBd>EO?%Tw@XQ|IB*enQ3j(5(4~$n2RkKYFw35-VFpwVpeyUXC z?Q%b}Iy%A+U8S9Xyl>yQz9^&z19erQ3>i24N$@4ikEyLC=Y0$`oV7O89qfwY-!WBj z!JZDRlHgnjXvc&gBO3$jkL&d*HRvdi3$$(ou$r2ha?%^@Q_vg&m)StS^7b|V?qC#w z#dt-u{^dzTUreW$i{S>_xYSVlg#=}JEvNe!-7vBY!BbYOkm1x)kOoYr*TDGu_wQNV zETBCYcI-JCq@@0l-hGj{`L~E=MRb34_qJW$sS90-Hstp&>7YPN{`db#) z#Z%E=89XXw-UCWRW3LR(8m#2vZMfQFwR`mQQfi(1H#kG5P~)vl&1}x|l#|io$Pr8^pa81y4+vm&KG1H+l9G~QW9|iZ zNnvr3@x$eg825`k)^6e3PN9c?J=5=(5<`67&fwHuWb(PB+KZD|yinTpQw9i9vv!)M zXut=IYReI!MBMPhH!YO(Z=XXI(^X1zsjYQdkj)h{`sUKjQx6gOUijyQG zz*gz+i|fIa28>%5J|J)Y(Pjq)4lEGn@B4ce2p3F0S;`DkdTnNN*^)$)3Pl4^)?2!8 z{5L=J)6~AmcL7lYt4S})-)<%5OMn7{IqIIAO!WF{HI|2rrju7iM1_dupC#Z?Ta%fQ z5gRKEp|Q*d61j4vQ|y5b?Ez)=)6{qYoSoF zs}#GP-F-1Ft`d`yiRe4-)=SJ^|FT0(MH~y4e}n~$lWYC-1%sRDVz(iU?eT@+0xXtw z(4+#it@*%OeR6U#PAE0c2sV^pkn%d}^_aL4j>kYa7yDc3kjJV7E9CgtPV()qY@ryi z1jvA169o-TEQ{YQr>Lm^kTT|(8;mL)e^=Zmof}SZn|Yt5vsP_3FT%d3j`oaZEi6Fm zr~KHkN|0dMJA=u@rm-Q0boweokZR(wTfqE#t|Tmp+i_>qZ!-c*C+8x)Tcl*{pBb~9 zipl}ym>(i!v_y?|%b2DLv0~NiN8+F&je1EXwnwpg=);RoMn=9Rw;pBGaI$z|gQr$# zUl=>949B!LtK;u(b1%itKQ5DAF1xPJ7k(UmAFF$7J|bCfy>R+s&2I4zuk*h;oFjzg zn#+ z?Rl6;_yt1S4g?9Q+AbeB_o@lp=lOxQjRaVPq4GCs+1ULSqZ`2r;2o)djekclb!E_3N+Mi!%%+*Yx1vbJkeH!8Z>psx zfw+$q*-^$v*^?HN%H&720SDtj7HH1I!qpTIfLJygm%;s5tpV%kS}*yT8l~RvK_GBQ z)p^{Q%H5)lMu4~mMgUribP{|3)iFZL=E9Y^XiWB9lZ?bc$G z1_zGt@SnO7Bv1Z=$6`Wcqd|Ns3crAWjw*};Y{Tdz{MxcGIzvDES5#5$@)ZVcpS^bF z82ZV15bA5d!I06^O--%{n4g{`1}$zlrgN!<=}`uEafnVBw|y{CY-FV%mS`b?G|3gV=U4W(Xd$&S<2eDl0Mz1pwp z&?ZR=1mb761g`_v^BpX-u7l|cqGYCbKc=ZDDf5NGSw|tjJ)2yuOnmxjlt5eelIo+j zt|r*($Rc<1qs6_$v{LNn=oph)dvMPbQ)T9X{!<08WWlyTA3FJdt)waO^zbN6BSmmN zu0MJUQR1mIKRR|d*{;?EkXYHg*kO;Q4ymd({B;VWL*ETNgX_0?#8qfaJC%<$$nl{Q z^C3NDMwq<_VJ4A8E_=?s=1N!;ogvvd^hDpsJ}StsOl>_^cF(keae)4)JDvodGfFnk zZW&PUza?!x^A@0|hRy}Xy!hbxOqTio(gL(87)vL12&c1oeT@$SxhqZ)#KKaQ_Vyjv zCuH`bLLHesK97K)oS1vfQ7br!3aVBgCQc&l{CqC= zcH*N66|`P+0*xH6f$UU#_F%CkMPoGA)S|RuCP@I&&PAm3sso6(mYOj#Phu zcUn`*ohhuhNP@5y2Yy9(iY2$r*kcp3Aa7IJ%D(UkpOKy_Q|zn5grc?1wE(wHt*R<8=~>Hw3}`2%+T^lH_766T__>jp&9{ z%>EG=>lqSc$>qdCpk@Xr^Nbd1g%}C{TQ7GbN{0p~3$zHxvoGsi+AZ`OfY{8Rn+O}? zt6@WKUdfz%OVpQ15`65z#Wo76Q1m2SgMr6Y9u6ht||O6UN;0uut>1YvC=|I z5T!!SiMiK8$?pjL|LkNVFR?Ve!oyeH@tD)1TxV4*yQGQWH^86 z26Zk+ifCT3?IWXs`p?}B6LaAnh!pv!)jB>F_ICZrF<+ZeT2`(zqrR9B{Ym}zzvvmh z9NA(T@RjKV*cPg0!^HGqnY9(apHst6;Ky#Rq-xvp-4|$CX+fH}mw+o6pw5nC5G<2D zae%2xu~4ol=ZYdrV(vJ;w{o24-^5dvro0nqGd18P3hw?)MV%i;r7X(shDX6I_pO!)8Tnn^aCa_%p!;oTZ9E|*1<#`j}FL;k-cf2!Vv zNs*z5eRgW&q?z7w%#n%xl&0&wInmx99!8v^zy-iB&Z4N{5aD(f|2nXFB#)O`1(S9bH>nAfM9{omj0R}aDYJ{dN>Sp3~g zr_n^XH(4IB8KIdky-IO~cZY_dH`BENpI~WRX7PNE);AE=1J(rgeu{1P6wx6e?}sNh zm#;$m9Fi$mS)Ypaqa$TrH2d_Ei+e^1Q|>pXTJk}>nlIm9#-#DbVqz~1hST<>^W&soOwc*L-+*{?T1Bo$!KFWI=1 z>!n#G(peVoRXy6NOuOdh(!5H((2T4a>Ztev9u^BiA0a!~nCK|C1d;t!=n0`;lwFlg z6wlOsYnwo$psgrwAA?AJi#-PVS3IL*d`%6{@$qnVt){?qY)8!05H19Us`!3#GuCIP z$;uq$vgluqOMs!c+5}U*JBfa5j9;!StfW*`bBm)xi{rlsoa0PbCm-N#j0J2UQw}oq zN>roQPg|F1B`0^x-sFG& zD>{rcc!mf2o9u>9W$M?Co*)$@ssb<@Z5<^uuvaJg)!syQJ<`dtk@5CxWRhhTmGXJC z(Da5+z6)RFfLbisONE%znan`w`8an3T>!TQ%;LHFfN?2X;x0VF6;{lv(&HXg_*r|J zKA_LxV6;rnP!jg{(_GA!f^O~dHc&L|cg}EEpH-i`XMicyZ&BJvt6|+}L1bu;XA2MbX(85QXE*-}g(hlr*70}7 z+d)#@V43jH;543q;qC7O(BIu>%H6a@t+8x}5rN)pnCQRY^IHEWv``(9m}LfL1S=p0 zVqcp(D=OA%X>WB8zjrqSjuAw^Z_$aU3lpFkF-nmf23W+X`y27cQTId zR~t`O-=CaU$*OZ_!*oMaalmv1pHMx(B|W2ojcjeJ23bDna%lw7ze)QB3%H?4iYv$n&{Zf~E6zt7vXPbn zqhYb#UeV}uoyD%cJ6l4TdV48fp7MsMsU~6>+~SZ)m!MvZH7ya zago3$!;@re#OE=2$wG$Smc{Yqy~f`rq8)E$Z0 zH_s$a4%Io;L@5Tu#5C-7MuqkC7UV}Ly(y*0*ib;R@pAG?02`@S5_Yh-o$r`S&%!11 z+-+0A0W7Z!{SD12w#V=qyH=+8y<^+E*mbily$g z_T^R#VW9J_F)kF~el&1ZjQ1L>CJ{*qN{4`yl`M4aUB5 zp(S9Ybt-PU!^Cc;HSplY>NRgUs}_UvzVvB-EkH{dS)Ddx{%evQ&+Uo&;4kSzO)WLi z^qib@`TgvyNU>fSNivi6YlY~;*#@-aUxv|*SFYghkJ%TF`~E%9gx78Chq>LVvonc~s8+nftee%$119?s-7=uWtUS`rVGhmHAeac8epT2fWC6<`$YtTu|LN|l|Dx==ex;EZy1Tn;P>?QZX@hP7LAslvLrOpq zL=Z*G)bD#G)pYsPCewbfohHKBh_Ol}9#_4M;sDzLVGQGoA%*BP=lmUMdLRp#yE2(Hb?QL)dyrN= zeiZS7oy@|Z)YNct z3IdEV-M5EU#-Xg^^Jl7*I7{t-{(*Xw4nX z6UQRCzXV|YpjE4NZzv%^O&cdX6Tu68^LwBB7t^f%R^05mnv%3)%8G2oEKvDdtZ}kA zPe!fn#Mw9+q$A^aQzFhisXzDI@xJVX44vCe+8#esvqTmfV2T7u+Y|BSG?Jn7?r^w1Fw#W z=mmzB)%KsTekO_Z(KAB7pJn_uMd{JHL$atYA#C%IeWkrSc`>^Ez|*T_9h$tPNQ-nW z@%u`n8l&LxOCXtonavHDl1p67)#p{0HhkDlkE^*}qNlB9utw0rUF3}d9TplSwG*|v zdqCCT{5GLifEH~;XXZ(+7@+?2PzTLix`47#%o6~#6CVFSBPL$?QeQHm70kC9lHVlc z;l6dvRL>dRo~_B_w*K4UvGj>q@z}mw_|G$SW3ZG=V#2Eu#ep9{5D%JU(5yXjbMwoz z^B;>jI?zH_?5d9)0-utoPZ*9-@cL5YxA_5Y`IGG+HR!;6C1DQi?C@_|i1%k|pPvAq z>n{q#zIqJkr&ne7s>WE=l;mhx^Oc@vIxEf@{&4-VZeZy6`CXQ8Mojz9ag_?sZ?-$r zm9TX9Z^CmFmM#Ki|LsIdiRW+ooVh$EDF7GdnI*_OuCTGW!O<1g)_-@H8MTiQqE8Kk zNR}_9e4l5J4ZRdXYk(V*CzOQEon4ioj+`Yg_qZ|Mh0@K!`097EGD8mt;l>7?XX8 z6*hrQ`g|M=GXBhYc+0Xqzzm&yuKF3Qx%rww_5?YKc7A^D>ftevtE^-75e%WLOV4Ul z3E`$=_4_k#-H3pAfCZw1sXtXe;V$9%*#j&48YHu}3c;q3qX$#SPw?>fLnW}@!rvwjSj)&aPwz)YlE5^yG!tqxksInPvOtN8 zhhjqnt89P(hdS#8%-p%W&s@ocp=VdQY^SG#S&HC^(m;J*JQopEfdEvTjM@Kiwa9E9 zEF`HCs%ofq)HWK8x<99RNy@`WTv9_x8+uKs!|tZK>AAFkdGsbGAlN9F23~*(mfy4^ zc$vC2#52^=ATLBJV5ImpY=4w2QFe9X**h!OzWgS*}r^} zj_&T)Xsy#8*7kdAw-^Pqo|DEr@RD-D@0=%0F7l(5Kv=0Lzw&Y4WWy)NwJ1ut& z`BCKr?k(ka>v>SmqjvU@8}2Rzh$e=_V}pcEwE+SU9EHpCBVU*BcrW5@n}A@74>qEY z_&@%aPEU=4?~t7qb=3je?~JG^cX+f?#aX}9$naAu;LHhC?&SuzL`u_h8h^)T;~|Z- zkVCWIUhO$m4vH)$%zG78yuasjB848R9X?X?R8!xvMVEa zd%sRIBd_L%0@KZ@p45wGy>%+LlR!JdP_V3Tm!JKBY@z;0QQiX`Pu(Lj*MiM{t*69E=+wtV zkrCuyLW`z|r~8r}iWqNIk^gcytPsCXQv{+f{9y}R4+RcLKf!TtDN(f8d0ycVXY3Sp+NZrbZ$ES{^ z3sWyN&p&NE`YG8$RakeKGd2nTx^Xh;EQ^tQR1d3lVy#-BjQ&=diX*}EhPx#To7 z{pUiWq8-4)N*5SL__|QoD7GGNd-W6+B;BzoT^oxu<26MO2!FP+ros^KD83e8So%Y_Mt`En1PBoyd8*;&0Sm)0V?tqSc&?a?lZA#=i)tk_Doq& zwTSOgRh}!N?ey%BBh}$#cmAbJ@I8QDV%*o~FhTAC9~aBu%&#%d!SDCq)raodKXC{t z<=)?(Ko7lJK2#8rOH`-N&=nJ&J-C|tD-TIj>ybZuwP zlm7mBM!&E?6?dQzEYAZI+<=d$-wC`ltM}pqdOa90H?RcmHa@`H5_pWqq_C)bCazRO zMelKiaHP%X=H_!n>AYkKaZWrH>SHl{0G{|N0gLs0q}d| ztr9Nz=ZOYicXl)))H?#Lf)aTnYjl zGpw$Qq_eKX-If)PpPkni5yEUXy7JY|$^`rR3G^o`yL0~`6M+KQU=7z1U>}V04IHgt z+e1)?y&PNrR5FRFaFxZ7hT7RZZEfh6MrKctqLZl=kA1f3e|Nbtm_q@4GgbZzit7k5 z{e^~#YFsl4tevNE1G9u}Mv63P*7=GbRqq1Hg(`|zpJsBzUESwjR_lQ2vIQvW_#ADK zZNF(tjZW5aiHLx{lJ?K=RTNEt@=98TlN$!=$eNwvJPgxdTyUtvrad{wVxb;kBb zzf&o0+8rFAaBL6@1pHEY z;H?X2-aDIRZnJ<3?#8b9gh`9`b4-41HZ2OiEFit%3tHacE`9->_+Uf;T5E^in4eyk zNZz?)2|V5&EX$tZq>VE=q)`Iz6UTLFRe(9w)&UT$4fxiYsHU-YH~{tKjV(6#VbWi} zM0$a0C9cRJ+S2Ektea~j+*~6;&?@^rxQ32>GO#+!;5BK3(?4krdQC^FVs;H&Wp7(q zS$Vw>_od?0h-pja3=X|t_JEAtB)a|Mqv~lC4`7wT>m3%2l|pjxfRJl+a?&O@7G&Y~ z2Z~l54xZ~M6|d6A$J>Ta-@`l5DU9bWTDTF8EgYws_yNtBFfJ(PVY{M#rUQ(0`>I|b z;pPPMhpL|~CM^6SU9B*=5cC8U7Ki}m!2Yf8SMA|#ioJvSrs>34g98KB%n)m7?v49v zh9$4I0gy^{#wZLLdk%7Q7P3Opct&g8?Et@r3RrX+HwQHiq{=#70L(`?M~hDY(4P1P z*_%ghw1nS67F&syu&pUTG(F;^j;l#4O4BIwmE@0twC!-qVzh$(skhIQfcOv&x#%;p z4>Q?uvUlm|oJsi}VT(O{n6FwA7IvQ%l%;iSd^c!O5WHkI?j(`F}(yfP(xt1Rk zx65#r1sgW7jSarGrTL~-47o@LFq~U{8-4v8!TGCm)fuk)nf;ZP>ktg6Vz6SkX z&ek3+t6iw~*uA8%U5jO`&K1P^f?X6t?M3^)q!A6SVo5-zTAW{7({pqE7leekZh{q# zF-qf3fyia%ieu!(}QWeA&DrH0F05-bAwYd$l>e|xo2Vf zmvi1})!gOAd0(b!($|ifek9MVO}!&dEg;Y9{j*;e{kWAKbcymKqQiUsf8aM2PTx4}Pm` zmAq_x2V(5#E>gma->cj#B=SQwlPE9kx~e>87mA!&kYqT@1==QM|Y>yITcn2+-M z&HRr<`l=}q9;*^Mr+xi|%`CN-27;L$L+t1j=tx}f_B|`1Qf)gYQ}I-TPKQ}%*1k-x zm?zuh<`-8a#4vhjE)sPlQw%)<}%e4?dK z$oT!I+B$j&zhus=^fYJg_Z^a6GzN9&f8w->B6H_h8sBiOWH68GEjYK>LZZBEcElY5 zn%mJIJQjwPfGKfD0N#FQf-8B{~gV-hSD2Vv}=9 zU_Ruvig#Y664i}W3ee#zF6IYbGeS(O8w4A^VdGIrM_e9@FY(Pv?6e#((H`Gz`4obp zd{kla&SG>c@|5}~835WX?%D~19t}~SMg#!|D{_r>z*v;-#Z}$WkEpZ$4c(9ac)NEf z3;SML^`5ga&Unr4wJnhN>-1C#x|p4GbmWt`t*=v2W>EsACbIAvuxh3&pOM67k#)Ll zkQfUEV>~Xa`*aoMK^GQc6gur^j?2sQXu0S@Gk#YFlVZ3wA9fZk5=+lxHIM# z5e0G$6YQ0+E)9Y|Ii)-*ivR^iA#c99gCq9nfXb15@~O4|5kIF@?k}6?n_crz_ZhU@ z>)qCDKCNlDCDz?s_lVZ(l}xT8ljpaL@83wvQGI_M<-O0zIr{3hA?GyX9Z!T0Gt6!I zn+tH;ZMxR$Uew8tfvdp3H0Qv1WPito5njCu%e&a1RLfs?>xTJqKNN*0pps=Z-ShC|G}fQ>S!)sFDX$&oEcKosZr4ugaPFL$A&z*bdbax-DZct z34z61sT*3#Z;7LgP(sI62BhL2N%V%(`ri<9O+pP(>BK_HL@k^bzReLo1E>%lw-CS>)Uq57cLf%Iiz!@=|Vi z$(C{lB5O}XnU=oEzojj_n8@Vx=Vw{87o?ga zxwA6)BD)L|Dy}O@;NMF4>4u9I?J098t!8A@soe4wIW_K&Ii{Blw!M8=Kc{&>6~-NI4c<6M)UN=?I`E zbWw(=o6Jo5X5uTJ?SI8zC%u6oWwkf^ldS`8yY0Kq zaAgDbG)ZD~d)3{KKkq%5Hg+1-_Q1I9v)V)SYikl$#CAYOC&|J4@aodvV{2lw8kh4i z7e7A)*sgSf$b9LSb!}cpsC*2vP)5=XIbJ2OC0d%w?zVrqugERJ0wGb(Q@P$PA%=xy zqWmf7r^k?vIKKWC3L-4wo>VODCB%BDJO2jg*_F^vL7<>*rcDh`BX^^m{Dz5vOV%-# z;1v(miJs^%A<<)l{Oay0PpJtyyCYThy`^)U0_w z&HEb^)N(`_74E*#3g+gF1A~J$JCd8iHg&>l#PMb~(T6Mg1IE0>4#Y*$8{Ra=XSu8d zXp#8oW#4Z1+a}rF9_y8cC~`+>pV83aTmh!gkX9>s-O=_p0??NQ zi|x3G74O9falSh(rWbvL@LHd$)!B;W%E-Itddln%a#t9AM>Er*&>z#oQiMjk^=dJ4 zfcc5}^j>(tm`-73G#Z=W_?^7#YtInti?}t7>9)FYv*tDE1dkEHIK`msNznW|Tb2hC zjll%**`mS<%ll{Jb{QZKfSu5_y1?dZd^W=J#$_iuEPlpfU<@EZ6O6-OQXFU{gpY(U`%i~D2FiwRLYS%DXGOi%k%y1PzKN3?;uw4 z@P`JJG05>5>hUf-Ye}56@+Hp&qRG-es}Cze87&%rDAYaNH!OkOgD)-K;fBKeak%cg zhN-Kazf{Ytaca&9&O%wZ?)m-xOYdtOo!ZV-X*VkkT)Y(cjj{`4quKa z5SYY@g?^4k3#{ZdfRnP+lfqel3cmZD%q8lUM{zNqb7DT%`cY88dFVw7!QR~n({Mk# zEU03HN=uy<%`d7ktOAa?PxqA%oi>=N%lN1E9%lmC|2F>lHrO4!9LZh3_HZtjL*O}~RL zuEGsev!&oNPMGY0nvX4-Db%O-A`H&Pb})iw7im6SEIDKd$kE4eiCfo?=fpN7xy zV$gi)x&|I5ZddPpd0ras@6~c3xiX{v#6ttmmhY?GnlVd3Q|nug+q~I#aOP*f>ZB+I zGXM!lXSSS4hXrU?w1Z~*FhcHa`bbCUJygA;{J8f|s6z9fm+A*bbK~o-6|92t`?eb+ zUBjM_)!_11U>#?NP||!P(YnL-#pVF;&%)<r9UV6c`EOaL6^T`X+fqI7M&_&QGSP*6D8h9VQax%;Gy*RLwiRBo{OIBe0w$ zfdK(@+xXxkxZyQXqi!dh+g2;LAftl`y2Ix$$YvcX#qldSS$(9f4K*kzC@Y%~WXL$; zK_oXNJXG`!d0PEEzbZ<=2I|bdp{O?C$C2|{UfYmz^NG6Y@cK1K_!8;(FE6=m-vNqk zzR4W<7X#{PZEYsOAL!hsH*Ayc61em}RmcLBqabNyjn4uU^(H@iNd(FZ15>?q0O7W# zYhRTHY=RyZz`+7OWkrg zE(t7sI}=f{$g;EOrV3YTBo`a0_m@dvh4gACCjPgS5TwYb;k|$CsAKY=UND$T1917N zCd;|S+HPPC2wdFWvO~JH=tAe=O0E~*(7KenpspRa)xZ5tNC|gd1@CFob%GAFOIynj z1vCZZHe31tiMo0@{K0)<#L7xIa9{0ZtD7b#uZ`HCa7Txt&fuVcLekv#zb_*RlGVV@ zshna!H{I085+SN?l(I-(yhS(X74@cBpf8;Z1_Xw^Wbj{Hyrh5OqQ;HZbhD4VNz?g+ zxx2ZEGE~tG9=)hG{Q@qd8#)|`0Ox~ZBtVtk3IjzIsQy$W$Nq;y1b&Ytn!2j5H5()= zUCs$t-S%h+sO+Gq*-79l+0IrsiJRFUgu9!G*iIn9N->TAFO*?%H>HGPTJQt(T~OsP zzuTWk&pvk*E(I0yiABtd!1p};y^dPJo1K`rm&Tb<0wFJIoe zcK0-2IJ)PN@PVi5&}lYjIy!uQX4KwxhD6ynLOPl^C;L&MBL^${@@k_d<%|z~C2&IG z5dVXff2Pl^??v~D1h(VoTXs{4P9_fhkNmx(byN`jEz2d*(kKHtTC?n3ZxsA-EwQa= zz^terR@sy{Fn4*ND;Bb^i=n`{+|5yQM&IGFeKazA@6grqW#UuMyd5=7fv;1{bXDTPlov==bJd$Gq7q&r_WLNXv?^uvDCi+At@Y&EOZHhzHhNvMa823s}iTf10uuDEiP5{g#2^PgkTb zX)<9`7vJ(@_;UmboDAI4DtU39Tz-dV=&p2gXe*5LC+!~vBo;|K7FafZ4%_7Y*Eqlk z|F7z9QTghfcj#-P9g2;z9rb$H1X5vb_&d6J%QZXbp-XkZ2{HJxmjJdW*)q`rguw>3 zXOLM*5A^OZf;{6JP56NwC92FB!T_mISu#QKbugzwu96sdb1$C3S`G)$0w6IuJ3S56 zqC`Dbd9a5cd2ZjQei{0;a%p#$|KyC|yT>1QMAiVio+WcGlThfMx^Osq2JYAAjCQ8(x!8Pp(jf6;c#Ej23m?H8NOot#Sg+Qzp!fKsY%{g#a&H36^y8PhG*2M~iZ#7_zsw zZ!iY9f- zJ)W-izSf@2GF;7k7Q^vFfa#5e>C1GUue;W2tgk;1K?lp6 z@V|xg5AFZu-SW=Xv81`?Y5@hCAVIdg#2G_RIUbn7_8lZj-kh^H*GSeRBwV>_x~!vn zzKJZ*qS|({#27RhmpBzS4f@qu+;wR2s#>tpYe+iV_-h3lP8QYEreo&u{clP3HFR~6 zL0|5)w5-ZVBr|uCuC%>Xz4S5rXEF68-moD5!Kh@tL}AL?0UMLPq$48onOp67Yk-GZ z0sQI08fzRyU*PEM?znjW`t((~by*n)`D2(L@KUkgvi?!l`g4uX;N6h8SDgjL1XWDN zw`K;2v&(ni7c=@u-Hn;~u>+35ueCQX7^kQIj6z4|hAfuxng+(cR^P^1?&_PfWydy{ z{PG`6rjr;pW)N#t9Zr%h=2)ix&hT%R`gZj9727q_S}gT7OmLuuGVk4-Nq-Fm7o~y- z#%AXPlz#*`NQyqws2=uN`ai*vQM`ZWw^Z2zpshGoBGgj#8nN@xcX#m9p~hoCl3|x! zss86~Gvf_jG1dmByuZ#F(ID2|Qm)$a~>Z=~j4xXv$LlA?=^nv9E&T%CSLqR&%hT4vOVc8SN z;w8+dnArbw^PoYF8;*#|Y%EG8GN!gQT91|0hfFd!>M?bt} zOVkZsd|I?%@S*$F(XK)uxL?pZ?X(gthlbhSYY-xX<09XbDy5|y!OZj1sK$Sn7{-0u z3%X?6s`=}8HTCkc)&6O(6*8a<_X7V>a$0tDWEXCYGu_&qwqcfAZa*u;+Fp%`j5C^h zt(hupc;B%)K!4(7Y}LNqZ6=j8xgC$-bq(J(wUhEOjz6gn4w;`jL0r-^G>j(trn~m^ z^n^4w%Ydw+!vDkt;GqR_E2?t>8JJYs=`lAmq8Nfrh(Zjh#L9jxTM*gqsBZP0x2HSw z5;jux5pM_G5bAy``dbR(j#yf5qN|{Ii$JU=?84`Xh=Wj%A2|~R9}_PaY{nvH6+YRw z67ybqQRPYn#vajplUFjq(1Hv_t4DXv(7A9U>(_}vxv>pEYgX{Q`kQ^B=o0+ zZ$Jf^_GjJ^2})G7RAa`uHrHUQ8wtnb+KkqI~i+kDZ!@rIkY$w0VQ7K0=aOTDC zcd~`H!B_UIve@%B2M(?Fxie3c^pk#^r z3)=>YB%jrby7yb!K>_8f5?YhSxe zB_cE0J6o_y+~(+QBbnb{$`!P+SR}=t;PtfjLb8m6T@QLT{UXCp4;|9?|R^osinVsNXrbSP7vFE2*QN9NYpAKan2Oh_Bf16HPz+ion`;E9kb$9?Cpb zwBp>0yPZsBhHSOo+`4{*Q60$tJ=KjHsq)kAYovmspKusjTjv`Q0yNyf`|(;IjG%($ zYNkQ-iKpl>b47Mfy6`R?9&3EB(`$)r-^&V?HLRj5Z$F1f(}lf}FIcee7DOyDG*YNh zQ7jf;Zi~hyBt%wK9dGLw-uYue-#qWKxQw!G`53$p=-BIUz9+xV!e1o0$LENeKn(HT zUFa%~$rg)HaB$!{k=Lhy?CkURITXi1`en?S8O85q!z4dSeCc{nu=etF5~}g02xic) zu}uoO9YaC*{B%M+QsKcYn@djd&WT(1&k?KW3&chgE^I(_K=2fmf<;fq^&*KIk!--I z)fYt5CTI8aeN8fHeiEJkEN-p30!By9K%ywK_^v5jX(D&MOiqu zsyy`5$mXX{f}jbGoUU%g!dYr}zKhgO>-kTGZlU0x0j7wl!ACyF8zRL7`kC;{U))yI zY_!w)Rn6-#*ZC?HTDS+>2E`|xcU$@QGm2=;i<@bHy0+#!Cd9M07Tn(MEn}dar&Rm*(KqJUH5<+ZIaYbo%<b9ej6jcD1*DKif5j9O$m>GB*9$;ODAr180jp*`mHxwH0@F()^t zWfCk0S^XXE8!X5>N;Qc%Etu5=$_V@pcbX5wJ0;bI+61S!5rA>5e_TVi3V@!#erO((Ue zpTty9fhajRP(_vCue_;$=G{G!rDstw8pg2tTB4TmWXrQ*yjFxAsntUL$i2riP2qbj z)XCTegGPns)b|w8dWrR2gwb^_tPE}-P^GS|)eXV2`<>yAf1kVWCDfT&$2XLp zLO(w}bimj9FxfEl!XOT@c`i+`VRz|IyO4DHGJ-n){?So)L^lLR2lYRv%}tY%J2|OG z-2Gy6>Uf+w>n}|3b!MGi*0@9M_t_4Y z-_gz>!D=1J`-9$GDLPbg`3}<-_HS|!7FbePczVcUOGhkn^BT>I?&)>wv^C#SEeUTo z!k`V2`HB~h>i6wLOok{O&Q%NTs_h+LAEJ6Lhr|?moH(2_P1R;=yUCiwBg+S1tm5vw!#nLSWJM#;_&8R?O-+Ul<0bC1*Mrv6elz^wCqH+ zlI4u>PL`n0l!_X{rh93UV>J~Zy|56_OU~9(J}zHlLs8x|HbE)3U}f+<#8{-F%d?JQBIHy-j^c@RL1eh%N9PvvNs>aE zk)0Q-3(3TSa@4Y4jTa^z*4F@abTmGH+S=2UZ3u$(|S1|t9@Z13jPompcSa$vc zrh+*HC%_%Tz9cE^L`Wb2e#wOihlHiK;4Jc%^a((bZiFfUXm0##rBGIBW>!?Mb()j| ze_{Skvo_)IHRvH|Bzmv3PFiFOX8%$^NaemM&aeU0bD1=ZMLYUE$H9Zgbs5oVaa@99 zkD^nB@{tmG4CN%N!M_XJLStT?pC8Tz6Sk^^x$SgYJ*KR9a7jK^Wc#==m>BZn-IHNs zR@!&gOg(si2Y7tZA;?)D2SF1#Ih3aa!_MT078i~&>H@Z|ycvIJDc#5X_cE!tSV=4w zLdS;{zuj9gQ4S7W+BRycYl5cMc#+y-OP0%BM>LP2*7HMQ@fwpnsMGkW?jGyQPGj0o zLDS@I!3a7t@j@NucM=C|N)-8#epMM>#P8m-ox&>2CGP;ro@a z?nYxg)C$FS1`4)~TKun(Xt?+XC%;tmAHIX?bN%*#ikHvDuYR{?HC0`0Lrg@v22AfM zEO6W{ef05j+uHAbY$-x)25*Lr$BE>)Xyo*r_w6EwmURlhkwH)vVB0_T&di2=%Un3l zv;y$FL?*0rtt{AoqB~T$?muK;op|*QWzFS<0e`|w%f~{#xm$K<%R=8Qankf-f}ffg zWn=yIV24i0Gs?&?01DNz(f2Q>ekm|HR!YS~Cw_#W`Tnh)U(Z7w2@IfjYZ$j+oQgnr z8y@J4*heB*H0aCFNN}wN%!C;ty>ToWTF|o#qAXU96P8zzTp~p+E;)4ES2WxcA7R{H zem@j@3y6{NBb`zOzQ!(1csu6Lp4!Gioza^Rz0r03!k~jAw&xmZQ{}G(h^D$Usc}E>844Uh1qGrmf>}W%1nWhLjnUS6clt$%V)+?0h!Kx*2j`TTD)*Win zA4TtUEDG*!BPjDGVX8#`q-!^!kl?d$;4Bf@sOxp>7HXWDqMZJ<(OkRE+{pR?6g`4r z`CEQdLR(}zs#PwnW+6LDg)1RWWLwx%?-a_EP%Llt=+p@89v+W~^>=ct)T|vjQA~fP zJYd7fPP}y+R%FG4Fa3=W!W>RdA=4Q*)?C=yWau0o@ARn8!G4lQBbDW^&+iX2gK*Nb z-1{|n)y}e2`72tx$)A`o!(Q zzDq`^DpVBLi#qLQ93bqyN$Nf(g)9v`w?BD<&*nQhB~crE8qXam|4PHNdrYXxzI|b3 zpI}37oa3LF0AI344{faQLe3kYIdY>>kK&4@E2FXn&#QPG>l$y>q_`$`VKC?XYtfCo zz*fAbj{SZiNm+333tN_ERTE}te9nfh$r!}FpRo?smY zU}aT(9&{wZUvNd`eUIopv|L>rZrxML8liiagm9c~VCi$ShiW_i>z|-7>tKaq1(P#h zol`xoeT~P&nNY>{?1d@!rBK}->8=AI8Mp(!O1)lx&)?1TGKI$WJtw{PB}i|>;i^p3 qfG5e)R7P5I*z|ne{uMlVg*KIS=d0=?Ypz@1PfbZnu}t1P?0*2W;z8R0 literal 0 HcmV?d00001 diff --git a/doc/_static/storage-protocol.png b/doc/_static/storage-protocol.png new file mode 100644 index 0000000000000000000000000000000000000000..56665519e540355f4e550005d003f18aee7b4313 GIT binary patch literal 26433 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. -License for compat/realpath.c ------------------------------ +License for compat-src/realpath.c +--------------------------------- Copyright (c) 2003 Constantin S. Svintsoff Redistribution and use in source and binary forms, with or without @@ -120,8 +120,8 @@ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -Lisense for compat/snprintf.c ------------------------------ +Lisense for compat-src/snprintf.c +--------------------------------- Copyright (c) 1995 Patrick Powell. This code is based on code written by Patrick Powell . diff --git a/src/utils/README.md b/src/utils/README.md index 4f496851a..bf864cd37 100644 --- a/src/utils/README.md +++ b/src/utils/README.md @@ -9,7 +9,7 @@ in the list below. Content of this library: -- compat/ -- subdir with compatibility implementations for non-POSIX systems +- compat-src/ -- subdir with compatibility implementations for non-POSIX systems - license: varies, MIT-compatible - declared in: config.h - depends on: config.h generated by cmake diff --git a/src/utils/compat.h b/src/utils/compat.h index c8d7ef31c..dade5ba11 100644 --- a/src/utils/compat.h +++ b/src/utils/compat.h @@ -4,16 +4,16 @@ * * Distributed under terms of the MIT license. */ +#ifndef _COMPAT_H +#define _COMPAT_H /** @file @brief auxiliary compatibility functions - Note that the declarations for the functions found in the compat/ + Note that the declarations for the functions found in the compat-src/ subdirectory are provided in config.h.in. */ -#ifndef _COMPAT_H -#define _COMPAT_H #include #ifdef WIN32 @@ -36,7 +36,7 @@ #endif /* - * compat/realpath.c + * compat-src/realpath.c */ /** realpath() - return the canonicalized absolute pathname */ @@ -46,7 +46,7 @@ char *realpath(const char *path, char *resolved); /* - * compat/snprintf.c + * compat-src/snprintf.c */ /** snprintf() - write formatted output to sized buffer */ diff --git a/src/utils/compat/config_snprintf.h.in b/src/utils/compat/config_snprintf.h.in deleted file mode 100644 index 6046c88d5..000000000 --- a/src/utils/compat/config_snprintf.h.in +++ /dev/null @@ -1,77 +0,0 @@ -#ifdef HAVE_STDARG_H -# undef HAVE_STDARG_H -#endif -#ifdef HAVE_STDDEF_H -# undef HAVE_STDDEF_H -#endif -#ifdef HAVE_STDINT_H -# undef HAVE_STDINT_H -#endif -#ifdef HAVE_STDLIB_H -# undef HAVE_STDLIB_H -#endif -#ifdef HAVE_FLOAT_H -# undef HAVE_FLOAT_H -#endif -#ifdef HAVE_INTTYPES_H -# undef HAVE_INTTYPES_H -#endif -#ifdef HAVE_LOCALE_H -# undef HAVE_LOCALE_H -#endif -#ifdef HAVE_LOCALECONV -# undef HAVE_LOCALECONV -#endif -#ifdef HAVE_LCONV_DECIMAL_POINT -# undef HAVE_LCONV_DECIMAL_POINT -#endif -#ifdef HAVE_LCONV_THOUSANDS_SEP -# undef HAVE_LCONV_THOUSANDS_SEP -#endif -#ifdef HAVE_LONG_DOUBLE -# undef HAVE_LONG_DOUBLE -#endif -#ifdef HAVE_LONG_LONG_INT -# undef HAVE_LONG_LONG_INT -#endif -#ifdef HAVE_UNSIGNED_LONG_LONG_INT -# undef HAVE_UNSIGNED_LONG_LONG_INT -#endif -#ifdef HAVE_INTMAX_T -# undef HAVE_INTMAX_T -#endif -#ifdef HAVE_UINTMAX_T -# undef HAVE_UINTMAX_T -#endif -#ifdef HAVE_UINTPTR_T -# undef HAVE_UINTPTR_T -#endif -#ifdef HAVE_PTRDIFF_T -# undef HAVE_PTRDIFF_T -#endif -#ifdef HAVE_VA_COPY -# undef HAVE_VA_COPY -#endif -#ifdef HAVE___VA_COPY -# undef HAVE___VA_COPY -#endif - -#cmakedefine01 HAVE_STDARG_H -#cmakedefine01 HAVE_STDDEF_H -#cmakedefine01 HAVE_STDINT_H -#cmakedefine01 HAVE_STDLIB_H -#cmakedefine01 HAVE_FLOAT_H -#cmakedefine01 HAVE_INTTYPES_H -#cmakedefine01 HAVE_LOCALE_H -#cmakedefine01 HAVE_LOCALECONV -#cmakedefine01 HAVE_LCONV_DECIMAL_POINT -#cmakedefine01 HAVE_LCONV_THOUSANDS_SEP -#cmakedefine01 HAVE_LONG_DOUBLE -#cmakedefine01 HAVE_LONG_LONG_INT -#cmakedefine01 HAVE_UNSIGNED_LONG_LONG_INT -#cmakedefine01 HAVE_INTMAX_T -#cmakedefine01 HAVE_UINTMAX_T -#cmakedefine01 HAVE_UINTPTR_T -#cmakedefine01 HAVE_PTRDIFF_T -#cmakedefine01 HAVE_VA_COPY -#cmakedefine01 HAVE___VA_COPY diff --git a/src/utils/compat/dirent.h b/src/utils/compat/dirent.h deleted file mode 100644 index 51c79ecfa..000000000 --- a/src/utils/compat/dirent.h +++ /dev/null @@ -1,1234 +0,0 @@ -/* - * Dirent interface for Microsoft Visual Studio - * - * Copyright (C) 2006-2012 Toni Ronkko - * This file is part of dirent. Dirent may be freely distributed - * under the MIT license. For all details and documentation, see - * https://github.com/tronkko/dirent - */ -#ifndef DIRENT_H -#define DIRENT_H - -/* - * Include windows.h without Windows Sockets 1.1 to prevent conflicts with - * Windows Sockets 2.0. - */ -#ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Indicates that d_type field is available in dirent structure */ -#define _DIRENT_HAVE_D_TYPE - -/* Indicates that d_namlen field is available in dirent structure */ -#define _DIRENT_HAVE_D_NAMLEN - -/* Entries missing from MSVC 6.0 */ -#if !defined(FILE_ATTRIBUTE_DEVICE) -# define FILE_ATTRIBUTE_DEVICE 0x40 -#endif - -/* File type and permission flags for stat(), general mask */ -#if !defined(S_IFMT) -# define S_IFMT _S_IFMT -#endif - -/* Directory bit */ -#if !defined(S_IFDIR) -# define S_IFDIR _S_IFDIR -#endif - -/* Character device bit */ -#if !defined(S_IFCHR) -# define S_IFCHR _S_IFCHR -#endif - -/* Pipe bit */ -#if !defined(S_IFFIFO) -# define S_IFFIFO _S_IFFIFO -#endif - -/* Regular file bit */ -#if !defined(S_IFREG) -# define S_IFREG _S_IFREG -#endif - -/* Read permission */ -#if !defined(S_IREAD) -# define S_IREAD _S_IREAD -#endif - -/* Write permission */ -#if !defined(S_IWRITE) -# define S_IWRITE _S_IWRITE -#endif - -/* Execute permission */ -#if !defined(S_IEXEC) -# define S_IEXEC _S_IEXEC -#endif - -/* Pipe */ -#if !defined(S_IFIFO) -# define S_IFIFO _S_IFIFO -#endif - -/* Block device */ -#if !defined(S_IFBLK) -# define S_IFBLK 0 -#endif - -/* Link */ -#if !defined(S_IFLNK) -# define S_IFLNK 0 -#endif - -/* Socket */ -#if !defined(S_IFSOCK) -# define S_IFSOCK 0 -#endif - -/* Read user permission */ -#if !defined(S_IRUSR) -# define S_IRUSR S_IREAD -#endif - -/* Write user permission */ -#if !defined(S_IWUSR) -# define S_IWUSR S_IWRITE -#endif - -/* Execute user permission */ -#if !defined(S_IXUSR) -# define S_IXUSR 0 -#endif - -/* Read group permission */ -#if !defined(S_IRGRP) -# define S_IRGRP 0 -#endif - -/* Write group permission */ -#if !defined(S_IWGRP) -# define S_IWGRP 0 -#endif - -/* Execute group permission */ -#if !defined(S_IXGRP) -# define S_IXGRP 0 -#endif - -/* Read others permission */ -#if !defined(S_IROTH) -# define S_IROTH 0 -#endif - -/* Write others permission */ -#if !defined(S_IWOTH) -# define S_IWOTH 0 -#endif - -/* Execute others permission */ -#if !defined(S_IXOTH) -# define S_IXOTH 0 -#endif - -/* Maximum length of file name */ -#if !defined(PATH_MAX) -# define PATH_MAX MAX_PATH -#endif -#if !defined(FILENAME_MAX) -# define FILENAME_MAX MAX_PATH -#endif -#if !defined(NAME_MAX) -# define NAME_MAX FILENAME_MAX -#endif - -/* File type flags for d_type */ -#define DT_UNKNOWN 0 -#define DT_REG S_IFREG -#define DT_DIR S_IFDIR -#define DT_FIFO S_IFIFO -#define DT_SOCK S_IFSOCK -#define DT_CHR S_IFCHR -#define DT_BLK S_IFBLK -#define DT_LNK S_IFLNK - -/* Macros for converting between st_mode and d_type */ -#define IFTODT(mode) ((mode) & S_IFMT) -#define DTTOIF(type) (type) - -/* - * File type macros. Note that block devices, sockets and links cannot be - * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are - * only defined for compatibility. These macros should always return false - * on Windows. - */ -#if !defined(S_ISFIFO) -# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) -#endif -#if !defined(S_ISDIR) -# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) -#endif -#if !defined(S_ISREG) -# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) -#endif -#if !defined(S_ISLNK) -# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) -#endif -#if !defined(S_ISSOCK) -# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) -#endif -#if !defined(S_ISCHR) -# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) -#endif -#if !defined(S_ISBLK) -# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) -#endif - -/* Return the exact length of the file name without zero terminator */ -#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) - -/* Return the maximum size of a file name */ -#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) - -/* Remove __attribute__ when we are not compiling with gcc */ -#ifndef __GNUC__ -# define __attribute__(x) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Wide-character version */ -struct _wdirent { - /* Always zero */ - long d_ino; - - /* File position within stream */ - long d_off; - - /* Structure size */ - unsigned short d_reclen; - - /* Length of name without \0 */ - size_t d_namlen; - - /* File type */ - int d_type; - - /* File name */ - wchar_t d_name[PATH_MAX+1]; -}; -typedef struct _wdirent _wdirent; - -struct _WDIR { - /* Current directory entry */ - struct _wdirent ent; - - /* Private file data */ - WIN32_FIND_DATAW data; - - /* True if data is valid */ - int cached; - - /* Win32 search handle */ - HANDLE handle; - - /* Initial directory name */ - wchar_t *patt; -}; -typedef struct _WDIR _WDIR; - -/* Multi-byte character version */ -struct dirent { - /* Always zero */ - long d_ino; - - /* File position within stream */ - long d_off; - - /* Structure size */ - unsigned short d_reclen; - - /* Length of name without \0 */ - size_t d_namlen; - - /* File type */ - int d_type; - - /* File name */ - char d_name[PATH_MAX+1]; -}; -typedef struct dirent dirent; - -struct DIR { - struct dirent ent; - struct _WDIR *wdirp; -}; -typedef struct DIR DIR; - - -/* Dirent functions */ -static DIR *opendir (const char *dirname); -static _WDIR *_wopendir (const wchar_t *dirname); - -static struct dirent *readdir (DIR *dirp) - __attribute__((unused)); -static struct _wdirent *_wreaddir (_WDIR *dirp) - __attribute__((unused)); - -static int readdir_r( - DIR *dirp, struct dirent *entry, struct dirent **result); -static int _wreaddir_r( - _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result) - __attribute__((unused)); - -static int closedir (DIR *dirp); -static int _wclosedir (_WDIR *dirp); - -static void rewinddir (DIR* dirp) - __attribute__((unused)); -static void _wrewinddir (_WDIR* dirp); - -static int scandir (const char *dirname, struct dirent ***namelist, - int (*filter)(const struct dirent*), - int (*compare)(const struct dirent**, const struct dirent**)) - __attribute__((unused)); - -static int alphasort (const struct dirent **a, const struct dirent **b); - -static int versionsort (const struct dirent **a, const struct dirent **b) - __attribute__((unused)); - - -/* For compatibility with Symbian */ -#define wdirent _wdirent -#define WDIR _WDIR -#define wopendir _wopendir -#define wreaddir _wreaddir -#define wclosedir _wclosedir -#define wrewinddir _wrewinddir - - -/* Internal utility functions */ -static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); -static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); - -static int dirent_mbstowcs_s( - size_t *pReturnValue, - wchar_t *wcstr, - size_t sizeInWords, - const char *mbstr, - size_t count); - -static int dirent_wcstombs_s( - size_t *pReturnValue, - char *mbstr, - size_t sizeInBytes, - const wchar_t *wcstr, - size_t count); - -static void dirent_set_errno (int error); - - -/* - * Open directory stream DIRNAME for read and return a pointer to the - * internal working area that is used to retrieve individual directory - * entries. - */ -static _WDIR* -_wopendir( - const wchar_t *dirname) -{ - _WDIR *dirp = NULL; - int error; - - /* Must have directory name */ - if (dirname == NULL || dirname[0] == '\0') { - dirent_set_errno (ENOENT); - return NULL; - } - - /* Allocate new _WDIR structure */ - dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); - if (dirp != NULL) { - DWORD n; - - /* Reset _WDIR structure */ - dirp->handle = INVALID_HANDLE_VALUE; - dirp->patt = NULL; - dirp->cached = 0; - - /* Compute the length of full path plus zero terminator - * - * Note that on WinRT there's no way to convert relative paths - * into absolute paths, so just assume it is an absolute path. - */ -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) - n = wcslen(dirname); -# else - n = GetFullPathNameW (dirname, 0, NULL, NULL); -# endif - - /* Allocate room for absolute directory name and search pattern */ - dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); - if (dirp->patt) { - - /* - * Convert relative directory name to an absolute one. This - * allows rewinddir() to function correctly even when current - * working directory is changed between opendir() and rewinddir(). - * - * Note that on WinRT there's no way to convert relative paths - * into absolute paths, so just assume it is an absolute path. - */ -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) - wcsncpy_s(dirp->patt, n+1, dirname, n); -# else - n = GetFullPathNameW (dirname, n, dirp->patt, NULL); -# endif - if (n > 0) { - wchar_t *p; - - /* Append search pattern \* to the directory name */ - p = dirp->patt + n; - if (dirp->patt < p) { - switch (p[-1]) { - case '\\': - case '/': - case ':': - /* Directory ends in path separator, e.g. c:\temp\ */ - /*NOP*/; - break; - - default: - /* Directory name doesn't end in path separator */ - *p++ = '\\'; - } - } - *p++ = '*'; - *p = '\0'; - - /* Open directory stream and retrieve the first entry */ - if (dirent_first (dirp)) { - /* Directory stream opened successfully */ - error = 0; - } else { - /* Cannot retrieve first entry */ - error = 1; - dirent_set_errno (ENOENT); - } - - } else { - /* Cannot retrieve full path name */ - dirent_set_errno (ENOENT); - error = 1; - } - - } else { - /* Cannot allocate memory for search pattern */ - error = 1; - } - - } else { - /* Cannot allocate _WDIR structure */ - error = 1; - } - - /* Clean up in case of error */ - if (error && dirp) { - _wclosedir (dirp); - dirp = NULL; - } - - return dirp; -} - -/* - * Read next directory entry. - * - * Returns pointer to static directory entry which may be overwritten by - * subsequent calls to _wreaddir(). - */ -static struct _wdirent* -_wreaddir( - _WDIR *dirp) -{ - struct _wdirent *entry; - - /* - * Read directory entry to buffer. We can safely ignore the return value - * as entry will be set to NULL in case of error. - */ - (void) _wreaddir_r (dirp, &dirp->ent, &entry); - - /* Return pointer to statically allocated directory entry */ - return entry; -} - -/* - * Read next directory entry. - * - * Returns zero on success. If end of directory stream is reached, then sets - * result to NULL and returns zero. - */ -static int -_wreaddir_r( - _WDIR *dirp, - struct _wdirent *entry, - struct _wdirent **result) -{ - WIN32_FIND_DATAW *datap; - - /* Read next directory entry */ - datap = dirent_next (dirp); - if (datap) { - size_t n; - DWORD attr; - - /* - * Copy file name as wide-character string. If the file name is too - * long to fit in to the destination buffer, then truncate file name - * to PATH_MAX characters and zero-terminate the buffer. - */ - n = 0; - while (n < PATH_MAX && datap->cFileName[n] != 0) { - entry->d_name[n] = datap->cFileName[n]; - n++; - } - entry->d_name[n] = 0; - - /* Length of file name excluding zero terminator */ - entry->d_namlen = n; - - /* File type */ - attr = datap->dwFileAttributes; - if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { - entry->d_type = DT_CHR; - } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { - entry->d_type = DT_DIR; - } else { - entry->d_type = DT_REG; - } - - /* Reset dummy fields */ - entry->d_ino = 0; - entry->d_off = 0; - entry->d_reclen = sizeof (struct _wdirent); - - /* Set result address */ - *result = entry; - - } else { - - /* Return NULL to indicate end of directory */ - *result = NULL; - - } - - return /*OK*/0; -} - -/* - * Close directory stream opened by opendir() function. This invalidates the - * DIR structure as well as any directory entry read previously by - * _wreaddir(). - */ -static int -_wclosedir( - _WDIR *dirp) -{ - int ok; - if (dirp) { - - /* Release search handle */ - if (dirp->handle != INVALID_HANDLE_VALUE) { - FindClose (dirp->handle); - dirp->handle = INVALID_HANDLE_VALUE; - } - - /* Release search pattern */ - if (dirp->patt) { - free (dirp->patt); - dirp->patt = NULL; - } - - /* Release directory structure */ - free (dirp); - ok = /*success*/0; - - } else { - - /* Invalid directory stream */ - dirent_set_errno (EBADF); - ok = /*failure*/-1; - - } - return ok; -} - -/* - * Rewind directory stream such that _wreaddir() returns the very first - * file name again. - */ -static void -_wrewinddir( - _WDIR* dirp) -{ - if (dirp) { - /* Release existing search handle */ - if (dirp->handle != INVALID_HANDLE_VALUE) { - FindClose (dirp->handle); - } - - /* Open new search handle */ - dirent_first (dirp); - } -} - -/* Get first directory entry (internal) */ -static WIN32_FIND_DATAW* -dirent_first( - _WDIR *dirp) -{ - WIN32_FIND_DATAW *datap; - - /* Open directory and retrieve the first entry */ - dirp->handle = FindFirstFileExW( - dirp->patt, FindExInfoStandard, &dirp->data, - FindExSearchNameMatch, NULL, 0); - if (dirp->handle != INVALID_HANDLE_VALUE) { - - /* a directory entry is now waiting in memory */ - datap = &dirp->data; - dirp->cached = 1; - - } else { - - /* Failed to re-open directory: no directory entry in memory */ - dirp->cached = 0; - datap = NULL; - - } - return datap; -} - -/* - * Get next directory entry (internal). - * - * Returns - */ -static WIN32_FIND_DATAW* -dirent_next( - _WDIR *dirp) -{ - WIN32_FIND_DATAW *p; - - /* Get next directory entry */ - if (dirp->cached != 0) { - - /* A valid directory entry already in memory */ - p = &dirp->data; - dirp->cached = 0; - - } else if (dirp->handle != INVALID_HANDLE_VALUE) { - - /* Get the next directory entry from stream */ - if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { - /* Got a file */ - p = &dirp->data; - } else { - /* The very last entry has been processed or an error occurred */ - FindClose (dirp->handle); - dirp->handle = INVALID_HANDLE_VALUE; - p = NULL; - } - - } else { - - /* End of directory stream reached */ - p = NULL; - - } - - return p; -} - -/* - * Open directory stream using plain old C-string. - */ -static DIR* -opendir( - const char *dirname) -{ - struct DIR *dirp; - int error; - - /* Must have directory name */ - if (dirname == NULL || dirname[0] == '\0') { - dirent_set_errno (ENOENT); - return NULL; - } - - /* Allocate memory for DIR structure */ - dirp = (DIR*) malloc (sizeof (struct DIR)); - if (dirp) { - wchar_t wname[PATH_MAX + 1]; - size_t n; - - /* Convert directory name to wide-character string */ - error = dirent_mbstowcs_s( - &n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1); - if (!error) { - - /* Open directory stream using wide-character name */ - dirp->wdirp = _wopendir (wname); - if (dirp->wdirp) { - /* Directory stream opened */ - error = 0; - } else { - /* Failed to open directory stream */ - error = 1; - } - - } else { - /* - * Cannot convert file name to wide-character string. This - * occurs if the string contains invalid multi-byte sequences or - * the output buffer is too small to contain the resulting - * string. - */ - error = 1; - } - - } else { - /* Cannot allocate DIR structure */ - error = 1; - } - - /* Clean up in case of error */ - if (error && dirp) { - free (dirp); - dirp = NULL; - } - - return dirp; -} - -/* - * Read next directory entry. - */ -static struct dirent* -readdir( - DIR *dirp) -{ - struct dirent *entry; - - /* - * Read directory entry to buffer. We can safely ignore the return value - * as entry will be set to NULL in case of error. - */ - (void) readdir_r (dirp, &dirp->ent, &entry); - - /* Return pointer to statically allocated directory entry */ - return entry; -} - -/* - * Read next directory entry into called-allocated buffer. - * - * Returns zero on success. If the end of directory stream is reached, then - * sets result to NULL and returns zero. - */ -static int -readdir_r( - DIR *dirp, - struct dirent *entry, - struct dirent **result) -{ - WIN32_FIND_DATAW *datap; - - /* Read next directory entry */ - datap = dirent_next (dirp->wdirp); - if (datap) { - size_t n; - int error; - - /* Attempt to convert file name to multi-byte string */ - error = dirent_wcstombs_s( - &n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1); - - /* - * If the file name cannot be represented by a multi-byte string, - * then attempt to use old 8+3 file name. This allows traditional - * Unix-code to access some file names despite of unicode - * characters, although file names may seem unfamiliar to the user. - * - * Be ware that the code below cannot come up with a short file - * name unless the file system provides one. At least - * VirtualBox shared folders fail to do this. - */ - if (error && datap->cAlternateFileName[0] != '\0') { - error = dirent_wcstombs_s( - &n, entry->d_name, PATH_MAX + 1, - datap->cAlternateFileName, PATH_MAX + 1); - } - - if (!error) { - DWORD attr; - - /* Length of file name excluding zero terminator */ - entry->d_namlen = n - 1; - - /* File attributes */ - attr = datap->dwFileAttributes; - if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { - entry->d_type = DT_CHR; - } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { - entry->d_type = DT_DIR; - } else { - entry->d_type = DT_REG; - } - - /* Reset dummy fields */ - entry->d_ino = 0; - entry->d_off = 0; - entry->d_reclen = sizeof (struct dirent); - - } else { - - /* - * Cannot convert file name to multi-byte string so construct - * an erroneous directory entry and return that. Note that - * we cannot return NULL as that would stop the processing - * of directory entries completely. - */ - entry->d_name[0] = '?'; - entry->d_name[1] = '\0'; - entry->d_namlen = 1; - entry->d_type = DT_UNKNOWN; - entry->d_ino = 0; - entry->d_off = -1; - entry->d_reclen = 0; - - } - - /* Return pointer to directory entry */ - *result = entry; - - } else { - - /* No more directory entries */ - *result = NULL; - - } - - return /*OK*/0; -} - -/* - * Close directory stream. - */ -static int -closedir( - DIR *dirp) -{ - int ok; - if (dirp) { - - /* Close wide-character directory stream */ - ok = _wclosedir (dirp->wdirp); - dirp->wdirp = NULL; - - /* Release multi-byte character version */ - free (dirp); - - } else { - - /* Invalid directory stream */ - dirent_set_errno (EBADF); - ok = /*failure*/-1; - - } - return ok; -} - -/* - * Rewind directory stream to beginning. - */ -static void -rewinddir( - DIR* dirp) -{ - /* Rewind wide-character string directory stream */ - _wrewinddir (dirp->wdirp); -} - -/* - * Scan directory for entries. - */ -static int -scandir( - const char *dirname, - struct dirent ***namelist, - int (*filter)(const struct dirent*), - int (*compare)(const struct dirent**, const struct dirent**)) -{ - struct dirent **files = NULL; - size_t size = 0; - size_t allocated = 0; - const size_t init_size = 1; - DIR *dir = NULL; - struct dirent *entry; - struct dirent *tmp = NULL; - size_t i; - int result = 0; - - /* Open directory stream */ - dir = opendir (dirname); - if (dir) { - - /* Read directory entries to memory */ - while (1) { - - /* Enlarge pointer table to make room for another pointer */ - if (size >= allocated) { - void *p; - size_t num_entries; - - /* Compute number of entries in the enlarged pointer table */ - if (size < init_size) { - /* Allocate initial pointer table */ - num_entries = init_size; - } else { - /* Double the size */ - num_entries = size * 2; - } - - /* Allocate first pointer table or enlarge existing table */ - p = realloc (files, sizeof (void*) * num_entries); - if (p != NULL) { - /* Got the memory */ - files = (dirent**) p; - allocated = num_entries; - } else { - /* Out of memory */ - result = -1; - break; - } - - } - - /* Allocate room for temporary directory entry */ - if (tmp == NULL) { - tmp = (struct dirent*) malloc (sizeof (struct dirent)); - if (tmp == NULL) { - /* Cannot allocate temporary directory entry */ - result = -1; - break; - } - } - - /* Read directory entry to temporary area */ - if (readdir_r (dir, tmp, &entry) == /*OK*/0) { - - /* Did we get an entry? */ - if (entry != NULL) { - int pass; - - /* Determine whether to include the entry in result */ - if (filter) { - /* Let the filter function decide */ - pass = filter (tmp); - } else { - /* No filter function, include everything */ - pass = 1; - } - - if (pass) { - /* Store the temporary entry to pointer table */ - files[size++] = tmp; - tmp = NULL; - - /* Keep up with the number of files */ - result++; - } - - } else { - - /* - * End of directory stream reached => sort entries and - * exit. - */ - qsort (files, size, sizeof (void*), - (int (*) (const void*, const void*)) compare); - break; - - } - - } else { - /* Error reading directory entry */ - result = /*Error*/ -1; - break; - } - - } - - } else { - /* Cannot open directory */ - result = /*Error*/ -1; - } - - /* Release temporary directory entry */ - if (tmp) { - free (tmp); - } - - /* Release allocated memory on error */ - if (result < 0) { - for (i = 0; i < size; i++) { - if (files && files[i]) - free (files[i]); - } - free (files); - files = NULL; - } - - /* Close directory stream */ - if (dir) { - closedir (dir); - } - - /* Pass pointer table to caller */ - if (namelist) { - *namelist = files; - } - return result; -} - -/* Alphabetical sorting */ -static int -alphasort( - const struct dirent **a, const struct dirent **b) -{ - return strcoll ((*a)->d_name, (*b)->d_name); -} - -/* Sort versions */ -static int -versionsort( - const struct dirent **a, const struct dirent **b) -{ - /* FIXME: implement strverscmp and use that */ - return alphasort (a, b); -} - -/* Convert multi-byte string to wide character string */ -static int -dirent_mbstowcs_s( - size_t *pReturnValue, - wchar_t *wcstr, - size_t sizeInWords, - const char *mbstr, - size_t count) -{ - int error; - int n; - size_t len; - UINT cp; - DWORD flags; - - /* Determine code page for multi-byte string */ - if (AreFileApisANSI ()) { - /* Default ANSI code page */ - cp = GetACP (); - } else { - /* Default OEM code page */ - cp = GetOEMCP (); - } - - /* - * Determine flags based on the character set. For more information, - * please see https://docs.microsoft.com/fi-fi/windows/desktop/api/stringapiset/nf-stringapiset-multibytetowidechar - */ - switch (cp) { - case 42: - case 50220: - case 50221: - case 50222: - case 50225: - case 50227: - case 50229: - case 57002: - case 57003: - case 57004: - case 57005: - case 57006: - case 57007: - case 57008: - case 57009: - case 57010: - case 57011: - case 65000: - /* MultiByteToWideChar does not support MB_ERR_INVALID_CHARS */ - flags = 0; - break; - - default: - /* - * Ask MultiByteToWideChar to return an error if a multi-byte - * character cannot be converted to a wide-character. - */ - flags = MB_ERR_INVALID_CHARS; - } - - /* Compute the length of input string without zero-terminator */ - len = 0; - while (mbstr[len] != '\0' && len < count) { - len++; - } - - /* Convert to wide-character string */ - n = MultiByteToWideChar( - /* Source code page */ cp, - /* Flags */ flags, - /* Pointer to string to convert */ mbstr, - /* Size of multi-byte string */ (int) len, - /* Pointer to output buffer */ wcstr, - /* Size of output buffer */ (int)(sizeInWords - 1) - ); - - /* Ensure that output buffer is zero-terminated */ - wcstr[n] = '\0'; - - /* Return length of wide-character string with zero-terminator */ - *pReturnValue = (size_t)n + 1; - - /* Return zero if conversion succeeded */ - if (n > 0) { - error = 0; - } else { - error = 1; - } - return error; -} - -/* Convert wide-character string to multi-byte string */ -static int -dirent_wcstombs_s( - size_t *pReturnValue, - char *mbstr, - size_t sizeInBytes, /* max size of mbstr */ - const wchar_t *wcstr, - size_t count) -{ - int n; - int error; - UINT cp; - size_t len; - BOOL flag = 0; - LPBOOL pflag; - - /* Determine code page for multi-byte string */ - if (AreFileApisANSI ()) { - /* Default ANSI code page */ - cp = GetACP (); - } else { - /* Default OEM code page */ - cp = GetOEMCP (); - } - - /* Compute the length of input string without zero-terminator */ - len = 0; - while (wcstr[len] != '\0' && len < count) { - len++; - } - - /* - * Determine if we can ask WideCharToMultiByte to return information on - * broken characters. For more information, please see - * https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-widechartomultibyte - */ - switch (cp) { - case CP_UTF7: - case CP_UTF8: - /* - * WideCharToMultiByte fails if we request information on default - * characters. This is just a nuisance but doesn't affect the - * converion: if Windows is configured to use UTF-8, then the default - * character should not be needed anyway. - */ - pflag = NULL; - break; - - default: - /* - * Request that WideCharToMultiByte sets the flag if it uses the - * default character. - */ - pflag = &flag; - } - - /* Convert wide-character string to multi-byte character string */ - n = WideCharToMultiByte( - /* Target code page */ cp, - /* Flags */ 0, - /* Pointer to unicode string */ wcstr, - /* Length of unicode string */ (int) len, - /* Pointer to output buffer */ mbstr, - /* Size of output buffer */ (int)(sizeInBytes - 1), - /* Default character */ NULL, - /* Whether default character was used or not */ pflag - ); - - /* Ensure that output buffer is zero-terminated */ - mbstr[n] = '\0'; - - /* Return length of multi-byte string with zero-terminator */ - *pReturnValue = (size_t)n + 1; - - /* Return zero if conversion succeeded without using default characters */ - if (n > 0 && flag == 0) { - error = 0; - } else { - error = 1; - } - return error; -} - -/* Set errno variable */ -static void -dirent_set_errno( - int error) -{ -#if defined(_MSC_VER) && _MSC_VER >= 1400 - - /* Microsoft Visual Studio 2005 and later */ - _set_errno (error); - -#else - - /* Non-Microsoft compiler or older Microsoft compiler */ - errno = error; - -#endif -} - -#ifdef __cplusplus -} -#endif -#endif /*DIRENT_H*/ diff --git a/src/utils/compat/getopt.c b/src/utils/compat/getopt.c deleted file mode 100644 index 44653c30f..000000000 --- a/src/utils/compat/getopt.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * getopt - POSIX like getopt for Windows console Application - * - * win-c - Windows Console Library - * Copyright (c) 2015 Koji Takami - * Released under the MIT license - * https://github.com/takamin/win-c/blob/master/LICENSE - */ -#include -#include -#include "getopt.h" - -char* optarg = 0; -int optind = 1; -int opterr = 1; -int optopt = 0; - -int postpone_count = 0; -int nextchar = 0; - -static void postpone(int argc, char* const argv[], int index) { - char** nc_argv = (char**)argv; - char* p = nc_argv[index]; - int j = index; - for(; j < argc - 1; j++) { - nc_argv[j] = nc_argv[j + 1]; - } - nc_argv[argc - 1] = p; -} -static int postpone_noopt(int argc, char* const argv[], int index) { - int i = index; - for(; i < argc; i++) { - if(*(argv[i]) == '-') { - postpone(argc, argv, index); - return 1; - } - } - return 0; -} -static int _getopt_(int argc, char* const argv[], - const char* optstring, - const struct option* longopts, int* longindex) -{ - while(1) { - int c; - const char* optptr = 0; - if(optind >= argc - postpone_count) { - c = 0; - optarg = 0; - break; - } - c = *(argv[optind] + nextchar); - if(c == '\0') { - nextchar = 0; - ++optind; - continue; - } - if(nextchar == 0) { - if(optstring[0] != '+' && optstring[0] != '-') { - while(c != '-') { - /* postpone non-opt parameter */ - if(!postpone_noopt(argc, argv, optind)) { - break; /* all args are non-opt param */ - } - ++postpone_count; - c = *argv[optind]; - } - } - if(c != '-') { - if(optstring[0] == '-') { - optarg = argv[optind]; - nextchar = 0; - ++optind; - return 1; - } - break; - } else { - if(strcmp(argv[optind], "--") == 0) { - optind++; - break; - } - ++nextchar; - if(longopts != 0 && *(argv[optind] + 1) == '-') { - char const* spec_long = argv[optind] + 2; - char const* pos_eq = strchr(spec_long, '='); - int spec_len = (pos_eq == NULL) ? - (int)strlen(spec_long) : (int)(pos_eq - spec_long); - int index_search = 0; - int index_found = -1; - const struct option* optdef = 0; - while(longopts->name != 0) { - if(strncmp(spec_long, longopts->name, spec_len) == 0) { - if(optdef != 0) { - if(opterr) { - fprintf(stderr, "ambiguous option: %s\n", spec_long); - } - return '?'; - } - optdef = longopts; - index_found = index_search; - } - longopts++; - index_search++; - } - if(optdef == 0) { - if(opterr) { - fprintf(stderr, "no such a option: %s\n", spec_long); - } - return '?'; - } - switch(optdef->has_arg) { - case no_argument: - optarg = 0; - if(pos_eq != 0) { - if(opterr) { - fprintf(stderr, "no argument for %s\n", optdef->name); - } - return '?'; - } - break; - case required_argument: - if(pos_eq == NULL) { - ++optind; - optarg = argv[optind]; - } else { - optarg = (char*)pos_eq + 1; - } - break; - } - ++optind; - nextchar = 0; - if(longindex != 0) { - *longindex = index_found; - } - if(optdef->flag != 0) { - *optdef->flag = optdef->val; - return 0; - } - return optdef->val; - } - continue; - } - } - optptr = strchr(optstring, c); - if(optptr == NULL) { - optopt = c; - if(opterr) { - fprintf(stderr, - "%s: invalid option -- %c\n", - argv[0], c); - } - ++nextchar; - return '?'; - } - if(*(optptr+1) != ':') { - nextchar++; - if(*(argv[optind] + nextchar) == '\0') { - ++optind; - nextchar = 0; - } - optarg = 0; - } else { - nextchar++; - if(*(argv[optind] + nextchar) != '\0') { - optarg = argv[optind] + nextchar; - } else { - ++optind; - if(optind < argc - postpone_count) { - optarg = argv[optind]; - } else { - optopt = c; - if(opterr) { - fprintf(stderr, - "%s: option requires an argument -- %c\n", - argv[0], c); - } - if(optstring[0] == ':' || - ((optstring[0] == '-' || optstring[0] == '+') && - optstring[1] == ':')) - { - c = ':'; - } else { - c = '?'; - } - } - } - ++optind; - nextchar = 0; - } - return c; - } - - /* end of option analysis */ - - /* fix the order of non-opt params to original */ - while((argc - optind - postpone_count) > 0) { - postpone(argc, argv, optind); - ++postpone_count; - } - - nextchar = 0; - postpone_count = 0; - return -1; -} - -int getopt(int argc, char* const argv[], - const char* optstring) -{ - return _getopt_(argc, argv, optstring, 0, 0); -} -int getopt_long(int argc, char* const argv[], - const char* optstring, - const struct option* longopts, int* longindex) -{ - return _getopt_(argc, argv, optstring, longopts, longindex); -} -/******************************************************** -int getopt_long_only(int argc, char* const argv[], - const char* optstring, - const struct option* longopts, int* longindex) -{ - return -1; -} -********************************************************/ diff --git a/src/utils/compat/getopt.h b/src/utils/compat/getopt.h deleted file mode 100644 index ac1f536b3..000000000 --- a/src/utils/compat/getopt.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * getopt - POSIX like getopt for Windows console Application - * - * win-c - Windows Console Library - * Copyright (c) 2015 Koji Takami - * Released under the MIT license - * https://github.com/takamin/win-c/blob/master/LICENSE - */ -#ifndef _GETOPT_H_ -#define _GETOPT_H_ - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#ifdef HAVE_GETOPT -#include -#else - - int getopt(int argc, char* const argv[], - const char* optstring); - - extern char *optarg; - extern int optind, opterr, optopt; - -#define no_argument 0 -#define required_argument 1 -#define optional_argument 2 - - struct option { - const char *name; - int has_arg; - int* flag; - int val; - }; - - int getopt_long(int argc, char* const argv[], - const char* optstring, - const struct option* longopts, int* longindex); -/**************************************************************************** - int getopt_long_only(int argc, char* const argv[], - const char* optstring, - const struct option* longopts, int* longindex); -****************************************************************************/ - -#endif /* HAVE_GETOPT */ - -#ifdef __cplusplus -} -#endif // __cplusplus -#endif // _GETOPT_H_ diff --git a/src/utils/compat/realpath.c b/src/utils/compat/realpath.c deleted file mode 100644 index b1945ce46..000000000 --- a/src/utils/compat/realpath.c +++ /dev/null @@ -1,226 +0,0 @@ -/* $OpenBSD: realpath.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ -/* - * Copyright (c) 2003 Constantin S. Svintsoff - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The names of the authors may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* Changes: - - 2020-04-11, Jesper Friis - Copied from https://android.googlesource.com/platform/bionic.git/+/android-4.0.1_r1/libc/bionic/realpath.c - Allowed `resolved` argument to be NULL. -*/ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#include "compat.h" - -#include -#include -#include -#include -#include -#include -#include - -/* - * char *realpath(const char *path, char *resolved); - * - * Find the real name of path, by removing all ".", ".." and symlink - * components. Returns (resolved) on success, or (NULL) on failure, - * in which case the path which caused trouble is left in (resolved). - * - * If resolved_path is specified as NULL, then realpath() uses - * malloc() to allocate a buffer of up to PATH_MAX bytes to hold the - * resolved pathname, and returns a pointer to this buffer. The - * caller should deallocate this buffer using free(). - */ -char * -_realpath(const char *path, char resolved[PATH_MAX]) -{ - struct stat sb; - char *p, *q, *s; - size_t left_len, resolved_len; - unsigned symlinks; - int serrno, slen; - char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; - - serrno = errno; - symlinks = 0; - if (path[0] == '/') { - resolved[0] = '/'; - resolved[1] = '\0'; - if (path[1] == '\0') - return (resolved); - resolved_len = 1; - left_len = strlcpy(left, path + 1, sizeof(left)); - } else { - if (getcwd(resolved, PATH_MAX) == NULL) { - strlcpy(resolved, ".", PATH_MAX); - return (NULL); - } - resolved_len = strlen(resolved); - left_len = strlcpy(left, path, sizeof(left)); - } - if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) { - errno = ENAMETOOLONG; - return (NULL); - } - /* - * Iterate over path components in `left'. - */ - while (left_len != 0) { - /* - * Extract the next path component and adjust `left' - * and its length. - */ - p = strchr(left, '/'); - s = p ? p : left + left_len; - if (s - left >= (int)sizeof(next_token)) { - errno = ENAMETOOLONG; - return (NULL); - } - memcpy(next_token, left, s - left); - next_token[s - left] = '\0'; - left_len -= s - left; - if (p != NULL) - memmove(left, s + 1, left_len + 1); - if (resolved[resolved_len - 1] != '/') { - if (resolved_len + 1 >= PATH_MAX) { - errno = ENAMETOOLONG; - return (NULL); - } - resolved[resolved_len++] = '/'; - resolved[resolved_len] = '\0'; - } - if (next_token[0] == '\0') - continue; - else if (strcmp(next_token, ".") == 0) - continue; - else if (strcmp(next_token, "..") == 0) { - /* - * Strip the last path component except when we have - * single "/" - */ - if (resolved_len > 1) { - resolved[resolved_len - 1] = '\0'; - q = strrchr(resolved, '/') + 1; - *q = '\0'; - resolved_len = q - resolved; - } - continue; - } - /* - * Append the next path component and lstat() it. If - * lstat() fails we still can return successfully if - * there are no more path components left. - */ - resolved_len = strlcat(resolved, next_token, PATH_MAX); - if (resolved_len >= PATH_MAX) { - errno = ENAMETOOLONG; - return (NULL); - } - if (lstat(resolved, &sb) != 0) { - if (errno == ENOENT && p == NULL) { - errno = serrno; - return (resolved); - } - return (NULL); - } - if (S_ISLNK(sb.st_mode)) { - if (symlinks++ > MAXSYMLINKS) { - errno = ELOOP; - return (NULL); - } - slen = readlink(resolved, symlink, sizeof(symlink) - 1); - if (slen < 0) - return (NULL); - symlink[slen] = '\0'; - if (symlink[0] == '/') { - resolved[1] = 0; - resolved_len = 1; - } else if (resolved_len > 1) { - /* Strip the last path component. */ - resolved[resolved_len - 1] = '\0'; - q = strrchr(resolved, '/') + 1; - *q = '\0'; - resolved_len = q - resolved; - } - /* - * If there are any path components left, then - * append them to symlink. The result is placed - * in `left'. - */ - if (p != NULL) { - if (symlink[slen - 1] != '/') { - if (slen + 1 >= (int)sizeof(symlink)) { - errno = ENAMETOOLONG; - return (NULL); - } - symlink[slen] = '/'; - symlink[slen + 1] = 0; - } - left_len = strlcat(symlink, left, sizeof(left)); - if (left_len >= sizeof(left)) { - errno = ENAMETOOLONG; - return (NULL); - } - } - left_len = strlcpy(left, symlink, sizeof(left)); - } - } - /* - * Remove trailing slash except when the resolved pathname - * is a single "/". - */ - if (resolved_len > 1 && resolved[resolved_len - 1] == '/') - resolved[resolved_len - 1] = '\0'; - return (resolved); -} - - -char * -realpath(const char *path, char *resolved) -{ - char *result, *buff = NULL; - - if (!resolved) { - if (!(buff = malloc(PATH_MAX + 1))) - return (NULL); - resolved = buff; - } - - if ((result = _realpath(path, resolved))) { - struct stat statbuf; - if (stat(result, &statbuf)) - result = NULL; - } - - if (!result && buff) - free(buff); - - return (result); -} diff --git a/src/utils/compat/snprintf.c b/src/utils/compat/snprintf.c deleted file mode 100644 index 9574c8464..000000000 --- a/src/utils/compat/snprintf.c +++ /dev/null @@ -1,2108 +0,0 @@ -/* - * Copyright (c) 1995 Patrick Powell. - * - * This code is based on code written by Patrick Powell . - * It may be used for any purpose as long as this notice remains intact on all - * source code distributions. - */ - -/* - * Copyright (c) 2008 Holger Weiss. - * - * This version of the code is maintained by Holger Weiss . - * My changes to the code may freely be used, modified and/or redistributed for - * any purpose. It would be nice if additions and fixes to this file (including - * trivial code cleanups) would be sent back in order to let me include them in - * the version available at . - * However, this is not a requirement for using or redistributing (possibly - * modified) versions of this file, nor is leaving this notice intact mandatory. - */ - -/* - * History - * - * 2008-01-20 Holger Weiss for C99-snprintf 1.1: - * - * Fixed the detection of infinite floating point values on IRIX (and - * possibly other systems) and applied another few minor cleanups. - * - * 2008-01-06 Holger Weiss for C99-snprintf 1.0: - * - * Added a lot of new features, fixed many bugs, and incorporated various - * improvements done by Andrew Tridgell , Russ Allbery - * , Hrvoje Niksic , Damien Miller - * , and others for the Samba, INN, Wget, and OpenSSH - * projects. The additions include: support the "e", "E", "g", "G", and - * "F" conversion specifiers (and use conversion style "f" or "F" for the - * still unsupported "a" and "A" specifiers); support the "hh", "ll", "j", - * "t", and "z" length modifiers; support the "#" flag and the (non-C99) - * "'" flag; use localeconv(3) (if available) to get both the current - * locale's decimal point character and the separator between groups of - * digits; fix the handling of various corner cases of field width and - * precision specifications; fix various floating point conversion bugs; - * handle infinite and NaN floating point values; don't attempt to write to - * the output buffer (which may be NULL) if a size of zero was specified; - * check for integer overflow of the field width, precision, and return - * values and during the floating point conversion; use the OUTCHAR() macro - * instead of a function for better performance; provide asprintf(3) and - * vasprintf(3) functions; add new test cases. The replacement functions - * have been renamed to use an "rpl_" prefix, the function calls in the - * main project (and in this file) must be redefined accordingly for each - * replacement function which is needed (by using Autoconf or other means). - * Various other minor improvements have been applied and the coding style - * was cleaned up for consistency. - * - * 2007-07-23 Holger Weiss for Mutt 1.5.13: - * - * C99 compliant snprintf(3) and vsnprintf(3) functions return the number - * of characters that would have been written to a sufficiently sized - * buffer (excluding the '\0'). The original code simply returned the - * length of the resulting output string, so that's been fixed. - * - * 1998-03-05 Michael Elkins for Mutt 0.90.8: - * - * The original code assumed that both snprintf(3) and vsnprintf(3) were - * missing. Some systems only have snprintf(3) but not vsnprintf(3), so - * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. - * - * 1998-01-27 Thomas Roessler for Mutt 0.89i: - * - * The PGP code was using unsigned hexadecimal formats. Unfortunately, - * unsigned formats simply didn't work. - * - * 1997-10-22 Brandon Long for Mutt 0.87.1: - * - * Ok, added some minimal floating point support, which means this probably - * requires libm on most operating systems. Don't yet support the exponent - * (e,E) and sigfig (g,G). Also, fmtint() was pretty badly broken, it just - * wasn't being exercised in ways which showed it, so that's been fixed. - * Also, formatted the code to Mutt conventions, and removed dead code left - * over from the original. Also, there is now a builtin-test, run with: - * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm && ./snprintf - * - * 2996-09-15 Brandon Long for Mutt 0.43: - * - * This was ugly. It is still ugly. I opted out of floating point - * numbers, but the formatter understands just about everything from the - * normal C string format, at least as far as I can tell from the Solaris - * 2.5 printf(3S) man page. - */ - -/* - * ToDo - * - * - Add wide character support. - * - Add support for "%a" and "%A" conversions. - * - Create test routines which predefine the expected results. Our test cases - * usually expose bugs in system implementations rather than in ours :-) - */ - -/* - * Usage - * - * 1) The following preprocessor macros should be defined to 1 if the feature or - * file in question is available on the target system (by using Autoconf or - * other means), though basic functionality should be available as long as - * HAVE_STDARG_H and HAVE_STDLIB_H are defined correctly: - * - * HAVE_VSNPRINTF - * HAVE_SNPRINTF - * HAVE_VASPRINTF - * HAVE_ASPRINTF - * HAVE_STDARG_H - * HAVE_STDDEF_H - * HAVE_STDINT_H - * HAVE_STDLIB_H - * HAVE_FLOAT_H - * HAVE_INTTYPES_H - * HAVE_LOCALE_H - * HAVE_LOCALECONV - * HAVE_LCONV_DECIMAL_POINT - * HAVE_LCONV_THOUSANDS_SEP - * HAVE_LONG_DOUBLE - * HAVE_LONG_LONG_INT - * HAVE_UNSIGNED_LONG_LONG_INT - * HAVE_INTMAX_T - * HAVE_UINTMAX_T - * HAVE_UINTPTR_T - * HAVE_PTRDIFF_T - * HAVE_VA_COPY - * HAVE___VA_COPY - * - * 2) The calls to the functions which should be replaced must be redefined - * throughout the project files (by using Autoconf or other means): - * - * #define vsnprintf rpl_vsnprintf - * #define snprintf rpl_snprintf - * #define vasprintf rpl_vasprintf - * #define asprintf rpl_asprintf - * - * 3) The required replacement functions should be declared in some header file - * included throughout the project files: - * - * #ifdef HAVE_CONFIG_H - * #include - * #endif - * #ifdef HAVE_STDARG_H - * #include - * #ifndef HAVE_VSNPRINTF - * int rpl_vsnprintf(char *, size_t, const char *, va_list); - * #endif - * #ifndef HAVE_SNPRINTF - * int rpl_snprintf(char *, size_t, const char *, ...); - * #endif - * #ifndef HAVE_VASPRINTF - * int rpl_vasprintf(char **, const char *, va_list); - * #endif - * #ifndef HAVE_ASPRINTF - * int rpl_asprintf(char **, const char *, ...); - * #endif - * #endif - * - * Autoconf macros for handling step 1 and step 2 are available at - * . - */ - -# ifdef HAVE_CONFIG_H -# include -# endif /* HAVE_CONFIG_H */ - -#ifdef HAVE_CONFIG_SNPRINTF_H -#include -#endif /* HAVE_CONFIG_SNPRINTF_H */ - - -#if TEST_SNPRINTF -#include /* For pow(3), NAN, and INFINITY. */ -#include /* For strcmp(3). */ -#if defined(__NetBSD__) || \ - defined(__FreeBSD__) || \ - defined(__OpenBSD__) || \ - defined(__NeXT__) || \ - defined(__bsd__) -#define OS_BSD 1 -#elif defined(sgi) || defined(__sgi) -#ifndef __c99 -#define __c99 /* Force C99 mode to get included on IRIX 6.5.30. */ -#endif /* !defined(__c99) */ -#define OS_IRIX 1 -#define OS_SYSV 1 -#elif defined(__svr4__) -#define OS_SYSV 1 -#elif defined(__linux__) -#define OS_LINUX 1 -#endif /* defined(__NetBSD__) || defined(__FreeBSD__) || [...] */ -#ifdef HAVE_CONFIG_H /* Undefine definitions possibly done in config.h. */ -#ifdef HAVE_SNPRINTF -#undef HAVE_SNPRINTF -#endif /* defined(HAVE_SNPRINTF) */ -#ifdef HAVE_VSNPRINTF -#undef HAVE_VSNPRINTF -#endif /* defined(HAVE_VSNPRINTF) */ -#ifdef HAVE_ASPRINTF -#undef HAVE_ASPRINTF -#endif /* defined(HAVE_ASPRINTF) */ -#ifdef HAVE_VASPRINTF -#undef HAVE_VASPRINTF -#endif /* defined(HAVE_VASPRINTF) */ -#ifdef snprintf -#undef snprintf -#endif /* defined(snprintf) */ -#ifdef vsnprintf -#undef vsnprintf -#endif /* defined(vsnprintf) */ -#ifdef asprintf -#undef asprintf -#endif /* defined(asprintf) */ -#ifdef vasprintf -#undef vasprintf -#endif /* defined(vasprintf) */ -#else /* By default, we assume a modern system for testing. */ -#ifndef HAVE_STDARG_H -#define HAVE_STDARG_H 1 -#endif /* HAVE_STDARG_H */ -#ifndef HAVE_STDDEF_H -#define HAVE_STDDEF_H 1 -#endif /* HAVE_STDDEF_H */ -#ifndef HAVE_STDINT_H -#define HAVE_STDINT_H 1 -#endif /* HAVE_STDINT_H */ -#ifndef HAVE_STDLIB_H -#define HAVE_STDLIB_H 1 -#endif /* HAVE_STDLIB_H */ -#ifndef HAVE_FLOAT_H -#define HAVE_FLOAT_H 1 -#endif /* HAVE_FLOAT_H */ -#ifndef HAVE_INTTYPES_H -#define HAVE_INTTYPES_H 1 -#endif /* HAVE_INTTYPES_H */ -#ifndef HAVE_LOCALE_H -#define HAVE_LOCALE_H 1 -#endif /* HAVE_LOCALE_H */ -#ifndef HAVE_LOCALECONV -#define HAVE_LOCALECONV 1 -#endif /* !defined(HAVE_LOCALECONV) */ -#ifndef HAVE_LCONV_DECIMAL_POINT -#define HAVE_LCONV_DECIMAL_POINT 1 -#endif /* HAVE_LCONV_DECIMAL_POINT */ -#ifndef HAVE_LCONV_THOUSANDS_SEP -#define HAVE_LCONV_THOUSANDS_SEP 1 -#endif /* HAVE_LCONV_THOUSANDS_SEP */ -#ifndef HAVE_LONG_DOUBLE -#define HAVE_LONG_DOUBLE 1 -#endif /* !defined(HAVE_LONG_DOUBLE) */ -#ifndef HAVE_LONG_LONG_INT -#define HAVE_LONG_LONG_INT 1 -#endif /* !defined(HAVE_LONG_LONG_INT) */ -#ifndef HAVE_UNSIGNED_LONG_LONG_INT -#define HAVE_UNSIGNED_LONG_LONG_INT 1 -#endif /* !defined(HAVE_UNSIGNED_LONG_LONG_INT) */ -#ifndef HAVE_INTMAX_T -#define HAVE_INTMAX_T 1 -#endif /* !defined(HAVE_INTMAX_T) */ -#ifndef HAVE_UINTMAX_T -#define HAVE_UINTMAX_T 1 -#endif /* !defined(HAVE_UINTMAX_T) */ -#ifndef HAVE_UINTPTR_T -#define HAVE_UINTPTR_T 1 -#endif /* !defined(HAVE_UINTPTR_T) */ -#ifndef HAVE_PTRDIFF_T -#define HAVE_PTRDIFF_T 1 -#endif /* !defined(HAVE_PTRDIFF_T) */ -#ifndef HAVE_VA_COPY -#define HAVE_VA_COPY 1 -#endif /* !defined(HAVE_VA_COPY) */ -#ifndef HAVE___VA_COPY -#define HAVE___VA_COPY 1 -#endif /* !defined(HAVE___VA_COPY) */ -#endif /* HAVE_CONFIG_H */ -#define snprintf rpl_snprintf -#define vsnprintf rpl_vsnprintf -#define asprintf rpl_asprintf -#define vasprintf rpl_vasprintf -#endif /* TEST_SNPRINTF */ - -#if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) || \ - !defined(HAVE_ASPRINTF) || !defined(HAVE_VASPRINTF) -#include /* For NULL, size_t, vsnprintf(3), and vasprintf(3). */ -#ifdef VA_START -#undef VA_START -#endif /* defined(VA_START) */ -#ifdef VA_SHIFT -#undef VA_SHIFT -#endif /* defined(VA_SHIFT) */ -#ifdef HAVE_STDARG_H -#include -#define VA_START(ap, last) va_start(ap, last) -#define VA_SHIFT(ap, value, type) /* No-op for ANSI C. */ -#else /* Assume is available. */ -#include -#define VA_START(ap, last) va_start(ap) /* "last" is ignored. */ -#define VA_SHIFT(ap, value, type) value = va_arg(ap, type) -#endif /* HAVE_STDARG_H */ - -#ifndef HAVE_VASPRINTF -#ifdef HAVE_STDLIB_H -#include /* For malloc(3). */ -#endif /* HAVE_STDLIB_H */ -#ifdef VA_COPY -#undef VA_COPY -#endif /* defined(VA_COPY) */ -#ifdef VA_END_COPY -#undef VA_END_COPY -#endif /* defined(VA_END_COPY) */ -#ifdef HAVE_VA_COPY -#define VA_COPY(dest, src) va_copy(dest, src) -#define VA_END_COPY(ap) va_end(ap) -#elif defined(HAVE___VA_COPY) -#define VA_COPY(dest, src) __va_copy(dest, src) -#define VA_END_COPY(ap) va_end(ap) -#else -#define VA_COPY(dest, src) (void)mymemcpy(&dest, &src, sizeof(va_list)) -#define VA_END_COPY(ap) /* No-op. */ -#define NEED_MYMEMCPY 1 -static void *mymemcpy(void *, void *, size_t); -#endif /* HAVE_VA_COPY */ -#endif /* !HAVE_VASPRINTF */ - -#ifndef HAVE_VSNPRINTF -#include /* For ERANGE and errno. */ -#include /* For *_MAX. */ -#ifdef HAVE_FLOAT_H -#include /* For *DBL_{MIN,MAX}_10_EXP. */ -#endif /* HAVE_FLOAT_H */ -#ifdef HAVE_INTTYPES_H -#include /* For intmax_t (if not defined in ). */ -#endif /* HAVE_INTTYPES_H */ -#ifdef HAVE_LOCALE_H -#include /* For localeconv(3). */ -#endif /* HAVE_LOCALE_H */ -#ifdef HAVE_STDDEF_H -#include /* For ptrdiff_t. */ -#endif /* HAVE_STDDEF_H */ -#ifdef HAVE_STDINT_H -#include /* For intmax_t. */ -#endif /* HAVE_STDINT_H */ - -/* Support for unsigned long long int. We may also need ULLONG_MAX. */ -#ifndef ULONG_MAX /* We may need ULONG_MAX as a fallback. */ -#ifdef UINT_MAX -#define ULONG_MAX UINT_MAX -#else -#define ULONG_MAX INT_MAX -#endif /* defined(UINT_MAX) */ -#endif /* !defined(ULONG_MAX) */ -#ifdef ULLONG -#undef ULLONG -#endif /* defined(ULLONG) */ -#if HAVE_UNSIGNED_LONG_LONG_INT -#define ULLONG unsigned long long int -#ifndef ULLONG_MAX -#define ULLONG_MAX ULONG_MAX -#endif /* !defined(ULLONG_MAX) */ -#else -#define ULLONG unsigned long int -#ifdef ULLONG_MAX -#undef ULLONG_MAX -#endif /* defined(ULLONG_MAX) */ -#define ULLONG_MAX ULONG_MAX -#endif /* HAVE_LONG_LONG_INT */ - -/* Support for uintmax_t. We also need UINTMAX_MAX. */ -#ifdef UINTMAX_T -#undef UINTMAX_T -#endif /* defined(UINTMAX_T) */ -#if HAVE_UINTMAX_T || defined(uintmax_t) -#define UINTMAX_T uintmax_t -#ifndef UINTMAX_MAX -#define UINTMAX_MAX ULLONG_MAX -#endif /* !defined(UINTMAX_MAX) */ -#else -#define UINTMAX_T ULLONG -#ifdef UINTMAX_MAX -#undef UINTMAX_MAX -#endif /* defined(UINTMAX_MAX) */ -#define UINTMAX_MAX ULLONG_MAX -#endif /* HAVE_UINTMAX_T || defined(uintmax_t) */ - -/* Support for long double. */ -#ifndef LDOUBLE -#if HAVE_LONG_DOUBLE -#define LDOUBLE long double -#define LDOUBLE_MIN_10_EXP LDBL_MIN_10_EXP -#define LDOUBLE_MAX_10_EXP LDBL_MAX_10_EXP -#else -#define LDOUBLE double -#define LDOUBLE_MIN_10_EXP DBL_MIN_10_EXP -#define LDOUBLE_MAX_10_EXP DBL_MAX_10_EXP -#endif /* HAVE_LONG_DOUBLE */ -#endif /* !defined(LDOUBLE) */ - -/* Support for long long int. */ -#ifndef LLONG -#if HAVE_LONG_LONG_INT -#define LLONG long long int -#else -#define LLONG long int -#endif /* HAVE_LONG_LONG_INT */ -#endif /* !defined(LLONG) */ - -/* Support for intmax_t. */ -#ifndef INTMAX_T -#if HAVE_INTMAX_T || defined(intmax_t) -#define INTMAX_T intmax_t -#else -#define INTMAX_T LLONG -#endif /* HAVE_INTMAX_T || defined(intmax_t) */ -#endif /* !defined(INTMAX_T) */ - -/* Support for uintptr_t. */ -#ifndef UINTPTR_T -#if HAVE_UINTPTR_T || defined(uintptr_t) -#define UINTPTR_T uintptr_t -#else -#define UINTPTR_T unsigned long int -#endif /* HAVE_UINTPTR_T || defined(uintptr_t) */ -#endif /* !defined(UINTPTR_T) */ - -/* Support for ptrdiff_t. */ -#ifndef PTRDIFF_T -#if HAVE_PTRDIFF_T || defined(ptrdiff_t) -#define PTRDIFF_T ptrdiff_t -#else -#define PTRDIFF_T long int -#endif /* HAVE_PTRDIFF_T || defined(ptrdiff_t) */ -#endif /* !defined(PTRDIFF_T) */ - -/* - * We need an unsigned integer type corresponding to ptrdiff_t (cf. C99: - * 7.19.6.1, 7). However, we'll simply use PTRDIFF_T and convert it to an - * unsigned type if necessary. This should work just fine in practice. - */ -#ifndef UPTRDIFF_T -#define UPTRDIFF_T PTRDIFF_T -#endif /* !defined(UPTRDIFF_T) */ - -/* - * We need a signed integer type corresponding to size_t (cf. C99: 7.19.6.1, 7). - * However, we'll simply use size_t and convert it to a signed type if - * necessary. This should work just fine in practice. - */ -#ifndef SSIZE_T -#define SSIZE_T size_t -#endif /* !defined(SSIZE_T) */ - -/* Either ERANGE or E2BIG should be available everywhere. */ -#ifndef ERANGE -#define ERANGE E2BIG -#endif /* !defined(ERANGE) */ -#ifndef EOVERFLOW -#define EOVERFLOW ERANGE -#endif /* !defined(EOVERFLOW) */ - -/* - * Buffer size to hold the octal string representation of UINT128_MAX without - * nul-termination ("3777777777777777777777777777777777777777777"). - */ -#ifdef MAX_CONVERT_LENGTH -#undef MAX_CONVERT_LENGTH -#endif /* defined(MAX_CONVERT_LENGTH) */ -#define MAX_CONVERT_LENGTH 43 - -/* Format read states. */ -#define PRINT_S_DEFAULT 0 -#define PRINT_S_FLAGS 1 -#define PRINT_S_WIDTH 2 -#define PRINT_S_DOT 3 -#define PRINT_S_PRECISION 4 -#define PRINT_S_MOD 5 -#define PRINT_S_CONV 6 - -/* Format flags. */ -#define PRINT_F_MINUS (1 << 0) -#define PRINT_F_PLUS (1 << 1) -#define PRINT_F_SPACE (1 << 2) -#define PRINT_F_NUM (1 << 3) -#define PRINT_F_ZERO (1 << 4) -#define PRINT_F_QUOTE (1 << 5) -#define PRINT_F_UP (1 << 6) -#define PRINT_F_UNSIGNED (1 << 7) -#define PRINT_F_TYPE_G (1 << 8) -#define PRINT_F_TYPE_E (1 << 9) - -/* Conversion flags. */ -#define PRINT_C_CHAR 1 -#define PRINT_C_SHORT 2 -#define PRINT_C_LONG 3 -#define PRINT_C_LLONG 4 -#define PRINT_C_LDOUBLE 5 -#define PRINT_C_SIZE 6 -#define PRINT_C_PTRDIFF 7 -#define PRINT_C_INTMAX 8 - -#ifndef MAX -#define MAX(x, y) ((x >= y) ? x : y) -#endif /* !defined(MAX) */ -#ifndef CHARTOINT -#define CHARTOINT(ch) (ch - '0') -#endif /* !defined(CHARTOINT) */ -#ifndef ISDIGIT -#define ISDIGIT(ch) ('0' <= (unsigned char)ch && (unsigned char)ch <= '9') -#endif /* !defined(ISDIGIT) */ -#ifndef ISNAN -#define ISNAN(x) (x != x) -#endif /* !defined(ISNAN) */ -#ifndef ISINF -#define ISINF(x) ((x < -1 || x > 1) && x + x == x) -#endif /* !defined(ISINF) */ - -#ifdef OUTCHAR -#undef OUTCHAR -#endif /* defined(OUTCHAR) */ -#define OUTCHAR(str, len, size, ch) \ -do { \ - if (len + 1 < size) \ - str[len] = ch; \ - (len)++; \ -} while (/* CONSTCOND */ 0) - -int rpl_snprintf(char *str, size_t size, const char *fmt, ...); -int rpl_vsnprintf(char *str, size_t size, const char *fmt, va_list ap); -int rpl_asprintf(char **buf, const char *fmt, ...); -int rpl_vasprintf(char **buf, const char *fmt, va_list ap); - -static void fmtstr(char *, size_t *, size_t, const char *, int, int, int); -static void fmtint(char *, size_t *, size_t, INTMAX_T, int, int, int, int); -static void fmtflt(char *, size_t *, size_t, LDOUBLE, int, int, int, int *); -static void printsep(char *, size_t *, size_t); -static int getnumsep(int); -static int getexponent(LDOUBLE); -static int convert(UINTMAX_T, char *, size_t, int, int); -static UINTMAX_T cast(LDOUBLE); -static UINTMAX_T myround(LDOUBLE); -static LDOUBLE mypow10(int); - -int -rpl_vsnprintf(char *str, size_t size, const char *format, va_list args) -{ - LDOUBLE fvalue; - INTMAX_T value; - unsigned char cvalue; - const char *strvalue; - INTMAX_T *intmaxptr; - PTRDIFF_T *ptrdiffptr; - SSIZE_T *sizeptr; - LLONG *llongptr; - long int *longptr; - int *intptr; - short int *shortptr; - signed char *charptr; - size_t len = 0; - int overflow = 0; - int base = 0; - int cflags = 0; - int flags = 0; - int width = 0; - int precision = -1; - int state = PRINT_S_DEFAULT; - char ch = *format++; - - /* - * C99 says: "If `n' is zero, nothing is written, and `s' may be a null - * pointer." (7.19.6.5, 2) We're forgiving and allow a NULL pointer - * even if a size larger than zero was specified. At least NetBSD's - * snprintf(3) does the same, as well as other versions of this file. - * (Though some of these versions will write to a non-NULL buffer even - * if a size of zero was specified, which violates the standard.) - */ - if (str == NULL && size != 0) - size = 0; - - while (ch != '\0') - switch (state) { - case PRINT_S_DEFAULT: - if (ch == '%') - state = PRINT_S_FLAGS; - else - OUTCHAR(str, len, size, ch); - ch = *format++; - break; - case PRINT_S_FLAGS: - switch (ch) { - case '-': - flags |= PRINT_F_MINUS; - ch = *format++; - break; - case '+': - flags |= PRINT_F_PLUS; - ch = *format++; - break; - case ' ': - flags |= PRINT_F_SPACE; - ch = *format++; - break; - case '#': - flags |= PRINT_F_NUM; - ch = *format++; - break; - case '0': - flags |= PRINT_F_ZERO; - ch = *format++; - break; - case '\'': /* SUSv2 flag (not in C99). */ - flags |= PRINT_F_QUOTE; - ch = *format++; - break; - default: - state = PRINT_S_WIDTH; - break; - } - break; - case PRINT_S_WIDTH: - if (ISDIGIT(ch)) { - ch = CHARTOINT(ch); - if (width > (INT_MAX - ch) / 10) { - overflow = 1; - goto out; - } - width = 10 * width + ch; - ch = *format++; - } else if (ch == '*') { - /* - * C99 says: "A negative field width argument is - * taken as a `-' flag followed by a positive - * field width." (7.19.6.1, 5) - */ - if ((width = va_arg(args, int)) < 0) { - flags |= PRINT_F_MINUS; - width = -width; - } - ch = *format++; - state = PRINT_S_DOT; - } else - state = PRINT_S_DOT; - break; - case PRINT_S_DOT: - if (ch == '.') { - state = PRINT_S_PRECISION; - ch = *format++; - } else - state = PRINT_S_MOD; - break; - case PRINT_S_PRECISION: - if (precision == -1) - precision = 0; - if (ISDIGIT(ch)) { - ch = CHARTOINT(ch); - if (precision > (INT_MAX - ch) / 10) { - overflow = 1; - goto out; - } - precision = 10 * precision + ch; - ch = *format++; - } else if (ch == '*') { - /* - * C99 says: "A negative precision argument is - * taken as if the precision were omitted." - * (7.19.6.1, 5) - */ - if ((precision = va_arg(args, int)) < 0) - precision = -1; - ch = *format++; - state = PRINT_S_MOD; - } else - state = PRINT_S_MOD; - break; - case PRINT_S_MOD: - switch (ch) { - case 'h': - ch = *format++; - if (ch == 'h') { /* It's a char. */ - ch = *format++; - cflags = PRINT_C_CHAR; - } else - cflags = PRINT_C_SHORT; - break; - case 'l': - ch = *format++; - if (ch == 'l') { /* It's a long long. */ - ch = *format++; - cflags = PRINT_C_LLONG; - } else - cflags = PRINT_C_LONG; - break; - case 'L': - cflags = PRINT_C_LDOUBLE; - ch = *format++; - break; - case 'j': - cflags = PRINT_C_INTMAX; - ch = *format++; - break; - case 't': - cflags = PRINT_C_PTRDIFF; - ch = *format++; - break; - case 'z': - cflags = PRINT_C_SIZE; - ch = *format++; - break; - } - state = PRINT_S_CONV; - break; - case PRINT_S_CONV: - switch (ch) { - case 'd': - /* FALLTHROUGH */ - case 'i': - switch (cflags) { - case PRINT_C_CHAR: - value = (signed char)va_arg(args, int); - break; - case PRINT_C_SHORT: - value = (short int)va_arg(args, int); - break; - case PRINT_C_LONG: - value = va_arg(args, long int); - break; - case PRINT_C_LLONG: - value = va_arg(args, LLONG); - break; - case PRINT_C_SIZE: - value = va_arg(args, SSIZE_T); - break; - case PRINT_C_INTMAX: - value = va_arg(args, INTMAX_T); - break; - case PRINT_C_PTRDIFF: - value = va_arg(args, PTRDIFF_T); - break; - default: - value = va_arg(args, int); - break; - } - fmtint(str, &len, size, value, 10, width, - precision, flags); - break; - case 'X': - flags |= PRINT_F_UP; - /* FALLTHROUGH */ - case 'x': - base = 16; - /* FALLTHROUGH */ - case 'o': - if (base == 0) - base = 8; - /* FALLTHROUGH */ - case 'u': - if (base == 0) - base = 10; - flags |= PRINT_F_UNSIGNED; - switch (cflags) { - case PRINT_C_CHAR: - value = (unsigned char)va_arg(args, - unsigned int); - break; - case PRINT_C_SHORT: - value = (unsigned short int)va_arg(args, - unsigned int); - break; - case PRINT_C_LONG: - value = va_arg(args, unsigned long int); - break; - case PRINT_C_LLONG: - value = va_arg(args, ULLONG); - break; - case PRINT_C_SIZE: - value = va_arg(args, size_t); - break; - case PRINT_C_INTMAX: - value = va_arg(args, UINTMAX_T); - break; - case PRINT_C_PTRDIFF: - value = va_arg(args, UPTRDIFF_T); - break; - default: - value = va_arg(args, unsigned int); - break; - } - fmtint(str, &len, size, value, base, width, - precision, flags); - break; - case 'A': - /* Not yet supported, we'll use "%F". */ - /* FALLTHROUGH */ - case 'E': - if (ch == 'E') - flags |= PRINT_F_TYPE_E; - /* FALLTHROUGH */ - case 'G': - if (ch == 'G') - flags |= PRINT_F_TYPE_G; - /* FALLTHROUGH */ - case 'F': - flags |= PRINT_F_UP; - /* FALLTHROUGH */ - case 'a': - /* Not yet supported, we'll use "%f". */ - /* FALLTHROUGH */ - case 'e': - if (ch == 'e') - flags |= PRINT_F_TYPE_E; - /* FALLTHROUGH */ - case 'g': - if (ch == 'g') - flags |= PRINT_F_TYPE_G; - /* FALLTHROUGH */ - case 'f': - if (cflags == PRINT_C_LDOUBLE) - fvalue = va_arg(args, LDOUBLE); - else - fvalue = va_arg(args, double); - fmtflt(str, &len, size, fvalue, width, - precision, flags, &overflow); - if (overflow) - goto out; - break; - case 'c': - cvalue = va_arg(args, int); - OUTCHAR(str, len, size, cvalue); - break; - case 's': - strvalue = va_arg(args, char *); - fmtstr(str, &len, size, strvalue, width, - precision, flags); - break; - case 'p': - /* - * C99 says: "The value of the pointer is - * converted to a sequence of printing - * characters, in an implementation-defined - * manner." (C99: 7.19.6.1, 8) - */ - if ((strvalue = va_arg(args, void *)) == NULL) - /* - * We use the glibc format. BSD prints - * "0x0", SysV "0". - */ - fmtstr(str, &len, size, "(nil)", width, - -1, flags); - else { - /* - * We use the BSD/glibc format. SysV - * omits the "0x" prefix (which we emit - * using the PRINT_F_NUM flag). - */ - flags |= PRINT_F_NUM; - flags |= PRINT_F_UNSIGNED; - fmtint(str, &len, size, - (UINTPTR_T)strvalue, 16, width, - precision, flags); - } - break; - case 'n': - switch (cflags) { - case PRINT_C_CHAR: - charptr = va_arg(args, signed char *); - *charptr = len; - break; - case PRINT_C_SHORT: - shortptr = va_arg(args, short int *); - *shortptr = len; - break; - case PRINT_C_LONG: - longptr = va_arg(args, long int *); - *longptr = len; - break; - case PRINT_C_LLONG: - llongptr = va_arg(args, LLONG *); - *llongptr = len; - break; - case PRINT_C_SIZE: - /* - * C99 says that with the "z" length - * modifier, "a following `n' conversion - * specifier applies to a pointer to a - * signed integer type corresponding to - * size_t argument." (7.19.6.1, 7) - */ - sizeptr = va_arg(args, SSIZE_T *); - *sizeptr = len; - break; - case PRINT_C_INTMAX: - intmaxptr = va_arg(args, INTMAX_T *); - *intmaxptr = len; - break; - case PRINT_C_PTRDIFF: - ptrdiffptr = va_arg(args, PTRDIFF_T *); - *ptrdiffptr = len; - break; - default: - intptr = va_arg(args, int *); - *intptr = len; - break; - } - break; - case '%': /* Print a "%" character verbatim. */ - OUTCHAR(str, len, size, ch); - break; - default: /* Skip other characters. */ - break; - } - ch = *format++; - state = PRINT_S_DEFAULT; - base = cflags = flags = width = 0; - precision = -1; - break; - } -out: - if (len < size) - str[len] = '\0'; - else if (size > 0) - str[size - 1] = '\0'; - - if (overflow || len > INT_MAX) { - errno = EOVERFLOW; - return -1; - } - return (int)len; -} - -static void -fmtstr(char *str, size_t *len, size_t size, const char *value, int width, - int precision, int flags) -{ - int padlen, strln; /* Amount to pad. */ - int noprecision = (precision == -1); - - if (value == NULL) /* We're forgiving. */ - value = "(null)"; - - /* If a precision was specified, don't read the string past it. */ - for (strln = 0; value[strln] != '\0' && - (noprecision || strln < precision); strln++) - continue; - - if ((padlen = width - strln) < 0) - padlen = 0; - if (flags & PRINT_F_MINUS) /* Left justify. */ - padlen = -padlen; - - while (padlen > 0) { /* Leading spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen--; - } - while (*value != '\0' && (noprecision || precision-- > 0)) { - OUTCHAR(str, *len, size, *value); - value++; - } - while (padlen < 0) { /* Trailing spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen++; - } -} - -static void -fmtint(char *str, size_t *len, size_t size, INTMAX_T value, int base, int width, - int precision, int flags) -{ - UINTMAX_T uvalue; - char iconvert[MAX_CONVERT_LENGTH]; - char sign = 0; - char hexprefix = 0; - int spadlen = 0; /* Amount to space pad. */ - int zpadlen = 0; /* Amount to zero pad. */ - int pos; - int separators = (flags & PRINT_F_QUOTE); - int noprecision = (precision == -1); - - if (flags & PRINT_F_UNSIGNED) - uvalue = value; - else { - uvalue = (value >= 0) ? value : -value; - if (value < 0) - sign = '-'; - else if (flags & PRINT_F_PLUS) /* Do a sign. */ - sign = '+'; - else if (flags & PRINT_F_SPACE) - sign = ' '; - } - - pos = convert(uvalue, iconvert, sizeof(iconvert), base, - flags & PRINT_F_UP); - - if (flags & PRINT_F_NUM && uvalue != 0) { - /* - * C99 says: "The result is converted to an `alternative form'. - * For `o' conversion, it increases the precision, if and only - * if necessary, to force the first digit of the result to be a - * zero (if the value and precision are both 0, a single 0 is - * printed). For `x' (or `X') conversion, a nonzero result has - * `0x' (or `0X') prefixed to it." (7.19.6.1, 6) - */ - switch (base) { - case 8: - if (precision <= pos) - precision = pos + 1; - break; - case 16: - hexprefix = (flags & PRINT_F_UP) ? 'X' : 'x'; - break; - } - } - - if (separators) /* Get the number of group separators we'll print. */ - separators = getnumsep(pos); - - zpadlen = precision - pos - separators; - spadlen = width /* Minimum field width. */ - - separators /* Number of separators. */ - - MAX(precision, pos) /* Number of integer digits. */ - - ((sign != 0) ? 1 : 0) /* Will we print a sign? */ - - ((hexprefix != 0) ? 2 : 0); /* Will we print a prefix? */ - - if (zpadlen < 0) - zpadlen = 0; - if (spadlen < 0) - spadlen = 0; - - /* - * C99 says: "If the `0' and `-' flags both appear, the `0' flag is - * ignored. For `d', `i', `o', `u', `x', and `X' conversions, if a - * precision is specified, the `0' flag is ignored." (7.19.6.1, 6) - */ - if (flags & PRINT_F_MINUS) /* Left justify. */ - spadlen = -spadlen; - else if (flags & PRINT_F_ZERO && noprecision) { - zpadlen += spadlen; - spadlen = 0; - } - while (spadlen > 0) { /* Leading spaces. */ - OUTCHAR(str, *len, size, ' '); - spadlen--; - } - if (sign != 0) /* Sign. */ - OUTCHAR(str, *len, size, sign); - if (hexprefix != 0) { /* A "0x" or "0X" prefix. */ - OUTCHAR(str, *len, size, '0'); - OUTCHAR(str, *len, size, hexprefix); - } - while (zpadlen > 0) { /* Leading zeros. */ - OUTCHAR(str, *len, size, '0'); - zpadlen--; - } - while (pos > 0) { /* The actual digits. */ - pos--; - OUTCHAR(str, *len, size, iconvert[pos]); - if (separators > 0 && pos > 0 && pos % 3 == 0) - printsep(str, len, size); - } - while (spadlen < 0) { /* Trailing spaces. */ - OUTCHAR(str, *len, size, ' '); - spadlen++; - } -} - -static void -fmtflt(char *str, size_t *len, size_t size, LDOUBLE fvalue, int width, - int precision, int flags, int *overflow) -{ - LDOUBLE ufvalue; - UINTMAX_T intpart; - UINTMAX_T fracpart; - UINTMAX_T mask; - const char *infnan = NULL; - char iconvert[MAX_CONVERT_LENGTH]; - char fconvert[MAX_CONVERT_LENGTH]; - char econvert[5]; /* "e-300" (without nul-termination). */ - char esign = 0; - char sign = 0; - int leadfraczeros = 0; - int exponent = 0; - int emitpoint = 0; - int omitzeros = 0; - int omitcount = 0; - int padlen = 0; - int epos = 0; - int fpos = 0; - int ipos = 0; - int separators = (flags & PRINT_F_QUOTE); - int estyle = (flags & PRINT_F_TYPE_E); -#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT - struct lconv *lc = localeconv(); -#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ - - /* - * AIX' man page says the default is 0, but C99 and at least Solaris' - * and NetBSD's man pages say the default is 6, and sprintf(3) on AIX - * defaults to 6. - */ - if (precision == -1) - precision = 6; - - if (fvalue < 0.0) - sign = '-'; - else if (flags & PRINT_F_PLUS) /* Do a sign. */ - sign = '+'; - else if (flags & PRINT_F_SPACE) - sign = ' '; - - if (ISNAN(fvalue)) - infnan = (flags & PRINT_F_UP) ? "NAN" : "nan"; - else if (ISINF(fvalue)) - infnan = (flags & PRINT_F_UP) ? "INF" : "inf"; - - if (infnan != NULL) { - if (sign != 0) - iconvert[ipos++] = sign; - while (*infnan != '\0') - iconvert[ipos++] = *infnan++; - fmtstr(str, len, size, iconvert, width, ipos, flags); - return; - } - - /* "%e" (or "%E") or "%g" (or "%G") conversion. */ - if (flags & PRINT_F_TYPE_E || flags & PRINT_F_TYPE_G) { - if (flags & PRINT_F_TYPE_G) { - /* - * If the precision is zero, it is treated as one (cf. - * C99: 7.19.6.1, 8). - */ - if (precision == 0) - precision = 1; - /* - * For "%g" (and "%G") conversions, the precision - * specifies the number of significant digits, which - * includes the digits in the integer part. The - * conversion will or will not be using "e-style" (like - * "%e" or "%E" conversions) depending on the precision - * and on the exponent. However, the exponent can be - * affected by rounding the converted value, so we'll - * leave this decision for later. Until then, we'll - * assume that we're going to do an "e-style" conversion - * (in order to get the exponent calculated). For - * "e-style", the precision must be decremented by one. - */ - precision--; - /* - * For "%g" (and "%G") conversions, trailing zeros are - * removed from the fractional portion of the result - * unless the "#" flag was specified. - */ - if (!(flags & PRINT_F_NUM)) - omitzeros = 1; - } - exponent = getexponent(fvalue); - estyle = 1; - } - -again: - /* - * Sorry, we only support 9, 19, or 38 digits (that is, the number of - * digits of the 32-bit, the 64-bit, or the 128-bit UINTMAX_MAX value - * minus one) past the decimal point due to our conversion method. - */ - switch (sizeof(UINTMAX_T)) { - case 16: - if (precision > 38) - precision = 38; - break; - case 8: - if (precision > 19) - precision = 19; - break; - default: - if (precision > 9) - precision = 9; - break; - } - - ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue; - if (estyle) /* We want exactly one integer digit. */ - ufvalue /= mypow10(exponent); - - if ((intpart = cast(ufvalue)) == UINTMAX_MAX) { - *overflow = 1; - return; - } - - /* - * Factor of ten with the number of digits needed for the fractional - * part. For example, if the precision is 3, the mask will be 1000. - */ - mask = mypow10(precision); - /* - * We "cheat" by converting the fractional part to integer by - * multiplying by a factor of ten. - */ - if ((fracpart = myround(mask * (ufvalue - intpart))) >= mask) { - /* - * For example, ufvalue = 2.99962, intpart = 2, and mask = 1000 - * (because precision = 3). Now, myround(1000 * 0.99962) will - * return 1000. So, the integer part must be incremented by one - * and the fractional part must be set to zero. - */ - intpart++; - fracpart = 0; - if (estyle && intpart == 10) { - /* - * The value was rounded up to ten, but we only want one - * integer digit if using "e-style". So, the integer - * part must be set to one and the exponent must be - * incremented by one. - */ - intpart = 1; - exponent++; - } - } - - /* - * Now that we know the real exponent, we can check whether or not to - * use "e-style" for "%g" (and "%G") conversions. If we don't need - * "e-style", the precision must be adjusted and the integer and - * fractional parts must be recalculated from the original value. - * - * C99 says: "Let P equal the precision if nonzero, 6 if the precision - * is omitted, or 1 if the precision is zero. Then, if a conversion - * with style `E' would have an exponent of X: - * - * - if P > X >= -4, the conversion is with style `f' (or `F') and - * precision P - (X + 1). - * - * - otherwise, the conversion is with style `e' (or `E') and precision - * P - 1." (7.19.6.1, 8) - * - * Note that we had decremented the precision by one. - */ - if (flags & PRINT_F_TYPE_G && estyle && - precision + 1 > exponent && exponent >= -4) { - precision -= exponent; - estyle = 0; - goto again; - } - - if (estyle) { - if (exponent < 0) { - exponent = -exponent; - esign = '-'; - } else - esign = '+'; - - /* - * Convert the exponent. The sizeof(econvert) is 5. So, the - * econvert buffer can hold e.g. "e+999" and "e-999". We don't - * support an exponent which contains more than three digits. - * Therefore, the following stores are safe. - */ - epos = convert(exponent, econvert, 3, 10, 0); - /* - * C99 says: "The exponent always contains at least two digits, - * and only as many more digits as necessary to represent the - * exponent." (7.19.6.1, 8) - */ - if (epos == 1) - econvert[epos++] = '0'; - econvert[epos++] = esign; - econvert[epos++] = (flags & PRINT_F_UP) ? 'E' : 'e'; - } - - /* Convert the integer part and the fractional part. */ - ipos = convert(intpart, iconvert, sizeof(iconvert), 10, 0); - if (fracpart != 0) /* convert() would return 1 if fracpart == 0. */ - fpos = convert(fracpart, fconvert, sizeof(fconvert), 10, 0); - - leadfraczeros = precision - fpos; - - if (omitzeros) { - if (fpos > 0) /* Omit trailing fractional part zeros. */ - while (omitcount < fpos && fconvert[omitcount] == '0') - omitcount++; - else { /* The fractional part is zero, omit it completely. */ - omitcount = precision; - leadfraczeros = 0; - } - precision -= omitcount; - } - - /* - * Print a decimal point if either the fractional part is non-zero - * and/or the "#" flag was specified. - */ - if (precision > 0 || flags & PRINT_F_NUM) - emitpoint = 1; - if (separators) /* Get the number of group separators we'll print. */ - separators = getnumsep(ipos); - - padlen = width /* Minimum field width. */ - - ipos /* Number of integer digits. */ - - epos /* Number of exponent characters. */ - - precision /* Number of fractional digits. */ - - separators /* Number of group separators. */ - - (emitpoint ? 1 : 0) /* Will we print a decimal point? */ - - ((sign != 0) ? 1 : 0); /* Will we print a sign character? */ - - if (padlen < 0) - padlen = 0; - - /* - * C99 says: "If the `0' and `-' flags both appear, the `0' flag is - * ignored." (7.19.6.1, 6) - */ - if (flags & PRINT_F_MINUS) /* Left justifty. */ - padlen = -padlen; - else if (flags & PRINT_F_ZERO && padlen > 0) { - if (sign != 0) { /* Sign. */ - OUTCHAR(str, *len, size, sign); - sign = 0; - } - while (padlen > 0) { /* Leading zeros. */ - OUTCHAR(str, *len, size, '0'); - padlen--; - } - } - while (padlen > 0) { /* Leading spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen--; - } - if (sign != 0) /* Sign. */ - OUTCHAR(str, *len, size, sign); - while (ipos > 0) { /* Integer part. */ - ipos--; - OUTCHAR(str, *len, size, iconvert[ipos]); - if (separators > 0 && ipos > 0 && ipos % 3 == 0) - printsep(str, len, size); - } - if (emitpoint) { /* Decimal point. */ -#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT - if (lc->decimal_point != NULL && *lc->decimal_point != '\0') - OUTCHAR(str, *len, size, *lc->decimal_point); - else /* We'll always print some decimal point character. */ -#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ - OUTCHAR(str, *len, size, '.'); - } - while (leadfraczeros > 0) { /* Leading fractional part zeros. */ - OUTCHAR(str, *len, size, '0'); - leadfraczeros--; - } - while (fpos > omitcount) { /* The remaining fractional part. */ - fpos--; - OUTCHAR(str, *len, size, fconvert[fpos]); - } - while (epos > 0) { /* Exponent. */ - epos--; - OUTCHAR(str, *len, size, econvert[epos]); - } - while (padlen < 0) { /* Trailing spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen++; - } -} - -static void -printsep(char *str, size_t *len, size_t size) -{ -#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP - struct lconv *lc = localeconv(); - int i; - - if (lc->thousands_sep != NULL) - for (i = 0; lc->thousands_sep[i] != '\0'; i++) - OUTCHAR(str, *len, size, lc->thousands_sep[i]); - else -#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ - OUTCHAR(str, *len, size, ','); -} - -static int -getnumsep(int digits) -{ - int separators = (digits - ((digits % 3 == 0) ? 1 : 0)) / 3; -#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP - int strln; - struct lconv *lc = localeconv(); - - /* We support an arbitrary separator length (including zero). */ - if (lc->thousands_sep != NULL) { - for (strln = 0; lc->thousands_sep[strln] != '\0'; strln++) - continue; - separators *= strln; - } -#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ - return separators; -} - -static int -getexponent(LDOUBLE value) -{ - LDOUBLE tmp = (value >= 0.0) ? value : -value; - int exponent = 0; - - /* - * We check for LDOUBLE_MAX_10_EXP >= exponent >= LDOUBLE_MIN_10_EXP in - * order to work around possible endless loops which could happen (at - * least) in the second loop (at least) if we're called with an infinite - * value. However, we checked for infinity before calling this function - * using our ISINF() macro, so this might be somewhat paranoid. - */ - while (tmp < 1.0 && tmp > 0.0 && --exponent >= LDOUBLE_MIN_10_EXP) - tmp *= 10; - while (tmp >= 10.0 && ++exponent <= LDOUBLE_MAX_10_EXP) - tmp /= 10; - - return exponent; -} - -static int -convert(UINTMAX_T value, char *buf, size_t size, int base, int caps) -{ - const char *digits = caps ? "0123456789ABCDEF" : "0123456789abcdef"; - size_t pos = 0; - - /* We return an unterminated buffer with the digits in reverse order. */ - do { - buf[pos++] = digits[value % base]; - value /= base; - } while (value != 0 && pos < size); - - return (int)pos; -} - -static UINTMAX_T -cast(LDOUBLE value) -{ - UINTMAX_T result; - - /* - * We check for ">=" and not for ">" because if UINTMAX_MAX cannot be - * represented exactly as an LDOUBLE value (but is less than LDBL_MAX), - * it may be increased to the nearest higher representable value for the - * comparison (cf. C99: 6.3.1.4, 2). It might then equal the LDOUBLE - * value although converting the latter to UINTMAX_T would overflow. - */ - if (value >= UINTMAX_MAX) - return UINTMAX_MAX; - - result = value; - /* - * At least on NetBSD/sparc64 3.0.2 and 4.99.30, casting long double to - * an integer type converts e.g. 1.9 to 2 instead of 1 (which violates - * the standard). Sigh. - */ - return (result <= value) ? result : result - 1; -} - -static UINTMAX_T -myround(LDOUBLE value) -{ - UINTMAX_T intpart = cast(value); - - return ((value -= intpart) < 0.5) ? intpart : intpart + 1; -} - -static LDOUBLE -mypow10(int exponent) -{ - LDOUBLE result = 1; - - while (exponent > 0) { - result *= 10; - exponent--; - } - while (exponent < 0) { - result /= 10; - exponent++; - } - return result; -} -#endif /* !HAVE_VSNPRINTF */ - -#if !HAVE_VASPRINTF -#if NEED_MYMEMCPY -void * -mymemcpy(void *dst, void *src, size_t len) -{ - const char *from = src; - char *to = dst; - - /* No need for optimization, we use this only to replace va_copy(3). */ - while (len-- > 0) - *to++ = *from++; - return dst; -} -#endif /* NEED_MYMEMCPY */ - -int -rpl_vasprintf(char **ret, const char *format, va_list ap) -{ - size_t size; - int len; - va_list aq; - - // cppcheck-suppress va_list_usedBeforeStarted - VA_COPY(aq, ap); - // cppcheck-suppress va_list_usedBeforeStarted - len = rpl_vsnprintf(NULL, 0, format, aq); - VA_END_COPY(aq); - if (len < 0 || (*ret = malloc(size = len + 1)) == NULL) - // cppcheck-suppress memleak - return -1; - // cppcheck-suppress memleak - return rpl_vsnprintf(*ret, size, format, ap); -} -#endif /* !HAVE_VASPRINTF */ - -#ifndef HAVE_SNPRINTF -#ifdef HAVE_STDARG_H -int -rpl_snprintf(char *str, size_t size, const char *format, ...) -#else -int -rpl_snprintf(va_alist) va_dcl -#endif /* HAVE_STDARG_H */ -{ -#ifndef HAVE_STDARG_H - char *str; - size_t size; - char *format; -#endif /* HAVE_STDARG_H */ - va_list ap; - int len; - - VA_START(ap, format); - VA_SHIFT(ap, str, char *); - VA_SHIFT(ap, size, size_t); - VA_SHIFT(ap, format, const char *); - len = rpl_vsnprintf(str, size, format, ap); - va_end(ap); - return len; -} -#endif /* !HAVE_SNPRINTF */ - -#ifndef HAVE_ASPRINTF -#ifdef HAVE_STDARG_H -int -rpl_asprintf(char **ret, const char *format, ...) -#else -int -rpl_asprintf(va_alist) va_dcl -#endif /* HAVE_STDARG_H */ -{ -#ifndef HAVE_STDARG_H - char **ret; - char *format; -#endif /* HAVE_STDARG_H */ - va_list ap; - int len; - - VA_START(ap, format); - VA_SHIFT(ap, ret, char **); - VA_SHIFT(ap, format, const char *); - len = rpl_vasprintf(ret, format, ap); - va_end(ap); - return len; -} -#endif /* !HAVE_ASPRINTF */ -#else /* Dummy declaration to avoid empty translation unit warnings. */ -int main(int argc, char **argv); -#endif /* !HAVE_SNPRINTF || !HAVE_VSNPRINTF || !HAVE_ASPRINTF || [...] */ - -#ifdef TEST_SNPRINTF -int -main(void) -{ - const char *float_fmt[] = { - /* "%E" and "%e" formats. */ -#if defined(HAVE_LONG_LONG_INT) && !defined(OS_BSD) && !defined(OS_IRIX) - "%.16e", - "%22.16e", - "%022.16e", - "%-22.16e", - "%#+'022.16e", -#endif /* HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX */ - "foo|%#+0123.9E|bar", - "%-123.9e", - "%123.9e", - "%+23.9e", - "%+05.8e", - "%-05.8e", - "%05.8e", - "%+5.8e", - "%-5.8e", - "% 5.8e", - "%5.8e", - "%+4.9e", -#ifndef OS_LINUX /* glibc sometimes gets these wrong. */ - "%+#010.0e", - "%#10.1e", - "%10.5e", - "% 10.5e", - "%5.0e", - "%5.e", - "%#5.0e", - "%#5.e", - "%3.2e", - "%3.1e", - "%-1.5e", - "%1.5e", - "%01.3e", - "%1.e", - "%.1e", - "%#.0e", - "%+.0e", - "% .0e", - "%.0e", - "%#.e", - "%+.e", - "% .e", - "%.e", - "%4e", - "%e", - "%E", -#endif /* !OS_LINUX */ - /* "%F" and "%f" formats. */ -#if !defined(OS_BSD) && !defined(OS_IRIX) - "% '022f", - "%+'022f", - "%-'22f", - "%'22f", -#ifdef HAVE_LONG_LONG_INT - "%.16f", - "%22.16f", - "%022.16f", - "%-22.16f", - "%#+'022.16f", -#endif /* HAVE_LONG_LONG_INT */ -#endif /* !OS_BSD && !OS_IRIX */ - "foo|%#+0123.9F|bar", - "%-123.9f", - "%123.9f", - "%+23.9f", - "%+#010.0f", - "%#10.1f", - "%10.5f", - "% 10.5f", - "%+05.8f", - "%-05.8f", - "%05.8f", - "%+5.8f", - "%-5.8f", - "% 5.8f", - "%5.8f", - "%5.0f", - "%5.f", - "%#5.0f", - "%#5.f", - "%+4.9f", - "%3.2f", - "%3.1f", - "%-1.5f", - "%1.5f", - "%01.3f", - "%1.f", - "%.1f", - "%#.0f", - "%+.0f", - "% .0f", - "%.0f", - "%#.f", - "%+.f", - "% .f", - "%.f", - "%4f", - "%f", - "%F", - /* "%G" and "%g" formats. */ -#if !defined(OS_BSD) && !defined(OS_IRIX) && !defined(OS_LINUX) - "% '022g", - "%+'022g", - "%-'22g", - "%'22g", -#ifdef HAVE_LONG_LONG_INT - "%.16g", - "%22.16g", - "%022.16g", - "%-22.16g", - "%#+'022.16g", -#endif /* HAVE_LONG_LONG_INT */ -#endif /* !OS_BSD && !OS_IRIX && !OS_LINUX */ - "foo|%#+0123.9G|bar", - "%-123.9g", - "%123.9g", - "%+23.9g", - "%+05.8g", - "%-05.8g", - "%05.8g", - "%+5.8g", - "%-5.8g", - "% 5.8g", - "%5.8g", - "%+4.9g", -#ifndef OS_LINUX /* glibc sometimes gets these wrong. */ - "%+#010.0g", - "%#10.1g", - "%10.5g", - "% 10.5g", - "%5.0g", - "%5.g", - "%#5.0g", - "%#5.g", - "%3.2g", - "%3.1g", - "%-1.5g", - "%1.5g", - "%01.3g", - "%1.g", - "%.1g", - "%#.0g", - "%+.0g", - "% .0g", - "%.0g", - "%#.g", - "%+.g", - "% .g", - "%.g", - "%4g", - "%g", - "%G", -#endif /* !OS_LINUX */ - NULL - }; - double float_val[] = { - -4.136, - -134.52, - -5.04030201, - -3410.01234, - -999999.999999, - -913450.29876, - -913450.2, - -91345.2, - -9134.2, - -913.2, - -91.2, - -9.2, - -9.9, - 4.136, - 134.52, - 5.04030201, - 3410.01234, - 999999.999999, - 913450.29876, - 913450.2, - 91345.2, - 9134.2, - 913.2, - 91.2, - 9.2, - 9.9, - 9.96, - 9.996, - 9.9996, - 9.99996, - 9.999996, - 9.9999996, - 9.99999996, - 0.99999996, - 0.99999999, - 0.09999999, - 0.00999999, - 0.00099999, - 0.00009999, - 0.00000999, - 0.00000099, - 0.00000009, - 0.00000001, - 0.0000001, - 0.000001, - 0.00001, - 0.0001, - 0.001, - 0.01, - 0.1, - 1.0, - 1.5, - -1.5, - -1.0, - -0.1, -#ifndef OS_BSD /* BSD sometimes gets these wrong. */ -#ifdef INFINITY - INFINITY, - -INFINITY, -#endif /* defined(INFINITY) */ -#ifdef NAN - NAN, -#endif /* defined(NAN) */ -#endif /* !OS_BSD */ - 0 - }; - const char *long_fmt[] = { - "foo|%0123ld|bar", -#if !OS_IRIX - "% '0123ld", - "%+'0123ld", - "%-'123ld", - "%'123ld", -#endif /* !OS_IRiX */ - "%123.9ld", - "% 123.9ld", - "%+123.9ld", - "%-123.9ld", - "%0123ld", - "% 0123ld", - "%+0123ld", - "%-0123ld", - "%10.5ld", - "% 10.5ld", - "%+10.5ld", - "%-10.5ld", - "%010ld", - "% 010ld", - "%+010ld", - "%-010ld", - "%4.2ld", - "% 4.2ld", - "%+4.2ld", - "%-4.2ld", - "%04ld", - "% 04ld", - "%+04ld", - "%-04ld", - "%5.5ld", - "%+22.33ld", - "%01.3ld", - "%1.5ld", - "%-1.5ld", - "%44ld", - "%4ld", - "%4.0ld", - "%4.ld", - "%.44ld", - "%.4ld", - "%.0ld", - "%.ld", - "%ld", - NULL - }; - long int long_val[] = { -#ifdef LONG_MAX - LONG_MAX, -#endif /* LONG_MAX */ -#ifdef LONG_MIN - LONG_MIN, -#endif /* LONG_MIN */ - -91340, - 91340, - 341, - 134, - 0203, - -1, - 1, - 0 - }; - const char *ulong_fmt[] = { - /* "%u" formats. */ - "foo|%0123lu|bar", -#ifdef OS_IRIX - "% '0123lu", - "%+'0123lu", - "%-'123lu", - "%'123lu", -#endif /* !OS_IRiX */ - "%123.9lu", - "% 123.9lu", - "%+123.9lu", - "%-123.9lu", - "%0123lu", - "% 0123lu", - "%+0123lu", - "%-0123lu", - "%5.5lu", - "%+22.33lu", - "%01.3lu", - "%1.5lu", - "%-1.5lu", - "%44lu", - "%lu", - /* "%o" formats. */ - "foo|%#0123lo|bar", - "%#123.9lo", - "%# 123.9lo", - "%#+123.9lo", - "%#-123.9lo", - "%#0123lo", - "%# 0123lo", - "%#+0123lo", - "%#-0123lo", - "%#5.5lo", - "%#+22.33lo", - "%#01.3lo", - "%#1.5lo", - "%#-1.5lo", - "%#44lo", - "%#lo", - "%123.9lo", - "% 123.9lo", - "%+123.9lo", - "%-123.9lo", - "%0123lo", - "% 0123lo", - "%+0123lo", - "%-0123lo", - "%5.5lo", - "%+22.33lo", - "%01.3lo", - "%1.5lo", - "%-1.5lo", - "%44lo", - "%lo", - /* "%X" and "%x" formats. */ - "foo|%#0123lX|bar", - "%#123.9lx", - "%# 123.9lx", - "%#+123.9lx", - "%#-123.9lx", - "%#0123lx", - "%# 0123lx", - "%#+0123lx", - "%#-0123lx", - "%#5.5lx", - "%#+22.33lx", - "%#01.3lx", - "%#1.5lx", - "%#-1.5lx", - "%#44lx", - "%#lx", - "%#lX", - "%123.9lx", - "% 123.9lx", - "%+123.9lx", - "%-123.9lx", - "%0123lx", - "% 0123lx", - "%+0123lx", - "%-0123lx", - "%5.5lx", - "%+22.33lx", - "%01.3lx", - "%1.5lx", - "%-1.5lx", - "%44lx", - "%lx", - "%lX", - NULL - }; - unsigned long int ulong_val[] = { -#ifdef ULONG_MAX - ULONG_MAX, -#endif /* ULONG_MAX */ - 91340, - 341, - 134, - 0203, - 1, - 0 - }; - const char *llong_fmt[] = { - "foo|%0123lld|bar", - "%123.9lld", - "% 123.9lld", - "%+123.9lld", - "%-123.9lld", - "%0123lld", - "% 0123lld", - "%+0123lld", - "%-0123lld", - "%5.5lld", - "%+22.33lld", - "%01.3lld", - "%1.5lld", - "%-1.5lld", - "%44lld", - "%lld", - NULL - }; - LLONG llong_val[] = { -#ifdef LLONG_MAX - LLONG_MAX, -#endif /* LLONG_MAX */ -#ifdef LLONG_MIN - LLONG_MIN, -#endif /* LLONG_MIN */ - -91340, - 91340, - 341, - 134, - 0203, - -1, - 1, - 0 - }; - const char *string_fmt[] = { - "foo|%10.10s|bar", - "%-10.10s", - "%10.10s", - "%10.5s", - "%5.10s", - "%10.1s", - "%1.10s", - "%10.0s", - "%0.10s", - "%-42.5s", - "%2.s", - "%.10s", - "%.1s", - "%.0s", - "%.s", - "%4s", - "%s", - NULL - }; - const char *string_val[] = { - "Hello", - "Hello, world!", - "Sound check: One, two, three.", - "This string is a little longer than the other strings.", - "1", - "", - NULL - }; -#if !OS_SYSV /* SysV uses a different format than we do. */ - const char *pointer_fmt[] = { - "foo|%p|bar", - "%42p", - "%p", - NULL - }; - const char *pointer_val[] = { - *pointer_fmt, - *string_fmt, - *string_val, - NULL - }; -#endif /* !OS_SYSV */ - char buf1[1024], buf2[1024]; - double value, digits = 9.123456789012345678901234567890123456789; - int i, j, r1, r2, failed = 0, num = 0; - -/* - * Use -DTEST_NILS in order to also test the conversion of nil values. Might - * segfault on systems which don't support converting a NULL pointer with "%s" - * and lets some test cases fail against BSD and glibc due to bugs in their - * implementations. - */ -#ifndef TEST_NILS -#define TEST_NILS 0 -#elif TEST_NILS -#undef TEST_NILS -#define TEST_NILS 1 -#endif /* !defined(TEST_NILS) */ -#ifdef TEST -#undef TEST -#endif /* defined(TEST) */ -#define TEST(fmt, val) \ -do { \ - for (i = 0; fmt[i] != NULL; i++) \ - for (j = 0; j == 0 || val[j - TEST_NILS] != 0; j++) { \ - r1 = sprintf(buf1, fmt[i], val[j]); \ - r2 = snprintf(buf2, sizeof(buf2), fmt[i], val[j]); \ - if (strcmp(buf1, buf2) != 0 || r1 != r2) { \ - (void)printf("Results don't match, " \ - "format string: %s\n" \ - "\t sprintf(3): [%s] (%d)\n" \ - "\tsnprintf(3): [%s] (%d)\n", \ - fmt[i], buf1, r1, buf2, r2); \ - failed++; \ - } \ - num++; \ - } \ -} while (/* CONSTCOND */ 0) - -#ifdef HAVE_LOCALE_H - (void)setlocale(LC_ALL, ""); -#endif /* HAVE_LOCALE_H */ - - (void)puts("Testing our snprintf(3) against your system's sprintf(3)."); - TEST(float_fmt, float_val); - TEST(long_fmt, long_val); - TEST(ulong_fmt, ulong_val); - TEST(llong_fmt, llong_val); - TEST(string_fmt, string_val); -#ifndef OS_SYSV /* SysV uses a different format than we do. */ - TEST(pointer_fmt, pointer_val); -#endif /* !OS_SYSV */ - (void)printf("Result: %d out of %d tests failed.\n", failed, num); - - (void)fputs("Checking how many digits we support: ", stdout); - for (i = 0; i < 100; i++) { - value = pow(10, i) * digits; - (void)sprintf(buf1, "%.1f", value); - (void)snprintf(buf2, sizeof(buf2), "%.1f", value); - if (strcmp(buf1, buf2) != 0) { - (void)printf("apparently %d.\n", i); - break; - } - } - return (failed == 0) ? 0 : 1; -} -#endif /* TEST_SNPRINTF */ - -/* vim: set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */ diff --git a/src/utils/config.h.in b/src/utils/config.h.in index abed1046f..1cafc2181 100644 --- a/src/utils/config.h.in +++ b/src/utils/config.h.in @@ -39,7 +39,7 @@ #cmakedefine HAVE_WINDOWS_H #cmakedefine HAVE_IO_H -/* Whether symbols exists. If not, they are defined in compat.c or compat/ */ +/* Whether symbols exists. If not, they are defined in compat.c or compat-src/ */ #cmakedefine HAVE_STRDUP #cmakedefine HAVE__STRDUP #cmakedefine HAVE_STRNDUP diff --git a/src/utils/fileutils.h b/src/utils/fileutils.h index 74b9c35ce..817385cde 100644 --- a/src/utils/fileutils.h +++ b/src/utils/fileutils.h @@ -40,7 +40,7 @@ enum { # ifndef WINDOWS # define WINDOWS # endif -# include "compat/dirent.h" +# include "compat-src/dirent.h" # ifndef PATHSEP # define PATHSEP ";" # endif diff --git a/storages/python/python-storage-plugins/http.py b/storages/python/python-storage-plugins/http.py index 7e7f42c35..730cb091f 100644 --- a/storages/python/python-storage-plugins/http.py +++ b/storages/python/python-storage-plugins/http.py @@ -22,9 +22,11 @@ def open(self, location, options=None): Arguments: location: web address to access options: Supported options: + - `single`: Whether the input is assumed to be in single- - entity form. The default (`"auto"`) will try to infer - it automatically. + entity form. The default (`"auto"`) will try to infer + it automatically. + """ self.options = Options("single=auto") diff --git a/storages/python/python-storage-plugins/image.py b/storages/python/python-storage-plugins/image.py index 17b27a962..ec7052f18 100644 --- a/storages/python/python-storage-plugins/image.py +++ b/storages/python/python-storage-plugins/image.py @@ -15,6 +15,7 @@ class image(dlite.DLiteStorageBase): Arguments: location: Path to YAML file. options: Supported options: + - `plugin`: Name of scikit image io plugin to use for loading the image. By default, the different plugins are tried (starting with imageio) until a suitable candidate is found. @@ -34,6 +35,7 @@ class image(dlite.DLiteStorageBase): - `order`: Order of spline interpolation for resize. Default to zero for binary images and 1 otherwise. Should be in the range 0-5. + """ meta = "http://onto-ns.com/meta/0.1/Image" diff --git a/storages/python/python-storage-plugins/mongodb.py b/storages/python/python-storage-plugins/mongodb.py index 5bd1b283a..fbec7acc4 100644 --- a/storages/python/python-storage-plugins/mongodb.py +++ b/storages/python/python-storage-plugins/mongodb.py @@ -30,15 +30,16 @@ def open(self, uri, options=None): An ampersand (&) may be used instead of the semicolon (;). Options: + - mode: "r" for opening in read-only mode, "w" for read/write mode. - database: Name of database to use (default: "test") - collection: Name of collection to use (default: "test_coll") - options for the constructor of the pymongo.MongoClient could pass by using the dlite.Options object, for example: - opt = Options("mode=r;database=test;collection=testc") - opt.update(directConnection=False, maxPoolSize=200) - storage = dlite.Storage("mongodb", "localhost", str(opt)) + opt = Options("mode=r;database=test;collection=testc") + opt.update(directConnection=False, maxPoolSize=200) + storage = dlite.Storage("mongodb", "localhost", str(opt)) see more details of the pymongo.MongoClient options https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient: diff --git a/storages/python/python-storage-plugins/postgresql.py b/storages/python/python-storage-plugins/postgresql.py index 1053d7d87..187dd45e2 100644 --- a/storages/python/python-storage-plugins/postgresql.py +++ b/storages/python/python-storage-plugins/postgresql.py @@ -62,19 +62,21 @@ def open(self, uri, options=None): An ampersand (&) may be used instead of the semicolon (;). Typical options supported by most drivers include: + - database : Name of database to connect to (default: dlite) - user : User name. - password : Password. - mode : append | r - Valid values are: - - append Append to existing file or create new file (default) - - r Open existing file for read-only + Valid values are: + + - `a`, `append`: Append to existing file or create new file (default) + - `r`: Open existing file for read-only After the options are passed, this method may set attribute `writable` to true if it is writable and to false otherwise. If `writable` is not set, it is assumed to be true. """ - self.options = Options(options, defaults="database=dlite;mode=append") + self.options = Options(options, defaults="database=dlite;mode=a") opts = self.options opts.setdefault("password", None) self.writable = False if opts.mode == "r" else True diff --git a/storages/python/python-storage-plugins/pyrdf.py b/storages/python/python-storage-plugins/pyrdf.py index 9d395ac88..1e0cfac61 100644 --- a/storages/python/python-storage-plugins/pyrdf.py +++ b/storages/python/python-storage-plugins/pyrdf.py @@ -30,8 +30,8 @@ def open(self, location: str, options: "Optional[str]" = None) -> None: - `w`: Truncate existing file or create new file. * `format`: File format. For a complete list of valid formats, see - https://rdflib.readthedocs.io/en/stable/intro_to_parsing.html - A sample list of valid format values: "turtle", "xml", "n3", + https://rdflib.readthedocs.io/en/stable/intro_to_parsing.html. + A sample list of valid format values: "turtle", "xml", "n3", "nt", "json-ld", "nquads". * `base_uri`: Base URI that is prepended to the instance UUID or @@ -41,6 +41,7 @@ def open(self, location: str, options: "Optional[str]" = None) -> None: * `include_meta`: Whether to also serialise metadata. The default is to only include metadata if `inst` is a data object. + """ self.options = Options(options, defaults="mode=a") self.writable = "r" not in self.options.mode diff --git a/storages/python/python-storage-plugins/template.py b/storages/python/python-storage-plugins/template.py index da73fc67d..1b01c5ebb 100644 --- a/storages/python/python-storage-plugins/template.py +++ b/storages/python/python-storage-plugins/template.py @@ -13,12 +13,14 @@ def open(self, location, options=None): Arguments: location: Path to file to generate. options: Supported options: + - `template`: Path to template file. - `engine`: Template engine to use. One of: - - "format": Use format() function from the standard library. - See https://docs.python.org/3/library/string.html#formatspec - for specifications. This is the default. - - "jinja": Use jinja. See https://jinja.palletsprojects.com/ + + - "format": Use format() function from the standard library. + See https://docs.python.org/3/library/string.html#formatspec + for specifications. This is the default. + - "jinja": Use jinja. See https://jinja.palletsprojects.com/ """ self.options = Options(options, defaults="engine=format") diff --git a/tools/dlite-codegen.c b/tools/dlite-codegen.c index 3a7d7c90d..6c11818b7 100644 --- a/tools/dlite-codegen.c +++ b/tools/dlite-codegen.c @@ -8,7 +8,7 @@ #include "config.h" #include "utils/compat.h" -#include "utils/compat/getopt.h" +#include "utils/compat-src/getopt.h" #include "utils/err.h" #include "utils/fileutils.h" #include "utils/tgen.h" diff --git a/tools/dlite-env.c b/tools/dlite-env.c index f02f9e303..7f4976d37 100644 --- a/tools/dlite-env.c +++ b/tools/dlite-env.c @@ -9,7 +9,7 @@ #include "dlite.h" #include "utils/compat.h" -#include "utils/compat/getopt.h" +#include "utils/compat-src/getopt.h" #include "utils/err.h" #include "utils/execprocess.h" #include "dlite-macros.h" diff --git a/tools/dlite-getuuid.c b/tools/dlite-getuuid.c index c08004e62..dacc7173e 100644 --- a/tools/dlite-getuuid.c +++ b/tools/dlite-getuuid.c @@ -5,7 +5,7 @@ #include "config.h" -#include "utils/compat/getopt.h" +#include "utils/compat-src/getopt.h" #include "utils/err.h" #include "getuuid.h" From 37d16710ffcaa05adce9de25cfe05042c4a62ec3 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 20:25:04 +0100 Subject: [PATCH 07/20] More documentation cleanup --- doc/conf.py.in | 14 +- doc/getting_started/build/build.md | 2 +- doc/getting_started/tutorial.md | 299 +++++++++++++++++------------ doc/user_guide/datamodels.md | 14 +- doc/user_guide/storage_plugins.md | 18 +- 5 files changed, 211 insertions(+), 136 deletions(-) diff --git a/doc/conf.py.in b/doc/conf.py.in index 8f7ef6bc4..75559a026 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -59,7 +59,7 @@ extensions = [ "sphinx.ext.napoleon", # API ref Google and NumPy style "sphinx.ext.viewcode", "sphinxcontrib.plantuml", # PlantUml - #"sphinx_toggleprompt", # Add button for show/hide example prompts + "sphinx_toggleprompt", # Add button for show/hide example prompts "sphinx_copybutton", # Copy button for codeblocks "sphinx_design", # Create panels in a grid layout or as drop-downs and more ] @@ -186,3 +186,15 @@ def autoapi_prepare_jinja_env(jinja_env: "Environment") -> None: jinja_env.filters["remove_plugin_path"] = lambda name: ".".join( name.split(".")[1:] ) if name.split(".")[0] in dlite_share_plugins else name + + + +def skip_dlite_dlite_members(app, what, name, obj, skip, options): + """Skip class and function whos names contains 'dlite.dlite.'.""" + if "dlite.dlite." in name: + return True + return None + + +def setup(sphinx): + sphinx.connect("autoapi-skip-member", skip_dlite_dlite_members) diff --git a/doc/getting_started/build/build.md b/doc/getting_started/build/build.md index 04c90753b..9a42d2726 100644 --- a/doc/getting_started/build/build.md +++ b/doc/getting_started/build/build.md @@ -92,7 +92,7 @@ Using [Visual Studio Code] (VS Code) it is possible to do development on the sys 1. Download and install [Visual Studio Code]. 2. Install the extension **Remote Development**. -3. [Clone DLite](#installing-from-source) +3. [Clone DLite](#build-from-source) 4. Open the DLite folder with VS Code. 5. Start VS Code, run the _Remote-Containers: Open Folder in Container..._ command from the Command Palette (F1) or quick actions Status bar item. This will build the container and restart VS Code in it. diff --git a/doc/getting_started/tutorial.md b/doc/getting_started/tutorial.md index 4495c475a..fe53bde32 100644 --- a/doc/getting_started/tutorial.md +++ b/doc/getting_started/tutorial.md @@ -1,7 +1,11 @@ -# Tutorial +Tutorial +======== -## Introduction -This tutorial shows the steps for creating and instantiating a Dlite entity, and populate it with data. For this, we will use an example `csv` file containing data corresponding to time measurements from a solar panel. The example file has the following format: +Introduction +------------ +This tutorial shows the steps for creating and instantiating a Dlite entity, and populate it with data. +For this, we will use an example `csv` file containing data corresponding to time measurements from a solar panel. +The example file has the following format: | time | Impp [A] | Isc [A] | MPP [W] | Vmpp [V] | Voc [V] | @@ -11,14 +15,18 @@ This tutorial shows the steps for creating and instantiating a Dlite entity, and -## Writing a DLite Entity -Let us write an Entity representing our solar panel measurement. We start by creating a `json` file called `myEntity.json`. +Writing a DLite Entity +---------------------- +Let us write an Entity representing our solar panel measurement. +We start by creating a `json` file called `myEntity.json`. ### **Step 1**: Giving it a unique identifier -Firstly, we must provide a unique identifier for our Entity. There are several ways of doing this: +Firstly, we must provide a unique identifier for our Entity. +There are several ways of doing this: #### **1. Uniform Resource Identifier (URI)** -We can create and assign a unique Uniform Resource Identifier (URI). A URI has the form **namespace/version/nameOfEntity**, and it can be either written directly in the json file: +We can create and assign a unique Uniform Resource Identifier (URI). +A URI has the form **namespace/version/nameOfEntity**, and it can be either written directly in the json file: ```json "uri": "http://www.ontotrans.eu/0.1/solarPanelMeasurement", @@ -32,27 +40,38 @@ or we can give the **namespace**, **version**, and **name** of our Entity as sep ``` #### **2. Universally Unique Identifier (UUID)** -Alternatively, we can generate and assign a universally unique identifier (UUID). In Python, this can be achieved by the use of the `dlite.get_uuid()` function or the [dlite-getuuid](https://github.com/SINTEF/dlite/blob/master/doc/user_guide/tools.md#dlite-getuuid) tool. +Alternatively, we can generate and assign a universally unique identifier (UUID). +In Python, this can be achieved by the use of the `dlite.get_uuid()` function or the [dlite-getuuid] tool. ```python -import uuid + >>> import uuid -# Generate a random UUID -entity_uuid = uuid.uuid4() + # Generate a random UUID + >>> entity_uuid = uuid.uuid4() + + >>> with open("myEntity.json", "w") as entity_file: + ... entity_file.write('"uuid": {}'.format(entity_uuid)) -with open("myEntity.json", "w") as entity_file: - entity_file.write('"uuid": {}'.format(entity_uuid)) ``` Our json file now contains the following: + ```json -"uuid": +"uuid": "" ``` + where `` is a unique identifier for our Entity. ### **Step 2**: Providing a link to the metadata -Let us assume that we have followed option 1 and provided a URI. The next step consists in adding a **meta** field that will link the Entity to its metadata. In DLite, all Entities are instances of the *EntitySchema*. The *EntitySchema* defines the structure of the Entity. In the **meta** field of our Entity, we need to provide the URI of the EntitySchema. For a schematic overview of the DLite data structures and further detail, see [Concepts section of the DLite User Guide](https://sintef.github.io/dlite/user_guide/concepts.html). We add this information to our json file in the following manner: +Let us assume that we have followed option 1 and provided a URI. +The next step consists in adding a **meta** field that will link the Entity to its metadata. +In DLite, all Entities are instances of the *EntitySchema*. +The *EntitySchema* defines the structure of the Entity. +In the **meta** field of our Entity, we need to provide the URI of the EntitySchema. +For a schematic overview of the DLite data structures and further detail, see [Concepts section of the DLite User Guide]. +We add this information to our json file in the following manner: + ```json "uri": "http://www.ontotrans.eu/0.1/solarPanelMeasurement", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", @@ -63,7 +82,8 @@ The **meta** field actually defaults to `http://onto-ns.com/meta/0.3/EntitySchem ::: ### **Step 3**: Adding a human-understandable description -Next, we want to include a human-understandable description of what the Entity represents. In our example case, such a **description** field could be +Next, we want to include a human-understandable description of what the Entity represents. +In our example case, such a **description** field could be ```json "uri": "http://www.ontotrans.eu/0.1/solarPanelMeasurement", @@ -72,7 +92,10 @@ Next, we want to include a human-understandable description of what the Entity r ``` ### **Step 4**: Defining the dimensions -We continue by defining the dimensions for our Entity. Each dimension should have a **name** and a human-understandable **description**. In our example case, we only have one dimension since all the quantities we are interested in are measured with the same frequency (i.e., we have the same number of data points for all the measured quantities). Note that even though we only have one dimension, we need to give it in a list format. +We continue by defining the dimensions for our Entity. +Each dimension should have a **name** and a human-understandable **description**. +In our example case, we only have one dimension since all the quantities we are interested in are measured with the same frequency (i.e., we have the same number of data points for all the measured quantities). +Note that even though we only have one dimension, we need to give it in a list format. We will give our dimension the generic name "N", and describe it as the number of measurements: @@ -145,9 +168,13 @@ Inserting the properties displayed in the table above, our Entity is complete an Both dimensions and properties can also be provided as arrays using a `dict` with the name as key. This may look like ```json +{ "dimensions": [ - {"name": "N", "description": "Number of measurements."} - ] + { + "name": "N", + "description": "Number of measurements." + } + ], "properties": [ { "name": "t", @@ -163,90 +190,96 @@ Both dimensions and properties can also be provided as arrays using a `dict` wit "shape": ["N"], "description": "Maximum Power" }, - ... + ... ] +} ``` + DLite supports both syntaxes. ::: -## Loading an Entity with DLite +Loading an Entity with DLite +---------------------------- We will now load our Entity in Python. There are several ways of doing this. ### 1. Using the json storage plugin: + ```python -import dlite + >>> import dlite -SolarPanelEntity = dlite.Instance.from_location('json', 'myEntity.json') + >>> SolarPanelEntity = dlite.Instance.from_location('json', 'myEntity.json') ``` ### 2. Adding the file to `storage_path` -In this method, we append the `json` file to `storage_path` and then use the [get_instance()](https://sintef.github.io/dlite/autoapi/dlite/index.html?highlight=get_instance#dlite.get_instance) function to load the Entity using its URI. In Python, this may be done by +In this method, we append the `json` file to `storage_path` and then use the [get_instance()] function to load the Entity using its URI. +In Python, this may be done by ``` python -import dlite + >>> import dlite + + # Append the entity file path to the search path for storages + >>> dlite.storage_path.append('myEntity.json') -# Append the entity file path to the search path for storages -dlite.storage_path.append('myEntity.json') + # Load Entity using its uri + >>> SolarPanelEntity = dlite.get_instance("http://www.ontotrans.eu/0.1/solarPanelMeasurement") -# Load Entity using its uri -SolarPanelEntity = dlite.get_instance("http://www.ontotrans.eu/0.1/solarPanelMeasurement") ``` **Note:** The same approach applies if a UUID is used. -## Connect loaded entity to data - -Now that we have loaded our Entity, we can instantiate it, and populate with the data from the `csv` file mentioned in the introduction. Note that you may have to provide a full file path to the `read_csv()` function, depending on where you have stored your `csv` file. +Connect loaded entity to data +----------------------------- +Now that we have loaded our Entity, we can instantiate it, and populate with the data from the `csv` file mentioned in the introduction. +Note that you may have to provide a full file path to the `read_csv()` function, depending on where you have stored your `csv` file. ```python -import pandas as pd + >>> import pandas as pd -# Read csv file using pandas, store its contents in a pandas DataFrame -solar_panel_dataframe = pd.read_csv('data.csv', sep=',', header = 0) + # Read csv file using pandas, store its contents in a pandas DataFrame + >>> solar_panel_dataframe = pd.read_csv('data.csv', sep=',', header = 0) -# Instantiate an instance of the Entity that we loaded above -inst = SolarPanelEntity({"N": len(solar_panel_dataframe)}) + # Instantiate an instance of the Entity that we loaded above + >>> inst = SolarPanelEntity({"N": len(solar_panel_dataframe)}) -# Create a dicitionary that defines the mapping between property names and column headers -mapping = { - "t":"time", - "MPP":"MPP [W]", - "voc":"Voc [V]", - "vmpp":"Vmpp [V]", - "isc" :"Isc [A]", - "impp":"Impp [A]" + # Create a dicitionary that defines the mapping between property names and column headers + >>> mapping = { + ... "t": "time", + ... "MPP": "MPP [W]", + ... "voc": "Voc [V]", + ... "vmpp": "Vmpp [V]", + ... "isc" : "Isc [A]", + ... "impp": "Impp [A]", + ... } -} - -# Loop through the dictionary keys and populate the instance with data -for key in mapping: - inst[key] = solar_panel_dataframe[mapping[key]] + # Loop through the dictionary keys and populate the instance with data + >>> for key in mapping: + ... inst[key] = solar_panel_dataframe[mapping[key]] ``` -More examples of how to use DLite are available in the [examples](https://github.com/SINTEF/dlite/tree/master/examples) directory. - +More examples of how to use DLite are available in the [examples] directory. -## Working with physical quantities +Working with physical quantities +-------------------------------- As you can read in the previous sections, a property of a dlite entity can be -defined with a **unit** (a unit of measurement). The dlite.Instance object can -set (or get) their properties from (or to) physical quanities (the product of a -numerical value and a unit of measurement). The dlite library uses the physical -quantities defined and implemented by the Python package -[pint](https://pint.readthedocs.io/en/stable/getting/overview.html). +defined with a **unit** (a unit of measurement). +The dlite.Instance object can set (or get) their properties from (or to) physical quanities (the product of a numerical value and a unit of measurement). +The dlite library uses the physical quantities defined and implemented by the Python package [pint]. -> You must install pint if you want to to work with physical quantities, -> try to run the command ```pip install pint```, see the page -> [pint installation](https://pint.readthedocs.io/en/stable/getting/index.html#installation). +```{note} +You must install pint if you want to to work with physical quantities, +try to run the command `pip install pint`, see the page [pint installation]. +``` This section illustrates how to work with physical quantities when manipulating -the dlite **instance** objects. The code example will use the following data model: +the dlite **instance** objects. +The code example will use the following data model: ```yaml uri: http://onto-ns.com/meta/0.1/BodyMassIndex @@ -276,73 +309,103 @@ properties: description: Underweight, normal, or overweight. ``` -The code below will create one BodyMassIndex instance object (variable ```person```) and assign the properties of the ```person``` using physical quantities. You can use the method ```person.set_quantity``` to set a property with a quantity or you can use the shortcut ```person.q``` like shown below: +The code below will create one BodyMassIndex instance object (variable `person`) and assign the properties of the `person` using physical quantities. +You can use the method `person.set_quantity` to set a property with a quantity or you can use the shortcut `person.q` like shown below: ```python -BodyMassIndex = dlite.get_instance("http://onto-ns.com/meta/0.1/BodyMassIndex") -# create a BodyMassIndex instance -person = BodyMassIndex() -# assign the name of the person -person.name = "John Doe" -# assign the age of the person without a physical quantity -person.age = 5 -assert person.age == 5.0 -# assign the age of the person with a physical quantity -person.set_quantity("age", 3.0, "years") -assert person.age == 3.0 -person.q.age = (1461, "days") -assert person.age == 4.0 -# assign the height and the weight -person.q.height = "1.72m" -person.q.weight = "63kg" -assert person.height == 172.0 -assert person.weight == 63.0 -# compute the BMI -person.q.bmi = person.q.weight.to("kg") / person.q.height.to("m") ** 2 -assert np.round(person.get_quantity("bmi").magnitude) == 21.0 -# the following line will give the same result as the line above -person.q.bmi = person.q.weight / person.q.height ** 2 -# the following line will raise a TypeError exception, because the property -# "category" with the type "string" cannot be converted into a quantity. -category = person.q.category -# assign the category -person.category = "normal" -if person.bmi < 18.5: - person.category = = "underweight" -elif person.bmi >= 25.0: - person.category = "overweight" + >>> BodyMassIndex = dlite.get_instance("http://onto-ns.com/meta/0.1/BodyMassIndex") + + # create a BodyMassIndex instance + >>> person = BodyMassIndex() + + # assign the name of the person + >>> person.name = "John Doe" + + # assign the age of the person without a physical quantity + >>> person.age = 5 + >>> assert person.age == 5.0 + + # assign the age of the person with a physical quantity + >>> person.set_quantity("age", 3.0, "years") + >>> assert person.age == 3.0 + >>> person.q.age = (1461, "days") + >>> assert person.age == 4.0 + + # assign the height and the weight + >>> person.q.height = "1.72m" + >>> person.q.weight = "63kg" + >>> assert person.height == 172.0 + >>> assert person.weight == 63.0 + + # compute the BMI + >>> person.q.bmi = person.q.weight.to("kg") / person.q.height.to("m") ** 2 + >>> assert np.round(person.get_quantity("bmi").magnitude) == 21.0 + + # the following line will give the same result as the line above + >>> person.q.bmi = person.q.weight / person.q.height ** 2 + + # the following line will raise a TypeError exception, because the property + # "category" with the type "string" cannot be converted into a quantity. + >>> category = person.q.category + + # assign the category + >>> person.category = "normal" + >>> if person.bmi < 18.5: + ... person.category = = "underweight" + ... elif person.bmi >= 25.0: + ... person.category = "overweight" + ``` -> If you need in your program to use a specific units registry, use the function -> [pint.set_application_registry](https://pint.readthedocs.io/en/0.10.1/tutorial.html#using-pint-in-your-projects) +```note +If you need in your program to use a specific units registry, use the function +[`pint.set_application_registry()`] +``` -The Python property ```q``` of the dlite.Instance objects as some other methods: +The Python property `q` of the dlite.Instance objects as some other methods: ```python -assert person.q.names() == ["age", "height", "weight", "bmi"] -assert person.q.units() == ["year", "cm", "kg", "kg/m^2"] -for name, quantity in persion.q.items(): - quantity.defaut_format = "~" # format the unit with a compact format - print(f"{name} = {quantity}") + >>> assert person.q.names() == ["age", "height", "weight", "bmi"] + >>> assert person.q.units() == ["year", "cm", "kg", "kg/m^2"] + >>> for name, quantity in persion.q.items(): + ... quantity.defaut_format = "~" # format the unit with a compact format + ... print(f"{name} = {quantity}") + ``` -> see [quantity formatting](https://pint.readthedocs.io/en/stable/user/formatting.html) +See [quantity formatting]. -You should not keep the Python property ```q``` as a local variable, it will result with wrong assigment if you work with several instances. +You should not keep the Python property `q` as a local variable, it will result with wrong assignment if you work with several instances. ```python -# >>> don't do that -p1 = person1.q -p2 = person2.q -# this will assign the weight to person2, and not to person1 -p1.weight = "34kg" -# <<< -# you must write this code -person1.q.weight = "34kg" -person2.q.weight = "32kg" -# for more complex formula, you can get the quantities in local variables -w1, h1 = person1.q.get("weight", "height") -w2, h2, a2 = person2.q.get("weight", "height", "age") + # Don't do this! + >>> p1 = person1.q + >>> p2 = person2.q + # this will assign the weight to person2, and not to person1 + >>> p1.weight = "34kg" + + # Instead, you should write the code like this + >>> person1.q.weight = "34kg" + >>> person2.q.weight = "32kg" + + # For more complex formulas, you can assign the quantities to local variables + >>> w1, h1 = person1.q.get("weight", "height") + >>> w2, h2, a2 = person2.q.get("weight", "height", "age") + ``` -The first line of the code above prepare the [singleton](https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/) dlite.quantity.QuantityHelper for the person1 and return the singleton. The second line do the same as the first line, so the variable ```p2``` is the singleton and is prepared to work on ```person2```. \ No newline at end of file +The first line of the code above prepare the [singleton] `dlite.quantity.QuantityHelper` for the person1 and return the singleton. +The second line do the same as the first line, so the variable `p2` is the singleton and is prepared to work on `person2`. + + + +[dlite-getuuid]: https://github.com/SINTEF/dlite/blob/master/doc/user_guide/tools.md#dlite-getuuid +[Concepts section of the DLite User Guide]: https://sintef.github.io/dlite/user_guide/concepts.html +[get_instance()]: https://sintef.github.io/dlite/autoapi/dlite/index.html?highlight=get_instance#dlite.get_instance + +[examples]: https://github.com/SINTEF/dlite/tree/master/examples +[pint]: https://pint.readthedocs.io/en/stable/getting/overview.html +[singleton]: https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/ +[quantity formatting]: https://pint.readthedocs.io/en/stable/user/formatting.html +[`pint.set_application_registry()`]: https://pint.readthedocs.io/en/0.10.1/tutorial.html#using-pint-in-your-projects +[pint installation]: https://pint.readthedocs.io/en/stable/getting/index.html#installation diff --git a/doc/user_guide/datamodels.md b/doc/user_guide/datamodels.md index 76c2c455e..84899adef 100644 --- a/doc/user_guide/datamodels.md +++ b/doc/user_guide/datamodels.md @@ -1,14 +1,14 @@ Representing a datamodel (entity) ----------------------------------- +================================= The underlying structure of DLite datamodels are described under [concepts]. Here, at set of rules on how to create a datamodel is presented. Note that several other possibilities are avilable, and this can be seen in the -examples and tests present in the repository. +examples and tests present in the repository. -We choose here to present only one method as mixing reprentation methods might +We choose here to present only one method as mixing reprentation methods might be confusing. Note, however that yaml and json representations are interchangable. A generic example with some comments for clarity can be seen below. @@ -31,8 +31,8 @@ The keywords in the datamodel have the following meaning: * `uri`: A URI that uniquely identifies the datamodel. * `description`: A human description that describes what this datamodel represents. * `dimensions`: Dimensions of the properties (referred to by the property shape). Properties can have the same dimensions, but not necessarily. Each dimension is described by: - - name of the dimension - - a human description of the dimension + - name of the dimension + - a human description of the dimension In the below example there is one dimension with name "N" and description "Number of skills." * `properties`: Sub-parts of the datamodel that describe the individual data fields. A property has a name and is further specified by the following keywords: - `description`: Human description of the property. @@ -48,7 +48,7 @@ A slightly more realistic example is the "Person" entity, where we want to descr ```yaml uri: http://onto-ns.com/meta/0.1/Person description: A person. -dimensions: +dimensions: N: Number of skills. properties: name: @@ -66,7 +66,7 @@ properties: dlite-validate -============== +-------------- The [dlite-validate tool][./tools.md#dlite_validate] can be used to check if a specific representation (in a file) is a valid DLite datamodel diff --git a/doc/user_guide/storage_plugins.md b/doc/user_guide/storage_plugins.md index c45d147f0..d9eb0d9df 100644 --- a/doc/user_guide/storage_plugins.md +++ b/doc/user_guide/storage_plugins.md @@ -4,10 +4,11 @@ Storage plugins / Drivers Content ------- 1. [Introduction](#introduction) - 2. [Working with storages in Python](#working-with-storages-in-python) - 3. [Using storages implicitely](#using-storages-implicitly) - 4. [Writing Python storage plugins](#writing-python-storage-plugins) - 5. [Working with storages from C and Fortran](#working-with-storages-from-c-and-fortran) + 2. [How to make storage plugins available](#how-to-make-storage-plugins-available) + 3. [Using storages implicitely from Python](#using-storages-implicitly-from-python) + 4. [Working with storages in Python](#working-with-storages-in-python) + 5. [Writing Python storage plugins](#writing-python-storage-plugins) + 6. [Working with storages from C and Fortran](#working-with-storages-from-c-and-fortran) Introduction @@ -30,7 +31,6 @@ Storage plugins can be written in either C or Python. How to make storage plugins available ------------------------------------- - As described below it is possible (and most often advisable) to create specific drivers (storage plugins) for your data. Additional storage plugins drivers can be made available by setting the environment variables `DLITE_STORAGE_PLUGIN_DIRS` or `DLITE_PYTHON_STORAGE_PLUGIN_DIRS` e.g.: @@ -45,15 +45,15 @@ import dlite dlite.python_storage_plugin_path.append("/path/to/plugins/dir") ``` -Often drivers are connected to very specific datamodel (entities). +Often drivers are connected to very specific datamodel (entities). DLite will find these datamodels if the path to their directory is set with the environment variable `DLITE_STORAGES` or added within python with `dlite.storage_path.append` similarly to described above for drivers. ```{attention} -Often, during development dlite will fail unexpectedly. This is typically either because of an error in the -datamodel or the driver. -The variable DLITE_PYDEBUG can be set as `export DLITE_PYDEBUG=` to get python debugging information. +Often, during development dlite will fail unexpectedly. This is typically either because of an error in the +datamodel or the driver. +The variable DLITE_PYDEBUG can be set as `export DLITE_PYDEBUG=` to get python debugging information. This will give information about the driver. It is advisable to first check that the datamodel is valid with the command `dlite-validate datamodelfilename`. ``` From ad881be03cbed0ec57ca6efafa3b4ae00c904879 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 20:31:56 +0100 Subject: [PATCH 08/20] Readding compat/, now named compat-src/ --- src/utils/compat-src/config_snprintf.h.in | 77 + src/utils/compat-src/dirent.h | 1234 ++++++++++++ src/utils/compat-src/getopt.c | 224 +++ src/utils/compat-src/getopt.h | 51 + src/utils/compat-src/realpath.c | 226 +++ src/utils/compat-src/snprintf.c | 2108 +++++++++++++++++++++ 6 files changed, 3920 insertions(+) create mode 100644 src/utils/compat-src/config_snprintf.h.in create mode 100644 src/utils/compat-src/dirent.h create mode 100644 src/utils/compat-src/getopt.c create mode 100644 src/utils/compat-src/getopt.h create mode 100644 src/utils/compat-src/realpath.c create mode 100644 src/utils/compat-src/snprintf.c diff --git a/src/utils/compat-src/config_snprintf.h.in b/src/utils/compat-src/config_snprintf.h.in new file mode 100644 index 000000000..6046c88d5 --- /dev/null +++ b/src/utils/compat-src/config_snprintf.h.in @@ -0,0 +1,77 @@ +#ifdef HAVE_STDARG_H +# undef HAVE_STDARG_H +#endif +#ifdef HAVE_STDDEF_H +# undef HAVE_STDDEF_H +#endif +#ifdef HAVE_STDINT_H +# undef HAVE_STDINT_H +#endif +#ifdef HAVE_STDLIB_H +# undef HAVE_STDLIB_H +#endif +#ifdef HAVE_FLOAT_H +# undef HAVE_FLOAT_H +#endif +#ifdef HAVE_INTTYPES_H +# undef HAVE_INTTYPES_H +#endif +#ifdef HAVE_LOCALE_H +# undef HAVE_LOCALE_H +#endif +#ifdef HAVE_LOCALECONV +# undef HAVE_LOCALECONV +#endif +#ifdef HAVE_LCONV_DECIMAL_POINT +# undef HAVE_LCONV_DECIMAL_POINT +#endif +#ifdef HAVE_LCONV_THOUSANDS_SEP +# undef HAVE_LCONV_THOUSANDS_SEP +#endif +#ifdef HAVE_LONG_DOUBLE +# undef HAVE_LONG_DOUBLE +#endif +#ifdef HAVE_LONG_LONG_INT +# undef HAVE_LONG_LONG_INT +#endif +#ifdef HAVE_UNSIGNED_LONG_LONG_INT +# undef HAVE_UNSIGNED_LONG_LONG_INT +#endif +#ifdef HAVE_INTMAX_T +# undef HAVE_INTMAX_T +#endif +#ifdef HAVE_UINTMAX_T +# undef HAVE_UINTMAX_T +#endif +#ifdef HAVE_UINTPTR_T +# undef HAVE_UINTPTR_T +#endif +#ifdef HAVE_PTRDIFF_T +# undef HAVE_PTRDIFF_T +#endif +#ifdef HAVE_VA_COPY +# undef HAVE_VA_COPY +#endif +#ifdef HAVE___VA_COPY +# undef HAVE___VA_COPY +#endif + +#cmakedefine01 HAVE_STDARG_H +#cmakedefine01 HAVE_STDDEF_H +#cmakedefine01 HAVE_STDINT_H +#cmakedefine01 HAVE_STDLIB_H +#cmakedefine01 HAVE_FLOAT_H +#cmakedefine01 HAVE_INTTYPES_H +#cmakedefine01 HAVE_LOCALE_H +#cmakedefine01 HAVE_LOCALECONV +#cmakedefine01 HAVE_LCONV_DECIMAL_POINT +#cmakedefine01 HAVE_LCONV_THOUSANDS_SEP +#cmakedefine01 HAVE_LONG_DOUBLE +#cmakedefine01 HAVE_LONG_LONG_INT +#cmakedefine01 HAVE_UNSIGNED_LONG_LONG_INT +#cmakedefine01 HAVE_INTMAX_T +#cmakedefine01 HAVE_UINTMAX_T +#cmakedefine01 HAVE_UINTPTR_T +#cmakedefine01 HAVE_PTRDIFF_T +#cmakedefine01 HAVE_VA_COPY +#cmakedefine01 HAVE___VA_COPY diff --git a/src/utils/compat-src/dirent.h b/src/utils/compat-src/dirent.h new file mode 100644 index 000000000..67e3e3f80 --- /dev/null +++ b/src/utils/compat-src/dirent.h @@ -0,0 +1,1234 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 2006-2012 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + +/* Remove __attribute__ when we are not compiling with gcc */ +#ifndef __GNUC__ +# define __attribute__(x) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX+1]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +/* Multi-byte character version */ +struct dirent { + /* Always zero */ + long d_ino; + + /* File position within stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX+1]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + + +/* Dirent functions */ +static DIR *opendir (const char *dirname); +static _WDIR *_wopendir (const wchar_t *dirname); + +static struct dirent *readdir (DIR *dirp) + __attribute__((unused)); +static struct _wdirent *_wreaddir (_WDIR *dirp) + __attribute__((unused)); + +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result); +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result) + __attribute__((unused)); + +static int closedir (DIR *dirp); +static int _wclosedir (_WDIR *dirp); + +static void rewinddir (DIR* dirp) + __attribute__((unused)); +static void _wrewinddir (_WDIR* dirp); + +static int scandir (const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) + __attribute__((unused)); + +static int alphasort (const struct dirent **a, const struct dirent **b); + +static int versionsort (const struct dirent **a, const struct dirent **b) + __attribute__((unused)); + + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); + +static int dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count); + +static int dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, + const wchar_t *wcstr, + size_t count); + +static void dirent_set_errno (int error); + + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR* +_wopendir( + const wchar_t *dirname) +{ + _WDIR *dirp = NULL; + int error; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); + if (dirp != NULL) { + DWORD n; + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + + /* Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + n = wcslen(dirname); +# else + n = GetFullPathNameW (dirname, 0, NULL, NULL); +# endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); + if (dirp->patt) { + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + wcsncpy_s(dirp->patt, n+1, dirname, n); +# else + n = GetFullPathNameW (dirname, n, dirp->patt, NULL); +# endif + if (n > 0) { + wchar_t *p; + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + if (dirp->patt < p) { + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (dirent_first (dirp)) { + /* Directory stream opened successfully */ + error = 0; + } else { + /* Cannot retrieve first entry */ + error = 1; + dirent_set_errno (ENOENT); + } + + } else { + /* Cannot retrieve full path name */ + dirent_set_errno (ENOENT); + error = 1; + } + + } else { + /* Cannot allocate memory for search pattern */ + error = 1; + } + + } else { + /* Cannot allocate _WDIR structure */ + error = 1; + } + + /* Clean up in case of error */ + if (error && dirp) { + _wclosedir (dirp); + dirp = NULL; + } + + return dirp; +} + +/* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ +static struct _wdirent* +_wreaddir( + _WDIR *dirp) +{ + struct _wdirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) _wreaddir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ +static int +_wreaddir_r( + _WDIR *dirp, + struct _wdirent *entry, + struct _wdirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp); + if (datap) { + size_t n; + DWORD attr; + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + n = 0; + while (n < PATH_MAX && datap->cFileName[n] != 0) { + entry->d_name[n] = datap->cFileName[n]; + n++; + } + entry->d_name[n] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n; + + /* File type */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct _wdirent); + + /* Set result address */ + *result = entry; + + } else { + + /* Return NULL to indicate end of directory */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int +_wclosedir( + _WDIR *dirp) +{ + int ok; + if (dirp) { + + /* Release search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + } + + /* Release search pattern */ + if (dirp->patt) { + free (dirp->patt); + dirp->patt = NULL; + } + + /* Release directory structure */ + free (dirp); + ok = /*success*/0; + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void +_wrewinddir( + _WDIR* dirp) +{ + if (dirp) { + /* Release existing search handle */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose (dirp->handle); + } + + /* Open new search handle */ + dirent_first (dirp); + } +} + +/* Get first directory entry (internal) */ +static WIN32_FIND_DATAW* +dirent_first( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *datap; + + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* a directory entry is now waiting in memory */ + datap = &dirp->data; + dirp->cached = 1; + + } else { + + /* Failed to re-open directory: no directory entry in memory */ + dirp->cached = 0; + datap = NULL; + + } + return datap; +} + +/* + * Get next directory entry (internal). + * + * Returns + */ +static WIN32_FIND_DATAW* +dirent_next( + _WDIR *dirp) +{ + WIN32_FIND_DATAW *p; + + /* Get next directory entry */ + if (dirp->cached != 0) { + + /* A valid directory entry already in memory */ + p = &dirp->data; + dirp->cached = 0; + + } else if (dirp->handle != INVALID_HANDLE_VALUE) { + + /* Get the next directory entry from stream */ + if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { + /* Got a file */ + p = &dirp->data; + } else { + /* The very last entry has been processed or an error occurred */ + FindClose (dirp->handle); + dirp->handle = INVALID_HANDLE_VALUE; + p = NULL; + } + + } else { + + /* End of directory stream reached */ + p = NULL; + + } + + return p; +} + +/* + * Open directory stream using plain old C-string. + */ +static DIR* +opendir( + const char *dirname) +{ + struct DIR *dirp; + int error; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno (ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + dirp = (DIR*) malloc (sizeof (struct DIR)); + if (dirp) { + wchar_t wname[PATH_MAX + 1]; + size_t n; + + /* Convert directory name to wide-character string */ + error = dirent_mbstowcs_s( + &n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1); + if (!error) { + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir (wname); + if (dirp->wdirp) { + /* Directory stream opened */ + error = 0; + } else { + /* Failed to open directory stream */ + error = 1; + } + + } else { + /* + * Cannot convert file name to wide-character string. This + * occurs if the string contains invalid multi-byte sequences or + * the output buffer is too small to contain the resulting + * string. + */ + error = 1; + } + + } else { + /* Cannot allocate DIR structure */ + error = 1; + } + + /* Clean up in case of error */ + if (error && dirp) { + free (dirp); + dirp = NULL; + } + + return dirp; +} + +/* + * Read next directory entry. + */ +static struct dirent* +readdir( + DIR *dirp) +{ + struct dirent *entry; + + /* + * Read directory entry to buffer. We can safely ignore the return value + * as entry will be set to NULL in case of error. + */ + (void) readdir_r (dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ +static int +readdir_r( + DIR *dirp, + struct dirent *entry, + struct dirent **result) +{ + WIN32_FIND_DATAW *datap; + + /* Read next directory entry */ + datap = dirent_next (dirp->wdirp); + if (datap) { + size_t n; + int error; + + /* Attempt to convert file name to multi-byte string */ + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, + * then attempt to use old 8+3 file name. This allows traditional + * Unix-code to access some file names despite of unicode + * characters, although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file + * name unless the file system provides one. At least + * VirtualBox shared folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = dirent_wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + DWORD attr; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* File attributes */ + attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { + entry->d_type = DT_CHR; + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + + /* Reset dummy fields */ + entry->d_ino = 0; + entry->d_off = 0; + entry->d_reclen = sizeof (struct dirent); + + } else { + + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + + } + + /* Return pointer to directory entry */ + *result = entry; + + } else { + + /* No more directory entries */ + *result = NULL; + + } + + return /*OK*/0; +} + +/* + * Close directory stream. + */ +static int +closedir( + DIR *dirp) +{ + int ok; + if (dirp) { + + /* Close wide-character directory stream */ + ok = _wclosedir (dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free (dirp); + + } else { + + /* Invalid directory stream */ + dirent_set_errno (EBADF); + ok = /*failure*/-1; + + } + return ok; +} + +/* + * Rewind directory stream to beginning. + */ +static void +rewinddir( + DIR* dirp) +{ + /* Rewind wide-character string directory stream */ + _wrewinddir (dirp->wdirp); +} + +/* + * Scan directory for entries. + */ +static int +scandir( + const char *dirname, + struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) +{ + struct dirent **files = NULL; + size_t size = 0; + size_t allocated = 0; + const size_t init_size = 1; + DIR *dir = NULL; + struct dirent *entry; + struct dirent *tmp = NULL; + size_t i; + int result = 0; + + /* Open directory stream */ + dir = opendir (dirname); + if (dir) { + + /* Read directory entries to memory */ + while (1) { + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + void *p; + size_t num_entries; + + /* Compute number of entries in the enlarged pointer table */ + if (size < init_size) { + /* Allocate initial pointer table */ + num_entries = init_size; + } else { + /* Double the size */ + num_entries = size * 2; + } + + /* Allocate first pointer table or enlarge existing table */ + p = realloc (files, sizeof (void*) * num_entries); + if (p != NULL) { + /* Got the memory */ + files = (dirent**) p; + allocated = num_entries; + } else { + /* Out of memory */ + result = -1; + break; + } + + } + + /* Allocate room for temporary directory entry */ + if (tmp == NULL) { + tmp = (struct dirent*) malloc (sizeof (struct dirent)); + if (tmp == NULL) { + /* Cannot allocate temporary directory entry */ + result = -1; + break; + } + } + + /* Read directory entry to temporary area */ + if (readdir_r (dir, tmp, &entry) == /*OK*/0) { + + /* Did we get an entry? */ + if (entry != NULL) { + int pass; + + /* Determine whether to include the entry in result */ + if (filter) { + /* Let the filter function decide */ + pass = filter (tmp); + } else { + /* No filter function, include everything */ + pass = 1; + } + + if (pass) { + /* Store the temporary entry to pointer table */ + files[size++] = tmp; + tmp = NULL; + + /* Keep up with the number of files */ + result++; + } + + } else { + + /* + * End of directory stream reached => sort entries and + * exit. + */ + qsort (files, size, sizeof (void*), + (int (*) (const void*, const void*)) compare); + break; + + } + + } else { + /* Error reading directory entry */ + result = /*Error*/ -1; + break; + } + + } + + } else { + /* Cannot open directory */ + result = /*Error*/ -1; + } + + /* Release temporary directory entry */ + if (tmp) { + free (tmp); + } + + /* Release allocated memory on error */ + if (result < 0) { + for (i = 0; i < size; i++) { + if (files && files[i]) + free (files[i]); + } + free (files); + files = NULL; + } + + /* Close directory stream */ + if (dir) { + closedir (dir); + } + + /* Pass pointer table to caller */ + if (namelist) { + *namelist = files; + } + return result; +} + +/* Alphabetical sorting */ +static int +alphasort( + const struct dirent **a, const struct dirent **b) +{ + return strcoll ((*a)->d_name, (*b)->d_name); +} + +/* Sort versions */ +static int +versionsort( + const struct dirent **a, const struct dirent **b) +{ + /* FIXME: implement strverscmp and use that */ + return alphasort (a, b); +} + +/* Convert multi-byte string to wide character string */ +static int +dirent_mbstowcs_s( + size_t *pReturnValue, + wchar_t *wcstr, + size_t sizeInWords, + const char *mbstr, + size_t count) +{ + int error; + int n; + size_t len; + UINT cp; + DWORD flags; + + /* Determine code page for multi-byte string */ + if (AreFileApisANSI ()) { + /* Default ANSI code page */ + cp = GetACP (); + } else { + /* Default OEM code page */ + cp = GetOEMCP (); + } + + /* + * Determine flags based on the character set. For more information, + * please see https://docs.microsoft.com/fi-fi/windows/desktop/api/stringapiset/nf-stringapiset-multibytetowidechar + */ + switch (cp) { + case 42: + case 50220: + case 50221: + case 50222: + case 50225: + case 50227: + case 50229: + case 57002: + case 57003: + case 57004: + case 57005: + case 57006: + case 57007: + case 57008: + case 57009: + case 57010: + case 57011: + case 65000: + /* MultiByteToWideChar does not support MB_ERR_INVALID_CHARS */ + flags = 0; + break; + + default: + /* + * Ask MultiByteToWideChar to return an error if a multi-byte + * character cannot be converted to a wide-character. + */ + flags = MB_ERR_INVALID_CHARS; + } + + /* Compute the length of input string without zero-terminator */ + len = 0; + while (mbstr[len] != '\0' && len < count) { + len++; + } + + /* Convert to wide-character string */ + n = MultiByteToWideChar( + /* Source code page */ cp, + /* Flags */ flags, + /* Pointer to string to convert */ mbstr, + /* Size of multi-byte string */ (int) len, + /* Pointer to output buffer */ wcstr, + /* Size of output buffer */ (int)(sizeInWords - 1) + ); + + /* Ensure that output buffer is zero-terminated */ + wcstr[n] = '\0'; + + /* Return length of wide-character string with zero-terminator */ + *pReturnValue = (size_t)n + 1; + + /* Return zero if conversion succeeded */ + if (n > 0) { + error = 0; + } else { + error = 1; + } + return error; +} + +/* Convert wide-character string to multi-byte string */ +static int +dirent_wcstombs_s( + size_t *pReturnValue, + char *mbstr, + size_t sizeInBytes, /* max size of mbstr */ + const wchar_t *wcstr, + size_t count) +{ + int n; + int error; + UINT cp; + size_t len; + BOOL flag = 0; + LPBOOL pflag; + + /* Determine code page for multi-byte string */ + if (AreFileApisANSI ()) { + /* Default ANSI code page */ + cp = GetACP (); + } else { + /* Default OEM code page */ + cp = GetOEMCP (); + } + + /* Compute the length of input string without zero-terminator */ + len = 0; + while (wcstr[len] != '\0' && len < count) { + len++; + } + + /* + * Determine if we can ask WideCharToMultiByte to return information on + * broken characters. For more information, please see + * https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-widechartomultibyte + */ + switch (cp) { + case CP_UTF7: + case CP_UTF8: + /* + * WideCharToMultiByte fails if we request information on default + * characters. This is just a nuisance but doesn't affect the + * converion: if Windows is configured to use UTF-8, then the default + * character should not be needed anyway. + */ + pflag = NULL; + break; + + default: + /* + * Request that WideCharToMultiByte sets the flag if it uses the + * default character. + */ + pflag = &flag; + } + + /* Convert wide-character string to multi-byte character string */ + n = WideCharToMultiByte( + /* Target code page */ cp, + /* Flags */ 0, + /* Pointer to unicode string */ wcstr, + /* Length of unicode string */ (int) len, + /* Pointer to output buffer */ mbstr, + /* Size of output buffer */ (int)(sizeInBytes - 1), + /* Default character */ NULL, + /* Whether default character was used or not */ pflag + ); + + /* Ensure that output buffer is zero-terminated */ + mbstr[n] = '\0'; + + /* Return length of multi-byte string with zero-terminator */ + *pReturnValue = (size_t)n + 1; + + /* Return zero if conversion succeeded without using default characters */ + if (n > 0 && flag == 0) { + error = 0; + } else { + error = 1; + } + return error; +} + +/* Set errno variable */ +static void +dirent_set_errno( + int error) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + + /* Microsoft Visual Studio 2005 and later */ + _set_errno (error); + +#else + + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; + +#endif +} + +#ifdef __cplusplus +} +#endif +#endif /* DIRENT_H */ diff --git a/src/utils/compat-src/getopt.c b/src/utils/compat-src/getopt.c new file mode 100644 index 000000000..44653c30f --- /dev/null +++ b/src/utils/compat-src/getopt.c @@ -0,0 +1,224 @@ +/* + * getopt - POSIX like getopt for Windows console Application + * + * win-c - Windows Console Library + * Copyright (c) 2015 Koji Takami + * Released under the MIT license + * https://github.com/takamin/win-c/blob/master/LICENSE + */ +#include +#include +#include "getopt.h" + +char* optarg = 0; +int optind = 1; +int opterr = 1; +int optopt = 0; + +int postpone_count = 0; +int nextchar = 0; + +static void postpone(int argc, char* const argv[], int index) { + char** nc_argv = (char**)argv; + char* p = nc_argv[index]; + int j = index; + for(; j < argc - 1; j++) { + nc_argv[j] = nc_argv[j + 1]; + } + nc_argv[argc - 1] = p; +} +static int postpone_noopt(int argc, char* const argv[], int index) { + int i = index; + for(; i < argc; i++) { + if(*(argv[i]) == '-') { + postpone(argc, argv, index); + return 1; + } + } + return 0; +} +static int _getopt_(int argc, char* const argv[], + const char* optstring, + const struct option* longopts, int* longindex) +{ + while(1) { + int c; + const char* optptr = 0; + if(optind >= argc - postpone_count) { + c = 0; + optarg = 0; + break; + } + c = *(argv[optind] + nextchar); + if(c == '\0') { + nextchar = 0; + ++optind; + continue; + } + if(nextchar == 0) { + if(optstring[0] != '+' && optstring[0] != '-') { + while(c != '-') { + /* postpone non-opt parameter */ + if(!postpone_noopt(argc, argv, optind)) { + break; /* all args are non-opt param */ + } + ++postpone_count; + c = *argv[optind]; + } + } + if(c != '-') { + if(optstring[0] == '-') { + optarg = argv[optind]; + nextchar = 0; + ++optind; + return 1; + } + break; + } else { + if(strcmp(argv[optind], "--") == 0) { + optind++; + break; + } + ++nextchar; + if(longopts != 0 && *(argv[optind] + 1) == '-') { + char const* spec_long = argv[optind] + 2; + char const* pos_eq = strchr(spec_long, '='); + int spec_len = (pos_eq == NULL) ? + (int)strlen(spec_long) : (int)(pos_eq - spec_long); + int index_search = 0; + int index_found = -1; + const struct option* optdef = 0; + while(longopts->name != 0) { + if(strncmp(spec_long, longopts->name, spec_len) == 0) { + if(optdef != 0) { + if(opterr) { + fprintf(stderr, "ambiguous option: %s\n", spec_long); + } + return '?'; + } + optdef = longopts; + index_found = index_search; + } + longopts++; + index_search++; + } + if(optdef == 0) { + if(opterr) { + fprintf(stderr, "no such a option: %s\n", spec_long); + } + return '?'; + } + switch(optdef->has_arg) { + case no_argument: + optarg = 0; + if(pos_eq != 0) { + if(opterr) { + fprintf(stderr, "no argument for %s\n", optdef->name); + } + return '?'; + } + break; + case required_argument: + if(pos_eq == NULL) { + ++optind; + optarg = argv[optind]; + } else { + optarg = (char*)pos_eq + 1; + } + break; + } + ++optind; + nextchar = 0; + if(longindex != 0) { + *longindex = index_found; + } + if(optdef->flag != 0) { + *optdef->flag = optdef->val; + return 0; + } + return optdef->val; + } + continue; + } + } + optptr = strchr(optstring, c); + if(optptr == NULL) { + optopt = c; + if(opterr) { + fprintf(stderr, + "%s: invalid option -- %c\n", + argv[0], c); + } + ++nextchar; + return '?'; + } + if(*(optptr+1) != ':') { + nextchar++; + if(*(argv[optind] + nextchar) == '\0') { + ++optind; + nextchar = 0; + } + optarg = 0; + } else { + nextchar++; + if(*(argv[optind] + nextchar) != '\0') { + optarg = argv[optind] + nextchar; + } else { + ++optind; + if(optind < argc - postpone_count) { + optarg = argv[optind]; + } else { + optopt = c; + if(opterr) { + fprintf(stderr, + "%s: option requires an argument -- %c\n", + argv[0], c); + } + if(optstring[0] == ':' || + ((optstring[0] == '-' || optstring[0] == '+') && + optstring[1] == ':')) + { + c = ':'; + } else { + c = '?'; + } + } + } + ++optind; + nextchar = 0; + } + return c; + } + + /* end of option analysis */ + + /* fix the order of non-opt params to original */ + while((argc - optind - postpone_count) > 0) { + postpone(argc, argv, optind); + ++postpone_count; + } + + nextchar = 0; + postpone_count = 0; + return -1; +} + +int getopt(int argc, char* const argv[], + const char* optstring) +{ + return _getopt_(argc, argv, optstring, 0, 0); +} +int getopt_long(int argc, char* const argv[], + const char* optstring, + const struct option* longopts, int* longindex) +{ + return _getopt_(argc, argv, optstring, longopts, longindex); +} +/******************************************************** +int getopt_long_only(int argc, char* const argv[], + const char* optstring, + const struct option* longopts, int* longindex) +{ + return -1; +} +********************************************************/ diff --git a/src/utils/compat-src/getopt.h b/src/utils/compat-src/getopt.h new file mode 100644 index 000000000..ac1f536b3 --- /dev/null +++ b/src/utils/compat-src/getopt.h @@ -0,0 +1,51 @@ +/* + * getopt - POSIX like getopt for Windows console Application + * + * win-c - Windows Console Library + * Copyright (c) 2015 Koji Takami + * Released under the MIT license + * https://github.com/takamin/win-c/blob/master/LICENSE + */ +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifdef HAVE_GETOPT +#include +#else + + int getopt(int argc, char* const argv[], + const char* optstring); + + extern char *optarg; + extern int optind, opterr, optopt; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + + struct option { + const char *name; + int has_arg; + int* flag; + int val; + }; + + int getopt_long(int argc, char* const argv[], + const char* optstring, + const struct option* longopts, int* longindex); +/**************************************************************************** + int getopt_long_only(int argc, char* const argv[], + const char* optstring, + const struct option* longopts, int* longindex); +****************************************************************************/ + +#endif /* HAVE_GETOPT */ + +#ifdef __cplusplus +} +#endif // __cplusplus +#endif // _GETOPT_H_ diff --git a/src/utils/compat-src/realpath.c b/src/utils/compat-src/realpath.c new file mode 100644 index 000000000..b1945ce46 --- /dev/null +++ b/src/utils/compat-src/realpath.c @@ -0,0 +1,226 @@ +/* $OpenBSD: realpath.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ +/* + * Copyright (c) 2003 Constantin S. Svintsoff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* Changes: + - 2020-04-11, Jesper Friis + Copied from https://android.googlesource.com/platform/bionic.git/+/android-4.0.1_r1/libc/bionic/realpath.c + Allowed `resolved` argument to be NULL. +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "compat.h" + +#include +#include +#include +#include +#include +#include +#include + +/* + * char *realpath(const char *path, char *resolved); + * + * Find the real name of path, by removing all ".", ".." and symlink + * components. Returns (resolved) on success, or (NULL) on failure, + * in which case the path which caused trouble is left in (resolved). + * + * If resolved_path is specified as NULL, then realpath() uses + * malloc() to allocate a buffer of up to PATH_MAX bytes to hold the + * resolved pathname, and returns a pointer to this buffer. The + * caller should deallocate this buffer using free(). + */ +char * +_realpath(const char *path, char resolved[PATH_MAX]) +{ + struct stat sb; + char *p, *q, *s; + size_t left_len, resolved_len; + unsigned symlinks; + int serrno, slen; + char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; + + serrno = errno; + symlinks = 0; + if (path[0] == '/') { + resolved[0] = '/'; + resolved[1] = '\0'; + if (path[1] == '\0') + return (resolved); + resolved_len = 1; + left_len = strlcpy(left, path + 1, sizeof(left)); + } else { + if (getcwd(resolved, PATH_MAX) == NULL) { + strlcpy(resolved, ".", PATH_MAX); + return (NULL); + } + resolved_len = strlen(resolved); + left_len = strlcpy(left, path, sizeof(left)); + } + if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) { + errno = ENAMETOOLONG; + return (NULL); + } + /* + * Iterate over path components in `left'. + */ + while (left_len != 0) { + /* + * Extract the next path component and adjust `left' + * and its length. + */ + p = strchr(left, '/'); + s = p ? p : left + left_len; + if (s - left >= (int)sizeof(next_token)) { + errno = ENAMETOOLONG; + return (NULL); + } + memcpy(next_token, left, s - left); + next_token[s - left] = '\0'; + left_len -= s - left; + if (p != NULL) + memmove(left, s + 1, left_len + 1); + if (resolved[resolved_len - 1] != '/') { + if (resolved_len + 1 >= PATH_MAX) { + errno = ENAMETOOLONG; + return (NULL); + } + resolved[resolved_len++] = '/'; + resolved[resolved_len] = '\0'; + } + if (next_token[0] == '\0') + continue; + else if (strcmp(next_token, ".") == 0) + continue; + else if (strcmp(next_token, "..") == 0) { + /* + * Strip the last path component except when we have + * single "/" + */ + if (resolved_len > 1) { + resolved[resolved_len - 1] = '\0'; + q = strrchr(resolved, '/') + 1; + *q = '\0'; + resolved_len = q - resolved; + } + continue; + } + /* + * Append the next path component and lstat() it. If + * lstat() fails we still can return successfully if + * there are no more path components left. + */ + resolved_len = strlcat(resolved, next_token, PATH_MAX); + if (resolved_len >= PATH_MAX) { + errno = ENAMETOOLONG; + return (NULL); + } + if (lstat(resolved, &sb) != 0) { + if (errno == ENOENT && p == NULL) { + errno = serrno; + return (resolved); + } + return (NULL); + } + if (S_ISLNK(sb.st_mode)) { + if (symlinks++ > MAXSYMLINKS) { + errno = ELOOP; + return (NULL); + } + slen = readlink(resolved, symlink, sizeof(symlink) - 1); + if (slen < 0) + return (NULL); + symlink[slen] = '\0'; + if (symlink[0] == '/') { + resolved[1] = 0; + resolved_len = 1; + } else if (resolved_len > 1) { + /* Strip the last path component. */ + resolved[resolved_len - 1] = '\0'; + q = strrchr(resolved, '/') + 1; + *q = '\0'; + resolved_len = q - resolved; + } + /* + * If there are any path components left, then + * append them to symlink. The result is placed + * in `left'. + */ + if (p != NULL) { + if (symlink[slen - 1] != '/') { + if (slen + 1 >= (int)sizeof(symlink)) { + errno = ENAMETOOLONG; + return (NULL); + } + symlink[slen] = '/'; + symlink[slen + 1] = 0; + } + left_len = strlcat(symlink, left, sizeof(left)); + if (left_len >= sizeof(left)) { + errno = ENAMETOOLONG; + return (NULL); + } + } + left_len = strlcpy(left, symlink, sizeof(left)); + } + } + /* + * Remove trailing slash except when the resolved pathname + * is a single "/". + */ + if (resolved_len > 1 && resolved[resolved_len - 1] == '/') + resolved[resolved_len - 1] = '\0'; + return (resolved); +} + + +char * +realpath(const char *path, char *resolved) +{ + char *result, *buff = NULL; + + if (!resolved) { + if (!(buff = malloc(PATH_MAX + 1))) + return (NULL); + resolved = buff; + } + + if ((result = _realpath(path, resolved))) { + struct stat statbuf; + if (stat(result, &statbuf)) + result = NULL; + } + + if (!result && buff) + free(buff); + + return (result); +} diff --git a/src/utils/compat-src/snprintf.c b/src/utils/compat-src/snprintf.c new file mode 100644 index 000000000..9574c8464 --- /dev/null +++ b/src/utils/compat-src/snprintf.c @@ -0,0 +1,2108 @@ +/* + * Copyright (c) 1995 Patrick Powell. + * + * This code is based on code written by Patrick Powell . + * It may be used for any purpose as long as this notice remains intact on all + * source code distributions. + */ + +/* + * Copyright (c) 2008 Holger Weiss. + * + * This version of the code is maintained by Holger Weiss . + * My changes to the code may freely be used, modified and/or redistributed for + * any purpose. It would be nice if additions and fixes to this file (including + * trivial code cleanups) would be sent back in order to let me include them in + * the version available at . + * However, this is not a requirement for using or redistributing (possibly + * modified) versions of this file, nor is leaving this notice intact mandatory. + */ + +/* + * History + * + * 2008-01-20 Holger Weiss for C99-snprintf 1.1: + * + * Fixed the detection of infinite floating point values on IRIX (and + * possibly other systems) and applied another few minor cleanups. + * + * 2008-01-06 Holger Weiss for C99-snprintf 1.0: + * + * Added a lot of new features, fixed many bugs, and incorporated various + * improvements done by Andrew Tridgell , Russ Allbery + * , Hrvoje Niksic , Damien Miller + * , and others for the Samba, INN, Wget, and OpenSSH + * projects. The additions include: support the "e", "E", "g", "G", and + * "F" conversion specifiers (and use conversion style "f" or "F" for the + * still unsupported "a" and "A" specifiers); support the "hh", "ll", "j", + * "t", and "z" length modifiers; support the "#" flag and the (non-C99) + * "'" flag; use localeconv(3) (if available) to get both the current + * locale's decimal point character and the separator between groups of + * digits; fix the handling of various corner cases of field width and + * precision specifications; fix various floating point conversion bugs; + * handle infinite and NaN floating point values; don't attempt to write to + * the output buffer (which may be NULL) if a size of zero was specified; + * check for integer overflow of the field width, precision, and return + * values and during the floating point conversion; use the OUTCHAR() macro + * instead of a function for better performance; provide asprintf(3) and + * vasprintf(3) functions; add new test cases. The replacement functions + * have been renamed to use an "rpl_" prefix, the function calls in the + * main project (and in this file) must be redefined accordingly for each + * replacement function which is needed (by using Autoconf or other means). + * Various other minor improvements have been applied and the coding style + * was cleaned up for consistency. + * + * 2007-07-23 Holger Weiss for Mutt 1.5.13: + * + * C99 compliant snprintf(3) and vsnprintf(3) functions return the number + * of characters that would have been written to a sufficiently sized + * buffer (excluding the '\0'). The original code simply returned the + * length of the resulting output string, so that's been fixed. + * + * 1998-03-05 Michael Elkins for Mutt 0.90.8: + * + * The original code assumed that both snprintf(3) and vsnprintf(3) were + * missing. Some systems only have snprintf(3) but not vsnprintf(3), so + * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + * 1998-01-27 Thomas Roessler for Mutt 0.89i: + * + * The PGP code was using unsigned hexadecimal formats. Unfortunately, + * unsigned formats simply didn't work. + * + * 1997-10-22 Brandon Long for Mutt 0.87.1: + * + * Ok, added some minimal floating point support, which means this probably + * requires libm on most operating systems. Don't yet support the exponent + * (e,E) and sigfig (g,G). Also, fmtint() was pretty badly broken, it just + * wasn't being exercised in ways which showed it, so that's been fixed. + * Also, formatted the code to Mutt conventions, and removed dead code left + * over from the original. Also, there is now a builtin-test, run with: + * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm && ./snprintf + * + * 2996-09-15 Brandon Long for Mutt 0.43: + * + * This was ugly. It is still ugly. I opted out of floating point + * numbers, but the formatter understands just about everything from the + * normal C string format, at least as far as I can tell from the Solaris + * 2.5 printf(3S) man page. + */ + +/* + * ToDo + * + * - Add wide character support. + * - Add support for "%a" and "%A" conversions. + * - Create test routines which predefine the expected results. Our test cases + * usually expose bugs in system implementations rather than in ours :-) + */ + +/* + * Usage + * + * 1) The following preprocessor macros should be defined to 1 if the feature or + * file in question is available on the target system (by using Autoconf or + * other means), though basic functionality should be available as long as + * HAVE_STDARG_H and HAVE_STDLIB_H are defined correctly: + * + * HAVE_VSNPRINTF + * HAVE_SNPRINTF + * HAVE_VASPRINTF + * HAVE_ASPRINTF + * HAVE_STDARG_H + * HAVE_STDDEF_H + * HAVE_STDINT_H + * HAVE_STDLIB_H + * HAVE_FLOAT_H + * HAVE_INTTYPES_H + * HAVE_LOCALE_H + * HAVE_LOCALECONV + * HAVE_LCONV_DECIMAL_POINT + * HAVE_LCONV_THOUSANDS_SEP + * HAVE_LONG_DOUBLE + * HAVE_LONG_LONG_INT + * HAVE_UNSIGNED_LONG_LONG_INT + * HAVE_INTMAX_T + * HAVE_UINTMAX_T + * HAVE_UINTPTR_T + * HAVE_PTRDIFF_T + * HAVE_VA_COPY + * HAVE___VA_COPY + * + * 2) The calls to the functions which should be replaced must be redefined + * throughout the project files (by using Autoconf or other means): + * + * #define vsnprintf rpl_vsnprintf + * #define snprintf rpl_snprintf + * #define vasprintf rpl_vasprintf + * #define asprintf rpl_asprintf + * + * 3) The required replacement functions should be declared in some header file + * included throughout the project files: + * + * #ifdef HAVE_CONFIG_H + * #include + * #endif + * #ifdef HAVE_STDARG_H + * #include + * #ifndef HAVE_VSNPRINTF + * int rpl_vsnprintf(char *, size_t, const char *, va_list); + * #endif + * #ifndef HAVE_SNPRINTF + * int rpl_snprintf(char *, size_t, const char *, ...); + * #endif + * #ifndef HAVE_VASPRINTF + * int rpl_vasprintf(char **, const char *, va_list); + * #endif + * #ifndef HAVE_ASPRINTF + * int rpl_asprintf(char **, const char *, ...); + * #endif + * #endif + * + * Autoconf macros for handling step 1 and step 2 are available at + * . + */ + +# ifdef HAVE_CONFIG_H +# include +# endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_CONFIG_SNPRINTF_H +#include +#endif /* HAVE_CONFIG_SNPRINTF_H */ + + +#if TEST_SNPRINTF +#include /* For pow(3), NAN, and INFINITY. */ +#include /* For strcmp(3). */ +#if defined(__NetBSD__) || \ + defined(__FreeBSD__) || \ + defined(__OpenBSD__) || \ + defined(__NeXT__) || \ + defined(__bsd__) +#define OS_BSD 1 +#elif defined(sgi) || defined(__sgi) +#ifndef __c99 +#define __c99 /* Force C99 mode to get included on IRIX 6.5.30. */ +#endif /* !defined(__c99) */ +#define OS_IRIX 1 +#define OS_SYSV 1 +#elif defined(__svr4__) +#define OS_SYSV 1 +#elif defined(__linux__) +#define OS_LINUX 1 +#endif /* defined(__NetBSD__) || defined(__FreeBSD__) || [...] */ +#ifdef HAVE_CONFIG_H /* Undefine definitions possibly done in config.h. */ +#ifdef HAVE_SNPRINTF +#undef HAVE_SNPRINTF +#endif /* defined(HAVE_SNPRINTF) */ +#ifdef HAVE_VSNPRINTF +#undef HAVE_VSNPRINTF +#endif /* defined(HAVE_VSNPRINTF) */ +#ifdef HAVE_ASPRINTF +#undef HAVE_ASPRINTF +#endif /* defined(HAVE_ASPRINTF) */ +#ifdef HAVE_VASPRINTF +#undef HAVE_VASPRINTF +#endif /* defined(HAVE_VASPRINTF) */ +#ifdef snprintf +#undef snprintf +#endif /* defined(snprintf) */ +#ifdef vsnprintf +#undef vsnprintf +#endif /* defined(vsnprintf) */ +#ifdef asprintf +#undef asprintf +#endif /* defined(asprintf) */ +#ifdef vasprintf +#undef vasprintf +#endif /* defined(vasprintf) */ +#else /* By default, we assume a modern system for testing. */ +#ifndef HAVE_STDARG_H +#define HAVE_STDARG_H 1 +#endif /* HAVE_STDARG_H */ +#ifndef HAVE_STDDEF_H +#define HAVE_STDDEF_H 1 +#endif /* HAVE_STDDEF_H */ +#ifndef HAVE_STDINT_H +#define HAVE_STDINT_H 1 +#endif /* HAVE_STDINT_H */ +#ifndef HAVE_STDLIB_H +#define HAVE_STDLIB_H 1 +#endif /* HAVE_STDLIB_H */ +#ifndef HAVE_FLOAT_H +#define HAVE_FLOAT_H 1 +#endif /* HAVE_FLOAT_H */ +#ifndef HAVE_INTTYPES_H +#define HAVE_INTTYPES_H 1 +#endif /* HAVE_INTTYPES_H */ +#ifndef HAVE_LOCALE_H +#define HAVE_LOCALE_H 1 +#endif /* HAVE_LOCALE_H */ +#ifndef HAVE_LOCALECONV +#define HAVE_LOCALECONV 1 +#endif /* !defined(HAVE_LOCALECONV) */ +#ifndef HAVE_LCONV_DECIMAL_POINT +#define HAVE_LCONV_DECIMAL_POINT 1 +#endif /* HAVE_LCONV_DECIMAL_POINT */ +#ifndef HAVE_LCONV_THOUSANDS_SEP +#define HAVE_LCONV_THOUSANDS_SEP 1 +#endif /* HAVE_LCONV_THOUSANDS_SEP */ +#ifndef HAVE_LONG_DOUBLE +#define HAVE_LONG_DOUBLE 1 +#endif /* !defined(HAVE_LONG_DOUBLE) */ +#ifndef HAVE_LONG_LONG_INT +#define HAVE_LONG_LONG_INT 1 +#endif /* !defined(HAVE_LONG_LONG_INT) */ +#ifndef HAVE_UNSIGNED_LONG_LONG_INT +#define HAVE_UNSIGNED_LONG_LONG_INT 1 +#endif /* !defined(HAVE_UNSIGNED_LONG_LONG_INT) */ +#ifndef HAVE_INTMAX_T +#define HAVE_INTMAX_T 1 +#endif /* !defined(HAVE_INTMAX_T) */ +#ifndef HAVE_UINTMAX_T +#define HAVE_UINTMAX_T 1 +#endif /* !defined(HAVE_UINTMAX_T) */ +#ifndef HAVE_UINTPTR_T +#define HAVE_UINTPTR_T 1 +#endif /* !defined(HAVE_UINTPTR_T) */ +#ifndef HAVE_PTRDIFF_T +#define HAVE_PTRDIFF_T 1 +#endif /* !defined(HAVE_PTRDIFF_T) */ +#ifndef HAVE_VA_COPY +#define HAVE_VA_COPY 1 +#endif /* !defined(HAVE_VA_COPY) */ +#ifndef HAVE___VA_COPY +#define HAVE___VA_COPY 1 +#endif /* !defined(HAVE___VA_COPY) */ +#endif /* HAVE_CONFIG_H */ +#define snprintf rpl_snprintf +#define vsnprintf rpl_vsnprintf +#define asprintf rpl_asprintf +#define vasprintf rpl_vasprintf +#endif /* TEST_SNPRINTF */ + +#if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) || \ + !defined(HAVE_ASPRINTF) || !defined(HAVE_VASPRINTF) +#include /* For NULL, size_t, vsnprintf(3), and vasprintf(3). */ +#ifdef VA_START +#undef VA_START +#endif /* defined(VA_START) */ +#ifdef VA_SHIFT +#undef VA_SHIFT +#endif /* defined(VA_SHIFT) */ +#ifdef HAVE_STDARG_H +#include +#define VA_START(ap, last) va_start(ap, last) +#define VA_SHIFT(ap, value, type) /* No-op for ANSI C. */ +#else /* Assume is available. */ +#include +#define VA_START(ap, last) va_start(ap) /* "last" is ignored. */ +#define VA_SHIFT(ap, value, type) value = va_arg(ap, type) +#endif /* HAVE_STDARG_H */ + +#ifndef HAVE_VASPRINTF +#ifdef HAVE_STDLIB_H +#include /* For malloc(3). */ +#endif /* HAVE_STDLIB_H */ +#ifdef VA_COPY +#undef VA_COPY +#endif /* defined(VA_COPY) */ +#ifdef VA_END_COPY +#undef VA_END_COPY +#endif /* defined(VA_END_COPY) */ +#ifdef HAVE_VA_COPY +#define VA_COPY(dest, src) va_copy(dest, src) +#define VA_END_COPY(ap) va_end(ap) +#elif defined(HAVE___VA_COPY) +#define VA_COPY(dest, src) __va_copy(dest, src) +#define VA_END_COPY(ap) va_end(ap) +#else +#define VA_COPY(dest, src) (void)mymemcpy(&dest, &src, sizeof(va_list)) +#define VA_END_COPY(ap) /* No-op. */ +#define NEED_MYMEMCPY 1 +static void *mymemcpy(void *, void *, size_t); +#endif /* HAVE_VA_COPY */ +#endif /* !HAVE_VASPRINTF */ + +#ifndef HAVE_VSNPRINTF +#include /* For ERANGE and errno. */ +#include /* For *_MAX. */ +#ifdef HAVE_FLOAT_H +#include /* For *DBL_{MIN,MAX}_10_EXP. */ +#endif /* HAVE_FLOAT_H */ +#ifdef HAVE_INTTYPES_H +#include /* For intmax_t (if not defined in ). */ +#endif /* HAVE_INTTYPES_H */ +#ifdef HAVE_LOCALE_H +#include /* For localeconv(3). */ +#endif /* HAVE_LOCALE_H */ +#ifdef HAVE_STDDEF_H +#include /* For ptrdiff_t. */ +#endif /* HAVE_STDDEF_H */ +#ifdef HAVE_STDINT_H +#include /* For intmax_t. */ +#endif /* HAVE_STDINT_H */ + +/* Support for unsigned long long int. We may also need ULLONG_MAX. */ +#ifndef ULONG_MAX /* We may need ULONG_MAX as a fallback. */ +#ifdef UINT_MAX +#define ULONG_MAX UINT_MAX +#else +#define ULONG_MAX INT_MAX +#endif /* defined(UINT_MAX) */ +#endif /* !defined(ULONG_MAX) */ +#ifdef ULLONG +#undef ULLONG +#endif /* defined(ULLONG) */ +#if HAVE_UNSIGNED_LONG_LONG_INT +#define ULLONG unsigned long long int +#ifndef ULLONG_MAX +#define ULLONG_MAX ULONG_MAX +#endif /* !defined(ULLONG_MAX) */ +#else +#define ULLONG unsigned long int +#ifdef ULLONG_MAX +#undef ULLONG_MAX +#endif /* defined(ULLONG_MAX) */ +#define ULLONG_MAX ULONG_MAX +#endif /* HAVE_LONG_LONG_INT */ + +/* Support for uintmax_t. We also need UINTMAX_MAX. */ +#ifdef UINTMAX_T +#undef UINTMAX_T +#endif /* defined(UINTMAX_T) */ +#if HAVE_UINTMAX_T || defined(uintmax_t) +#define UINTMAX_T uintmax_t +#ifndef UINTMAX_MAX +#define UINTMAX_MAX ULLONG_MAX +#endif /* !defined(UINTMAX_MAX) */ +#else +#define UINTMAX_T ULLONG +#ifdef UINTMAX_MAX +#undef UINTMAX_MAX +#endif /* defined(UINTMAX_MAX) */ +#define UINTMAX_MAX ULLONG_MAX +#endif /* HAVE_UINTMAX_T || defined(uintmax_t) */ + +/* Support for long double. */ +#ifndef LDOUBLE +#if HAVE_LONG_DOUBLE +#define LDOUBLE long double +#define LDOUBLE_MIN_10_EXP LDBL_MIN_10_EXP +#define LDOUBLE_MAX_10_EXP LDBL_MAX_10_EXP +#else +#define LDOUBLE double +#define LDOUBLE_MIN_10_EXP DBL_MIN_10_EXP +#define LDOUBLE_MAX_10_EXP DBL_MAX_10_EXP +#endif /* HAVE_LONG_DOUBLE */ +#endif /* !defined(LDOUBLE) */ + +/* Support for long long int. */ +#ifndef LLONG +#if HAVE_LONG_LONG_INT +#define LLONG long long int +#else +#define LLONG long int +#endif /* HAVE_LONG_LONG_INT */ +#endif /* !defined(LLONG) */ + +/* Support for intmax_t. */ +#ifndef INTMAX_T +#if HAVE_INTMAX_T || defined(intmax_t) +#define INTMAX_T intmax_t +#else +#define INTMAX_T LLONG +#endif /* HAVE_INTMAX_T || defined(intmax_t) */ +#endif /* !defined(INTMAX_T) */ + +/* Support for uintptr_t. */ +#ifndef UINTPTR_T +#if HAVE_UINTPTR_T || defined(uintptr_t) +#define UINTPTR_T uintptr_t +#else +#define UINTPTR_T unsigned long int +#endif /* HAVE_UINTPTR_T || defined(uintptr_t) */ +#endif /* !defined(UINTPTR_T) */ + +/* Support for ptrdiff_t. */ +#ifndef PTRDIFF_T +#if HAVE_PTRDIFF_T || defined(ptrdiff_t) +#define PTRDIFF_T ptrdiff_t +#else +#define PTRDIFF_T long int +#endif /* HAVE_PTRDIFF_T || defined(ptrdiff_t) */ +#endif /* !defined(PTRDIFF_T) */ + +/* + * We need an unsigned integer type corresponding to ptrdiff_t (cf. C99: + * 7.19.6.1, 7). However, we'll simply use PTRDIFF_T and convert it to an + * unsigned type if necessary. This should work just fine in practice. + */ +#ifndef UPTRDIFF_T +#define UPTRDIFF_T PTRDIFF_T +#endif /* !defined(UPTRDIFF_T) */ + +/* + * We need a signed integer type corresponding to size_t (cf. C99: 7.19.6.1, 7). + * However, we'll simply use size_t and convert it to a signed type if + * necessary. This should work just fine in practice. + */ +#ifndef SSIZE_T +#define SSIZE_T size_t +#endif /* !defined(SSIZE_T) */ + +/* Either ERANGE or E2BIG should be available everywhere. */ +#ifndef ERANGE +#define ERANGE E2BIG +#endif /* !defined(ERANGE) */ +#ifndef EOVERFLOW +#define EOVERFLOW ERANGE +#endif /* !defined(EOVERFLOW) */ + +/* + * Buffer size to hold the octal string representation of UINT128_MAX without + * nul-termination ("3777777777777777777777777777777777777777777"). + */ +#ifdef MAX_CONVERT_LENGTH +#undef MAX_CONVERT_LENGTH +#endif /* defined(MAX_CONVERT_LENGTH) */ +#define MAX_CONVERT_LENGTH 43 + +/* Format read states. */ +#define PRINT_S_DEFAULT 0 +#define PRINT_S_FLAGS 1 +#define PRINT_S_WIDTH 2 +#define PRINT_S_DOT 3 +#define PRINT_S_PRECISION 4 +#define PRINT_S_MOD 5 +#define PRINT_S_CONV 6 + +/* Format flags. */ +#define PRINT_F_MINUS (1 << 0) +#define PRINT_F_PLUS (1 << 1) +#define PRINT_F_SPACE (1 << 2) +#define PRINT_F_NUM (1 << 3) +#define PRINT_F_ZERO (1 << 4) +#define PRINT_F_QUOTE (1 << 5) +#define PRINT_F_UP (1 << 6) +#define PRINT_F_UNSIGNED (1 << 7) +#define PRINT_F_TYPE_G (1 << 8) +#define PRINT_F_TYPE_E (1 << 9) + +/* Conversion flags. */ +#define PRINT_C_CHAR 1 +#define PRINT_C_SHORT 2 +#define PRINT_C_LONG 3 +#define PRINT_C_LLONG 4 +#define PRINT_C_LDOUBLE 5 +#define PRINT_C_SIZE 6 +#define PRINT_C_PTRDIFF 7 +#define PRINT_C_INTMAX 8 + +#ifndef MAX +#define MAX(x, y) ((x >= y) ? x : y) +#endif /* !defined(MAX) */ +#ifndef CHARTOINT +#define CHARTOINT(ch) (ch - '0') +#endif /* !defined(CHARTOINT) */ +#ifndef ISDIGIT +#define ISDIGIT(ch) ('0' <= (unsigned char)ch && (unsigned char)ch <= '9') +#endif /* !defined(ISDIGIT) */ +#ifndef ISNAN +#define ISNAN(x) (x != x) +#endif /* !defined(ISNAN) */ +#ifndef ISINF +#define ISINF(x) ((x < -1 || x > 1) && x + x == x) +#endif /* !defined(ISINF) */ + +#ifdef OUTCHAR +#undef OUTCHAR +#endif /* defined(OUTCHAR) */ +#define OUTCHAR(str, len, size, ch) \ +do { \ + if (len + 1 < size) \ + str[len] = ch; \ + (len)++; \ +} while (/* CONSTCOND */ 0) + +int rpl_snprintf(char *str, size_t size, const char *fmt, ...); +int rpl_vsnprintf(char *str, size_t size, const char *fmt, va_list ap); +int rpl_asprintf(char **buf, const char *fmt, ...); +int rpl_vasprintf(char **buf, const char *fmt, va_list ap); + +static void fmtstr(char *, size_t *, size_t, const char *, int, int, int); +static void fmtint(char *, size_t *, size_t, INTMAX_T, int, int, int, int); +static void fmtflt(char *, size_t *, size_t, LDOUBLE, int, int, int, int *); +static void printsep(char *, size_t *, size_t); +static int getnumsep(int); +static int getexponent(LDOUBLE); +static int convert(UINTMAX_T, char *, size_t, int, int); +static UINTMAX_T cast(LDOUBLE); +static UINTMAX_T myround(LDOUBLE); +static LDOUBLE mypow10(int); + +int +rpl_vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + LDOUBLE fvalue; + INTMAX_T value; + unsigned char cvalue; + const char *strvalue; + INTMAX_T *intmaxptr; + PTRDIFF_T *ptrdiffptr; + SSIZE_T *sizeptr; + LLONG *llongptr; + long int *longptr; + int *intptr; + short int *shortptr; + signed char *charptr; + size_t len = 0; + int overflow = 0; + int base = 0; + int cflags = 0; + int flags = 0; + int width = 0; + int precision = -1; + int state = PRINT_S_DEFAULT; + char ch = *format++; + + /* + * C99 says: "If `n' is zero, nothing is written, and `s' may be a null + * pointer." (7.19.6.5, 2) We're forgiving and allow a NULL pointer + * even if a size larger than zero was specified. At least NetBSD's + * snprintf(3) does the same, as well as other versions of this file. + * (Though some of these versions will write to a non-NULL buffer even + * if a size of zero was specified, which violates the standard.) + */ + if (str == NULL && size != 0) + size = 0; + + while (ch != '\0') + switch (state) { + case PRINT_S_DEFAULT: + if (ch == '%') + state = PRINT_S_FLAGS; + else + OUTCHAR(str, len, size, ch); + ch = *format++; + break; + case PRINT_S_FLAGS: + switch (ch) { + case '-': + flags |= PRINT_F_MINUS; + ch = *format++; + break; + case '+': + flags |= PRINT_F_PLUS; + ch = *format++; + break; + case ' ': + flags |= PRINT_F_SPACE; + ch = *format++; + break; + case '#': + flags |= PRINT_F_NUM; + ch = *format++; + break; + case '0': + flags |= PRINT_F_ZERO; + ch = *format++; + break; + case '\'': /* SUSv2 flag (not in C99). */ + flags |= PRINT_F_QUOTE; + ch = *format++; + break; + default: + state = PRINT_S_WIDTH; + break; + } + break; + case PRINT_S_WIDTH: + if (ISDIGIT(ch)) { + ch = CHARTOINT(ch); + if (width > (INT_MAX - ch) / 10) { + overflow = 1; + goto out; + } + width = 10 * width + ch; + ch = *format++; + } else if (ch == '*') { + /* + * C99 says: "A negative field width argument is + * taken as a `-' flag followed by a positive + * field width." (7.19.6.1, 5) + */ + if ((width = va_arg(args, int)) < 0) { + flags |= PRINT_F_MINUS; + width = -width; + } + ch = *format++; + state = PRINT_S_DOT; + } else + state = PRINT_S_DOT; + break; + case PRINT_S_DOT: + if (ch == '.') { + state = PRINT_S_PRECISION; + ch = *format++; + } else + state = PRINT_S_MOD; + break; + case PRINT_S_PRECISION: + if (precision == -1) + precision = 0; + if (ISDIGIT(ch)) { + ch = CHARTOINT(ch); + if (precision > (INT_MAX - ch) / 10) { + overflow = 1; + goto out; + } + precision = 10 * precision + ch; + ch = *format++; + } else if (ch == '*') { + /* + * C99 says: "A negative precision argument is + * taken as if the precision were omitted." + * (7.19.6.1, 5) + */ + if ((precision = va_arg(args, int)) < 0) + precision = -1; + ch = *format++; + state = PRINT_S_MOD; + } else + state = PRINT_S_MOD; + break; + case PRINT_S_MOD: + switch (ch) { + case 'h': + ch = *format++; + if (ch == 'h') { /* It's a char. */ + ch = *format++; + cflags = PRINT_C_CHAR; + } else + cflags = PRINT_C_SHORT; + break; + case 'l': + ch = *format++; + if (ch == 'l') { /* It's a long long. */ + ch = *format++; + cflags = PRINT_C_LLONG; + } else + cflags = PRINT_C_LONG; + break; + case 'L': + cflags = PRINT_C_LDOUBLE; + ch = *format++; + break; + case 'j': + cflags = PRINT_C_INTMAX; + ch = *format++; + break; + case 't': + cflags = PRINT_C_PTRDIFF; + ch = *format++; + break; + case 'z': + cflags = PRINT_C_SIZE; + ch = *format++; + break; + } + state = PRINT_S_CONV; + break; + case PRINT_S_CONV: + switch (ch) { + case 'd': + /* FALLTHROUGH */ + case 'i': + switch (cflags) { + case PRINT_C_CHAR: + value = (signed char)va_arg(args, int); + break; + case PRINT_C_SHORT: + value = (short int)va_arg(args, int); + break; + case PRINT_C_LONG: + value = va_arg(args, long int); + break; + case PRINT_C_LLONG: + value = va_arg(args, LLONG); + break; + case PRINT_C_SIZE: + value = va_arg(args, SSIZE_T); + break; + case PRINT_C_INTMAX: + value = va_arg(args, INTMAX_T); + break; + case PRINT_C_PTRDIFF: + value = va_arg(args, PTRDIFF_T); + break; + default: + value = va_arg(args, int); + break; + } + fmtint(str, &len, size, value, 10, width, + precision, flags); + break; + case 'X': + flags |= PRINT_F_UP; + /* FALLTHROUGH */ + case 'x': + base = 16; + /* FALLTHROUGH */ + case 'o': + if (base == 0) + base = 8; + /* FALLTHROUGH */ + case 'u': + if (base == 0) + base = 10; + flags |= PRINT_F_UNSIGNED; + switch (cflags) { + case PRINT_C_CHAR: + value = (unsigned char)va_arg(args, + unsigned int); + break; + case PRINT_C_SHORT: + value = (unsigned short int)va_arg(args, + unsigned int); + break; + case PRINT_C_LONG: + value = va_arg(args, unsigned long int); + break; + case PRINT_C_LLONG: + value = va_arg(args, ULLONG); + break; + case PRINT_C_SIZE: + value = va_arg(args, size_t); + break; + case PRINT_C_INTMAX: + value = va_arg(args, UINTMAX_T); + break; + case PRINT_C_PTRDIFF: + value = va_arg(args, UPTRDIFF_T); + break; + default: + value = va_arg(args, unsigned int); + break; + } + fmtint(str, &len, size, value, base, width, + precision, flags); + break; + case 'A': + /* Not yet supported, we'll use "%F". */ + /* FALLTHROUGH */ + case 'E': + if (ch == 'E') + flags |= PRINT_F_TYPE_E; + /* FALLTHROUGH */ + case 'G': + if (ch == 'G') + flags |= PRINT_F_TYPE_G; + /* FALLTHROUGH */ + case 'F': + flags |= PRINT_F_UP; + /* FALLTHROUGH */ + case 'a': + /* Not yet supported, we'll use "%f". */ + /* FALLTHROUGH */ + case 'e': + if (ch == 'e') + flags |= PRINT_F_TYPE_E; + /* FALLTHROUGH */ + case 'g': + if (ch == 'g') + flags |= PRINT_F_TYPE_G; + /* FALLTHROUGH */ + case 'f': + if (cflags == PRINT_C_LDOUBLE) + fvalue = va_arg(args, LDOUBLE); + else + fvalue = va_arg(args, double); + fmtflt(str, &len, size, fvalue, width, + precision, flags, &overflow); + if (overflow) + goto out; + break; + case 'c': + cvalue = va_arg(args, int); + OUTCHAR(str, len, size, cvalue); + break; + case 's': + strvalue = va_arg(args, char *); + fmtstr(str, &len, size, strvalue, width, + precision, flags); + break; + case 'p': + /* + * C99 says: "The value of the pointer is + * converted to a sequence of printing + * characters, in an implementation-defined + * manner." (C99: 7.19.6.1, 8) + */ + if ((strvalue = va_arg(args, void *)) == NULL) + /* + * We use the glibc format. BSD prints + * "0x0", SysV "0". + */ + fmtstr(str, &len, size, "(nil)", width, + -1, flags); + else { + /* + * We use the BSD/glibc format. SysV + * omits the "0x" prefix (which we emit + * using the PRINT_F_NUM flag). + */ + flags |= PRINT_F_NUM; + flags |= PRINT_F_UNSIGNED; + fmtint(str, &len, size, + (UINTPTR_T)strvalue, 16, width, + precision, flags); + } + break; + case 'n': + switch (cflags) { + case PRINT_C_CHAR: + charptr = va_arg(args, signed char *); + *charptr = len; + break; + case PRINT_C_SHORT: + shortptr = va_arg(args, short int *); + *shortptr = len; + break; + case PRINT_C_LONG: + longptr = va_arg(args, long int *); + *longptr = len; + break; + case PRINT_C_LLONG: + llongptr = va_arg(args, LLONG *); + *llongptr = len; + break; + case PRINT_C_SIZE: + /* + * C99 says that with the "z" length + * modifier, "a following `n' conversion + * specifier applies to a pointer to a + * signed integer type corresponding to + * size_t argument." (7.19.6.1, 7) + */ + sizeptr = va_arg(args, SSIZE_T *); + *sizeptr = len; + break; + case PRINT_C_INTMAX: + intmaxptr = va_arg(args, INTMAX_T *); + *intmaxptr = len; + break; + case PRINT_C_PTRDIFF: + ptrdiffptr = va_arg(args, PTRDIFF_T *); + *ptrdiffptr = len; + break; + default: + intptr = va_arg(args, int *); + *intptr = len; + break; + } + break; + case '%': /* Print a "%" character verbatim. */ + OUTCHAR(str, len, size, ch); + break; + default: /* Skip other characters. */ + break; + } + ch = *format++; + state = PRINT_S_DEFAULT; + base = cflags = flags = width = 0; + precision = -1; + break; + } +out: + if (len < size) + str[len] = '\0'; + else if (size > 0) + str[size - 1] = '\0'; + + if (overflow || len > INT_MAX) { + errno = EOVERFLOW; + return -1; + } + return (int)len; +} + +static void +fmtstr(char *str, size_t *len, size_t size, const char *value, int width, + int precision, int flags) +{ + int padlen, strln; /* Amount to pad. */ + int noprecision = (precision == -1); + + if (value == NULL) /* We're forgiving. */ + value = "(null)"; + + /* If a precision was specified, don't read the string past it. */ + for (strln = 0; value[strln] != '\0' && + (noprecision || strln < precision); strln++) + continue; + + if ((padlen = width - strln) < 0) + padlen = 0; + if (flags & PRINT_F_MINUS) /* Left justify. */ + padlen = -padlen; + + while (padlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen--; + } + while (*value != '\0' && (noprecision || precision-- > 0)) { + OUTCHAR(str, *len, size, *value); + value++; + } + while (padlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen++; + } +} + +static void +fmtint(char *str, size_t *len, size_t size, INTMAX_T value, int base, int width, + int precision, int flags) +{ + UINTMAX_T uvalue; + char iconvert[MAX_CONVERT_LENGTH]; + char sign = 0; + char hexprefix = 0; + int spadlen = 0; /* Amount to space pad. */ + int zpadlen = 0; /* Amount to zero pad. */ + int pos; + int separators = (flags & PRINT_F_QUOTE); + int noprecision = (precision == -1); + + if (flags & PRINT_F_UNSIGNED) + uvalue = value; + else { + uvalue = (value >= 0) ? value : -value; + if (value < 0) + sign = '-'; + else if (flags & PRINT_F_PLUS) /* Do a sign. */ + sign = '+'; + else if (flags & PRINT_F_SPACE) + sign = ' '; + } + + pos = convert(uvalue, iconvert, sizeof(iconvert), base, + flags & PRINT_F_UP); + + if (flags & PRINT_F_NUM && uvalue != 0) { + /* + * C99 says: "The result is converted to an `alternative form'. + * For `o' conversion, it increases the precision, if and only + * if necessary, to force the first digit of the result to be a + * zero (if the value and precision are both 0, a single 0 is + * printed). For `x' (or `X') conversion, a nonzero result has + * `0x' (or `0X') prefixed to it." (7.19.6.1, 6) + */ + switch (base) { + case 8: + if (precision <= pos) + precision = pos + 1; + break; + case 16: + hexprefix = (flags & PRINT_F_UP) ? 'X' : 'x'; + break; + } + } + + if (separators) /* Get the number of group separators we'll print. */ + separators = getnumsep(pos); + + zpadlen = precision - pos - separators; + spadlen = width /* Minimum field width. */ + - separators /* Number of separators. */ + - MAX(precision, pos) /* Number of integer digits. */ + - ((sign != 0) ? 1 : 0) /* Will we print a sign? */ + - ((hexprefix != 0) ? 2 : 0); /* Will we print a prefix? */ + + if (zpadlen < 0) + zpadlen = 0; + if (spadlen < 0) + spadlen = 0; + + /* + * C99 says: "If the `0' and `-' flags both appear, the `0' flag is + * ignored. For `d', `i', `o', `u', `x', and `X' conversions, if a + * precision is specified, the `0' flag is ignored." (7.19.6.1, 6) + */ + if (flags & PRINT_F_MINUS) /* Left justify. */ + spadlen = -spadlen; + else if (flags & PRINT_F_ZERO && noprecision) { + zpadlen += spadlen; + spadlen = 0; + } + while (spadlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + spadlen--; + } + if (sign != 0) /* Sign. */ + OUTCHAR(str, *len, size, sign); + if (hexprefix != 0) { /* A "0x" or "0X" prefix. */ + OUTCHAR(str, *len, size, '0'); + OUTCHAR(str, *len, size, hexprefix); + } + while (zpadlen > 0) { /* Leading zeros. */ + OUTCHAR(str, *len, size, '0'); + zpadlen--; + } + while (pos > 0) { /* The actual digits. */ + pos--; + OUTCHAR(str, *len, size, iconvert[pos]); + if (separators > 0 && pos > 0 && pos % 3 == 0) + printsep(str, len, size); + } + while (spadlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + spadlen++; + } +} + +static void +fmtflt(char *str, size_t *len, size_t size, LDOUBLE fvalue, int width, + int precision, int flags, int *overflow) +{ + LDOUBLE ufvalue; + UINTMAX_T intpart; + UINTMAX_T fracpart; + UINTMAX_T mask; + const char *infnan = NULL; + char iconvert[MAX_CONVERT_LENGTH]; + char fconvert[MAX_CONVERT_LENGTH]; + char econvert[5]; /* "e-300" (without nul-termination). */ + char esign = 0; + char sign = 0; + int leadfraczeros = 0; + int exponent = 0; + int emitpoint = 0; + int omitzeros = 0; + int omitcount = 0; + int padlen = 0; + int epos = 0; + int fpos = 0; + int ipos = 0; + int separators = (flags & PRINT_F_QUOTE); + int estyle = (flags & PRINT_F_TYPE_E); +#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT + struct lconv *lc = localeconv(); +#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ + + /* + * AIX' man page says the default is 0, but C99 and at least Solaris' + * and NetBSD's man pages say the default is 6, and sprintf(3) on AIX + * defaults to 6. + */ + if (precision == -1) + precision = 6; + + if (fvalue < 0.0) + sign = '-'; + else if (flags & PRINT_F_PLUS) /* Do a sign. */ + sign = '+'; + else if (flags & PRINT_F_SPACE) + sign = ' '; + + if (ISNAN(fvalue)) + infnan = (flags & PRINT_F_UP) ? "NAN" : "nan"; + else if (ISINF(fvalue)) + infnan = (flags & PRINT_F_UP) ? "INF" : "inf"; + + if (infnan != NULL) { + if (sign != 0) + iconvert[ipos++] = sign; + while (*infnan != '\0') + iconvert[ipos++] = *infnan++; + fmtstr(str, len, size, iconvert, width, ipos, flags); + return; + } + + /* "%e" (or "%E") or "%g" (or "%G") conversion. */ + if (flags & PRINT_F_TYPE_E || flags & PRINT_F_TYPE_G) { + if (flags & PRINT_F_TYPE_G) { + /* + * If the precision is zero, it is treated as one (cf. + * C99: 7.19.6.1, 8). + */ + if (precision == 0) + precision = 1; + /* + * For "%g" (and "%G") conversions, the precision + * specifies the number of significant digits, which + * includes the digits in the integer part. The + * conversion will or will not be using "e-style" (like + * "%e" or "%E" conversions) depending on the precision + * and on the exponent. However, the exponent can be + * affected by rounding the converted value, so we'll + * leave this decision for later. Until then, we'll + * assume that we're going to do an "e-style" conversion + * (in order to get the exponent calculated). For + * "e-style", the precision must be decremented by one. + */ + precision--; + /* + * For "%g" (and "%G") conversions, trailing zeros are + * removed from the fractional portion of the result + * unless the "#" flag was specified. + */ + if (!(flags & PRINT_F_NUM)) + omitzeros = 1; + } + exponent = getexponent(fvalue); + estyle = 1; + } + +again: + /* + * Sorry, we only support 9, 19, or 38 digits (that is, the number of + * digits of the 32-bit, the 64-bit, or the 128-bit UINTMAX_MAX value + * minus one) past the decimal point due to our conversion method. + */ + switch (sizeof(UINTMAX_T)) { + case 16: + if (precision > 38) + precision = 38; + break; + case 8: + if (precision > 19) + precision = 19; + break; + default: + if (precision > 9) + precision = 9; + break; + } + + ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue; + if (estyle) /* We want exactly one integer digit. */ + ufvalue /= mypow10(exponent); + + if ((intpart = cast(ufvalue)) == UINTMAX_MAX) { + *overflow = 1; + return; + } + + /* + * Factor of ten with the number of digits needed for the fractional + * part. For example, if the precision is 3, the mask will be 1000. + */ + mask = mypow10(precision); + /* + * We "cheat" by converting the fractional part to integer by + * multiplying by a factor of ten. + */ + if ((fracpart = myround(mask * (ufvalue - intpart))) >= mask) { + /* + * For example, ufvalue = 2.99962, intpart = 2, and mask = 1000 + * (because precision = 3). Now, myround(1000 * 0.99962) will + * return 1000. So, the integer part must be incremented by one + * and the fractional part must be set to zero. + */ + intpart++; + fracpart = 0; + if (estyle && intpart == 10) { + /* + * The value was rounded up to ten, but we only want one + * integer digit if using "e-style". So, the integer + * part must be set to one and the exponent must be + * incremented by one. + */ + intpart = 1; + exponent++; + } + } + + /* + * Now that we know the real exponent, we can check whether or not to + * use "e-style" for "%g" (and "%G") conversions. If we don't need + * "e-style", the precision must be adjusted and the integer and + * fractional parts must be recalculated from the original value. + * + * C99 says: "Let P equal the precision if nonzero, 6 if the precision + * is omitted, or 1 if the precision is zero. Then, if a conversion + * with style `E' would have an exponent of X: + * + * - if P > X >= -4, the conversion is with style `f' (or `F') and + * precision P - (X + 1). + * + * - otherwise, the conversion is with style `e' (or `E') and precision + * P - 1." (7.19.6.1, 8) + * + * Note that we had decremented the precision by one. + */ + if (flags & PRINT_F_TYPE_G && estyle && + precision + 1 > exponent && exponent >= -4) { + precision -= exponent; + estyle = 0; + goto again; + } + + if (estyle) { + if (exponent < 0) { + exponent = -exponent; + esign = '-'; + } else + esign = '+'; + + /* + * Convert the exponent. The sizeof(econvert) is 5. So, the + * econvert buffer can hold e.g. "e+999" and "e-999". We don't + * support an exponent which contains more than three digits. + * Therefore, the following stores are safe. + */ + epos = convert(exponent, econvert, 3, 10, 0); + /* + * C99 says: "The exponent always contains at least two digits, + * and only as many more digits as necessary to represent the + * exponent." (7.19.6.1, 8) + */ + if (epos == 1) + econvert[epos++] = '0'; + econvert[epos++] = esign; + econvert[epos++] = (flags & PRINT_F_UP) ? 'E' : 'e'; + } + + /* Convert the integer part and the fractional part. */ + ipos = convert(intpart, iconvert, sizeof(iconvert), 10, 0); + if (fracpart != 0) /* convert() would return 1 if fracpart == 0. */ + fpos = convert(fracpart, fconvert, sizeof(fconvert), 10, 0); + + leadfraczeros = precision - fpos; + + if (omitzeros) { + if (fpos > 0) /* Omit trailing fractional part zeros. */ + while (omitcount < fpos && fconvert[omitcount] == '0') + omitcount++; + else { /* The fractional part is zero, omit it completely. */ + omitcount = precision; + leadfraczeros = 0; + } + precision -= omitcount; + } + + /* + * Print a decimal point if either the fractional part is non-zero + * and/or the "#" flag was specified. + */ + if (precision > 0 || flags & PRINT_F_NUM) + emitpoint = 1; + if (separators) /* Get the number of group separators we'll print. */ + separators = getnumsep(ipos); + + padlen = width /* Minimum field width. */ + - ipos /* Number of integer digits. */ + - epos /* Number of exponent characters. */ + - precision /* Number of fractional digits. */ + - separators /* Number of group separators. */ + - (emitpoint ? 1 : 0) /* Will we print a decimal point? */ + - ((sign != 0) ? 1 : 0); /* Will we print a sign character? */ + + if (padlen < 0) + padlen = 0; + + /* + * C99 says: "If the `0' and `-' flags both appear, the `0' flag is + * ignored." (7.19.6.1, 6) + */ + if (flags & PRINT_F_MINUS) /* Left justifty. */ + padlen = -padlen; + else if (flags & PRINT_F_ZERO && padlen > 0) { + if (sign != 0) { /* Sign. */ + OUTCHAR(str, *len, size, sign); + sign = 0; + } + while (padlen > 0) { /* Leading zeros. */ + OUTCHAR(str, *len, size, '0'); + padlen--; + } + } + while (padlen > 0) { /* Leading spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen--; + } + if (sign != 0) /* Sign. */ + OUTCHAR(str, *len, size, sign); + while (ipos > 0) { /* Integer part. */ + ipos--; + OUTCHAR(str, *len, size, iconvert[ipos]); + if (separators > 0 && ipos > 0 && ipos % 3 == 0) + printsep(str, len, size); + } + if (emitpoint) { /* Decimal point. */ +#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT + if (lc->decimal_point != NULL && *lc->decimal_point != '\0') + OUTCHAR(str, *len, size, *lc->decimal_point); + else /* We'll always print some decimal point character. */ +#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */ + OUTCHAR(str, *len, size, '.'); + } + while (leadfraczeros > 0) { /* Leading fractional part zeros. */ + OUTCHAR(str, *len, size, '0'); + leadfraczeros--; + } + while (fpos > omitcount) { /* The remaining fractional part. */ + fpos--; + OUTCHAR(str, *len, size, fconvert[fpos]); + } + while (epos > 0) { /* Exponent. */ + epos--; + OUTCHAR(str, *len, size, econvert[epos]); + } + while (padlen < 0) { /* Trailing spaces. */ + OUTCHAR(str, *len, size, ' '); + padlen++; + } +} + +static void +printsep(char *str, size_t *len, size_t size) +{ +#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP + struct lconv *lc = localeconv(); + int i; + + if (lc->thousands_sep != NULL) + for (i = 0; lc->thousands_sep[i] != '\0'; i++) + OUTCHAR(str, *len, size, lc->thousands_sep[i]); + else +#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ + OUTCHAR(str, *len, size, ','); +} + +static int +getnumsep(int digits) +{ + int separators = (digits - ((digits % 3 == 0) ? 1 : 0)) / 3; +#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP + int strln; + struct lconv *lc = localeconv(); + + /* We support an arbitrary separator length (including zero). */ + if (lc->thousands_sep != NULL) { + for (strln = 0; lc->thousands_sep[strln] != '\0'; strln++) + continue; + separators *= strln; + } +#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */ + return separators; +} + +static int +getexponent(LDOUBLE value) +{ + LDOUBLE tmp = (value >= 0.0) ? value : -value; + int exponent = 0; + + /* + * We check for LDOUBLE_MAX_10_EXP >= exponent >= LDOUBLE_MIN_10_EXP in + * order to work around possible endless loops which could happen (at + * least) in the second loop (at least) if we're called with an infinite + * value. However, we checked for infinity before calling this function + * using our ISINF() macro, so this might be somewhat paranoid. + */ + while (tmp < 1.0 && tmp > 0.0 && --exponent >= LDOUBLE_MIN_10_EXP) + tmp *= 10; + while (tmp >= 10.0 && ++exponent <= LDOUBLE_MAX_10_EXP) + tmp /= 10; + + return exponent; +} + +static int +convert(UINTMAX_T value, char *buf, size_t size, int base, int caps) +{ + const char *digits = caps ? "0123456789ABCDEF" : "0123456789abcdef"; + size_t pos = 0; + + /* We return an unterminated buffer with the digits in reverse order. */ + do { + buf[pos++] = digits[value % base]; + value /= base; + } while (value != 0 && pos < size); + + return (int)pos; +} + +static UINTMAX_T +cast(LDOUBLE value) +{ + UINTMAX_T result; + + /* + * We check for ">=" and not for ">" because if UINTMAX_MAX cannot be + * represented exactly as an LDOUBLE value (but is less than LDBL_MAX), + * it may be increased to the nearest higher representable value for the + * comparison (cf. C99: 6.3.1.4, 2). It might then equal the LDOUBLE + * value although converting the latter to UINTMAX_T would overflow. + */ + if (value >= UINTMAX_MAX) + return UINTMAX_MAX; + + result = value; + /* + * At least on NetBSD/sparc64 3.0.2 and 4.99.30, casting long double to + * an integer type converts e.g. 1.9 to 2 instead of 1 (which violates + * the standard). Sigh. + */ + return (result <= value) ? result : result - 1; +} + +static UINTMAX_T +myround(LDOUBLE value) +{ + UINTMAX_T intpart = cast(value); + + return ((value -= intpart) < 0.5) ? intpart : intpart + 1; +} + +static LDOUBLE +mypow10(int exponent) +{ + LDOUBLE result = 1; + + while (exponent > 0) { + result *= 10; + exponent--; + } + while (exponent < 0) { + result /= 10; + exponent++; + } + return result; +} +#endif /* !HAVE_VSNPRINTF */ + +#if !HAVE_VASPRINTF +#if NEED_MYMEMCPY +void * +mymemcpy(void *dst, void *src, size_t len) +{ + const char *from = src; + char *to = dst; + + /* No need for optimization, we use this only to replace va_copy(3). */ + while (len-- > 0) + *to++ = *from++; + return dst; +} +#endif /* NEED_MYMEMCPY */ + +int +rpl_vasprintf(char **ret, const char *format, va_list ap) +{ + size_t size; + int len; + va_list aq; + + // cppcheck-suppress va_list_usedBeforeStarted + VA_COPY(aq, ap); + // cppcheck-suppress va_list_usedBeforeStarted + len = rpl_vsnprintf(NULL, 0, format, aq); + VA_END_COPY(aq); + if (len < 0 || (*ret = malloc(size = len + 1)) == NULL) + // cppcheck-suppress memleak + return -1; + // cppcheck-suppress memleak + return rpl_vsnprintf(*ret, size, format, ap); +} +#endif /* !HAVE_VASPRINTF */ + +#ifndef HAVE_SNPRINTF +#ifdef HAVE_STDARG_H +int +rpl_snprintf(char *str, size_t size, const char *format, ...) +#else +int +rpl_snprintf(va_alist) va_dcl +#endif /* HAVE_STDARG_H */ +{ +#ifndef HAVE_STDARG_H + char *str; + size_t size; + char *format; +#endif /* HAVE_STDARG_H */ + va_list ap; + int len; + + VA_START(ap, format); + VA_SHIFT(ap, str, char *); + VA_SHIFT(ap, size, size_t); + VA_SHIFT(ap, format, const char *); + len = rpl_vsnprintf(str, size, format, ap); + va_end(ap); + return len; +} +#endif /* !HAVE_SNPRINTF */ + +#ifndef HAVE_ASPRINTF +#ifdef HAVE_STDARG_H +int +rpl_asprintf(char **ret, const char *format, ...) +#else +int +rpl_asprintf(va_alist) va_dcl +#endif /* HAVE_STDARG_H */ +{ +#ifndef HAVE_STDARG_H + char **ret; + char *format; +#endif /* HAVE_STDARG_H */ + va_list ap; + int len; + + VA_START(ap, format); + VA_SHIFT(ap, ret, char **); + VA_SHIFT(ap, format, const char *); + len = rpl_vasprintf(ret, format, ap); + va_end(ap); + return len; +} +#endif /* !HAVE_ASPRINTF */ +#else /* Dummy declaration to avoid empty translation unit warnings. */ +int main(int argc, char **argv); +#endif /* !HAVE_SNPRINTF || !HAVE_VSNPRINTF || !HAVE_ASPRINTF || [...] */ + +#ifdef TEST_SNPRINTF +int +main(void) +{ + const char *float_fmt[] = { + /* "%E" and "%e" formats. */ +#if defined(HAVE_LONG_LONG_INT) && !defined(OS_BSD) && !defined(OS_IRIX) + "%.16e", + "%22.16e", + "%022.16e", + "%-22.16e", + "%#+'022.16e", +#endif /* HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX */ + "foo|%#+0123.9E|bar", + "%-123.9e", + "%123.9e", + "%+23.9e", + "%+05.8e", + "%-05.8e", + "%05.8e", + "%+5.8e", + "%-5.8e", + "% 5.8e", + "%5.8e", + "%+4.9e", +#ifndef OS_LINUX /* glibc sometimes gets these wrong. */ + "%+#010.0e", + "%#10.1e", + "%10.5e", + "% 10.5e", + "%5.0e", + "%5.e", + "%#5.0e", + "%#5.e", + "%3.2e", + "%3.1e", + "%-1.5e", + "%1.5e", + "%01.3e", + "%1.e", + "%.1e", + "%#.0e", + "%+.0e", + "% .0e", + "%.0e", + "%#.e", + "%+.e", + "% .e", + "%.e", + "%4e", + "%e", + "%E", +#endif /* !OS_LINUX */ + /* "%F" and "%f" formats. */ +#if !defined(OS_BSD) && !defined(OS_IRIX) + "% '022f", + "%+'022f", + "%-'22f", + "%'22f", +#ifdef HAVE_LONG_LONG_INT + "%.16f", + "%22.16f", + "%022.16f", + "%-22.16f", + "%#+'022.16f", +#endif /* HAVE_LONG_LONG_INT */ +#endif /* !OS_BSD && !OS_IRIX */ + "foo|%#+0123.9F|bar", + "%-123.9f", + "%123.9f", + "%+23.9f", + "%+#010.0f", + "%#10.1f", + "%10.5f", + "% 10.5f", + "%+05.8f", + "%-05.8f", + "%05.8f", + "%+5.8f", + "%-5.8f", + "% 5.8f", + "%5.8f", + "%5.0f", + "%5.f", + "%#5.0f", + "%#5.f", + "%+4.9f", + "%3.2f", + "%3.1f", + "%-1.5f", + "%1.5f", + "%01.3f", + "%1.f", + "%.1f", + "%#.0f", + "%+.0f", + "% .0f", + "%.0f", + "%#.f", + "%+.f", + "% .f", + "%.f", + "%4f", + "%f", + "%F", + /* "%G" and "%g" formats. */ +#if !defined(OS_BSD) && !defined(OS_IRIX) && !defined(OS_LINUX) + "% '022g", + "%+'022g", + "%-'22g", + "%'22g", +#ifdef HAVE_LONG_LONG_INT + "%.16g", + "%22.16g", + "%022.16g", + "%-22.16g", + "%#+'022.16g", +#endif /* HAVE_LONG_LONG_INT */ +#endif /* !OS_BSD && !OS_IRIX && !OS_LINUX */ + "foo|%#+0123.9G|bar", + "%-123.9g", + "%123.9g", + "%+23.9g", + "%+05.8g", + "%-05.8g", + "%05.8g", + "%+5.8g", + "%-5.8g", + "% 5.8g", + "%5.8g", + "%+4.9g", +#ifndef OS_LINUX /* glibc sometimes gets these wrong. */ + "%+#010.0g", + "%#10.1g", + "%10.5g", + "% 10.5g", + "%5.0g", + "%5.g", + "%#5.0g", + "%#5.g", + "%3.2g", + "%3.1g", + "%-1.5g", + "%1.5g", + "%01.3g", + "%1.g", + "%.1g", + "%#.0g", + "%+.0g", + "% .0g", + "%.0g", + "%#.g", + "%+.g", + "% .g", + "%.g", + "%4g", + "%g", + "%G", +#endif /* !OS_LINUX */ + NULL + }; + double float_val[] = { + -4.136, + -134.52, + -5.04030201, + -3410.01234, + -999999.999999, + -913450.29876, + -913450.2, + -91345.2, + -9134.2, + -913.2, + -91.2, + -9.2, + -9.9, + 4.136, + 134.52, + 5.04030201, + 3410.01234, + 999999.999999, + 913450.29876, + 913450.2, + 91345.2, + 9134.2, + 913.2, + 91.2, + 9.2, + 9.9, + 9.96, + 9.996, + 9.9996, + 9.99996, + 9.999996, + 9.9999996, + 9.99999996, + 0.99999996, + 0.99999999, + 0.09999999, + 0.00999999, + 0.00099999, + 0.00009999, + 0.00000999, + 0.00000099, + 0.00000009, + 0.00000001, + 0.0000001, + 0.000001, + 0.00001, + 0.0001, + 0.001, + 0.01, + 0.1, + 1.0, + 1.5, + -1.5, + -1.0, + -0.1, +#ifndef OS_BSD /* BSD sometimes gets these wrong. */ +#ifdef INFINITY + INFINITY, + -INFINITY, +#endif /* defined(INFINITY) */ +#ifdef NAN + NAN, +#endif /* defined(NAN) */ +#endif /* !OS_BSD */ + 0 + }; + const char *long_fmt[] = { + "foo|%0123ld|bar", +#if !OS_IRIX + "% '0123ld", + "%+'0123ld", + "%-'123ld", + "%'123ld", +#endif /* !OS_IRiX */ + "%123.9ld", + "% 123.9ld", + "%+123.9ld", + "%-123.9ld", + "%0123ld", + "% 0123ld", + "%+0123ld", + "%-0123ld", + "%10.5ld", + "% 10.5ld", + "%+10.5ld", + "%-10.5ld", + "%010ld", + "% 010ld", + "%+010ld", + "%-010ld", + "%4.2ld", + "% 4.2ld", + "%+4.2ld", + "%-4.2ld", + "%04ld", + "% 04ld", + "%+04ld", + "%-04ld", + "%5.5ld", + "%+22.33ld", + "%01.3ld", + "%1.5ld", + "%-1.5ld", + "%44ld", + "%4ld", + "%4.0ld", + "%4.ld", + "%.44ld", + "%.4ld", + "%.0ld", + "%.ld", + "%ld", + NULL + }; + long int long_val[] = { +#ifdef LONG_MAX + LONG_MAX, +#endif /* LONG_MAX */ +#ifdef LONG_MIN + LONG_MIN, +#endif /* LONG_MIN */ + -91340, + 91340, + 341, + 134, + 0203, + -1, + 1, + 0 + }; + const char *ulong_fmt[] = { + /* "%u" formats. */ + "foo|%0123lu|bar", +#ifdef OS_IRIX + "% '0123lu", + "%+'0123lu", + "%-'123lu", + "%'123lu", +#endif /* !OS_IRiX */ + "%123.9lu", + "% 123.9lu", + "%+123.9lu", + "%-123.9lu", + "%0123lu", + "% 0123lu", + "%+0123lu", + "%-0123lu", + "%5.5lu", + "%+22.33lu", + "%01.3lu", + "%1.5lu", + "%-1.5lu", + "%44lu", + "%lu", + /* "%o" formats. */ + "foo|%#0123lo|bar", + "%#123.9lo", + "%# 123.9lo", + "%#+123.9lo", + "%#-123.9lo", + "%#0123lo", + "%# 0123lo", + "%#+0123lo", + "%#-0123lo", + "%#5.5lo", + "%#+22.33lo", + "%#01.3lo", + "%#1.5lo", + "%#-1.5lo", + "%#44lo", + "%#lo", + "%123.9lo", + "% 123.9lo", + "%+123.9lo", + "%-123.9lo", + "%0123lo", + "% 0123lo", + "%+0123lo", + "%-0123lo", + "%5.5lo", + "%+22.33lo", + "%01.3lo", + "%1.5lo", + "%-1.5lo", + "%44lo", + "%lo", + /* "%X" and "%x" formats. */ + "foo|%#0123lX|bar", + "%#123.9lx", + "%# 123.9lx", + "%#+123.9lx", + "%#-123.9lx", + "%#0123lx", + "%# 0123lx", + "%#+0123lx", + "%#-0123lx", + "%#5.5lx", + "%#+22.33lx", + "%#01.3lx", + "%#1.5lx", + "%#-1.5lx", + "%#44lx", + "%#lx", + "%#lX", + "%123.9lx", + "% 123.9lx", + "%+123.9lx", + "%-123.9lx", + "%0123lx", + "% 0123lx", + "%+0123lx", + "%-0123lx", + "%5.5lx", + "%+22.33lx", + "%01.3lx", + "%1.5lx", + "%-1.5lx", + "%44lx", + "%lx", + "%lX", + NULL + }; + unsigned long int ulong_val[] = { +#ifdef ULONG_MAX + ULONG_MAX, +#endif /* ULONG_MAX */ + 91340, + 341, + 134, + 0203, + 1, + 0 + }; + const char *llong_fmt[] = { + "foo|%0123lld|bar", + "%123.9lld", + "% 123.9lld", + "%+123.9lld", + "%-123.9lld", + "%0123lld", + "% 0123lld", + "%+0123lld", + "%-0123lld", + "%5.5lld", + "%+22.33lld", + "%01.3lld", + "%1.5lld", + "%-1.5lld", + "%44lld", + "%lld", + NULL + }; + LLONG llong_val[] = { +#ifdef LLONG_MAX + LLONG_MAX, +#endif /* LLONG_MAX */ +#ifdef LLONG_MIN + LLONG_MIN, +#endif /* LLONG_MIN */ + -91340, + 91340, + 341, + 134, + 0203, + -1, + 1, + 0 + }; + const char *string_fmt[] = { + "foo|%10.10s|bar", + "%-10.10s", + "%10.10s", + "%10.5s", + "%5.10s", + "%10.1s", + "%1.10s", + "%10.0s", + "%0.10s", + "%-42.5s", + "%2.s", + "%.10s", + "%.1s", + "%.0s", + "%.s", + "%4s", + "%s", + NULL + }; + const char *string_val[] = { + "Hello", + "Hello, world!", + "Sound check: One, two, three.", + "This string is a little longer than the other strings.", + "1", + "", + NULL + }; +#if !OS_SYSV /* SysV uses a different format than we do. */ + const char *pointer_fmt[] = { + "foo|%p|bar", + "%42p", + "%p", + NULL + }; + const char *pointer_val[] = { + *pointer_fmt, + *string_fmt, + *string_val, + NULL + }; +#endif /* !OS_SYSV */ + char buf1[1024], buf2[1024]; + double value, digits = 9.123456789012345678901234567890123456789; + int i, j, r1, r2, failed = 0, num = 0; + +/* + * Use -DTEST_NILS in order to also test the conversion of nil values. Might + * segfault on systems which don't support converting a NULL pointer with "%s" + * and lets some test cases fail against BSD and glibc due to bugs in their + * implementations. + */ +#ifndef TEST_NILS +#define TEST_NILS 0 +#elif TEST_NILS +#undef TEST_NILS +#define TEST_NILS 1 +#endif /* !defined(TEST_NILS) */ +#ifdef TEST +#undef TEST +#endif /* defined(TEST) */ +#define TEST(fmt, val) \ +do { \ + for (i = 0; fmt[i] != NULL; i++) \ + for (j = 0; j == 0 || val[j - TEST_NILS] != 0; j++) { \ + r1 = sprintf(buf1, fmt[i], val[j]); \ + r2 = snprintf(buf2, sizeof(buf2), fmt[i], val[j]); \ + if (strcmp(buf1, buf2) != 0 || r1 != r2) { \ + (void)printf("Results don't match, " \ + "format string: %s\n" \ + "\t sprintf(3): [%s] (%d)\n" \ + "\tsnprintf(3): [%s] (%d)\n", \ + fmt[i], buf1, r1, buf2, r2); \ + failed++; \ + } \ + num++; \ + } \ +} while (/* CONSTCOND */ 0) + +#ifdef HAVE_LOCALE_H + (void)setlocale(LC_ALL, ""); +#endif /* HAVE_LOCALE_H */ + + (void)puts("Testing our snprintf(3) against your system's sprintf(3)."); + TEST(float_fmt, float_val); + TEST(long_fmt, long_val); + TEST(ulong_fmt, ulong_val); + TEST(llong_fmt, llong_val); + TEST(string_fmt, string_val); +#ifndef OS_SYSV /* SysV uses a different format than we do. */ + TEST(pointer_fmt, pointer_val); +#endif /* !OS_SYSV */ + (void)printf("Result: %d out of %d tests failed.\n", failed, num); + + (void)fputs("Checking how many digits we support: ", stdout); + for (i = 0; i < 100; i++) { + value = pow(10, i) * digits; + (void)sprintf(buf1, "%.1f", value); + (void)snprintf(buf2, sizeof(buf2), "%.1f", value); + if (strcmp(buf1, buf2) != 0) { + (void)printf("apparently %d.\n", i); + break; + } + } + return (failed == 0) ? 0 : 1; +} +#endif /* TEST_SNPRINTF */ + +/* vim: set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */ From fb21ce30fe261ef120c6954a43d5eb53ed096f73 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 21:04:15 +0100 Subject: [PATCH 09/20] Improved documentation of dlite.Instance --- bindings/python/dlite-entity-python.i | 45 +++++++++++++++++++-------- bindings/python/dlite-entity.i | 34 ++++++++++---------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/bindings/python/dlite-entity-python.i b/bindings/python/dlite-entity-python.i index c65c9ede2..6700e1ef4 100644 --- a/bindings/python/dlite-entity-python.i +++ b/bindings/python/dlite-entity-python.i @@ -376,19 +376,38 @@ def get_instance( f'"{self.uri if self.uri else self.meta.uri}"' ) - meta = property(get_meta, doc="Reference to the metadata of this instance.") - dimensions = property( - lambda self: dict((d.name, int(v)) - for d, v in zip(self.meta['dimensions'], - self.get_dimensions())), - doc='Dictionary with dimensions name-value pairs.') - properties = property(lambda self: - {p.name: self[p.name] for p in self.meta['properties']}, - doc='Dictionary with property name-value pairs.') - is_data = property(_is_data, doc='Whether this is a data instance.') - is_meta = property(_is_meta, doc='Whether this is a metadata instance.') - is_metameta = property(_is_metameta, - doc='Whether this is a meta-metadata instance.') + @property + def meta(self): + """Reference to the metadata of this instance.""" + return self.get_meta() + + @property + def dimensions(self): + """Dictionary with dimensions name-value pairs.""" + return dict( + (d.name, int(v)) + for d, v in zip(self.meta['dimensions'], self.get_dimensions()) + ) + + @property + def properties(self): + """Dictionary with property name-value pairs.""" + return {p.name: self[p.name] for p in self.meta['properties']} + + @property + def is_data(self): + """Whether this is a data instance.""" + return self._is_data() + + @property + def is_meta(self): + """Whether this is a metadata instance.""" + return self._is_meta() + + @property + def is_metameta(self): + """Whether this is a meta-metadata instance.""" + return self._is_metameta() @classmethod def from_metaid(cls, metaid, dimensions, id=None): diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index f1d8e731c..0e5a0efd9 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -244,24 +244,22 @@ struct _Triple { * Instance * -------- */ %feature("docstring", "\ -Returns a new instance. - -Instance(metaid=None, dims=None, id=None, url=None, storage=None, driver=None, - location=None, options=None, dimensions=None, properties=None, - description=None) - - Is called from one of the following class methods defined in dlite.py: - - - from_metaid(cls, metaid, dimensions, id=None) - - from_url(cls, url, metaid=None) - - from_storage(cls, storage, id=None, metaid=None) - - from_location(cls, driver, location, options=None, id=None) - - from_json(cls, jsoninput, id=None, metaid=None) - - from_bson(cls, bsoninput) - - from_dict(cls, d, id=None, single=None, check_storages=True) - - create_metadata(cls, uri, dimensions, properties, description) - - For details, see the documentation for the class methods. +Represents a DLite instance. + +This is the most central class in DLite. It has a complex `__init__()` +method and is intended to be instantiated with one of the following class +methods: + + - from_metaid(cls, metaid, dimensions, id=None) + - from_url(cls, url, metaid=None) + - from_storage(cls, storage, id=None, metaid=None) + - from_location(cls, driver, location, options=None, id=None) + - from_json(cls, jsoninput, id=None, metaid=None) + - from_bson(cls, bsoninput) + - from_dict(cls, d, id=None, single=None, check_storages=True) + - create_metadata(cls, uri, dimensions, properties, description) + +For details, see the documentation of the individual class methods. ") _DLiteInstance; %apply(int *IN_ARRAY1, int DIM1) {(int *dims, int ndims)}; From 693136292f3799b608343601f143c936d17ab10b Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 21:18:11 +0100 Subject: [PATCH 10/20] Further improved documentation. --- bindings/python/dlite-entity-python.i | 31 ++++++++++++++++++++------- bindings/python/dlite-entity.i | 20 ++++++++++------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/bindings/python/dlite-entity-python.i b/bindings/python/dlite-entity-python.i index 6700e1ef4..3cf4a2264 100644 --- a/bindings/python/dlite-entity-python.i +++ b/bindings/python/dlite-entity-python.i @@ -86,11 +86,11 @@ class Metadata(Instance): return inst # For convenience. Easier to use than self.properties["properties"] - props = property( - fget=lambda self: {p.name: p for p in self.properties["properties"]}, - doc="A dict mapping property name to the `Property` object for the " - "described metadata.", - ) + @property + def props(self): + """A dict mapping property name to the `Property` object for the + described metadata.""" + return {p.name: p for p in self.properties["properties"]} def getprop(self, name): """Returns the metadata property object with the given name.""" @@ -248,9 +248,24 @@ def get_instance( '' if self.unit is None else self.unit, '' if self.description is None else self.description) - type = property(get_type, doc='Type name.') - dtype = property(get_dtype, doc='Type number.') - shape = property(get_shape, set_shape, doc='Array of dimension indices.') + @property + def type(self): + """Type name.""" + return self.get_type() + + @property + def dtype(self): + """Type number.""" + return self.get_dtype() + + @property + def shape(self): + """Array of dimension indices.""" + return self.get_shape() + + @shape.setter + def shape(self, value): + return self.set_shape(value) # Too be removed... def _get_dims_depr(self): diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index 0e5a0efd9..b5dc7447c 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -250,17 +250,21 @@ This is the most central class in DLite. It has a complex `__init__()` method and is intended to be instantiated with one of the following class methods: - - from_metaid(cls, metaid, dimensions, id=None) - - from_url(cls, url, metaid=None) - - from_storage(cls, storage, id=None, metaid=None) - - from_location(cls, driver, location, options=None, id=None) - - from_json(cls, jsoninput, id=None, metaid=None) - - from_bson(cls, bsoninput) - - from_dict(cls, d, id=None, single=None, check_storages=True) - - create_metadata(cls, uri, dimensions, properties, description) +- from_metaid(cls, metaid, dimensions, id=None) +- from_url(cls, url, metaid=None) +- from_storage(cls, storage, id=None, metaid=None) +- from_location(cls, driver, location, options=None, id=None) +- from_json(cls, jsoninput, id=None, metaid=None) +- from_bson(cls, bsoninput) +- from_dict(cls, d, id=None, single=None, check_storages=True) +- create_metadata(cls, uri, dimensions, properties, description) For details, see the documentation of the individual class methods. +Attributes: + uuid: The UUID of the instance. + uri: The URI of the instance. + ") _DLiteInstance; %apply(int *IN_ARRAY1, int DIM1) {(int *dims, int ndims)}; %apply(struct _DLiteDimension *dimensions, int ndimensions) { From 75c108c7f988af5c55bebd885272553ce842428c Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 22:40:08 +0100 Subject: [PATCH 11/20] Documentation cleanup --- bindings/python/dlite-collection-python.i | 19 ++++--- bindings/python/dlite-entity.i | 67 ++++++++++++++++++----- bindings/python/dlite-misc-python.i | 4 ++ bindings/python/dlite-python.i | 1 + doc/CMakeLists.txt | 52 ++++++++++-------- doc/getting_started/tutorial.md | 29 +++++++++- 6 files changed, 127 insertions(+), 45 deletions(-) diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i index 2a28c9e55..91bb23150 100644 --- a/bindings/python/dlite-collection-python.i +++ b/bindings/python/dlite-collection-python.i @@ -56,19 +56,24 @@ class Collection(Instance): to it. Likewise, the len() of a collection will return the number of instances. - Use the get_relations() method (or the convenience methods - get_subjects(), get_predicates() and get_objects()) to iterate + Use the `get_relations()` method (or the convenience methods + `get_subjects()`, `get_predicates()` and `get_objects()`) to iterate over relations. The number of relations is available via the `nrelations` property. - Relations are (s, p, o, d=None)-triples with an optional fourth field + Relations are `(s, p, o, d=None)`-triples with an optional fourth field `d`, specifying the datatype of the object. The datatype may have the following values: - - None: object is an IRI. - - Starts with '@': object is a language-tagged plain literal. - The language identifier follows the '@'-sign. - - Otherwise: object is a literal with datatype `d`. + - None: object is an IRI. + - Starts with '@': object is a language-tagged plain literal. + The language identifier follows the '@'-sign. + - Otherwise: object is a literal with datatype `d`. + + Arguments: + id: URI or UUID of the new instance. The default is to create a + collection with no URI and random (version 4) UUID. + """ def __new__(cls, id=None): """Creates an empty collection.""" diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index b5dc7447c..879f63da6 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -93,10 +93,23 @@ char** dlite_swig_istore_get_uuids() %} - /* --------- * Dimension * --------- */ +%feature("docstring", "\ +A dimension represented by its name and description. +Metadata can define a set of common dimension for its properties that +are referred to by the property shapes. + +Arguments: + name: Name that of dimension. + description: Description of the dimension. + +Attributes: + name: Dimension name. + description: Dimension description. + +") _DLiteDimension; %rename(Dimension) _DLiteDimension; struct _DLiteDimension { char *name; @@ -123,7 +136,31 @@ struct _DLiteDimension { * Property * -------- */ %feature("docstring", "\ -Creates a new property with the provided attributes. +Represents a property. +All metadata must have one or more properties that defines the instances +of the metadata. + +Arguments: + name: Name of the property. + type: Property type. Ex: 'int', 'blob14', 'float64', 'ref'... + ref: Optional. URL to metadata. Only needed for `type='ref'`. + shape: Optional. Specifies the dimensionality of property. If `shape` + is not given, the property is a scalar (dimensionality zero). + It should be an sequence of dimension names. + unit: Optional. The unit for properties with a unit. The unit should + be a valid unit label defined by EMMO or a custom ontology. + description: Optional: A human description of the property. + +Attributes: + name: Property name. + size: Number of bytes needed to represent a single instance of `type` + in memory. + ref: Value of the `ref` argument. + ndims: Number of dimensions of the property. A scalar has `ndims=0`. + unit: The unit of the property. + description: Property description. + dims: Deprecated alias for shape. + ") _DLiteProperty; %rename(Property) _DLiteProperty; struct _DLiteProperty { @@ -193,18 +230,20 @@ struct _DLiteProperty { * Relation * -------- */ %feature("docstring", "\ -Relations in DLite corresponds to RDF-triples, but are internally 4 fields: - - s: subject - - p: predicate - - o: object - - d: datatype - -The datatype is the datatype for literal objects. It may have three forms: - - None: object is an IRI (rdfs:Resource). - - Starts with '@': object is a language-tagged plain literal - (rdf:langString). The language identifier follows the '@'-sign. - Ex: '@en' for english. - - Otherwise: object is a literal with datatype `d`. Ex: 'xsd:int'. +A DLite relation representing an RDF triple. + +Arguments: + s: Subject IRI. + p: Predicate IRI. + o: Either an IRI for non-literal objects or the literal value for + literal objects. + d: The datatype IRI for literal objects. It may have three forms: + + - None: object is an IRI (rdfs:Resource). + - Starts with '@': object is a language-tagged plain literal + (rdf:langString). The language identifier follows the '@'-sign. + Ex: '@en' for english. + - Otherwise: object is a literal with datatype `d`. Ex: 'xsd:int'. As an internal implementation detail, relations also have an `id` field. It may change in the future, so please don't rely on it. diff --git a/bindings/python/dlite-misc-python.i b/bindings/python/dlite-misc-python.i index 544989b0c..5de269502 100644 --- a/bindings/python/dlite-misc-python.i +++ b/bindings/python/dlite-misc-python.i @@ -32,6 +32,9 @@ class errctl(): - "": Write errors to stderr (default). - "": Write errors to stdout. + Attributes: + filename: Filename to redirect errors to. + """ def __init__(self, hide=(), show=(), filename=""): allcodes = [-i for i in range(1, _dlite._get_number_of_errors())] @@ -79,6 +82,7 @@ class errctl(): silent = errctl(filename="None") +"""Context manager for a silent code block. Same as `errctl(filename="None")`.""" # A set for keeping track of already issued deprecation warnings _deprecation_warning_record = set() diff --git a/bindings/python/dlite-python.i b/bindings/python/dlite-python.i index f0fa88e32..38870a21c 100644 --- a/bindings/python/dlite-python.i +++ b/bindings/python/dlite-python.i @@ -1477,6 +1477,7 @@ def instance_cast(inst, newtype=None): # Deprecated exceptions class DLiteSearchError(_dlite.DLiteLookupError): + """Deprecated. Has been renamed to DLiteLookupError.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) deprecation_warning( diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index f93ec7c21..5be55188b 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -33,9 +33,10 @@ set(DOXYGEN_DOXYFILE_OUT "${BINARY_BUILD_DIR}/Doxyfile") set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_XML_DIR}/index.xml) configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in" - "${BINARY_BUILD_DIR}/conf.py" - @ONLY) + "${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in" + "${BINARY_BUILD_DIR}/conf.py" + @ONLY +) file(COPY "${CMAKE_SOURCE_DIR}/LICENSE" @@ -48,24 +49,28 @@ file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/contributors_guide" "${CMAKE_CURRENT_SOURCE_DIR}/getting_started" "${CMAKE_CURRENT_SOURCE_DIR}/user_guide" - DESTINATION - "${BINARY_BUILD_DIR}" + DESTINATION "${BINARY_BUILD_DIR}" ) configure_file( - ${DOXYGEN_DOXYFILE_IN} - ${DOXYGEN_DOXYFILE_OUT} - @ONLY) + ${DOXYGEN_DOXYFILE_IN} + ${DOXYGEN_DOXYFILE_OUT} + @ONLY +) # Doxygen command -add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} - DEPENDS ${DLITE_PUBLIC_HEADERS} - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} - MAIN_DEPENDENCY ${DOXYFILE_OUT} - WORKING_DIRECTORY ${BINARY_BUILD_DIR} - COMMENT "Generating docs" - VERBATIM) +add_custom_command( + OUTPUT ${DOXYGEN_INDEX_FILE} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + MAIN_DEPENDENCY ${DOXYGEN_DOXYFILE_OUT} + DEPENDS + ${DLITE_PUBLIC_HEADERS} + dlitepy + WORKING_DIRECTORY ${BINARY_BUILD_DIR} + COMMENT "Generating docs" + VERBATIM +) # Named constom target add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE}) @@ -74,15 +79,16 @@ add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE}) add_custom_command(OUTPUT ${SPHINX_INDEX_FILE} COMMAND ${SPHINX_EXECUTABLE} - -q -b html - -c "${BINARY_BUILD_DIR}" - -d "${SPHINX_CACHE_DIR}" - ${BINARY_BUILD_DIR} - "${SPHINX_HTML_DIR}" + -q -b html + -c "${BINARY_BUILD_DIR}" + -d "${SPHINX_CACHE_DIR}" + ${BINARY_BUILD_DIR} + "${SPHINX_HTML_DIR}" DEPENDS - ${DOXYGEN_INDEX_FILE} - ${BINARY_BUILD_DIR}/respirator.py + ${DOXYGEN_INDEX_FILE} + ${BINARY_BUILD_DIR}/respirator.py MAIN_DEPENDENCY ${BINARY_BUILD_DIR}/conf.py - COMMENT "Building HTML documentation with Sphinx") + COMMENT "Building HTML documentation with Sphinx" +) add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE}) diff --git a/doc/getting_started/tutorial.md b/doc/getting_started/tutorial.md index fe53bde32..2570c8029 100644 --- a/doc/getting_started/tutorial.md +++ b/doc/getting_started/tutorial.md @@ -190,7 +190,34 @@ Both dimensions and properties can also be provided as arrays using a `dict` wit "shape": ["N"], "description": "Maximum Power" }, - ... + { + "name": "impp", + "type": "float64", + "unit": "A", + "shape": ["N"], + "description": "Maximum power point current." + }, + { + "name": "isc", + "type": "float64", + "unit": "A", + "shape": ["N"], + "description": "Short circuit current." + }, + { + "name": "vmpp", + "type": "float64", + "unit": "V", + "shape": ["N"], + "description": "Maximum power point voltage." + }, + { + "name": "voc", + "type": "float64", + "unit": "V", + "shape": ["N"], + "description": "Open circuit voltage." + } ] } ``` From fe0a1af61c3f229645fab83d64170ee4491f22e9 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 14:59:50 +0100 Subject: [PATCH 12/20] Update bindings/python/dlite-collection-python.i Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- bindings/python/dlite-collection-python.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i index 91bb23150..e7e377299 100644 --- a/bindings/python/dlite-collection-python.i +++ b/bindings/python/dlite-collection-python.i @@ -67,7 +67,7 @@ class Collection(Instance): - None: object is an IRI. - Starts with '@': object is a language-tagged plain literal. - The language identifier follows the '@'-sign. + The language identifier follows the '@'-sign. e.g. `text@en` - Otherwise: object is a literal with datatype `d`. Arguments: From c62ccecafd76ed663ffa834ab6496140cf2a4a88 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:00:01 +0100 Subject: [PATCH 13/20] Update bindings/python/dlite-entity.i Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- bindings/python/dlite-entity.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index 879f63da6..a7686d14c 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -98,7 +98,7 @@ char** dlite_swig_istore_get_uuids() * --------- */ %feature("docstring", "\ A dimension represented by its name and description. -Metadata can define a set of common dimension for its properties that +Metadata can define a set of common dimensions for its properties that are referred to by the property shapes. Arguments: From 1830571b36479bfd4679f36e1968b977a722ef79 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:00:19 +0100 Subject: [PATCH 14/20] Update bindings/python/dlite-entity.i Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- bindings/python/dlite-entity.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index a7686d14c..cd455e84f 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -99,7 +99,7 @@ char** dlite_swig_istore_get_uuids() %feature("docstring", "\ A dimension represented by its name and description. Metadata can define a set of common dimensions for its properties that -are referred to by the property shapes. +are referred to by the shape of each property. Arguments: name: Name that of dimension. From 8dbccd614c61906763a0261491d001042f01b57d Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:00:35 +0100 Subject: [PATCH 15/20] Update bindings/python/dlite-entity.i Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- bindings/python/dlite-entity.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index cd455e84f..f4d77b6c8 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -102,8 +102,8 @@ Metadata can define a set of common dimensions for its properties that are referred to by the shape of each property. Arguments: - name: Name that of dimension. - description: Description of the dimension. + name: Dimension name. + description: Dimension description. Attributes: name: Dimension name. From 3bfc70318f9a97e857075d9833fed1ad024c93f2 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:01:41 +0100 Subject: [PATCH 16/20] Update bindings/python/dlite-entity.i Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- bindings/python/dlite-entity.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index f4d77b6c8..915a5d783 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -137,7 +137,7 @@ struct _DLiteDimension { * -------- */ %feature("docstring", "\ Represents a property. -All metadata must have one or more properties that defines the instances +All metadata must have one or more properties that define the instances of the metadata. Arguments: From c12f3117b17215ee533fc6dce936c0ac96d3dede Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:01:50 +0100 Subject: [PATCH 17/20] Update bindings/python/dlite-entity.i Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- bindings/python/dlite-entity.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index 915a5d783..73ffee937 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -141,7 +141,7 @@ All metadata must have one or more properties that define the instances of the metadata. Arguments: - name: Name of the property. + name: Property name. type: Property type. Ex: 'int', 'blob14', 'float64', 'ref'... ref: Optional. URL to metadata. Only needed for `type='ref'`. shape: Optional. Specifies the dimensionality of property. If `shape` From 95744d09f812928bd2876c1848188c9436e73dba Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:01:57 +0100 Subject: [PATCH 18/20] Update bindings/python/dlite-entity.i Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- bindings/python/dlite-entity.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/dlite-entity.i b/bindings/python/dlite-entity.i index 73ffee937..a01b06d51 100644 --- a/bindings/python/dlite-entity.i +++ b/bindings/python/dlite-entity.i @@ -146,7 +146,7 @@ Arguments: ref: Optional. URL to metadata. Only needed for `type='ref'`. shape: Optional. Specifies the dimensionality of property. If `shape` is not given, the property is a scalar (dimensionality zero). - It should be an sequence of dimension names. + It should be a sequence of dimension names. unit: Optional. The unit for properties with a unit. The unit should be a valid unit label defined by EMMO or a custom ontology. description: Optional: A human description of the property. From 530ee9f487fdeaf48b6e4bf448b9f2c35cd389da Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:02:21 +0100 Subject: [PATCH 19/20] Update doc/getting_started/tutorial.md Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- doc/getting_started/tutorial.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/getting_started/tutorial.md b/doc/getting_started/tutorial.md index 2570c8029..138250e53 100644 --- a/doc/getting_started/tutorial.md +++ b/doc/getting_started/tutorial.md @@ -21,7 +21,7 @@ Let us write an Entity representing our solar panel measurement. We start by creating a `json` file called `myEntity.json`. ### **Step 1**: Giving it a unique identifier -Firstly, we must provide a unique identifier for our Entity. +First, we must provide a unique identifier for our Entity. There are several ways of doing this: #### **1. Uniform Resource Identifier (URI)** From 5d1b22daa1d21d0d061ab6ab3fabda689e8396da Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Wed, 30 Oct 2024 15:48:51 +0100 Subject: [PATCH 20/20] Update bindings/python/dlite-collection-python.i --- bindings/python/dlite-collection-python.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i index e7e377299..debfbd26c 100644 --- a/bindings/python/dlite-collection-python.i +++ b/bindings/python/dlite-collection-python.i @@ -67,7 +67,7 @@ class Collection(Instance): - None: object is an IRI. - Starts with '@': object is a language-tagged plain literal. - The language identifier follows the '@'-sign. e.g. `text@en` + The language identifier follows the '@'-sign. e.g. `@en` - Otherwise: object is a literal with datatype `d`. Arguments: