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_palce_empty(self): """Verify a document can be placed in an empty tree.""" tree = build(EMPTY) doc = MockDocumentSkip.new(tree, os.path.join(EMPTY, 'temp'), EMPTY, 'TEMP') tree._place(doc) # pylint: disable=W0212 self.assertEqual(1, len(tree))
def test_palce_empty_no_parent(self): """Verify a document with parent cannot be placed in an empty tree.""" tree = build(EMPTY) doc = MockDocumentSkip.new(tree, os.path.join(EMPTY, 'temp'), EMPTY, 'TEMP', parent='REQ') self.assertRaises(DoorstopError, tree._place, doc) # pylint: disable=W0212
def run_edit(args, cwd, error, catch=True): """Process arguments and run the `doorstop edit` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ item = document = None ext = utilities.get_ext(args, '.yml', '.yml', whole_tree=False, error=error) with utilities.capture(catch=catch) as success: tree = build(cwd=cwd, root=args.project) # find item or document if not args.document: try: item = tree.find_item(args.label) except common.DoorstopError as exc: if args.item: raise exc from None if not item: document = tree.find_document(args.label) # edit item or document if item: item.edit(tool=args.tool) else: _export_import(args, cwd, error, document, ext) if not success: return False if item: show("opened item: {} ({})".format(item.uid, item.relpath)) return True
def run_add(args, cwd, _, catch=True): """Process arguments and run the `doorstop add` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ with utilities.capture(catch=catch) as success: tree = build(cwd=cwd, root=args.project) document = tree.find_document(args.prefix) if args.force: log.warn("creating items without the server...") else: server.check() for _ in range(args.count): number = 0 while number is not None and number < document.next: if number: log.warn("server is behind, requesting next number...") if args.force: number = None else: number = server.get_next_number(args.prefix) item = document.add_item(number=number, level=args.level) show("added item: {} ({})".format(item.uid, item.relpath)) if not success: return False return True
def get_doc(prefix): # type: (str) -> Document tree = build(root=settings.DOORSTOP_REPO) doc = None for _doc in tree.documents: if _doc.prefix == prefix: doc = _doc break return doc
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) self._vcs = MyPyGit2(self._user) self.action(self._action) if self._curr_file: tree = build(root=settings.DOORSTOP_REPO) context['item'] = tree.find_item(self._curr_file) else: context['item'] = None context['patch'] = self._vcs.diff_patch() context['table'] = GitFileStatus(data=self._vcs.modified_files()) return context
def run_import(args, cwd, error, catch=True, _tree=None): """Process arguments and run the `doorstop import` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ document = item = None # Parse arguments attrs = utilities.literal_eval(args.attrs, error) mapping = utilities.literal_eval(args.map, error) if args.path: if not args.prefix: error("when [path] specified, [prefix] is also required") elif args.document: error("'--document' cannot be used with [path] [prefix]") elif args.item: error("'--item' cannot be used with [path] [prefix]") ext = utilities.get_ext(args, None, None, False, error) elif not (args.document or args.item): error("specify [path], '--document', or '--item' to import") # Import document or item with utilities.capture(catch=catch) as success: if args.path: tree = _tree or build(cwd=cwd, root=args.project) document = tree.find_document(args.prefix) msg = "importing '{}' into document {}...".format(args.path, document) show(msg, flush=True) importer.import_file(args.path, document, ext, mapping=mapping) elif args.document: prefix, path = args.document document = importer.create_document(prefix, path, parent=args.parent) elif args.item: prefix, uid = args.item item = importer.add_item(prefix, uid, attrs=attrs) if not success: return False # Display result if document: show("imported document: {} ({})".format(document.prefix, document.relpath)) else: assert item show("imported item: {} ({})".format(item.uid, item.relpath)) return True
def _iter_items(args, cwd, error): """Build a tree and iterate through items. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors Items are filtered to: - `args.label` == 'all': all items - `args.label` == document prefix: the document's items - `args.label` == item UID: a single item Documents and items are inferred unless flagged by: - `args.document`: `args.label` is a prefix - `args.item`: `args.label` is an UID """ # Parse arguments if args.label == 'all': if args.item: error("argument -i/--item: not allowed with 'all'") if args.document: error("argument -d/--document: not allowed with 'all'") # Build tree item = None document = None tree = build(cwd=cwd, root=args.project) # Determine if tree, document, or item was requested if args.label != 'all': if not args.item: try: document = tree.find_document(args.label) except common.DoorstopError as exc: if args.document: raise exc from None if not document: item = tree.find_item(args.label) # Yield items from the requested object if item: yield item elif document: for item in document: yield item else: for document in tree: for item in document: yield item
def display_tree(self, *_): """Display the currently selected tree.""" # Set the current tree self.tree = builder.build(root=self.stringvar_project.get()) log.info("displaying tree...") # Display the documents in the tree values = ["{} ({})".format(document.prefix, document.relpath) for document in self.tree] self.combobox_documents['values'] = values # Select the first document self.combobox_documents.current(0)
def _openProject(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.DirectoryOnly) dialog.setOptions(QFileDialog.ShowDirsOnly) if dialog.exec(): # TODO: this returns a list... should give user an error if they select multiple dir = dialog.selectedFiles()[0] self._tree = builder.build(root=dir) for doc in self._tree: self._docTree.addItem(doc) self._reqStack.addDoc(doc) self._reqView.addDoc(doc) self.mainContainer.show()
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 display_tree(self, *_): """Display the currently selected tree.""" # Set the current tree self.tree = builder.build(root=self.stringvar_project.get()) log.info("displaying tree...") # Display the documents in the tree values = [ "{} ({})".format(document.prefix, document.relpath) for document in self.tree ] self.combobox_documents['values'] = values # Select the first document self.combobox_documents.current(0)
def run_publish(args, cwd, error, catch=True): """Process arguments and run the `doorstop publish` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ # Parse arguments whole_tree = args.prefix == 'all' ext = utilities.get_ext(args, '.txt', '.html', whole_tree, error) # Publish documents with utilities.capture(catch=catch) as success: publisher.check(ext) tree = build(cwd=cwd, root=args.project) if not whole_tree: document = tree.find_document(args.prefix) if not success: return False # Set publishing arguments kwargs = {} if args.width: kwargs['width'] = args.width # Write to output file(s) if args.path: if whole_tree: show("publishing tree to '{}'...".format(args.path), flush=True) path = publisher.publish(tree, args.path, ext, **kwargs) else: msg = "publishing document {} to '{}'...".format(document, args.path) show(msg, flush=True) path = publisher.publish(document, args.path, ext, **kwargs) if path: show("published: {}".format(path)) # Display to standard output else: if whole_tree: error("only single documents can be displayed") for line in publisher.publish_lines(document, ext, **kwargs): show(line) return True
def display_tree(self, *_): """Display the currently selected tree.""" # Set the current tree self.tree = builder.build(root=self.stringvar_project.get()) log.info("displaying tree...") # Display the documents in the tree values = ["{} ({})".format(document.prefix, document.relpath) for document in self.tree] self.combobox_documents['values'] = values # Select the first document if len(self.tree): # pylint: disable=len-as-condition self.combobox_documents.current(0) else: logging.warning("no documents to display")
def run_reorder(args, cwd, error, catch=True, _tree=None): """Process arguments and run the `doorstop reorder` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ reordered = False with utilities.capture(catch=catch) as success: tree = _tree or build(cwd=cwd, root=args.project) document = tree.find_document(args.prefix) if not success: return False with utilities.capture(catch=catch) as success: # automatically order if args.auto: show("reordering document {}...".format(document), flush=True) document.reorder(manual=False) reordered = True # or, reorder from a previously updated index elif document.index: relpath = os.path.relpath(document.index, cwd) if ask("reorder from '{}'?".format(relpath)): show("reordering document {}...".format(document), flush=True) document.reorder(automatic=not args.manual) reordered = True else: del document.index # or, create a new index to update else: document.index = True # create index relpath = os.path.relpath(document.index, cwd) editor.edit(relpath, tool=args.tool) get('reorder')(args, cwd, error, catch=False, _tree=tree) if not success: show("after fixing the error: doorstop reorder {}".format(document)) return False if reordered: show("reordered document: {}".format(document)) return True
def run_create(args, cwd, _, catch=True): """Process arguments and run the `doorstop create` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ with utilities.capture(catch=catch) as success: tree = build(cwd=cwd, root=args.project) document = tree.create_document(args.path, args.prefix, parent=args.parent, digits=args.digits) if not success: return False show("created document: {} ({})".format(document.prefix, document.relpath)) return True
def run_unlink(args, cwd, _, catch=True): """Process arguments and run the `doorstop unlink` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ with utilities.capture(catch=catch) as success: tree = build(cwd=cwd, root=args.project) child, parent = tree.unlink_items(args.child, args.parent) if not success: return False msg = "unlinked items: {} ({}) -> {} ({})" show(msg.format(child.uid, child.relpath, parent.uid, parent.relpath)) return True
def run_remove(args, cwd, _, catch=True): """Process arguments and run the `doorstop remove` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ with utilities.capture(catch=catch) as success: tree = build(cwd=cwd, root=args.project) item = tree.find_item(args.uid) item.delete() if not success: return False show("removed item: {} ({})".format(item.uid, item.relpath)) return True
def _get_tree(args, cwd, request_next_number=None, load=False): """Build a tree and optionally load all documents. :param args: Namespace of CLI arguments :param cwd: current working directory :param request_next_number: server method to get a document's next number :param load: force the early loading of all documents :return: built :class:`~doorstop.core.tree.Tree` """ utilities.show("building tree...", flush=True) tree = build(cwd=cwd, root=args.project, request_next_number=request_next_number) if load: utilities.show("loading documents...", flush=True) tree.load() return tree
def run(args, cwd, error, catch=True): # pylint: disable=W0613 """Process arguments and run the `doorstop` subcommand. :param args: Namespace of CLI arguments :param cwd: current working directory :param error: function to call for CLI errors :param catch: catch and log :class:`~doorstop.common.DoorstopError` """ with utilities.capture(catch=catch) as success: show("building tree...", flush=True) tree = build(cwd=cwd, root=args.project) show("loading documents...", flush=True) tree.load() show("validating items...", flush=True) valid = tree.validate() if not success: return False if len(tree) > 1 and valid: show('\n' + tree.draw() + '\n') return valid
def test_build_with_skips(self): """Verify documents can be skipped while building a tree.""" tree = build(FILES) self.assertEqual(0, len(tree))
def test_build(self): """Verify a tree can be built.""" tree = build(FILES) self.assertEqual(4, len(tree))
def test_run_empty(self): """Verify an empty directory is an empty hierarchy.""" tree = build(EMPTY) self.assertEqual(0, len(tree))
def __init__(self): self._user = None # type: Optional[User] self._tree = build(root=settings.DOORSTOP_REPO) # type: Tree self._doc = None # type: Optional[Document] self._item = None # type: Optional[DjItem] self._form = None