From 856cd1bb8441a4b027dfb56123958538a87c7c5b Mon Sep 17 00:00:00 2001 From: Loren Segal Date: Sun, 25 Oct 2009 00:16:07 -0400 Subject: [PATCH] Rewrite generators doc as Template arch --- .yardopts | 2 +- docs/Generators.md | 211 ---------------------------------- docs/Templates.md | 281 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+), 212 deletions(-) delete mode 100644 docs/Generators.md create mode 100644 docs/Templates.md diff --git a/.yardopts b/.yardopts index 5ac773e7e..6db65625f 100644 --- a/.yardopts +++ b/.yardopts @@ -6,7 +6,7 @@ docs/CodeObjects.md docs/Tags.md docs/Parser.md docs/Handlers.md -docs/Generators.md +docs/Templates.md docs/FAQ.md docs/Glossary.md LICENSE diff --git a/docs/Generators.md b/docs/Generators.md deleted file mode 100644 index ccf040cb0..000000000 --- a/docs/Generators.md +++ /dev/null @@ -1,211 +0,0 @@ -Generators Architecture -======================= - -Note: This document describes the architecture of the current generators -implementation which is likely to undergo large changes in the 0.2.4 -release. Keep this in mind if you plan on extending or implementing -custom generators. - -Generators are the main component in the output generation process of YARD, -which is invoked when conventional HTML/text output needs to be generated -for a set of code objects. - -Design Goals ------------- - -The general design attempts to be as abstracted from actual content and templates -as possible. Unlike RDoc which uses one file to describe the entire template, -YARD splits up the generation of code objects into small components, allowing -template modification for smaller subsets of a full template without having to -duplicate the entire template itself. This is necessary because of YARD's support -for plugins. YARD is designed for extensibility by external plugins, and because -of this, no one plugin can be responsible for the entire template because no -one plugin knows about the other plugins being used. For instance, if an RSpec -plugin was added to support and document specifications in class templates, -this information would need to be transparently added to the template to work -in conjunction with any other plugin that performed similar template modifications. -The design goals can be summarized as follows: - - 1. Output should be able to be generated for any arbitrary format with little - modification to YARD's source code. The addition of extra templates should - be sufficient. - 2. The output generated for an object should independently generated data - from arbitrary sources. These independent components are called "sections". - 3. Sections should be able to be inserted into any object without affecting - any existing sections in the document. This allows for easy modification - of templates by plugins. - -Generators ----------- - -Generator classes are the objects used to orchestrate the design goals listed -above. Specifically, they organize the sections and render the template contents -depending on the format. The main method used to initiate output is the -{YARD::Generators::Base#generate #generate} method which takes a list of -objects to generate output for. A good example of this is the FullDocGenerator, -which generates conventional HTML documentation: - - # all_objects is an array of module and class objects - Generators::FullDocGenerator.new(options).generate(all_objects) - -Generator Options ------------------ - -A generator keeps state when it is generating output. This state is kept in -an options hash which is initially passed to it during instantiation. Some -default options set the template style (`:template`), the output format (`:format`), -and the serializer to use (`:serializer`). For example, initializing the -{YARD::Generators::QuickDocGenerator} to output as text instead of HTML can be -done as follows: - - YARD::Generators::QuickDocGenerator.new(:format => :text).generate(objects) - -Serializer ----------- - -This class abstracts the logic involved in deciding how to serialize data to -the expected endpoint. For instance, there is both a {YARD::Serializers::StdoutSerializer StdoutSerializer} -and {YARD::Serializers::FileSystemSerializer FileSystemSerializer} class for -outputting to console or to a file respectively. When endpoints with locations -are used (like files or URLs), the serializer implements the {YARD::Serializers::Base#serialized_path #serialized_path} -method. This allows the translation from a code object to its path at the endpoint, -which enables inter-document linking. - -Generated objects are automatically serialized using the object if present, -otherwise the generated object is returned as a string to its parent. Nested -generator objects automatically set the serializer to nil so that they return -as a String to their parent. - -Templates ---------- - -Templates for a generator are by default found inside the one of the template -root paths (there can be multiple template paths). A standard template -directory looks like the following tree: - - (Assuming templates/ is a template root path) - templates/ - |-- default - | |-- attributes - | | |-- html - | | | `-- header.erb - | | `-- text - | | `-- header.erb - | |-- class - | | `-- html - | | `-- header.erb - | |-- constants - | | `-- html - | | |-- constants.erb - | | |-- header.erb - | | |-- included.erb - | | `-- inherited.erb - ... - -The path `default` refers to the template style and the directories at the next -level (such as `attributes`) refer to templates for a generator. The next directory -refers to the output format being used defined by the `:format` generator option. -As we saw in the above example, the format option can be set to `:text`, which -would use the `text/` directory instead of `html/`. Finally, the individual .erb -files are the sections that make up the generator. - -Sections --------- - -As mentioned above, sections are smaller components that correlate to template -fragments. Practically speaking, a section can either be a template fragment -(a conventional .erb file or other supported templating language), a method -(which returns a String) or another Generator object (which in turn has its own -list of sections). - -Creating a Generator --------------------- - -To create a generator, subclass {YARD::Generators::Base} and implement the -`#sections_for(object)` method. This method should return a list of sections where -a Symbol refers to a method or template name and a class refers to a generator. - - def sections_for(object) - case object - when MethodObject - [:main, [G(AnotherGenerator)], :footer] - else - [] - end - end - -A few points about the above example: - - * The method can return different lists depending on the object. - * The list of objects is not flat, we will see how nested lists can be used - in a future example. - * The convenience method `G()` instantiates a generator out of the class using - the existing options. - -If a section is a Symbol, the generator first checks if a method is defined -with that name, otherwise it checks in the template directories. If a method -by the symbol name is defined, you need to manually call {YARD::Generators::Base#render #render} -to return the contents of the template. - -Nested Sections ---------------- - -Sections often require the ability to encapsulate a set of sub-sections in markup -(HTML, for instance). Rather than use heavier Generator subclass objects, a more -lightweight solution is to nest a set of sub-sections as a list that follows -a section, for example: - - def sections_for(object) - [:header, [:section_a, :section_b]] - end - -The above example nests `section_a` and `section_b` within the `header` section. -Practically speaking, these sections can be placed in the result by `yield`ing -to them. A sample header.erb template might contain: - - <h2>Header</h2> - <div id="contents"> - <%= yield %> - </div> - -This template code would place the output of `section_a` and `section_b` within -the above div element. Using yield, we can also change the object that is being -generated. For example, we may want to yield the first method of the class. -We can do this like so: - - <h2>First method</h2> - <%= yield(current_object.meths.first) %> - -This would run the nested sections for the method object instead of the class. - -Before Filters --------------- - -Generators can run before filters using the {YARD::Generators::Base.before_section before_section} method -for all or a specific section to test if the section should be generated or -skipped. For instance, we can do the following to generate the section :foo only -for MethodObjects: - - class MyGenerator < YARD::Generators::Base - before_section :foo, :is_method? - - def sections_for(object) - [:foo, :bar, :baz] - end - - private - - def is_method?(object) - object.is_a?(MethodObject) - end - end - -Without the argument `:foo`, the before filter would be applied to all sections. -Note that we must return `false` to skip a section. A return type of nil is not -enough to skip the section. - -There is also a {YARD::Generators::Base.before_list before_list} method to run -a filter before the entire generator is run. This is useful for doing necessary -filesystem setup or for generating assets (stylesheets) before generating output -for the objects. Note that in this case you will need to serialize your data directly -using the serializer object (described above). diff --git a/docs/Templates.md b/docs/Templates.md new file mode 100644 index 000000000..169497d92 --- /dev/null +++ b/docs/Templates.md @@ -0,0 +1,281 @@ +Templates Architecture +====================== + +Templates are the main component in the output rendering process of YARD, +which is invoked when conventional HTML/text output needs to be rendered +for a set of code objects. + +Design Goals +------------ + +The general design attempts to be as abstracted from actual content and templates +as possible. Unlike RDoc which uses one file to describe the entire template, +YARD splits up the rendering of code objects into small components, allowing +template modification for smaller subsets of a full template without having to +duplicate the entire template itself. This is necessary because of YARD's support +for plugins. YARD is designed for extensibility by external plugins, and because +of this, no one plugin can be responsible for the entire template because no +one plugin knows about the other plugins being used. For instance, if an RSpec +plugin was added to support and document specifications in class templates, +this information would need to be transparently added to the template to work +in conjunction with any other plugin that performed similar template modifications. +The design goals can be summarized as follows: + + 1. Output should be able to be rendered for any arbitrary format with little + modification to YARD's source code. The addition of extra templates should + be sufficient. + 2. The output rendered for an object should independently rendered data + from arbitrary sources. These independent components are called "sections". + 3. Sections should be able to be inserted into any object without affecting + any existing sections in the document. This allows for easy modification + of templates by plugins. + +Templates +--------- + +Template modules are the objects used to orchestrate the design goals listed +above. Specifically, they organize the sections and render the template contents +depending on the format. + +Engine +------ + +The Engine class orchestrates the creation and rendering of Template modules and +handles serialization or specific rendering scenarios (like HTML). To create +a template, use the {YARD::Templates::Engine.template template} method. The two most +common methods used to initiate output are the {YARD::Templates::Engine.render render} +and {YARD::Templates::Engine.generate generate} methods which generate and +optionally serialize output to a file. The latter, `#generate`, is used +specially to generate HTML documentation and copy over assets that may be +needed. For instance, an object may be rendered with: + + YARD::Templates::Engine.render(:object => myobject) + +A set of objects may be rendered into HTML documentation by using: + + # all_objects is an array of module and class objects + # options includes a :serializer key to copy output to the file system + YARD::Templates::Engine.generate(all_objects, options) + +Note that these methods should not be called directly. The {YARD::CodeObjects::Base} +class has a {YARD::CodeObjects::Base#format #format} helper method to render an +object. For instance, the above render example is equivalent to the simple +call `myobject.format`. The `generate` method is a special kind of render +and is called from the {YARD::CLI::Yardoc} command line utility. + +Template Options +---------------- + +A template keeps state when it is rendering output. This state is kept in +an options hash which is initially passed to it during instantiation. Some +default options set the template style (`:template`), the output format (`:format`), +and the serializer to use (`:serializer`). This options hash is modifiable +from all methods seen above. For example, initializing a template to output as +HTML instead of text can be done as follows: + + myobject.format(:format => :html) + +Serializer +---------- + +This class abstracts the logic involved in deciding how to serialize data to +the expected endpoint. For instance, there is both a {YARD::Serializers::StdoutSerializer StdoutSerializer} +and {YARD::Serializers::FileSystemSerializer FileSystemSerializer} class for +outputting to console or to a file respectively. When endpoints with locations +are used (like files or URLs), the serializer implements the {YARD::Serializers::Base#serialized_path #serialized_path} +method. This allows the translation from a code object to its path at the endpoint, +which enables inter-document linking. + +Rendered objects are automatically serialized using the object if present, +otherwise the rendered object is returned as a string to its parent. Nested +Templates automatically set the serializer to nil so that they return +as a String to their parent. + +Creating a Template +------------------- + +Templates are represented by a directory inside the {YARD::Templates::Engine.template_paths} +on disk. A standard template directory looks like the following tree: + + (Assuming templates/ is a template path) + templates + `-- default + |-- class + | |-- dot + | | |-- setup.rb + | | `-- superklass.erb + | |-- html + | | |-- constructor_details.erb + | | |-- setup.rb + | | `-- subclasses.erb + | |-- setup.rb + | `-- text + | |-- setup.rb + | `-- subclasses.erb + |-- docstring + | |-- html + | | |-- abstract.erb + | | |-- deprecated.erb + | | |-- index.erb + | | `-- text.erb + | |-- setup.rb + | `-- text + | |-- abstract.erb + | |-- deprecated.erb + | |-- index.erb + | `-- text.erb + +The path `default` refers to the template style (:template key in options hash) +and the directories at the next level (such as `class`) refer to template +`:type` (options hash key) for a template. The next directory refers to the +output format being used defined by the `:format` template option. + +As we saw in the above example, the format option can be set to `:html`, which +would use the `html/` directory instead of `text/`. Finally, the individual .erb +files are the sections that make up the template. + +Note that the subdirectory `html/` is also its own "template" that inherits +from the parent directory. We will see more on this later. + +setup.rb +-------- + +Every template should have at least one `setup.rb` file that defines the +{YARD::Templates::Template#init #init} method to set the +{YARD::Templates::Template#sections #sections} used by the template. If +a setup.rb is not defined in the template itself, there should be a template +that is inherited (via parent directory or explcitly) that sets the sections +on a newly created template. + +A standard setup.rb file looks like: + + def init + sections :section1, :section2, :section3 + end + +Sections +-------- + +Sections are smaller components that correlate to template +fragments. Practically speaking, a section can either be a template fragment +(a conventional .erb file or other supported templating language), a method +(which returns a String) or another {YARD::Templates::Template} (which in turn has its own +list of sections). + +Nested Sections +--------------- + +Sections often require the ability to encapsulate a set of sub-sections in markup +(HTML, for instance). Rather than use heavier Template subclass objects, a more +lightweight solution is to nest a set of sub-sections as a list that follows +a section, for example: + + def sections_for(object) + [:header, [:section_a, :section_b]] + end + +The above example nests `section_a` and `section_b` within the `header` section. +Practically speaking, these sections can be placed in the result by `yield`ing +to them. A sample header.erb template might contain: + + <h2>Header</h2> + <div id="contents"> + <%= yield %> + </div> + +This template code would place the output of `section_a` and `section_b` within +the above div element. Using yield, we can also change the object that is being +rendered. For example, we may want to yield the first method of the class. +We can do this like so: + + <h2>First method</h2> + <%= yield(current_object.meths.first) %> + +This would run the nested sections for the method object instead of the class. + +Inheriting Templates +-------------------- + +Parent directory templates are automatically inherited (or mixed in, to be +more accurate) by the current template. This means that the 'default/class/html' +template automatically inherits from 'default/class'. This also means that anything +defined in 'default/class/setup.rb' can be overridden by 'default/class/html/setup.rb'. + +Since the Template module is a module, and not a class, they can be mixed in +explicitly (via include/extend) from other templates, which allows templates +to share erb files or helper logic. The 'default/class' template explicitly +mixes in the 'default/module' template, since it uses much of the same sections. +This is done with the helper {YARD::Templates::Template::ClassMethods#T T} method, which +is simply a shorthand for {YARD::Templates::Engine.template Engine.template}. +It can then override (using standard inheritance) the sections from the module +template and insert sections pertaining to classes. This is one of the design +goals described above. + +For instance, the first line in `default/class/html/setup.rb` is: + + include T('default/module/html') + +This includes the 'default/module/html', which means it also includes 'default/module' +by extension. This allows class to make use of any of module's erb files. + +Inserting Sections +------------------ + +The ability to insert sections was mentioned above. The class template, for +instance, will modify the #init method to insert class specific sections: + + def init + super + sections.place(:subclasses).before(:children) + sections.delete(:children) + sections.place([:constructor_details, [T('method_details')]]).before(:methodmissing) + end + +Observe how sections has been modified after the super method was called (the +super method would have been defined in `default/module/setup.rb`). The +custom method {Array#place} is added by YARD to allow sections to be inserted +before or after another section by it's given name rather than index. This +allows the overriding of templates in a way that does not depend on where +the section is located (since it may have been overriden by another module). + +Overriding Templates by Registering a Template Path +--------------------------------------------------- + +Inheriting templates explicitly is useful when creating a customized template +that wants to take advantage of code re-use. However, most users who want +to customize YARD templates will want to override existing behaviour without +creating a template from scratch. + +YARD solves this problem by allowing other template paths to be registered. +Because template modules are represented by a relative path such as 'default/class', +they can be found within any of the registered template paths. A new template +path is registered as: + + YARD::Templates::Engine.register_template_path '/path/to/mytemplates' + +At this point, any time the 'default/class' template is loaded, the template +will first be looked for inside the newly registered template path. If found, +it will be used as the template module, with the modules from the other +template paths implicitly mixed in. + +Therefore, by using the same directory structure as a builtin YARD template, +a user can customize or override individual templates as if the old ones were +inherited. A real world example would further modify the 'default/class' template +seen above by creating such a path in our '/path/to/mytemplates' custom template +path: + + /path/to/mytemplates/: + |-- class + | |-- html + | | |-- customsection.erb + | |-- setup.rb + +The `setup.rb` file would look like: + + def init + super + sections.push :customsection + end + +Now, when a class object is formatted as HTML, our customsection.erb will be +appended to the rendered data.