Skip to content

Commit

Permalink
Merge 07f3678 into e33935e
Browse files Browse the repository at this point in the history
  • Loading branch information
NouberNou authored Jul 20, 2019
2 parents e33935e + 07f3678 commit 993c238
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 16 deletions.
2 changes: 1 addition & 1 deletion doorstop/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def run_add(args, cwd, _, catch=True):

# add items to it
for _ in range(args.count):
item = document.add_item(level=args.level)
item = document.add_item(level=args.level, template=args.template)
utilities.show("added item: {} ({})".format(item.uid, item.relpath))

# Edit item if requested
Expand Down
11 changes: 11 additions & 0 deletions doorstop/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,17 @@ def _add(subs, shared):
type=utilities.positive_int,
help="number of items to create",
)
sub.add_argument(
'--template',
default=None,
help=(
"Template content file to be added to the new item. "
"Will search for the file in a folder called .doorstop "
"in the root directory, if this argument is not specified "
"and a .doorstop/default.md file exists that file will be "
"loaded by default."
),
)
sub.add_argument(
'--edit',
action='store_true',
Expand Down
14 changes: 13 additions & 1 deletion doorstop/cli/tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ def test_add_specific_level(self):
self.assertIs(None, main(['add', 'TUT', '--level', '1.42']))
self.assertTrue(os.path.isfile(self.path))

def test_add_custom_template(self):
"""Verify 'doorstop add' can be called with a custom template."""
os.mkdir('.doorstop')
with open(os.path.join('.doorstop', 'custom.md'), 'w') as template_file:
template_file.write("This is a custom template.")
self.assertIs(None, main(['add', 'TUT', '--template', 'custom.md']))
self.assertTrue(os.path.isfile(self.path))
with open(self.path, 'r') as test_item:
self.assertIn("This is a custom template.", test_item.read())
os.remove(os.path.join('.doorstop', 'custom.md'))
os.rmdir('.doorstop')

def test_add_error(self):
"""Verify 'doorstop add' returns an error with an unknown prefix."""
self.assertRaises(SystemExit, main, ['add', 'UNKNOWN'])
Expand Down Expand Up @@ -201,7 +213,7 @@ def test_add_no_server(self):
def test_add_custom_server(self, mock_add_item):
"""Verify 'doorstop add' can be called with a custom server."""
self.assertIs(None, main(['add', 'TUT', '--server', '1.2.3.4']))
mock_add_item.assert_called_once_with(level=None)
mock_add_item.assert_called_once_with(level=None, template=None)

def test_add_force(self):
"""Verify 'doorstop add' can be called with a missing server."""
Expand Down
13 changes: 11 additions & 2 deletions doorstop/core/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,12 +389,13 @@ def index(self):
# actions ################################################################

# decorators are applied to methods in the associated classes
def add_item(self, number=None, level=None, reorder=True):
def add_item(self, number=None, level=None, reorder=True, template=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
:param template: template used for default content
:return: added :class:`~doorstop.core.item.Item`
Expand All @@ -415,7 +416,15 @@ def add_item(self, number=None, level=None, reorder=True):
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)
item = Item.new(
self.tree,
self,
self.path,
self.root,
uid,
level=next_level,
template=template,
)
if self._attribute_defaults:
item.set_attributes(self._attribute_defaults)
if level and reorder:
Expand Down
28 changes: 25 additions & 3 deletions doorstop/core/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,26 @@ def __init__(self, document, path, root=os.getcwd(), **kwargs):
self._data['normative'] = Item.DEFAULT_NORMATIVE
self._data['derived'] = Item.DEFAULT_DERIVED
self._data['reviewed'] = Item.DEFAULT_REVIEWED
self._data['text'] = Item.DEFAULT_TEXT

default_text = Item.DEFAULT_TEXT

template_name = kwargs.get('template', 'default.md')
if os.path.isdir(os.path.join(root, '.doorstop')):
if not template_name:
template_name = 'default.md'

template_path = os.path.join(root, '.doorstop', template_name)

if os.path.isfile(template_path):
with open(template_path, 'r') as template_file:
default_text = Text(template_file.read())
else:
if template_name != 'default.md':
raise DoorstopError(
"item template does not exist: {}".format(template_name)
)

self._data['text'] = default_text
self._data['ref'] = Item.DEFAULT_REF
self._data['links'] = set()
if settings.ENABLE_HEADERS:
Expand All @@ -112,7 +131,7 @@ def __lt__(self, other):
@staticmethod
@add_item
def new(
tree, document, path, root, uid, level=None, auto=None
tree, document, path, root, uid, level=None, template=None, auto=None
): # pylint: disable=R0913
"""Create a new item.
Expand All @@ -124,6 +143,7 @@ def new(
:param uid: UID for the new item
:param level: level for the new item
:param template: template content for new item
:param auto: automatically save the item
:raises: :class:`~doorstop.common.DoorstopError` if the item
Expand All @@ -139,7 +159,9 @@ def new(
log.debug("creating item file at {}...".format(path2))
Item._create(path2, name='item')
# Initialize the item
item = Item(document, path2, root=root, tree=tree, auto=False)
item = Item(
document, path2, root=root, tree=tree, template=template, auto=False
)
item.level = level if level is not None else item.level
if auto or (auto is None and Item.auto):
item.save()
Expand Down
1 change: 1 addition & 0 deletions doorstop/core/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
FILES = os.path.join(os.path.dirname(__file__), 'files')
SYS = os.path.join(FILES, 'parent')
TST = os.path.join(FILES, 'child')
ROOT_TEMPLATE = os.path.join(FILES, 'templates')
EMPTY = os.path.join(FILES, 'empty') # an empty directory
EXTERNAL = os.path.join(FILES, 'external') # external files to reference
NEW = os.path.join(FILES, 'new') # new document with no items
Expand Down
1 change: 1 addition & 0 deletions doorstop/core/tests/files/templates/.doorstop/custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a custom template.
1 change: 1 addition & 0 deletions doorstop/core/tests/files/templates/.doorstop/default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a default template.
76 changes: 69 additions & 7 deletions doorstop/core/tests/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

"""Unit tests for the doorstop.core.document module."""

# pylint: disable=unused-argument,protected-access
# pylint: disable=unused-argument,protected-access,C0302

import logging
import os
Expand All @@ -13,7 +13,15 @@
from doorstop import common
from doorstop.common import DoorstopError, DoorstopInfo, DoorstopWarning
from doorstop.core.document import Document
from doorstop.core.tests import EMPTY, FILES, NEW, ROOT, MockDocument, MockItem
from doorstop.core.tests import (
EMPTY,
FILES,
NEW,
ROOT,
ROOT_TEMPLATE,
MockDocument,
MockItem,
)
from doorstop.core.types import Level

YAML_DEFAULT = """
Expand Down Expand Up @@ -406,7 +414,13 @@ def test_add_item(self, mock_new, mock_reorder):
with patch('doorstop.settings.REORDER', True):
self.document.add_item()
mock_new.assert_called_once_with(
None, self.document, FILES, ROOT, 'REQ006', level=Level('2.2')
None,
self.document,
FILES,
ROOT,
'REQ006',
level=Level('2.2'),
template=None,
)
self.assertEqual(0, mock_reorder.call_count)

Expand All @@ -417,7 +431,7 @@ def test_add_item_with_level(self, mock_new, mock_reorder):
with patch('doorstop.settings.REORDER', True):
item = self.document.add_item(level='4.2')
mock_new.assert_called_once_with(
None, self.document, FILES, ROOT, 'REQ006', level='4.2'
None, self.document, FILES, ROOT, 'REQ006', level='4.2', template=None
)
mock_reorder.assert_called_once_with(keep=item)

Expand All @@ -426,7 +440,13 @@ def test_add_item_with_number(self, mock_new):
"""Verify an item can be added to a document with a number."""
self.document.add_item(number=999)
mock_new.assert_called_once_with(
None, self.document, FILES, ROOT, 'REQ999', level=Level('2.2')
None,
self.document,
FILES,
ROOT,
'REQ999',
level=Level('2.2'),
template=None,
)

@patch('doorstop.core.item.Item.new')
Expand All @@ -436,7 +456,43 @@ def test_add_item_empty(self, mock_new):
document.prefix = 'NEW'
self.assertIsNot(None, document.add_item(reorder=False))
mock_new.assert_called_once_with(
None, document, NEW, ROOT, 'NEW001', level=None
None, document, NEW, ROOT, 'NEW001', level=None, template=None
)

@patch('doorstop.core.item.Item.new')
def test_add_item_template_no_template_folder(self, mock_new):
"""Verify an item can be added to an new document when no template folder exists."""
document = MockDocument(NEW, ROOT)
document.prefix = 'NEW'
self.assertIsNot(None, document.add_item(reorder=False))
mock_new.assert_called_once_with(
None, document, NEW, ROOT, 'NEW001', level=None, template=None
)

@patch('doorstop.core.item.Item.new')
def test_add_item_template_default(self, mock_new):
"""Verify an item with a default template can be added to an new document."""
document = MockDocument(NEW, ROOT_TEMPLATE)
document.prefix = 'NEW'
self.assertIsNot(None, document.add_item(reorder=False))
mock_new.assert_called_once_with(
None, document, NEW, ROOT_TEMPLATE, 'NEW001', level=None, template=None
)

@patch('doorstop.core.item.Item.new')
def test_add_item_template_custom(self, mock_new):
"""Verify an item with a custom template can be added to an new document."""
document = MockDocument(NEW, ROOT_TEMPLATE)
document.prefix = 'NEW'
self.assertIsNot(None, document.add_item(reorder=False, template="custom.md"))
mock_new.assert_called_once_with(
None,
document,
NEW,
ROOT_TEMPLATE,
'NEW001',
level=None,
template="custom.md",
)

@patch('doorstop.core.item.Item.new')
Expand All @@ -448,7 +504,13 @@ def test_add_item_after_header(self, mock_new):
self.document._iter = Mock(return_value=[mock_item])
self.document.add_item()
mock_new.assert_called_once_with(
None, self.document, FILES, ROOT, 'REQ002', level=Level('1.1')
None,
self.document,
FILES,
ROOT,
'REQ002',
level=Level('1.1'),
template=None,
)

def test_add_item_contains(self):
Expand Down
47 changes: 45 additions & 2 deletions doorstop/core/tests/test_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
from doorstop import common, core
from doorstop.common import DoorstopError
from doorstop.core.item import Item, UnknownItem
from doorstop.core.tests import EMPTY, EXTERNAL, FILES, MockItem, MockSimpleDocument
from doorstop.core.tests import (
EMPTY,
EXTERNAL,
FILES,
ROOT_TEMPLATE,
MockItem,
MockSimpleDocument,
)
from doorstop.core.types import Stamp, Text
from doorstop.core.vcs.mockvcs import WorkingCopy

Expand Down Expand Up @@ -581,13 +588,49 @@ def test_new(self):
"""Verify items can be created."""
MockItem._create.reset_mock()
item = MockItem.new(
None, MockSimpleDocument(), EMPTY, FILES, 'TEST00042', level=(1, 2, 3)
None,
MockSimpleDocument(),
EMPTY,
FILES,
'TEST00042',
level=(1, 2, 3),
template=None,
)
path = os.path.join(EMPTY, 'TEST00042.yml')
self.assertEqual(path, item.path)
self.assertEqual((1, 2, 3), item.level)
MockItem._create.assert_called_once_with(path, name='item')

@patch('doorstop.core.item.Item', MockItem)
def test_new_default_template(self):
"""Verify items can be created with a default template."""
MockItem._create.reset_mock()
item = MockItem.new(
None,
MockSimpleDocument(),
EMPTY,
ROOT_TEMPLATE,
'TEST00042',
level=(1, 2, 3),
template=None,
)
self.assertEqual("This is a default template.", item.text)

@patch('doorstop.core.item.Item', MockItem)
def test_new_custom_template(self):
"""Verify items can be created with a custom template."""
MockItem._create.reset_mock()
item = MockItem.new(
None,
MockSimpleDocument(),
EMPTY,
ROOT_TEMPLATE,
'TEST00042',
level=(1, 2, 3),
template="custom.md",
)
self.assertEqual("This is a custom template.", item.text)

@patch('doorstop.core.item.Item', MockItem)
def test_new_cache(self):
"""Verify new items are cached."""
Expand Down

0 comments on commit 993c238

Please sign in to comment.