diff --git a/HISTORY.txt b/HISTORY.txt index 7bf7690e..a99b7516 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,5 +1,6 @@ 7.1.0: - Inline TOML tables for things like requests[security]! + - Attempt to preserve comments in Pipfiles. 7.0.6: - NO_SPIN is now automatic when CI is set. - Additionally, vendor pip (a patched version) for doing advanced dependency resolution. diff --git a/pipenv/project.py b/pipenv/project.py index c176de0e..59837db4 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -7,7 +7,7 @@ import base64 import hashlib import pipfile -import toml +import contoml import delegator from requests.compat import OrderedDict @@ -174,7 +174,7 @@ class Project(object): @property def parsed_pipfile(self): with open(self.pipfile_location) as f: - return toml.load(f, _dict=OrderedDict) + return contoml.loads(f.read()) @property def _pipfile(self): @@ -294,16 +294,27 @@ class Project(object): if path is None: path = self.pipfile_location - for section in ('packages', 'dev-packages'): - for package in data[section]: - # Convert things to inline tables — fancy :) - if hasattr(data[section][package], 'keys'): - _data = data[section][package] - data[section][package] = toml._get_empty_inline_table(dict) - data[section][package].update(_data) - formatted_data = format_toml(toml.dumps(data, preserve=True)) + try: + formatted_data = contoml.dumps(data) + except RuntimeError: + import toml + for section in ('packages', 'dev-packages'): + for package in data[section]: + + # Convert things to inline tables — fancy :) + if hasattr(data[section][package], 'keys'): + _data = data[section][package] + data[section][package] = toml._get_empty_inline_table(dict) + data[section][package].update(_data) + + formatted_data = toml.dumps(data) + else: + pass + finally: + pass + with open(path, 'w') as f: f.write(formatted_data) diff --git a/pipenv/vendor/contoml/__init__.py b/pipenv/vendor/contoml/__init__.py new file mode 100755 index 00000000..9dba5e20 --- /dev/null +++ b/pipenv/vendor/contoml/__init__.py @@ -0,0 +1,48 @@ +from ._version import VERSION + +__version__ = VERSION + + +def loads(text): + """ + Parses TOML text into a dict-like object and returns it. + """ + from prettytoml.parser import parse_tokens + from prettytoml.lexer import tokenize as lexer + from .file import TOMLFile + + tokens = tuple(lexer(text, is_top_level=True)) + elements = parse_tokens(tokens) + return TOMLFile(elements) + + +def load(file_path): + """ + Parses a TOML file into a dict-like object and returns it. + """ + return loads(open(file_path).read()) + + +def dumps(value): + """ + Dumps a data structure to TOML source code. + + The given value must be either a dict of dict values, a dict, or a TOML file constructed by this module. + """ + + from contoml.file.file import TOMLFile + + if not isinstance(value, TOMLFile): + raise RuntimeError("Can only dump a TOMLFile instance loaded by load() or loads()") + + return value.dumps() + + +def dump(obj, file_path, prettify=False): + """ + Dumps a data structure to the filesystem as TOML. + + The given value must be either a dict of dict values, a dict, or a TOML file constructed by this module. + """ + with open(file_path, 'w') as fp: + fp.write(dumps(obj)) diff --git a/pipenv/vendor/contoml/_version.py b/pipenv/vendor/contoml/_version.py new file mode 100755 index 00000000..e0f15470 --- /dev/null +++ b/pipenv/vendor/contoml/_version.py @@ -0,0 +1 @@ +VERSION = 'master' diff --git a/pipenv/vendor/contoml/file/__init__.py b/pipenv/vendor/contoml/file/__init__.py new file mode 100755 index 00000000..1aba5121 --- /dev/null +++ b/pipenv/vendor/contoml/file/__init__.py @@ -0,0 +1,3 @@ + + +from .file import TOMLFile diff --git a/pipenv/vendor/contoml/file/array.py b/pipenv/vendor/contoml/file/array.py new file mode 100755 index 00000000..40e5acb3 --- /dev/null +++ b/pipenv/vendor/contoml/file/array.py @@ -0,0 +1,40 @@ +from prettytoml.elements.table import TableElement +from prettytoml.errors import InvalidValueError +from contoml.file.freshtable import FreshTable +from prettytoml import util + + +class ArrayOfTables(list): + + def __init__(self, toml_file, name, iterable=None): + if iterable: + list.__init__(self, iterable) + self._name = name + self._toml_file = toml_file + + def append(self, value): + if isinstance(value, dict): + table = FreshTable(parent=self, name=self._name, is_array=True) + table._append_to_parent() + index = len(self._toml_file[self._name]) - 1 + for key_seq, value in util.flatten_nested(value).items(): + # self._toml_file._setitem_with_key_seq((self._name, index) + key_seq, value) + self._toml_file._array_setitem_with_key_seq(self._name, index, key_seq, value) + # for k, v in value.items(): + # table[k] = v + else: + raise InvalidValueError('Can only append a dict to an array of tables') + + def __getitem__(self, item): + try: + return list.__getitem__(self, item) + except IndexError: + if item == len(self): + return FreshTable(parent=self, name=self._name, is_array=True) + else: + raise + + def append_fresh_table(self, fresh_table): + list.append(self, fresh_table) + if self._toml_file: + self._toml_file.append_fresh_table(fresh_table) diff --git a/pipenv/vendor/contoml/file/cascadedict.py b/pipenv/vendor/contoml/file/cascadedict.py new file mode 100755 index 00000000..4e97c060 --- /dev/null +++ b/pipenv/vendor/contoml/file/cascadedict.py @@ -0,0 +1,56 @@ +import operator +from functools import reduce +from contoml.file import raw + + +class CascadeDict: + """ + A dict-like object made up of one or more other dict-like objects where querying for an item cascade-gets + it from all the internal dicts in order of their listing, and setting an item sets it on the first dict listed. + """ + + def __init__(self, *internal_dicts): + assert internal_dicts, 'internal_dicts cannot be empty' + self._internal_dicts = tuple(internal_dicts) + + def cascaded_with(self, one_more_dict): + """ + Returns another instance with one more dict cascaded at the end. + """ + return CascadeDict(self._internal_dicts, one_more_dict,) + + def __getitem__(self, item): + for d in self._internal_dicts: + try: + return d[item] + except KeyError: + pass + raise KeyError + + def __setitem__(self, key, value): + self._internal_dicts[0][key] = value + + def keys(self): + return set(reduce(operator.or_, (set(d.keys()) for d in self._internal_dicts))) + + def items(self): + all_items = reduce(operator.add, (list(d.items()) for d in reversed(self._internal_dicts))) + unique_items = {k: v for k, v in all_items}.items() + return tuple(unique_items) + + def __contains__(self, item): + for d in self._internal_dicts: + if item in d: + return True + return False + + @property + def neutralized(self): + return {k: raw.to_raw(v) for k, v in self.items()} + + @property + def primitive_value(self): + return self.neutralized + + def __repr__(self): + return repr(self.primitive_value) diff --git a/pipenv/vendor/contoml/file/file.py b/pipenv/vendor/contoml/file/file.py new file mode 100755 index 00000000..16017b99 --- /dev/null +++ b/pipenv/vendor/contoml/file/file.py @@ -0,0 +1,293 @@ +from prettytoml.errors import NoArrayFoundError, DuplicateKeysError, DuplicateTablesError +from contoml.file import structurer, toplevels, raw +from contoml.file.array import ArrayOfTables +from contoml.file.freshtable import FreshTable +import prettytoml.elements.factory as element_factory +import prettytoml.util as util + + +class TOMLFile: + """ + A TOMLFile object that tries its best to prserve formatting and order of mappings of the input source. + + Raises InvalidTOMLFileError on invalid input elements. + + Raises DuplicateKeysError, DuplicateTableError when appropriate. + """ + + def __init__(self, _elements): + self._elements = [] + self._navigable = {} + self.append_elements(_elements) + + def __getitem__(self, item): + try: + value = self._navigable[item] + if isinstance(value, (list, tuple)): + return ArrayOfTables(toml_file=self, name=item, iterable=value) + else: + return value + except KeyError: + return FreshTable(parent=self, name=item, is_array=False) + + def get(self, item, default=None): + """This was not here for who knows why.""" + + if item not in self: + return default + else: + return self.__getitem__(item) + + def __contains__(self, item): + return item in self.keys() + + def _setitem_with_key_seq(self, key_seq, value): + """ + Sets a the value in the TOML file located by the given key sequence. + + Example: + self._setitem(('key1', 'key2', 'key3'), 'text_value') + is equivalent to doing + self['key1']['key2']['key3'] = 'text_value' + """ + table = self + key_so_far = tuple() + for key in key_seq[:-1]: + key_so_far += (key,) + self._make_sure_table_exists(key_so_far) + table = table[key] + table[key_seq[-1]] = value + + def _array_setitem_with_key_seq(self, array_name, index, key_seq, value): + """ + Sets a the array value in the TOML file located by the given key sequence. + + Example: + self._array_setitem(array_name, index, ('key1', 'key2', 'key3'), 'text_value') + is equivalent to doing + self.array(array_name)[index]['key1']['key2']['key3'] = 'text_value' + """ + table = self.array(array_name)[index] + key_so_far = tuple() + for key in key_seq[:-1]: + key_so_far += (key,) + new_table = self._array_make_sure_table_exists(array_name, index, key_so_far) + if new_table is not None: + table = new_table + else: + table = table[key] + table[key_seq[-1]] = value + + def _make_sure_table_exists(self, name_seq): + """ + Makes sure the table with the full name comprising of name_seq exists. + """ + t = self + for key in name_seq[:-1]: + t = t[key] + name = name_seq[-1] + if name not in t: + self.append_elements([element_factory.create_table_header_element(name_seq), + element_factory.create_table({})]) + + def _array_make_sure_table_exists(self, array_name, index, name_seq): + """ + Makes sure the table with the full name comprising of name_seq exists. + """ + t = self[array_name][index] + for key in name_seq[:-1]: + t = t[key] + name = name_seq[-1] + if name not in t: + new_table = element_factory.create_table({}) + self.append_elements([element_factory.create_table_header_element((array_name,) + name_seq), new_table]) + return new_table + + def __delitem__(self, key): + table_element_index = self._elements.index(self._navigable[key]) + self._elements[table_element_index] = element_factory.create_table({}) + self._on_element_change() + + def __setitem__(self, key, value): + + # Setting an array-of-tables + if key and isinstance(value, (tuple, list)) and value and all(isinstance(v, dict) for v in value): + for table in value: + self.array(key).append(table) + + # Or setting a whole single table + elif isinstance(value, dict): + + if key and key in self: + del self[key] + + for key_seq, child_value in util.flatten_nested({key: value}).items(): + self._setitem_with_key_seq(key_seq, child_value) + + # if key in self._navigable: + # del self[key] + # index = self._elements.index(self._navigable[key]) + # self._elements = self._elements[:index] + [element_factory.create_table(value)] + self._elements[index+1:] + # else: + # if key: + # self._elements.append(element_factory.create_table_header_element(key)) + # self._elements.append(element_factory.create_table(value)) + + + # Or updating the anonymous section table + else: + # It's mea + self[''][key] = value + + self._on_element_change() + + def _detect_toplevels(self): + """ + Returns a sequence of TopLevel instances for the current state of this table. + """ + return tuple(e for e in toplevels.identify(self.elements) if isinstance(e, toplevels.Table)) + + def _update_table_fallbacks(self, table_toplevels): + """ + Updates the fallbacks on all the table elements to make relative table access possible. + + Raises DuplicateKeysError if appropriate. + """ + + if len(self.elements) <= 1: + return + + def parent_of(toplevel): + # Returns an TopLevel parent of the given entry, or None. + for parent_toplevel in table_toplevels: + if toplevel.name.sub_names[:-1] == parent_toplevel.name.sub_names: + return parent_toplevel + + for entry in table_toplevels: + if entry.name.is_qualified: + parent = parent_of(entry) + if parent: + child_name = entry.name.without_prefix(parent.name) + parent.table_element.set_fallback({child_name.sub_names[0]: entry.table_element}) + + def _recreate_navigable(self): + if self._elements: + self._navigable = structurer.structure(toplevels.identify(self._elements)) + + def array(self, name): + """ + Returns the array of tables with the given name. + """ + if name in self._navigable: + if isinstance(self._navigable[name], (list, tuple)): + return self[name] + else: + raise NoArrayFoundError + else: + return ArrayOfTables(toml_file=self, name=name) + + def _on_element_change(self): + self._recreate_navigable() + + table_toplevels = self._detect_toplevels() + self._update_table_fallbacks(table_toplevels) + + def append_elements(self, elements): + """ + Appends more elements to the contained internal elements. + """ + self._elements = self._elements + list(elements) + self._on_element_change() + + def prepend_elements(self, elements): + """ + Prepends more elements to the contained internal elements. + """ + self._elements = list(elements) + self._elements + self._on_element_change() + + def dumps(self): + """ + Returns the TOML file serialized back to str. + """ + return ''.join(element.serialized() for element in self._elements) + + def dump(self, file_path): + with open(file_path, mode='w') as fp: + fp.write(self.dumps()) + + def keys(self): + return set(self._navigable.keys()) | {''} + + def values(self): + return self._navigable.values() + + def items(self): + items = self._navigable.items() + + def has_anonymous_entry(): + return any(key == '' for (key, _) in items) + + if has_anonymous_entry(): + return items + else: + return items + [('', self[''])] + + @property + def primitive(self): + """ + Returns a primitive object representation for this container (which is a dict). + + WARNING: The returned container does not contain any markup or formatting metadata. + """ + raw_container = raw.to_raw(self._navigable) + + # Collapsing the anonymous table onto the top-level container is present + if '' in raw_container: + raw_container.update(raw_container['']) + del raw_container[''] + + return raw_container + + def append_fresh_table(self, fresh_table): + """ + Gets called by FreshTable instances when they get written to. + """ + if fresh_table.name: + elements = [] + if fresh_table.is_array: + elements += [element_factory.create_array_of_tables_header_element(fresh_table.name)] + else: + elements += [element_factory.create_table_header_element(fresh_table.name)] + + elements += [fresh_table, element_factory.create_newline_element()] + self.append_elements(elements) + + else: + # It's an anonymous table + self.prepend_elements([fresh_table, element_factory.create_newline_element()]) + + @property + def elements(self): + return self._elements + + def __str__(self): + + is_empty = (not self['']) and (not tuple(k for k in self.keys() if k)) + + def key_name(key): + return '[ANONYMOUS]' if not key else key + + def pair(key, value): + return '%s = %s' % (key_name(key), str(value)) + + content_text = '' if is_empty else \ + '\n\t' + ',\n\t'.join(pair(k, v) for (k, v) in self.items() if v) + '\n' + + return "TOMLFile{%s}" % content_text + + def __repr__(self): + return str(self) + + + diff --git a/pipenv/vendor/contoml/file/freshtable.py b/pipenv/vendor/contoml/file/freshtable.py new file mode 100755 index 00000000..f28e2f74 --- /dev/null +++ b/pipenv/vendor/contoml/file/freshtable.py @@ -0,0 +1,45 @@ +from prettytoml.elements.table import TableElement + + +class FreshTable(TableElement): + """ + A fresh TableElement that appended itself to each of parents when it first gets written to at most once. + + parents is a sequence of objects providing an append_fresh_table(TableElement) method + """ + + def __init__(self, parent, name, is_array=False): + TableElement.__init__(self, sub_elements=[]) + + self._parent = parent + self._name = name + self._is_array = is_array + + # As long as this flag is false, setitem() operations will append the table header and this table + # to the toml_file's elements + self.__appended = False + + @property + def name(self): + return self._name + + @property + def is_array(self): + return self._is_array + + def _append_to_parent(self): + """ + Causes this ephemeral table to be persisted on the TOMLFile. + """ + + if self.__appended: + return + + if self._parent is not None: + self._parent.append_fresh_table(self) + + self.__appended = True + + def __setitem__(self, key, value): + TableElement.__setitem__(self, key, value) + self._append_to_parent() diff --git a/pipenv/vendor/contoml/file/peekableit.py b/pipenv/vendor/contoml/file/peekableit.py new file mode 100755 index 00000000..b5658a71 --- /dev/null +++ b/pipenv/vendor/contoml/file/peekableit.py @@ -0,0 +1,30 @@ +import itertools + + +class PeekableIterator: + + # Returned by peek() when the iterator is exhausted. Truthiness is False. + Nothing = tuple() + + def __init__(self, iter): + self._iter = iter + + def __next__(self): + return next(self._iter) + + def next(self): + return self.__next__() + + def __iter__(self): + return self + + def peek(self): + """ + Returns PeekableIterator.Nothing when the iterator is exhausted. + """ + try: + v = next(self._iter) + self._iter = itertools.chain((v,), self._iter) + return v + except StopIteration: + return PeekableIterator.Nothing diff --git a/pipenv/vendor/contoml/file/raw.py b/pipenv/vendor/contoml/file/raw.py new file mode 100755 index 00000000..8cffdb6e --- /dev/null +++ b/pipenv/vendor/contoml/file/raw.py @@ -0,0 +1,16 @@ +from prettytoml.elements.abstracttable import AbstractTable + + +def to_raw(x): + from contoml.file.cascadedict import CascadeDict + + if isinstance(x, AbstractTable): + return x.primitive_value + elif isinstance(x, CascadeDict): + return x.neutralized + elif isinstance(x, (list, tuple)): + return [to_raw(y) for y in x] + elif isinstance(x, dict): + return {k: to_raw(v) for (k, v) in x.items()} + else: + return x diff --git a/pipenv/vendor/contoml/file/structurer.py b/pipenv/vendor/contoml/file/structurer.py new file mode 100755 index 00000000..72d002cd --- /dev/null +++ b/pipenv/vendor/contoml/file/structurer.py @@ -0,0 +1,116 @@ +from contoml.file import toplevels +from contoml.file.cascadedict import CascadeDict + + +class NamedDict(dict): + """ + A dict that can use Name instances as keys. + """ + + def __init__(self, other_dict=None): + dict.__init__(self) + if other_dict: + for k, v in other_dict.items(): + self[k] = v + + def __setitem__(self, key, value): + """ + key can be an Name instance. + + When key is a path in the form of an Name instance, all the parents and grandparents of the value are + created along the way as instances of NamedDict. If the parent of the value exists, it is replaced with a + CascadeDict() that cascades the old parent value with a new NamedDict that contains the given child name + and value. + """ + if isinstance(key, toplevels.Name): + + if len(key.sub_names) == 1: + name = key.sub_names[0] + if name in self: + self[name] = CascadeDict(self[name], value) + else: + self[name] = value + + elif len(key.sub_names) > 1: + name = key.sub_names[0] + rest_of_key = key.drop(1) + if name in self: + named_dict = NamedDict() + named_dict[rest_of_key] = value + self[name] = CascadeDict(self[name], named_dict) + else: + self[name] = NamedDict() + self[name][rest_of_key] = value + else: + return dict.__setitem__(self, key, value) + + def __contains__(self, item): + try: + _ = self[item] + return True + except KeyError: + return False + + def append(self, key, value): + """ + Makes sure the value pointed to by key exists and is a list and appends the given value to it. + """ + if key in self: + self[key].append(value) + else: + self[key] = [value] + + def __getitem__(self, item): + + if isinstance(item, toplevels.Name): + d = self + for name in item.sub_names: + d = d[name] + return d + else: + return dict.__getitem__(self, item) + + +def structure(table_toplevels): + """ + Accepts an ordered sequence of TopLevel instances and returns a navigable object structure representation of the + TOML file. + """ + + table_toplevels = tuple(table_toplevels) + obj = NamedDict() + + last_array_of_tables = None # The Name of the last array-of-tables header + + for toplevel in table_toplevels: + + if isinstance(toplevel, toplevels.AnonymousTable): + obj[''] = toplevel.table_element + + elif isinstance(toplevel, toplevels.Table): + if last_array_of_tables and toplevel.name.is_prefixed_with(last_array_of_tables): + seq = obj[last_array_of_tables] + unprefixed_name = toplevel.name.without_prefix(last_array_of_tables) + + seq[-1] = CascadeDict(seq[-1], NamedDict({unprefixed_name: toplevel.table_element})) + else: + obj[toplevel.name] = toplevel.table_element + else: # It's an ArrayOfTables + + if last_array_of_tables and toplevel.name != last_array_of_tables and \ + toplevel.name.is_prefixed_with(last_array_of_tables): + + seq = obj[last_array_of_tables] + unprefixed_name = toplevel.name.without_prefix(last_array_of_tables) + + if unprefixed_name in seq[-1]: + seq[-1][unprefixed_name].append(toplevel.table_element) + else: + cascaded_with = NamedDict({unprefixed_name: [toplevel.table_element]}) + seq[-1] = CascadeDict(seq[-1], cascaded_with) + + else: + obj.append(toplevel.name, toplevel.table_element) + last_array_of_tables = toplevel.name + + return obj diff --git a/pipenv/vendor/contoml/file/test_cascadedict.py b/pipenv/vendor/contoml/file/test_cascadedict.py new file mode 100755 index 00000000..d692711e --- /dev/null +++ b/pipenv/vendor/contoml/file/test_cascadedict.py @@ -0,0 +1,25 @@ +from contoml.file.cascadedict import CascadeDict + + +def test_cascadedict(): + + d1 = {'a': 1, 'b': 2, 'c': 3} + d2 = {'b': 12, 'e': 4, 'f': 5} + + cascade = CascadeDict(d1, d2) + + # Test querying + assert cascade['a'] == 1 + assert cascade['b'] == 2 + assert cascade['c'] == 3 + assert cascade['e'] == 4 + assert cascade.keys() == {'a', 'b', 'c', 'e', 'f'} + assert set(cascade.items()) == {('a', 1), ('b', 2), ('c', 3), ('e', 4), ('f', 5)} + + # Test mutating + cascade['a'] = 11 + cascade['f'] = 'fff' + cascade['super'] = 'man' + assert d1['a'] == 11 + assert d1['super'] == 'man' + assert d1['f'] == 'fff' diff --git a/pipenv/vendor/contoml/file/test_entries.py b/pipenv/vendor/contoml/file/test_entries.py new file mode 100755 index 00000000..25584e82 --- /dev/null +++ b/pipenv/vendor/contoml/file/test_entries.py @@ -0,0 +1,20 @@ +from prettytoml import parser, lexer +from contoml.file import toplevels + + +def test_entry_extraction(): + text = open('sample.toml').read() + elements = parser.parse_tokens(lexer.tokenize(text)) + + e = tuple(toplevels.identify(elements)) + + assert len(e) == 13 + assert isinstance(e[0], toplevels.AnonymousTable) + + +def test_entry_names(): + name_a = toplevels.Name(('super', 'sub1')) + name_b = toplevels.Name(('super', 'sub1', 'sub2', 'sub3')) + + assert name_b.is_prefixed_with(name_a) + assert name_b.without_prefix(name_a).sub_names == ('sub2', 'sub3') diff --git a/pipenv/vendor/contoml/file/test_peekableit.py b/pipenv/vendor/contoml/file/test_peekableit.py new file mode 100755 index 00000000..5c053a38 --- /dev/null +++ b/pipenv/vendor/contoml/file/test_peekableit.py @@ -0,0 +1,12 @@ +from contoml.file.peekableit import PeekableIterator + + +def test_peekable_iterator(): + + peekable = PeekableIterator(i for i in (1, 2, 3, 4)) + + assert peekable.peek() == 1 + assert peekable.peek() == 1 + assert peekable.peek() == 1 + + assert [next(peekable), next(peekable), next(peekable), next(peekable)] == [1, 2, 3, 4] diff --git a/pipenv/vendor/contoml/file/test_structurer.py b/pipenv/vendor/contoml/file/test_structurer.py new file mode 100755 index 00000000..b3ea4b4e --- /dev/null +++ b/pipenv/vendor/contoml/file/test_structurer.py @@ -0,0 +1,41 @@ +from prettytoml import lexer, parser +from contoml.file import toplevels +from prettytoml.parser import elementsanitizer +from contoml.file.structurer import NamedDict, structure +from prettytoml.parser.tokenstream import TokenStream + + +def test_NamedDict(): + + d = NamedDict() + + d[toplevels.Name(('super', 'sub1', 'sub2'))] = {'sub3': 12} + d[toplevels.Name(('super', 'sub1', 'sub2'))]['sub4'] = 42 + + assert d[toplevels.Name(('super', 'sub1', 'sub2', 'sub3'))] == 12 + assert d[toplevels.Name(('super', 'sub1', 'sub2', 'sub4'))] == 42 + + +def test_structure(): + tokens = lexer.tokenize(open('sample.toml').read()) + elements = elementsanitizer.sanitize(parser.parse_tokens(tokens)) + entries_ = tuple(toplevels.identify(elements)) + + s = structure(entries_) + + assert s['']['title'] == 'TOML Example' + assert s['owner']['name'] == 'Tom Preston-Werner' + assert s['database']['ports'][1] == 8001 + assert s['servers']['alpha']['dc'] == 'eqdc10' + assert s['clients']['data'][1][0] == 1 + assert s['clients']['key3'] == 'The quick brown fox jumps over the lazy dog.' + + assert s['fruit'][0]['name'] == 'apple' + assert s['fruit'][0]['physical']['color'] == 'red' + assert s['fruit'][0]['physical']['shape'] == 'round' + assert s['fruit'][0]['variety'][0]['name'] == 'red delicious' + assert s['fruit'][0]['variety'][1]['name'] == 'granny smith' + + assert s['fruit'][1]['name'] == 'banana' + assert s['fruit'][1]['variety'][0]['name'] == 'plantain' + assert s['fruit'][1]['variety'][0]['points'][2]['y'] == 4 diff --git a/pipenv/vendor/contoml/file/toplevels.py b/pipenv/vendor/contoml/file/toplevels.py new file mode 100755 index 00000000..64038072 --- /dev/null +++ b/pipenv/vendor/contoml/file/toplevels.py @@ -0,0 +1,142 @@ +""" + Top-level entries in a TOML file. +""" + +from prettytoml import elements +from prettytoml.elements import TableElement, TableHeaderElement +from .peekableit import PeekableIterator + + +class TopLevel: + """ + A abstract top-level entry. + """ + + def __init__(self, names, table_element): + self._table_element = table_element + self._names = Name(names) + + @property + def table_element(self): + return self._table_element + + @property + def name(self): + """ + The distinct name of a table entry as an Name instance. + """ + return self._names + + +class Name: + + def __init__(self, names): + self._names = names + + @property + def sub_names(self): + return self._names + + def drop(self, n=0): + """ + Returns the name after dropping the first n entries of it. + """ + return Name(names=self._names[n:]) + + def is_prefixed_with(self, names): + if isinstance(names, Name): + return self.is_prefixed_with(names.sub_names) + + for i, name in enumerate(names): + if self._names[i] != name: + return False + return True + + def without_prefix(self, names): + if isinstance(names, Name): + return self.without_prefix(names.sub_names) + + for i, name in enumerate(names): + if name != self._names[i]: + return Name(self._names[i:]) + return Name(names=self.sub_names[len(names):]) + + @property + def is_qualified(self): + return len(self._names) > 1 + + def __str__(self): + return '.'.join(self.sub_names) + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + return str(self) == str(other) + + def __ne__(self, other): + return not self.__eq__(other) + + +class AnonymousTable(TopLevel): + + def __init__(self, table_element): + TopLevel.__init__(self, ('',), table_element) + + +class Table(TopLevel): + + def __init__(self, names, table_element): + TopLevel.__init__(self, names=names, table_element=table_element) + + +class ArrayOfTables(TopLevel): + + def __init__(self, names, table_element): + TopLevel.__init__(self, names=names, table_element=table_element) + + +def _validate_file_elements(file_elements): + pass + + +def identify(file_elements): + """ + Outputs an ordered sequence of instances of TopLevel types. + + Elements start with an optional TableElement, followed by zero or more pairs of (TableHeaderElement, TableElement). + """ + + if not file_elements: + return + + _validate_file_elements(file_elements) + + # An iterator over enumerate(the non-metadata) elements + iterator = PeekableIterator((element_i, element) for (element_i, element) in enumerate(file_elements) + if element.type != elements.TYPE_METADATA) + + try: + _, first_element = iterator.peek() + if isinstance(first_element, TableElement): + iterator.next() + yield AnonymousTable(first_element) + except KeyError: + pass + except StopIteration: + return + + for element_i, element in iterator: + + if not isinstance(element, TableHeaderElement): + continue + + # If TableHeader of a regular table, return Table following it + if not element.is_array_of_tables: + table_element_i, table_element = next(iterator) + yield Table(names=element.names, table_element=table_element) + + # If TableHeader of an array of tables, do your thing + else: + table_element_i, table_element = next(iterator) + yield ArrayOfTables(names=element.names, table_element=table_element) diff --git a/pipenv/vendor/iso8601/__init__.py b/pipenv/vendor/iso8601/__init__.py new file mode 100644 index 00000000..11b1adcb --- /dev/null +++ b/pipenv/vendor/iso8601/__init__.py @@ -0,0 +1 @@ +from .iso8601 import * diff --git a/pipenv/vendor/iso8601/iso8601.py b/pipenv/vendor/iso8601/iso8601.py new file mode 100644 index 00000000..0c149f67 --- /dev/null +++ b/pipenv/vendor/iso8601/iso8601.py @@ -0,0 +1,214 @@ +"""ISO 8601 date time string parsing + +Basic usage: +>>> import iso8601 +>>> iso8601.parse_date("2007-01-25T12:00:00Z") +datetime.datetime(2007, 1, 25, 12, 0, tzinfo=) +>>> + +""" + +import datetime +from decimal import Decimal +import sys +import re + +__all__ = ["parse_date", "ParseError", "UTC", + "FixedOffset"] + +if sys.version_info >= (3, 0, 0): + _basestring = str +else: + _basestring = basestring + + +# Adapted from http://delete.me.uk/2005/03/iso8601.html +ISO8601_REGEX = re.compile( + r""" + (?P[0-9]{4}) + ( + ( + (-(?P[0-9]{1,2})) + | + (?P[0-9]{2}) + (?!$) # Don't allow YYYYMM + ) + ( + ( + (-(?P[0-9]{1,2})) + | + (?P[0-9]{2}) + ) + ( + ( + (?P[ T]) + (?P[0-9]{2}) + (:{0,1}(?P[0-9]{2})){0,1} + ( + :{0,1}(?P[0-9]{1,2}) + ([.,](?P[0-9]+)){0,1} + ){0,1} + (?P + Z + | + ( + (?P[-+]) + (?P[0-9]{2}) + :{0,1} + (?P[0-9]{2}){0,1} + ) + ){0,1} + ){0,1} + ) + ){0,1} # YYYY-MM + ){0,1} # YYYY only + $ + """, + re.VERBOSE +) + +class ParseError(Exception): + """Raised when there is a problem parsing a date string""" + +if sys.version_info >= (3, 2, 0): + UTC = datetime.timezone.utc + def FixedOffset(offset_hours, offset_minutes, name): + return datetime.timezone( + datetime.timedelta( + hours=offset_hours, minutes=offset_minutes), + name) +else: + # Yoinked from python docs + ZERO = datetime.timedelta(0) + class Utc(datetime.tzinfo): + """UTC Timezone + + """ + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + + def __repr__(self): + return "" + + UTC = Utc() + + class FixedOffset(datetime.tzinfo): + """Fixed offset in hours and minutes from UTC + + """ + def __init__(self, offset_hours, offset_minutes, name): + self.__offset_hours = offset_hours # Keep for later __getinitargs__ + self.__offset_minutes = offset_minutes # Keep for later __getinitargs__ + self.__offset = datetime.timedelta( + hours=offset_hours, minutes=offset_minutes) + self.__name = name + + def __eq__(self, other): + if isinstance(other, FixedOffset): + return ( + (other.__offset == self.__offset) + and + (other.__name == self.__name) + ) + return NotImplemented + + def __getinitargs__(self): + return (self.__offset_hours, self.__offset_minutes, self.__name) + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return ZERO + + def __repr__(self): + return "" % (self.__name, self.__offset) + + +def to_int(d, key, default_to_zero=False, default=None, required=True): + """Pull a value from the dict and convert to int + + :param default_to_zero: If the value is None or empty, treat it as zero + :param default: If the value is missing in the dict use this default + + """ + value = d.get(key) or default + if (value in ["", None]) and default_to_zero: + return 0 + if value is None: + if required: + raise ParseError("Unable to read %s from %s" % (key, d)) + else: + return int(value) + +def parse_timezone(matches, default_timezone=UTC): + """Parses ISO 8601 time zone specs into tzinfo offsets + + """ + + if matches["timezone"] == "Z": + return UTC + # This isn't strictly correct, but it's common to encounter dates without + # timezones so I'll assume the default (which defaults to UTC). + # Addresses issue 4. + if matches["timezone"] is None: + return default_timezone + sign = matches["tz_sign"] + hours = to_int(matches, "tz_hour") + minutes = to_int(matches, "tz_minute", default_to_zero=True) + description = "%s%02d:%02d" % (sign, hours, minutes) + if sign == "-": + hours = -hours + minutes = -minutes + return FixedOffset(hours, minutes, description) + +def parse_date(datestring, default_timezone=UTC): + """Parses ISO 8601 dates into datetime objects + + The timezone is parsed from the date string. However it is quite common to + have dates without a timezone (not strictly correct). In this case the + default timezone specified in default_timezone is used. This is UTC by + default. + + :param datestring: The date to parse as a string + :param default_timezone: A datetime tzinfo instance to use when no timezone + is specified in the datestring. If this is set to + None then a naive datetime object is returned. + :returns: A datetime.datetime instance + :raises: ParseError when there is a problem parsing the date or + constructing the datetime instance. + + """ + if not isinstance(datestring, _basestring): + raise ParseError("Expecting a string %r" % datestring) + m = ISO8601_REGEX.match(datestring) + if not m: + raise ParseError("Unable to parse date string %r" % datestring) + groups = m.groupdict() + + tz = parse_timezone(groups, default_timezone=default_timezone) + + groups["second_fraction"] = int(Decimal("0.%s" % (groups["second_fraction"] or 0)) * Decimal("1000000.0")) + + try: + return datetime.datetime( + year=to_int(groups, "year"), + month=to_int(groups, "month", default=to_int(groups, "monthdash", required=False, default=1)), + day=to_int(groups, "day", default=to_int(groups, "daydash", required=False, default=1)), + hour=to_int(groups, "hour", default_to_zero=True), + minute=to_int(groups, "minute", default_to_zero=True), + second=to_int(groups, "second", default_to_zero=True), + microsecond=groups["second_fraction"], + tzinfo=tz, + ) + except Exception as e: + raise ParseError(e) diff --git a/pipenv/vendor/iso8601/test_iso8601.py b/pipenv/vendor/iso8601/test_iso8601.py new file mode 100644 index 00000000..0d01ffbb --- /dev/null +++ b/pipenv/vendor/iso8601/test_iso8601.py @@ -0,0 +1,102 @@ +# coding=UTF-8 +from __future__ import absolute_import + +import copy +import datetime +import pickle + +import pytest + +from iso8601 import iso8601 + +def test_iso8601_regex(): + assert iso8601.ISO8601_REGEX.match("2006-10-11T00:14:33Z") + +def test_fixedoffset_eq(): + # See https://bitbucket.org/micktwomey/pyiso8601/issues/19 + datetime.tzinfo() == iso8601.FixedOffset(2, 0, '+2:00') + +def test_parse_no_timezone_different_default(): + tz = iso8601.FixedOffset(2, 0, "test offset") + d = iso8601.parse_date("2007-01-01T08:00:00", default_timezone=tz) + assert d == datetime.datetime(2007, 1, 1, 8, 0, 0, 0, tz) + assert d.tzinfo == tz + +def test_parse_utc_different_default(): + """Z should mean 'UTC', not 'default'. + + """ + tz = iso8601.FixedOffset(2, 0, "test offset") + d = iso8601.parse_date("2007-01-01T08:00:00Z", default_timezone=tz) + assert d == datetime.datetime(2007, 1, 1, 8, 0, 0, 0, iso8601.UTC) + +@pytest.mark.parametrize("invalid_date, error_string", [ + ("2013-10-", "Unable to parse date string"), + ("2013-", "Unable to parse date string"), + ("", "Unable to parse date string"), + (None, "Expecting a string"), + ("wibble", "Unable to parse date string"), + ("23", "Unable to parse date string"), + ("131015T142533Z", "Unable to parse date string"), + ("131015", "Unable to parse date string"), + ("20141", "Unable to parse date string"), + ("201402", "Unable to parse date string"), + ("2007-06-23X06:40:34.00Z", "Unable to parse date string"), # https://code.google.com/p/pyiso8601/issues/detail?id=14 + ("2007-06-23 06:40:34.00Zrubbish", "Unable to parse date string"), # https://code.google.com/p/pyiso8601/issues/detail?id=14 + ("20114-01-03T01:45:49", "Unable to parse date string"), +]) +def test_parse_invalid_date(invalid_date, error_string): + assert isinstance(invalid_date, str) or invalid_date is None # Why? 'cos I've screwed up the parametrize before :) + with pytest.raises(iso8601.ParseError) as exc: + iso8601.parse_date(invalid_date) + assert exc.errisinstance(iso8601.ParseError) + assert str(exc.value).startswith(error_string) + +@pytest.mark.parametrize("valid_date,expected_datetime,isoformat", [ + ("2007-06-23 06:40:34.00Z", datetime.datetime(2007, 6, 23, 6, 40, 34, 0, iso8601.UTC), "2007-06-23T06:40:34+00:00"), # Handle a separator other than T + ("1997-07-16T19:20+01:00", datetime.datetime(1997, 7, 16, 19, 20, 0, 0, iso8601.FixedOffset(1, 0, "+01:00")), "1997-07-16T19:20:00+01:00"), # Parse with no seconds + ("2007-01-01T08:00:00", datetime.datetime(2007, 1, 1, 8, 0, 0, 0, iso8601.UTC), "2007-01-01T08:00:00+00:00"), # Handle timezone-less dates. Assumes UTC. http://code.google.com/p/pyiso8601/issues/detail?id=4 + ("2006-10-20T15:34:56.123+02:30", datetime.datetime(2006, 10, 20, 15, 34, 56, 123000, iso8601.FixedOffset(2, 30, "+02:30")), None), + ("2006-10-20T15:34:56Z", datetime.datetime(2006, 10, 20, 15, 34, 56, 0, iso8601.UTC), "2006-10-20T15:34:56+00:00"), + ("2007-5-7T11:43:55.328Z", datetime.datetime(2007, 5, 7, 11, 43, 55, 328000, iso8601.UTC), "2007-05-07T11:43:55.328000+00:00"), # http://code.google.com/p/pyiso8601/issues/detail?id=6 + ("2006-10-20T15:34:56.123Z", datetime.datetime(2006, 10, 20, 15, 34, 56, 123000, iso8601.UTC), "2006-10-20T15:34:56.123000+00:00"), + ("2013-10-15T18:30Z", datetime.datetime(2013, 10, 15, 18, 30, 0, 0, iso8601.UTC), "2013-10-15T18:30:00+00:00"), + ("2013-10-15T22:30+04", datetime.datetime(2013, 10, 15, 22, 30, 0, 0, iso8601.FixedOffset(4, 0, "+04:00")), "2013-10-15T22:30:00+04:00"), #