def test_load_save(self): """Verify text formatting is preserved.""" item = Item(self.ITEM) item.load() item.save() text = common.read_text(self.ITEM) self.assertEqual(self.backup, text)
def _iter(self, reload=False): """Yield the document's items.""" if self._itered and not reload: msg = "iterating document {}'s loaded items...".format(self) log.debug(msg) yield from list(self._items) return log.info("loading document {}'s items...".format(self)) # Reload the document's item self._items = [] for dirpath, dirnames, filenames in os.walk(self.path): for dirname in list(dirnames): path = os.path.join(dirpath, dirname, Document.CONFIG) if os.path.exists(path): path = os.path.dirname(path) dirnames.remove(dirname) log.trace("skipped embedded document: {}".format(path)) for filename in filenames: path = os.path.join(dirpath, filename) try: item = Item(path, root=self.root, document=self, tree=self.tree) except DoorstopError: pass # skip non-item files else: self._items.append(item) if reload: item.load(reload=reload) if settings.CACHE_ITEMS and self.tree: self.tree._item_cache[item.uid] = item # pylint: disable=W0212 log.trace("cached item: {}".format(item)) # Set meta attributes self._itered = True # Yield items yield from list(self._items)
def test_load_save(self): """Verify text formatting is preserved.""" item = Item(self.ITEM) item.load() item.save() with open(self.ITEM, 'r') as infile: text = infile.read() self.assertEqual(self.backup, text)
def add_item(self, number=None, level=None, reorder=True): """Create a new item for the document and return it. :param number: desired item number :param level: desired item level :param reorder: update levels of document items :return: added :class:`~doorstop.core.item.Item` """ number = max(number or 0, self.next_number) log.debug("next number: {}".format(number)) try: last = self.items[-1] except IndexError: next_level = level else: if level: next_level = level elif last.level.heading: next_level = last.level >> 1 next_level.heading = False else: next_level = last.level + 1 log.debug("next level: {}".format(next_level)) uid = UID(self.prefix, self.sep, number, self.digits) item = Item.new(self.tree, self, self.path, self.root, uid, level=next_level) if level and reorder: self.reorder(keep=item) return item
def add_item(prefix, uid, attrs=None, document=None, request_next_number=None): """Create a Doorstop document from existing document information. :param prefix: previously imported document's prefix :param uid: existing item's UID :param attrs: dictionary of Doorstop and custom attributes :param document: explicit document to add the item :param request_next_number: server method to get a document's next number :return: imported Item """ if document: # Get an explicit tree tree = document.tree assert tree # tree should be set internally else: # Get an implicit tree and document tree = _get_tree(request_next_number=request_next_number) document = tree.find_document(prefix) # Add an item using the specified UID log.info("importing item '{}'...".format(uid)) item = Item.new(tree, document, document.path, document.root, uid, auto=False) for key, value in (attrs or {}).items(): item.set(key, value) item.save() log.info("imported: {}".format(item)) return item
def test_tree_skips_subdocuments_when_skipall_file_present(self): """Verify items can be found using a convenience function.""" temp = tempfile.mkdtemp() cwd = temp root = temp # Step 1: Create a new tree with one item in a document in a subfolder. tree = build(cwd, root) tree.create_document(cwd, 'TST') subfolder = os.path.join(temp, 'SUBFOLDER') os.makedirs(os.path.join(temp, subfolder)) sub_document = tree.create_document(subfolder, 'TST_SUB', parent='TST') item = Item.new(tree, sub_document, subfolder, cwd, "TST_SUB-001") item.save() # Step 2: Put a .doorstop.skip-all into the subfolder. path = os.path.join(subfolder, '.doorstop.skip-all') open(path, 'a').close() # Verify that building tree ignores subfolder's document. same_tree_again = build(cwd, root) self.assertEqual(1, len(same_tree_again.documents))
def test_tree_finds_subdocuments(self): """Verify items can be found using a convenience function.""" temp = tempfile.mkdtemp() cwd = temp root = temp # Step 1: Create a new tree with one item in a document in a subfolder. tree = build(cwd, root) document = tree.create_document(cwd, 'TST') subfolder = os.path.join(temp, 'SUBFOLDER') os.makedirs(os.path.join(temp, subfolder)) sub_document = tree.create_document(subfolder, 'TST_SUB', parent='TST') item = Item.new(tree, sub_document, subfolder, cwd, "TST_SUB-001") item.save() # Step 2: Read existing tree same_tree_again = build(cwd, root) # Verify that the tree has: # - both root-level and subfolder documents # - item in a subdocument self.assertEqual(2, len(same_tree_again.documents)) sub_document = same_tree_again.documents[1] self.assertIsNotNone(document) self.assertEqual(1, len(sub_document.items)) item = sub_document.items[0] self.assertEqual('TST_SUB-001', item.uid)
def test_attributes_with_spec(self, mock_warning): """Verify all other `Item` attributes raise an exception.""" spec = Item(None, os.path.join(FILES, 'REQ001.yml')) self.item = UnknownItem(self.item.uid, spec=spec) self.assertRaises(AttributeError, getattr, self.item, 'path') self.assertRaises(AttributeError, getattr, self.item, 'text') self.assertRaises(AttributeError, getattr, self.item, 'delete') self.assertRaises(AttributeError, getattr, self.item, 'not_on_item') self.assertEqual(3, mock_warning.call_count)
def _iter(self, reload=False): """Yield the document's items.""" if self._itered and not reload: msg = "iterating document {}'s loaded items...".format(self) log.debug(msg) yield from list(self._items) return log.info("loading document {}'s items...".format(self)) # Reload the document's item self._items = [] for dirpath, dirnames, filenames in os.walk(self.path): for dirname in list(dirnames): path = os.path.join(dirpath, dirname, Document.CONFIG) if os.path.exists(path): path = os.path.dirname(path) dirnames.remove(dirname) log.trace("skipped embedded document: {}".format(path)) for filename in filenames: path = os.path.join(dirpath, filename) try: item = Item(path, root=self.root, document=self, tree=self.tree) except DoorstopError: pass # skip non-item files else: self._items.append(item) if reload: try: item.load(reload=reload) except Exception: log.error("Unable to load: %s", item) raise if settings.CACHE_ITEMS and self.tree: self.tree._item_cache[item.uid] = item # pylint: disable=W0212 log.trace("cached item: {}".format(item)) # Set meta attributes self._itered = True # Yield items yield from list(self._items)
def add(self): """Create a new item for the document and return it.""" number = self.next logging.debug("next number: {}".format(number)) try: last = self.items[-1] except IndexError: level = None else: level = last.level[:-1] + (last.level[-1] + 1,) logging.debug("next level: {}".format(level)) item = Item.new(self.path, self.root, self.prefix, self.sep, self.digits, number, level=level) self._items.append(item) return item
def add_item(self, number=None, level=None, reorder=True, defaults=None): """Create a new item for the document and return it. :param number: desired item number :param level: desired item level :param reorder: update levels of document items :return: added :class:`~doorstop.core.item.Item` """ number = max(number or 0, self.next_number) log.debug("next number: {}".format(number)) try: last = self.items[-1] except IndexError: next_level = level else: if level: next_level = level elif last.level.heading: next_level = last.level >> 1 next_level.heading = False else: next_level = last.level + 1 log.debug("next level: {}".format(next_level)) # Load more defaults before the item is created to avoid partially # constructed items in case the loading fails. more_defaults = self._load_with_include(defaults) if defaults else None uid = UID(self.prefix, self.sep, number, self.digits) item = Item.new(self.tree, self, self.path, self.root, uid, level=next_level) if self._attribute_defaults: item.set_attributes(self._attribute_defaults) if more_defaults: item.set_attributes(more_defaults) if level and reorder: self.reorder(keep=item) return item
def test_tree_finds_documents(self): """Verify items can be found using a convenience function.""" temp = tempfile.mkdtemp() cwd = temp root = temp # Step 1: Create a new tree with one item. tree = build(cwd, root) document = tree.create_document(cwd, 'TST') item = Item.new(tree, document, cwd, cwd, "TST-001") item.save() # Step 2: Find a newly created tree same_tree_again = build(cwd, root) # Verify that that the tree, document and its item can be found. self.assertEqual(1, len(same_tree_again.documents)) document = same_tree_again.document self.assertIsNotNone(document) self.assertEqual(1, len(document.items)) item = document.items[0] self.assertEqual('TST-001', item.uid)
def test_tree_does_not_find_documents_when_skipall_file_present(self): """Verify items can be found using a convenience function.""" temp = tempfile.mkdtemp() cwd = temp root = temp # Step 1: Create a new tree with one item. tree = build(cwd, root) document = tree.create_document(cwd, 'TST') item = Item.new(tree, document, cwd, cwd, "TST-001") item.save() # Step 2: Put a .doorstop.skip-all to the root of the tree. path = os.path.join(temp, '.doorstop.skip-all') open(path, 'a').close() # Step 3: Find a newly created tree same_tree_again = build(cwd, root) # Verify that the tree does not have a document because it was ignored. document = same_tree_again.document self.assertIsNone(document)
def add_item(self, number=None, level=None, reorder=True, defaults=None, name=None): """Create a new item for the document and return it. :param number: desired item number :param level: desired item level :param reorder: update levels of document items :return: added :class:`~doorstop.core.item.Item` """ uid = None if name is None: number = max(number or 0, self.next_number) log.debug("next number: {}".format(number)) uid = UID(self.prefix, self.sep, number, self.digits) else: try: uid = UID(self.prefix, self.sep, int(name), self.digits) except ValueError: if not self.sep: msg = "cannot add item with name '{}' to document '{}' without a separator".format( name, self.prefix) raise DoorstopError(msg) if self.sep not in settings.SEP_CHARS: msg = "cannot add item with name '{}' to document '{}' with an invalid separator '{}'".format( name, self.prefix, self.sep) raise DoorstopError(msg) uid = UID(self.prefix, self.sep, name) if uid.prefix != self.prefix or uid.name != name: msg = "invalid item name '{}'".format(name) raise DoorstopError(msg) try: last = self.items[-1] except IndexError: next_level = level else: if level: next_level = level elif last.level.heading: next_level = last.level >> 1 next_level.heading = False else: next_level = last.level + 1 log.debug("next level: {}".format(next_level)) # Load more defaults before the item is created to avoid partially # constructed items in case the loading fails. more_defaults = self._load_with_include(defaults) if defaults else None item = Item.new(self.tree, self, self.path, self.root, uid, level=next_level) if self._attribute_defaults: item.set_attributes(self._attribute_defaults) if more_defaults: item.set_attributes(more_defaults) if level and reorder: self.reorder(keep=item) return item