Passed
Pull Request — develop (#458)
by
unknown
02:49
created

doorstop.cli.commands.run_clear()   A

Complexity

Conditions 4

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nop 4
dl 0
loc 20
rs 9.95
c 0
b 0
f 0
1
"""Command functions."""
2
3
import os
4
import time
5
6
from doorstop import common
7
from doorstop.cli import utilities
8
from doorstop.core.builder import build
9
from doorstop.core import editor, importer, exporter, publisher
10
from doorstop import server
11
# add import 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
12
from doorstop.core.my_programs import valMatrix, verMatrix, finder
13
# end add imports
14
15
log = common.logger(__name__)
16
17
18
def get(name):
19
    """Get a command function by name."""
20
    if name:
21
        log.debug("running command '{}'...".format(name))
22
        return globals()['run_' + name]
23
    else:
24
        log.debug("launching main command...")
25
        return run
26
27
28
def run(args, cwd, error, catch=True):  # pylint: disable=W0613
29
    """Process arguments and run the `doorstop` subcommand.
30
31
    :param args: Namespace of CLI arguments
32
    :param cwd: current working directory
33
    :param error: function to call for CLI errors
34
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
35
36
    """
37
    with utilities.capture(catch=catch) as success:
38
39
        # get the tree
40
        tree = _get_tree(args, cwd, load=True)
41
42
        # validate it
43
        utilities.show("validating items...", flush=True)
44
        valid = tree.validate()
45
46
    if not success:
47
        return False
48
49
    if len(tree) > 1 and valid:
50
        utilities.show('\n' + tree.draw() + '\n')
51
52
    return valid
53
54
55
def run_create(args, cwd, _, catch=True):
56
    """Process arguments and run the `doorstop create` subcommand.
57
58
    :param args: Namespace of CLI arguments
59
    :param cwd: current working directory
60
    :param error: function to call for CLI errors
61
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
62
63
    """
64
    with utilities.capture(catch=catch) as success:
65
66
        # get the tree
67
        tree = _get_tree(args, cwd)
68
69
        # create a new document
70
        document = tree.create_document(args.path, args.prefix,
71
                                        parent=args.parent, digits=args.digits)
72
73
    if not success:
74
        return False
75
76
    utilities.show("created document: {} ({})".format(document.prefix,
77
                                                      document.relpath))
78
    return True
79
80
81
def run_delete(args, cwd, _, catch=True):
82
    """Process arguments and run the `doorstop delete` subcommand.
83
84
    :param args: Namespace of CLI arguments
85
    :param cwd: current working directory
86
    :param error: function to call for CLI errors
87
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
88
89
    """
90
    with utilities.capture(catch=catch) as success:
91
92
        # get the document
93
        tree = _get_tree(args, cwd)
94
        document = tree.find_document(args.prefix)
95
96
        # delete it
97
        prefix, relpath = document.prefix, document.relpath
98
        document.delete()
99
100
    if not success:
101
        return False
102
103
    utilities.show("deleted document: {} ({})".format(prefix, relpath))
104
105
    return True
106
107
108
def run_add(args, cwd, _, catch=True):
109
    """Process arguments and run the `doorstop add` subcommand.
110
111
    :param args: Namespace of CLI arguments
112
    :param cwd: current working directory
113
    :param error: function to call for CLI errors
114
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
115
116
    """
117
    with utilities.capture(catch=catch) as success:
118
119
        # get the document
120
        request_next_number = _request_next_number(args)
121
        tree = _get_tree(args, cwd, request_next_number=request_next_number)
122
        document = tree.find_document(args.prefix)
123
124
        # add items to it
125
        for _ in range(args.count):
126
            item = document.add_item(level=args.level)
127
            utilities.show("added item: {} ({})".format(item.uid,
128
                                                        item.relpath))
129
130
    if not success:
131
        return False
132
133
    return True
134
135
136
def run_remove(args, cwd, _, catch=True):
137
    """Process arguments and run the `doorstop remove` subcommand.
138
139
    :param args: Namespace of CLI arguments
140
    :param cwd: current working directory
141
    :param error: function to call for CLI errors
142
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
143
144
    """
145
    with utilities.capture(catch=catch) as success:
146
147
        # get the item
148
        tree = _get_tree(args, cwd)
149
        item = tree.find_item(args.uid)
150
151
        # delete it
152
        item.delete()
153
154
    if not success:
155
        return False
156
157
    utilities.show("removed item: {} ({})".format(item.uid, item.relpath))
158
159
    return True
160
161
162
def run_edit(args, cwd, error, catch=True):
163
    """Process arguments and run the `doorstop edit` subcommand.
164
165
    :param args: Namespace of CLI arguments
166
    :param cwd: current working directory
167
    :param error: function to call for CLI errors
168
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
169
170
    """
171
    item = document = None
172
    ext = utilities.get_ext(args, error, '.yml', '.yml', whole_tree=False)
173
174
    with utilities.capture(catch=catch) as success:
175
176
        # get the item or document
177
        request_next_number = _request_next_number(args)
178
        tree = _get_tree(args, cwd, request_next_number=request_next_number)
179
        if not args.document:
180
            try:
181
                item = tree.find_item(args.label)
182
            except common.DoorstopError as exc:
183
                if args.item:
184
                    raise exc from None
185
        if not item:
186
            document = tree.find_document(args.label)
187
188
        # edit it
189
        if item:
190
            item.edit(tool=args.tool)
191
        else:
192
            _export_import(args, cwd, error, document, ext)
193
194
    if not success:
195
        return False
196
197
    if item:
198
        utilities.show("opened item: {} ({})".format(item.uid, item.relpath))
199
200
    return True
201
202
203
def run_reorder(args, cwd, error, catch=True, _tree=None):
204
    """Process arguments and run the `doorstop reorder` subcommand.
205
206
    :param args: Namespace of CLI arguments
207
    :param cwd: current working directory
208
    :param error: function to call for CLI errors
209
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
210
211
    """
212
    reordered = False
213
214
    with utilities.capture(catch=catch) as success:
215
216
        # get the document
217
        tree = _tree or _get_tree(args, cwd)
218
        document = tree.find_document(args.prefix)
219
220
    if not success:
221
        return False
222
223
    with utilities.capture(catch=catch) as success:
224
225
        # automatically order
226
        if args.auto:
227
            msg = "reordering document {}...".format(document)
228
            utilities.show(msg, flush=True)
229
            document.reorder(manual=False)
230
            reordered = True
231
232
        # or, reorder from a previously updated index
233
        elif document.index:
234
            relpath = os.path.relpath(document.index, cwd)
235
            if utilities.ask("reorder from '{}'?".format(relpath)):
236
                msg = "reordering document {}...".format(document)
237
                utilities.show(msg, flush=True)
238
                document.reorder(automatic=not args.manual)
239
                reordered = True
240
            else:
241
                del document.index
242
243
        # or, create a new index to update
244
        else:
245
            document.index = True  # create index
246
            relpath = os.path.relpath(document.index, cwd)
247
            editor.edit(relpath, tool=args.tool)
248
            get('reorder')(args, cwd, error, catch=False, _tree=tree)
249
250
    if not success:
251
        msg = "after fixing the error: doorstop reorder {}".format(document)
252
        utilities.show(msg)
253
        return False
254
255
    if reordered:
256
        utilities.show("reordered document: {}".format(document))
257
258
    return True
259
260
261
def run_link(args, cwd, _, catch=True):
262
    """Process arguments and run the `doorstop link` subcommand.
263
264
    :param args: Namespace of CLI arguments
265
    :param cwd: current working directory
266
    :param error: function to call for CLI errors
267
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
268
269
    """
270
    with utilities.capture(catch=catch) as success:
271
272
        # get the tree
273
        tree = _get_tree(args, cwd)
274
275
        # link items
276
        child, parent = tree.link_items(args.child, args.parent)
277
278
    if not success:
279
        return False
280
281
    msg = "linked items: {} ({}) -> {} ({})".format(child.uid,
282
                                                    child.relpath,
283
                                                    parent.uid,
284
                                                    parent.relpath)
285
    utilities.show(msg)
286
287
    return True
288
289
290
def run_unlink(args, cwd, _, catch=True):
291
    """Process arguments and run the `doorstop unlink` subcommand.
292
293
    :param args: Namespace of CLI arguments
294
    :param cwd: current working directory
295
    :param error: function to call for CLI errors
296
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
297
298
    """
299
    with utilities.capture(catch=catch) as success:
300
301
        # get the tree
302
        tree = _get_tree(args, cwd)
303
304
        # unlink items
305
        child, parent = tree.unlink_items(args.child, args.parent)
306
307
    if not success:
308
        return False
309
310
    msg = "unlinked items: {} ({}) -> {} ({})".format(child.uid,
311
                                                      child.relpath,
312
                                                      parent.uid,
313
                                                      parent.relpath)
314
    utilities.show(msg)
315
316
    return True
317
318
319
def run_clear(args, cwd, error, catch=True):
320
    """Process arguments and run the `doorstop clear` subcommand.
321
322
    :param args: Namespace of CLI arguments
323
    :param cwd: current working directory
324
    :param error: function to call for CLI errors
325
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
326
327
    """
328
    with utilities.capture(catch=catch) as success:
329
330
        for item in _iter_items(args, cwd, error):
331
            msg = "clearing item {}'s suspect links...".format(item.uid)
332
            utilities.show(msg)
333
            item.clear()
334
335
    if not success:
336
        return False
337
338
    return True
339
340
341
def run_review(args, cwd, error, catch=True):
342
    """Process arguments and run the `doorstop review` subcommand.
343
344
    :param args: Namespace of CLI arguments
345
    :param cwd: current working directory
346
    :param error: function to call for CLI errors
347
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
348
349
    """
350
    with utilities.capture(catch=catch) as success:
351
352
        for item in _iter_items(args, cwd, error):
353
            utilities.show("marking item {} as reviewed...".format(item.uid))
354
            item.review()
355
356
    if not success:
357
        return False
358
359
    return True
360
361
362
def run_import(args, cwd, error, catch=True, _tree=None):
363
    """Process arguments and run the `doorstop import` subcommand.
364
365
    :param args: Namespace of CLI arguments
366
    :param cwd: current working directory
367
    :param error: function to call for CLI errors
368
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
369
370
    """
371
    document = item = None
372
    attrs = utilities.literal_eval(args.attrs, error)
373
    mapping = utilities.literal_eval(args.map, error)
374
    if args.path:
375
        if not args.prefix:
376
            error("when [path] specified, [prefix] is also required")
377
        elif args.document:
378
            error("'--document' cannot be used with [path] [prefix]")
379
        elif args.item:
380
            error("'--item' cannot be used with [path] [prefix]")
381
        ext = utilities.get_ext(args, error, None, None)
382
    elif not (args.document or args.item):
383
        error("specify [path], '--document', or '--item' to import")
384
385
    with utilities.capture(catch=catch) as success:
386
387
        if args.path:
388
389
            # get the document
390
            request_next_number = _request_next_number(args)
391
            tree = _tree or _get_tree(args, cwd,
392
                                      request_next_number=request_next_number)
393
            document = tree.find_document(args.prefix)
394
395
            # import items into it
396
            msg = "importing '{}' into document {}...".format(args.path,
397
                                                              document)
398
            utilities.show(msg, flush=True)
399
            importer.import_file(args.path, document, ext, mapping=mapping)
0 ignored issues
show
introduced by
The variable ext does not seem to be defined for all execution paths.
Loading history...
400
401
        elif args.document:
402
            prefix, path = args.document
403
            document = importer.create_document(prefix, path,
404
                                                parent=args.parent)
405
        elif args.item:
406
            prefix, uid = args.item
407
            request_next_number = _request_next_number(args)
408
            item = importer.add_item(prefix, uid, attrs=attrs,
409
                                     request_next_number=request_next_number)
410
    if not success:
411
        return False
412
413
    if document:
414
        utilities.show("imported document: {} ({})".format(document.prefix,
415
                                                           document.relpath))
416
    else:
417
        assert item
418
        utilities.show("imported item: {} ({})".format(item.uid, item.relpath))
419
420
    return True
421
422
423
def run_export(args, cwd, error, catch=True, auto=False, _tree=None):
424
    """Process arguments and run the `doorstop export` subcommand.
425
426
    :param args: Namespace of CLI arguments
427
    :param cwd: current working directory
428
    :param error: function to call for CLI errors
429
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
430
431
    :param auto: include placeholders for new items on import
432
433
    """
434
    whole_tree = args.prefix == 'all'
435
    ext = utilities.get_ext(args, error, '.yml', '.csv', whole_tree=whole_tree)
436
437
    # Get the tree or document
438
    with utilities.capture(catch=catch) as success:
439
440
        exporter.check(ext)
441
        tree = _tree or _get_tree(args, cwd, load=whole_tree)
442
        if not whole_tree:
443
            document = tree.find_document(args.prefix)
444
445
    if not success:
446
        return False
447
448
    # Write to output file(s)
449
    if args.path:
450
        if whole_tree:
451
            msg = "exporting tree to '{}'...".format(args.path)
452
            utilities.show(msg, flush=True)
453
            path = exporter.export(tree, args.path, ext, auto=auto)
454
        else:
455
            msg = "exporting document {} to '{}'...".format(document,
0 ignored issues
show
introduced by
The variable document does not seem to be defined in case BooleanNotNode on line 442 is False. Are you sure this can never be the case?
Loading history...
456
                                                            args.path)
457
            utilities.show(msg, flush=True)
458
            path = exporter.export(document, args.path, ext, auto=auto)
459
        if path:
460
            utilities.show("exported: {}".format(path))
461
462
    # Or, display to standard output
463
    else:
464
        if whole_tree:
465
            error("only single documents can be displayed")
466
        for line in exporter.export_lines(document, ext):
467
            utilities.show(line)
468
469
    return True
470
471
472
def run_publish(args, cwd, error, catch=True):
473
    """Process arguments and run the `doorstop publish` subcommand.
474
475
    :param args: Namespace of CLI arguments
476
    :param cwd: current working directory
477
    :param error: function to call for CLI errors
478
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
479
480
    """
481
    whole_tree = args.prefix == 'all'
482
    ext = utilities.get_ext(args, error, '.txt', '.html', whole_tree)
483
484
    # Get the tree or document
485
    with utilities.capture(catch=catch) as success:
486
487
        publisher.check(ext)
488
        tree = _get_tree(args, cwd, load=whole_tree)
489
        if not whole_tree:
490
            document = tree.find_document(args.prefix)
491
492
    if not success:
493
        return False
494
495
    # Set publishing arguments
496
    kwargs = {}
497
    if args.width:
498
        kwargs['width'] = args.width
499
500
    # Write to output file(s)
501
    if args.path:
502
        if whole_tree:
503
            msg = "publishing tree to '{}'...".format(args.path)
504
            utilities.show(msg, flush=True)
505
            path = publisher.publish(tree, args.path, ext, **kwargs)
506
        else:
507
            msg = "publishing document {} to '{}'...".format(document,
0 ignored issues
show
introduced by
The variable document does not seem to be defined in case BooleanNotNode on line 489 is False. Are you sure this can never be the case?
Loading history...
508
                                                             args.path)
509
            utilities.show(msg, flush=True)
510
            path = publisher.publish(document, args.path, ext, **kwargs)
511
        if path:
512
            utilities.show("published: {}".format(path))
513
514
    # Or, display to standard output
515
    else:
516
        if whole_tree:
517
            error("only single documents can be displayed")
518
        for line in publisher.publish_lines(document, ext, **kwargs):
519
            utilities.show(line)
520
521
    return True
522
523
                                                                           # add 28.11.2019 /11.12.2019 added findRef
524 View Code Duplication
def run_valMatrix(args, cwd, error, catch=True):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
525
    """
526
    Process arguments and run the `doorstop valMatrix` subcommand added by Peter Bauer.
527
528
    :param args: Namespace of CLI arguments
529
    :param cwd: current working directory
530
    :param error: function to call for CLI errors
531
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
532
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
533
    """
534
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
535
    # Get document or tree
536
    with utilities.capture(catch=catch) as success:
537
538
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
539
        tree = _get_tree(args, cwd, load='all')
540
        if not args.prefix == 'all':
541
             document = tree.find_document(args.prefix)
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 12 spaces were expected, but 13 were found.
Loading history...
542
543
    if not success:
544
        return False
545
546
547
    if args.path:
548
        if args.prefix == 'all':
549
            msg = "publishing tree to '{}'...".format(args.path)
550
            utilities.show(msg, flush=True)
551
            valMatrix.publish_Matrix(tree, args.path)
552
        else:
553
            msg = "publishing document to '{}'...".format(args.path)
554
            utilities.show(msg, flush=True)
555
            valMatrix.publish_Matrix(document, args.path)
0 ignored issues
show
introduced by
The variable document does not seem to be defined in case BooleanNotNode on line 540 is False. Are you sure this can never be the case?
Loading history...
556
557
    else:
558
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
559
        error('There must be a tree or documetn like object given to the command!')
560
561
    return True
562
563
564
565
566 View Code Duplication
def run_verMatrix(args, cwd, error, catch=True):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
567
    """
568
    Process arguments and run the `doorstop verMatrix` subcommand added by Peter Bauer.
569
570
    :param args: Namespace of CLI arguments
571
    :param cwd: current working directory
572
    :param error: function to call for CLI errors
573
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
574
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
575
    """
576
  
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
577
    # Get document or tree
578
    with utilities.capture(catch=catch) as success:
579
580
       
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
581
        tree = _get_tree(args, cwd, load='all')
582
        if not args.prefix == 'all':
583
             document = tree.find_document(args.prefix)
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 12 spaces were expected, but 13 were found.
Loading history...
584
585
    if not success:
586
        return False
587
588
    if args.path:
589
        if args.prefix == 'all':
590
            msg = "publishing tree to '{}'...".format(args.path)
591
            utilities.show(msg, flush=True)
592
            verMatrix.publish_Matrix(tree, args.path)
593
        else:
594
            msg = "publishing document to '{}'...".format(args.path)
595
            utilities.show(msg, flush=True)
596
            verMatrix.publish_Matrix(document, args.path)
0 ignored issues
show
introduced by
The variable document does not seem to be defined in case BooleanNotNode on line 582 is False. Are you sure this can never be the case?
Loading history...
597
598
    else:  
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
599
        error('There must be a tree or documetn like object given to the command!')
600
601
    return True
602
603
604
605
606
def run_findRef(args, cwd, error):
607
    """"Find references in code for given UID"""
608
    if args.UID and args.directory:
609
        finder.find(args, cwd)
610
    else: 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
611
        error('UID and directory must be given to find references!')
612
613
614
def run_ref(args, cwd, error):
0 ignored issues
show
Unused Code introduced by
The argument error seems to be unused.
Loading history...
615
    """Add new reference to requirement"""
616
    if args.UID:
617
        tree =  _get_tree(args, cwd)
0 ignored issues
show
Coding Style introduced by
Exactly one space required after assignment
Loading history...
618
        item = tree.find_item(args.UID)
619
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
620
        item.add_ref(args.reference) 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
621
          
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
622
623
    else:
624
        error:('UID must be given to add new reference!')
0 ignored issues
show
Coding Style introduced by
Exactly one space required after :
Loading history...
625
626
    return True
627
628
629
def run_clearRef(args, cwd, error, catch=True):
630
    """ Clear referneces if checked
631
632
    :param args: Namespace of CLI arguments
633
    :param cwd: current working directory
634
    :param error: function to call for CLI errors
635
    :param catch: catch and log :class:`~doorstop.common.DoorstopError`
636
637
    """
638
    with utilities.capture(catch=catch) as success:
639
640
        for item in _iter_items(args, cwd, error):
641
            if args.referenceID == 'all':
642
                msg = "clearing item {}'s suspect references...".format(item.uid)
643
            else:
644
                msg = "clearing item {}'s suspect references with ID: {}".format(item.uid, args.referenceID )
0 ignored issues
show
Coding Style introduced by
No space allowed before bracket
Loading history...
645
            item.clearRef(args.referenceID)  
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
646
            utilities.show(msg)     
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
647
648
    if not success:
649
        return False
650
651
    return True
652
653
654
                                                                                 #end add
655
656
657
658
def _request_next_number(args):
659
    """Get the server's "next number" method if a server exists."""
660
    if args.force:
661
        log.warn("creating items without the server...")
0 ignored issues
show
introduced by
Using deprecated method warn()
Loading history...
662
        return None
663
    else:
664
        server.check()
665
        return server.get_next_number
666
667
668
def _get_tree(args, cwd, request_next_number=None, load=False):
669
    """Build a tree and optionally load all documents.
670
671
    :param args: Namespace of CLI arguments
672
    :param cwd: current working directory
673
    :param request_next_number: server method to get a document's next number
674
    :param load: force the early loading of all documents
675
676
    :return: built :class:`~doorstop.core.tree.Tree`
677
678
    """
679
    utilities.show("building tree...", flush=True)
680
    tree = build(cwd=cwd, root=args.project,
681
                 request_next_number=request_next_number)
682
683
    if load:
684
        utilities.show("loading documents...", flush=True)
685
        tree.load()
686
687
    return tree
688
689
690
def _iter_items(args, cwd, error):
691
    """Build a tree and iterate through items.
692
693
    :param args: Namespace of CLI arguments
694
    :param cwd: current working directory
695
    :param error: function to call for CLI errors
696
697
    Items are filtered to:
698
699
    - `args.label` == 'all': all items
700
    - `args.label` == document prefix: the document's items
701
    - `args.label` == item UID: a single item
702
703
    Documents and items are inferred unless flagged by:
704
705
    - `args.document`: `args.label` is a prefix
706
    - `args.item`: `args.label` is an UID
707
708
    """
709
    # Parse arguments
710
    if args.label == 'all':
711
        if args.item:
712
            error("argument -i/--item: not allowed with 'all'")
713
        if args.document:
714
            error("argument -d/--document: not allowed with 'all'")
715
716
    # Build tree
717
    item = None
718
    document = None
719
    tree = tree = _get_tree(args, cwd)
720
721
    # Determine if tree, document, or item was requested
722
    if args.label != 'all':
723
        if not args.item:
724
            try:
725
                document = tree.find_document(args.label)
726
            except common.DoorstopError as exc:
727
                if args.document:
728
                    raise exc from None
729
        if not document:
730
            item = tree.find_item(args.label)
731
732
    # Yield items from the requested object
733
    if item:
734
        yield item
735
    elif document:
736
        for item in document:
737
            yield item
738
    else:
739
        for document in tree:
740
            for item in document:
741
                yield item
742
743
744
def _export_import(args, cwd, error, document, ext):
745
    """Edit a document by calling export followed by import.
746
747
    :param args: Namespace of CLI arguments
748
    :param cwd: current working directory
749
    :param error: function to call for CLI errors
750
    :param document: :class:`~doorstop.core.document.Document` to edit
751
    :param ext: extension for export format
752
753
    """
754
    # Export the document to file
755
    args.prefix = document.prefix
756
    path = "{}-{}{}".format(args.prefix, int(time.time()), ext)
757
    args.path = path
758
    get('export')(args, cwd, error, catch=False, auto=True,
759
                  _tree=document.tree)
760
761
    # Open the exported file
762
    editor.edit(path, tool=args.tool)
763
764
    # Import the file to the same document
765
    if utilities.ask("import from '{}'?".format(path)):
766
        args.attrs = {}
767
        args.map = {}
768
        get('import')(args, cwd, error, catch=False, _tree=document.tree)
769
        common.delete(path)
770
    else:
771
        utilities.show("import canceled")
772
        if utilities.ask("delete '{}'?".format(path)):
773
            common.delete(path)
774
        else:
775
            msg = "to manually import: doorstop import {0}".format(path)
776
            utilities.show(msg)
777