Scripting Interface
Being written in Python, Doorstop allows you to leverage the full power of Python to write scripts to manipulate requirements, run custom queries across all documents, and even inject your own validation rules.
REPL
For ad hoc introspection, let Doorstop build your tree of documents in your preferred Python REPL or notebook session:
>>> import doorstop
>>> tree = doorstop.build()
>>> tree
<Tree REQ <- [ TUT <- [ HLT ], LLT ]>
>>> len(tree.documents)
4
>>> document = tree.find_document('REQ')
>>> document
Document('/Users/Browning/Documents/doorstop/reqs')
>>> sum(1 for item in document if item.active)
18
Generic Scripting
For reusable workflows, create a Python script that acts on your tree of documents:
#!/usr/bin/env python
import doorstop
tree = doorstop.build()
document = tree.find_document('REQ')
count = sum(1 for item in document if item.active)
print(f"{count} active items in {document}")
Validation Hooks
To extend the default set of validations that can be performed, Doorstop provides a "hook" mechanism to simplify scripts that need to operate on multiple documents or items.
For this use case, create a script to call in place of the default command-line interface:
#!/usr/bin/env python
import sys
from doorstop import build, DoorstopInfo, DoorstopWarning, DoorstopError
def main():
tree = build()
success = tree.validate(document_hook=check_document, item_hook=check_item)
sys.exit(0 if success else 1)
def check_document(document, tree):
if sum(1 for i in document if i.normative) < 10:
yield DoorstopInfo("fewer than 10 normative items")
def check_item(item, document, tree):
if not item.get('type'):
yield DoorstopWarning("no type specified")
if item.derived and not item.get('rationale'):
yield DoorstopError("derived but no rationale")
if __name__ == '__main__':
main()
Both document_hook
and item_hook
are optional, but if provided these callbacks will be passed each corresponding instance. Each callback should yield instances of Doorstop's exception classes based on severity of the issue.
Validation Hook per folder
Doorstop also has an extension which allows creating an item validation per folder, allowing the document to have different validations for each document section.
To enable this mechanism you must insert into your .doorstop.yml
file the following lines:
extensions:
item_validator: .req_sha_item_validator.py # a python file path relative to .doorstop.yml
or
extensions:
item_validator: ../validators/my_complex_validator.py # a python file path relative to .doorstop.yml
The referenced file must have a function called item_validator
with a single parameter item
.
Example:
def item_validator(item):
if getattr(item, "references") == None:
return [] # early return
for ref in item.references:
if ref['sha'] != item._hash_reference(ref['path']):
yield DoorstopError("Hash has changed and it was not reviewed properly")
Although it is not required, it is recommended to yield a Doorstop type such as,
DoorstopInfo
, DoorstopError
, or DoorstopWarning
.