diff --git a/awscli/clidocs.py b/awscli/clidocs.py index e063ac35100e..baab35ca1881 100644 --- a/awscli/clidocs.py +++ b/awscli/clidocs.py @@ -11,10 +11,12 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import logging +import os from bcdoc.docevents import DOC_EVENTS from awscli import SCALAR_TYPES from awscli.argprocess import ParamShorthandDocGen +from awscli.topictags import TopicTagDB LOG = logging.getLogger(__name__) @@ -167,7 +169,12 @@ def doc_relateditems_start(self, help_command, **kwargs): def doc_relateditem(self, help_command, related_item, **kwargs): doc = help_command.doc - doc.style.li(related_item) + doc.write('* ') + doc.style.sphinx_reference_label( + label='cli:%s' % related_item, + text=related_item + ) + doc.write('\n') class ProviderDocumentEventHandler(CLIDocumentEventHandler): @@ -494,26 +501,40 @@ def _do_doc_member_for_output(self, doc, member_name, member_shape, stack): class TopicListerDocumentEventHandler(CLIDocumentEventHandler): + DESCRIPTION = ( + 'This is the AWS CLI Topic Guide. It gives access to a set ' + 'of topics that provide a deeper understanding of the CLI. To access ' + 'the list of topics from the command line, run ``aws help topics``. ' + 'To access a specific topic from the command line, run ' + '``aws help [topicname]``, where ``topicname`` is the name of the ' + 'topic as it appears in the output from ``aws help topics``.') + def __init__(self, help_command): self.help_command = help_command self.register(help_command.session, help_command.event_class) self.help_command.doc.translation_map = self.build_translation_map() + self._topic_tag_db = TopicTagDB() + self._topic_tag_db.load_json_index() def doc_breadcrumbs(self, help_command, **kwargs): doc = help_command.doc if doc.target != 'man': - doc.write('[ :ref:`aws ` ]') + doc.write('[ ') + doc.style.sphinx_reference_label(label='cli:aws', text='aws') + doc.write(' ]') def doc_title(self, help_command, **kwargs): doc = help_command.doc doc.style.new_paragraph() - doc.writeln('.. _cli:aws help %s:' % self.help_command.name) - doc.style.h1(help_command.title) + doc.style.link_target_definition( + refname='cli:aws help %s' % self.help_command.name, + link='') + doc.style.h1('AWS CLI Topic Guide') def doc_description(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Description') - doc.include_doc_string(help_command.description) + doc.include_doc_string(self.DESCRIPTION) doc.style.new_paragraph() def doc_synopsis_start(self, help_command, **kwargs): @@ -532,28 +553,30 @@ def doc_subitems_start(self, help_command, **kwargs): doc = help_command.doc doc.style.h2('Available Topics') - categories = help_command.categories - entries = help_command.entries + categories = self._topic_tag_db.query('category') + topic_names = self._topic_tag_db.get_all_topic_names() + # Sort the categories category_names = sorted(categories.keys()) for category_name in category_names: doc.style.h3(category_name) doc.style.new_paragraph() - # For each topics under the category, list the topic's entry. + # Write out the topic and a description for each topic under + # each category. for topic_name in sorted(categories[category_name]): - doc.style.li(entries[topic_name]) - # Make a hidden toctree in order to link the topics files - # with the rest of CLI documents without actaully showing it - # in the man or html pages - self._make_hidden_toctree(help_command.doc, help_command.topic_names) - - def _make_hidden_toctree(self, doc, items): - if doc.target == 'html': - doc.write('\n.. toctree::\n') - doc.write(' :maxdepth: 1\n') - doc.write(' :hidden:\n\n') - for item in items: - doc.writeln(' %s' % item) + description = self._topic_tag_db.get_tag_single_value( + topic_name, 'description') + doc.write('* ') + doc.style.sphinx_reference_label( + label='cli:aws help %s' % topic_name, + text=topic_name + ) + doc.write(': %s\n' % description) + # Add a hidden toctree to make sure everything is connected in + # the document. + doc.style.hidden_toctree() + for topic_name in topic_names: + doc.style.hidden_tocitem(topic_name) class TopicDocumentEventHandler(TopicListerDocumentEventHandler): @@ -561,14 +584,53 @@ class TopicDocumentEventHandler(TopicListerDocumentEventHandler): def doc_breadcrumbs(self, help_command, **kwargs): doc = help_command.doc if doc.target != 'man': - doc.write( - '[ :ref:`aws ` . ' - ':ref:`topics ` ]') + doc.write('[ ') + doc.style.sphinx_reference_label(label='cli:aws', text='aws') + doc.write(' . ') + doc.style.sphinx_reference_label( + label='cli:aws help topics', + text='topics' + ) + doc.write(' ]') + + def doc_title(self, help_command, **kwargs): + doc = help_command.doc + doc.style.new_paragraph() + doc.style.link_target_definition( + refname='cli:aws help %s' % self.help_command.name, + link='') + title = self._topic_tag_db.get_tag_single_value( + help_command.name, 'title') + doc.style.h1(title) def doc_description(self, help_command, **kwargs): doc = help_command.doc - doc.writeln(help_command.contents) + topic_filename = os.path.join(self._topic_tag_db.topic_dir, + help_command.name + '.rst') + contents = self._remove_tags_from_content(topic_filename) + doc.writeln(contents) doc.style.new_paragraph() + def _remove_tags_from_content(self, filename): + with open(filename, 'r') as f: + lines = f.readlines() + + content_begin_index = 0 + for i, line in enumerate(lines): + # If a line is encountered that does not begin with the tag + # end the search for tags and mark where tags end. + if not self._line_has_tag(line): + content_begin_index = i + break + + # Join all of the non-tagged lines back together. + return ''.join(lines[i:]) + + def _line_has_tag(self, line): + for tag in self._topic_tag_db.valid_tags: + if line.startswith(':' + tag + ':'): + return True + return False + def doc_subitems_start(self, help_command, **kwargs): pass diff --git a/awscli/clidriver.py b/awscli/clidriver.py index ef9532550d1b..75864b221e09 100644 --- a/awscli/clidriver.py +++ b/awscli/clidriver.py @@ -501,7 +501,7 @@ def __call__(self, args, parsed_globals): parsed_args, remaining = operation_parser.parse_known_args(args) if parsed_args.help == 'help': op_help = self.create_help_command() - return op_help(parsed_args, parsed_globals) + return op_help(remaining, parsed_globals) elif parsed_args.help: remaining.append(parsed_args.help) if remaining: diff --git a/awscli/help.py b/awscli/help.py index 292cccf7ed28..c122f4fa7fbd 100644 --- a/awscli/help.py +++ b/awscli/help.py @@ -188,6 +188,7 @@ def __init__(self, session, obj, command_table, arg_table): if arg_table is None: arg_table = {} self.arg_table = arg_table + self._subcommand_table = {} self._related_items = [] self.renderer = get_renderer() self.doc = ReSTDocument(target='man') @@ -217,12 +218,24 @@ def name(self): """ pass + @property + def subcommand_table(self): + """These are the commands that may follow after the help command""" + return self._subcommand_table + @property def related_items(self): """This is list of items that are related to the help command""" return self._related_items def __call__(self, args, parsed_globals): + if args: + subcommand_parser = ArgTableArgParser({}, self.subcommand_table) + parsed, remaining = subcommand_parser.parse_known_args(args) + if getattr(parsed, 'subcommand', None) is not None: + return self.subcommand_table[parsed.subcommand](remaining, + parsed_globals) + # Create an event handler for a Provider Document instance = self.EventHandlerClass(self) # Now generate all of the events for a Provider document. @@ -248,10 +261,9 @@ def __init__(self, session, command_table, arg_table, self.description = description self.synopsis = synopsis self.help_usage = usage - self._topic_table = None + self._subcommand_table = None self._topic_tag_db = None - self._related_items = [ - 'AWS CLI Topic Guide (`aws help topics <../topic/index.html>`_)'] + self._related_items = ['aws help topics'] @property def event_class(self): @@ -262,37 +274,28 @@ def name(self): return self.obj.name @property - def topic_table(self): - if self._topic_table is None: + def subcommand_table(self): + if self._subcommand_table is None: if self._topic_tag_db is None: self._topic_tag_db = TopicTagDB() self._topic_tag_db.load_json_index() - self._topic_table = self._create_topic_table() - return self._topic_table + self._subcommand_table = self._create_subcommand_table() + return self._subcommand_table - def _create_topic_table(self): - topic_table = {} + def _create_subcommand_table(self): + subcommand_table = {} # Add the ``aws help topics`` command to the ``topic_table`` topic_lister_command = TopicListerCommand( self.session, self._topic_tag_db) - topic_table['topics'] = topic_lister_command + subcommand_table['topics'] = topic_lister_command topic_names = self._topic_tag_db.get_all_topic_names() # Add all of the possible topics to the ``topic_table`` for topic_name in topic_names: topic_help_command = TopicHelpCommand( self.session, topic_name, self._topic_tag_db) - topic_table[topic_name] = topic_help_command - return topic_table - - def __call__(self, args, parsed_globals): - if args: - topic_parser = ArgTableArgParser({}, self.topic_table) - parsed_topic, remaining = topic_parser.parse_known_args(args) - self.topic_table[parsed_topic.subcommand].__call__( - args, parsed_globals) - else: - super(ProviderHelpCommand, self).__call__(args, parsed_globals) + subcommand_table[topic_name] = topic_help_command + return subcommand_table class ServiceHelpCommand(HelpCommand): @@ -350,21 +353,11 @@ def name(self): class TopicListerCommand(HelpCommand): EventHandlerClass = TopicListerDocumentEventHandler - DESCRIPTION = ( - 'This is the AWS CLI Topic Guide. It gives access to a set ' - 'of topics that provide a deeper understanding of the CLI. To access ' - 'the list of topics from the command line, run ``aws help topics``. ' - 'To access a specific topic from the command line, run ' - '``aws help [topicname]``, where ``topicname`` is the name of the ' - 'topic as it appears in the output from ``aws help topics``.') - def __init__(self, session, topic_tag_db): super(TopicListerCommand, self).__init__(session, None, {}, {}) self._topic_tag_db = topic_tag_db self._categories = None self._entries = None - self._related_items = [ - 'AWS CLI Reference Guide (`aws help <../reference/index.html>`_)'] @property def event_class(self): @@ -374,54 +367,6 @@ def event_class(self): def name(self): return 'topics' - @property - def title(self): - return 'AWS CLI Topic Guide' - - @property - def description(self): - return self.DESCRIPTION - - @property - def categories(self): - """ - :returns: A dictionary where the keys are all of the possible - topic categories and the values are all of the topics that - are grouped in that category - """ - if self._categories is None: - self._categories = self._topic_tag_db.query('category') - return self._categories - - @property - def topic_names(self): - """ - :returns: A list of all of the topic names. - """ - return self._topic_tag_db.get_all_topic_names() - - @property - def entries(self): - """ - :returns: A dictionary where the keys are the names of all topics - and the values are the respective entries that appears in the - catagory that a topic belongs to - """ - if self._entries is None: - topic_entry_template = '`%s <%s.html>`_: %s' - topic_entries = {} - for topic_name in self.topic_names: - # Get the description of the topic. - sentence_description = self._topic_tag_db.get_tag_single_value( - topic_name, 'description') - # Create the full entry description. - full_description = topic_entry_template % ( - topic_name, topic_name, sentence_description) - # Add the entry to the dictionary. - topic_entries[topic_name] = full_description - self._entries = topic_entries - return self._entries - class TopicHelpCommand(HelpCommand): EventHandlerClass = TopicDocumentEventHandler @@ -439,41 +384,3 @@ def event_class(self): @property def name(self): return self._topic_name - - @property - def title(self): - return self._topic_tag_db.get_tag_single_value( - self._topic_name, 'title') - - @property - def contents(self): - """ - :returns: The contents of the topic source file with all of its tags - excluded. - """ - if self._contents is None: - topic_filename = os.path.join(self._topic_tag_db.topic_dir, - self.name + '.rst') - self._contents = self._remove_tags_from_content(topic_filename) - return self._contents - - def _remove_tags_from_content(self, filename): - with open(filename, 'r') as f: - lines = f.readlines() - - content_begin_index = 0 - for i, line in enumerate(lines): - # If a line is encountered that does not begin with the tag - # end the search for tags and mark where tags end. - if not self._line_has_tag(line): - content_begin_index = i - break - - # Join all of the non-tagged lines back together. - return ''.join(lines[i:]) - - def _line_has_tag(self, line): - for tag in self._topic_tag_db.valid_tags: - if line.startswith(':' + tag + ':'): - return True - return False diff --git a/doc/source/htmlgen b/doc/source/htmlgen index 8cb2ee3fbb26..e2f1a63bf6ef 100755 --- a/doc/source/htmlgen +++ b/doc/source/htmlgen @@ -78,17 +78,17 @@ def do_provider(driver): help_command.renderer = FileRenderer(os.path.join(REF_PATH, 'index.rst')) help_command(None, None) - topic_help_command = help_command.topic_table['topics'] + topic_help_command = help_command.subcommand_table['topics'] topic_help_command.renderer = FileRenderer(os.path.join(TOPIC_PATH, 'index.rst')) topic_help_command.doc.target = 'html' help_command(['topics'], None) - topics = help_command.topic_table + topics = help_command.subcommand_table print('Writing topics:') for topic in topics: if topic == 'topics': continue - topic_help_command = help_command.topic_table[topic] + topic_help_command = help_command.subcommand_table[topic] do_topic(driver, TOPIC_PATH, topic_help_command) services = sorted(help_command.command_table) diff --git a/tests/unit/docs/test_help_output.py b/tests/unit/docs/test_help_output.py index d26a5efd13d8..3bb21df49768 100644 --- a/tests/unit/docs/test_help_output.py +++ b/tests/unit/docs/test_help_output.py @@ -53,7 +53,7 @@ def test_output(self): self.assert_contains('* sts') # Make sure it its a related item self.assert_contains('========\nSee Also\n========') - self.assert_contains('AWS CLI Topic Guide') + self.assert_contains('aws help topics') def test_service_help_output(self): self.driver.main(['ec2', 'help']) @@ -102,34 +102,23 @@ def test_topic_list_help_output(self): self.assert_contains('Available Topics') # Assert the general order of topic categories. self.assert_text_order( - '--------------\nGeneral Topics\n--------------', + '-------\nGeneral\n-------', '--\nS3\n--', - '---------------\nTroubleshooting\n---------------', starting_from='Available Topics' ) # Make sure that the topic elements elements show up as well. self.assert_contains( - '* `return-codes `_: Describes' + '* return-codes: Describes' ) # Make sure the topic elements are underneath the categories as well # and they get added to each category they fall beneath self.assert_text_order( - '--------------\nGeneral Topics\n--------------', - '* `return-codes `_: Describes', + '-------\nGeneral\n-------', + '* return-codes: Describes', '--\nS3\n--', - starting_from='--------------\nGeneral Topics\n--------------' + starting_from='-------\nGeneral\n-------' ) - self.assert_text_order( - '--\nS3\n--', - '* `return-codes `_: Describes', - '---------------\nTroubleshooting\n---------------', - starting_from='--\nS3\n--' - ) - # Make sure it its a related item - self.assert_contains('========\nSee Also\n========') - self.assert_contains('AWS CLI Reference Guide') - def test_topic_help_command(self): self.driver.main(['help', 'return-codes']) self.assert_contains(