pdoc 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. data/README.markdown +34 -0
  2. data/Rakefile +46 -0
  3. data/bin/pdoc +58 -0
  4. data/lib/pdoc.rb +32 -0
  5. data/lib/pdoc/error.rb +4 -0
  6. data/lib/pdoc/generators.rb +6 -0
  7. data/lib/pdoc/generators/abstract_generator.rb +16 -0
  8. data/lib/pdoc/generators/html.rb +8 -0
  9. data/lib/pdoc/generators/html/helpers.rb +256 -0
  10. data/lib/pdoc/generators/html/page.rb +71 -0
  11. data/lib/pdoc/generators/html/syntax_highlighter.rb +41 -0
  12. data/lib/pdoc/generators/html/template.rb +37 -0
  13. data/lib/pdoc/generators/html/website.rb +194 -0
  14. data/lib/pdoc/generators/json.rb +15 -0
  15. data/lib/pdoc/generators/pythonesque.rb +105 -0
  16. data/lib/pdoc/models.rb +47 -0
  17. data/lib/pdoc/models/argument.rb +37 -0
  18. data/lib/pdoc/models/base.rb +107 -0
  19. data/lib/pdoc/models/callable.rb +19 -0
  20. data/lib/pdoc/models/class.rb +28 -0
  21. data/lib/pdoc/models/class_method.rb +18 -0
  22. data/lib/pdoc/models/class_property.rb +9 -0
  23. data/lib/pdoc/models/constant.rb +9 -0
  24. data/lib/pdoc/models/constructor.rb +14 -0
  25. data/lib/pdoc/models/container.rb +114 -0
  26. data/lib/pdoc/models/entity.rb +54 -0
  27. data/lib/pdoc/models/instance_method.rb +18 -0
  28. data/lib/pdoc/models/instance_property.rb +9 -0
  29. data/lib/pdoc/models/mixin.rb +10 -0
  30. data/lib/pdoc/models/namespace.rb +10 -0
  31. data/lib/pdoc/models/root.rb +27 -0
  32. data/lib/pdoc/models/section.rb +19 -0
  33. data/lib/pdoc/models/signature.rb +27 -0
  34. data/lib/pdoc/models/utility.rb +11 -0
  35. data/lib/pdoc/parser.rb +109 -0
  36. data/lib/pdoc/parser/argument_description_nodes.rb +21 -0
  37. data/lib/pdoc/parser/basic_nodes.rb +31 -0
  38. data/lib/pdoc/parser/description_nodes.rb +42 -0
  39. data/lib/pdoc/parser/documentation_nodes.rb +483 -0
  40. data/lib/pdoc/parser/ebnf_arguments_nodes.rb +58 -0
  41. data/lib/pdoc/parser/ebnf_expression_nodes.rb +227 -0
  42. data/lib/pdoc/parser/fragment.rb +55 -0
  43. data/lib/pdoc/parser/section_content_nodes.rb +19 -0
  44. data/lib/pdoc/parser/tags_nodes.rb +14 -0
  45. data/lib/pdoc/parser/treetop_files/argument_description.treetop +31 -0
  46. data/lib/pdoc/parser/treetop_files/basic.treetop +41 -0
  47. data/lib/pdoc/parser/treetop_files/description.treetop +7 -0
  48. data/lib/pdoc/parser/treetop_files/documentation.treetop +75 -0
  49. data/lib/pdoc/parser/treetop_files/ebnf_arguments.treetop +33 -0
  50. data/lib/pdoc/parser/treetop_files/ebnf_expression.treetop +70 -0
  51. data/lib/pdoc/parser/treetop_files/ebnf_javascript.treetop +54 -0
  52. data/lib/pdoc/parser/treetop_files/events.treetop +17 -0
  53. data/lib/pdoc/parser/treetop_files/section_content.treetop +8 -0
  54. data/lib/pdoc/parser/treetop_files/tags.treetop +31 -0
  55. data/lib/pdoc/runner.rb +110 -0
  56. data/lib/pdoc/treemaker.rb +94 -0
  57. data/pdoc.gemspec +31 -0
  58. data/templates/html/assets/images/pdoc/alias.png +0 -0
  59. data/templates/html/assets/images/pdoc/class.png +0 -0
  60. data/templates/html/assets/images/pdoc/class_deprecated.png +0 -0
  61. data/templates/html/assets/images/pdoc/class_method.png +0 -0
  62. data/templates/html/assets/images/pdoc/class_property.png +0 -0
  63. data/templates/html/assets/images/pdoc/constant.png +0 -0
  64. data/templates/html/assets/images/pdoc/constructor.png +0 -0
  65. data/templates/html/assets/images/pdoc/deprecated.png +0 -0
  66. data/templates/html/assets/images/pdoc/description.png +0 -0
  67. data/templates/html/assets/images/pdoc/information.png +0 -0
  68. data/templates/html/assets/images/pdoc/instance_method.png +0 -0
  69. data/templates/html/assets/images/pdoc/instance_property.png +0 -0
  70. data/templates/html/assets/images/pdoc/method.png +0 -0
  71. data/templates/html/assets/images/pdoc/method_deprecated.png +0 -0
  72. data/templates/html/assets/images/pdoc/mixin.png +0 -0
  73. data/templates/html/assets/images/pdoc/namespace.png +0 -0
  74. data/templates/html/assets/images/pdoc/property.png +0 -0
  75. data/templates/html/assets/images/pdoc/related_to.png +0 -0
  76. data/templates/html/assets/images/pdoc/search-background.png +0 -0
  77. data/templates/html/assets/images/pdoc/section-background.png +0 -0
  78. data/templates/html/assets/images/pdoc/section.png +0 -0
  79. data/templates/html/assets/images/pdoc/selected-section-background.png +0 -0
  80. data/templates/html/assets/images/pdoc/subclass.png +0 -0
  81. data/templates/html/assets/images/pdoc/superclass.png +0 -0
  82. data/templates/html/assets/images/pdoc/utility.png +0 -0
  83. data/templates/html/assets/javascripts/pdoc/application.js +478 -0
  84. data/templates/html/assets/javascripts/pdoc/prototype.js +4874 -0
  85. data/templates/html/assets/javascripts/pdoc/tabs.js +506 -0
  86. data/templates/html/assets/stylesheets/pdoc/api.css +677 -0
  87. data/templates/html/assets/stylesheets/pdoc/pygments.css +62 -0
  88. data/templates/html/helpers.rb +35 -0
  89. data/templates/html/index.erb +18 -0
  90. data/templates/html/item_index.js.erb +6 -0
  91. data/templates/html/layout.erb +67 -0
  92. data/templates/html/leaf.erb +22 -0
  93. data/templates/html/node.erb +30 -0
  94. data/templates/html/partials/class_relationships.erb +19 -0
  95. data/templates/html/partials/classes.erb +7 -0
  96. data/templates/html/partials/constructor.erb +5 -0
  97. data/templates/html/partials/description.erb +5 -0
  98. data/templates/html/partials/link_list.erb +1 -0
  99. data/templates/html/partials/method_signatures.erb +14 -0
  100. data/templates/html/partials/methodized_note.erb +9 -0
  101. data/templates/html/partials/mixins.erb +7 -0
  102. data/templates/html/partials/namespaces.erb +7 -0
  103. data/templates/html/partials/related_utilities.erb +5 -0
  104. data/templates/html/partials/relationships.erb +11 -0
  105. data/templates/html/partials/short_description_list.erb +7 -0
  106. data/templates/html/partials/title.erb +22 -0
  107. data/templates/html/section.erb +18 -0
  108. data/test/unit/parser/argument_description_test.rb +40 -0
  109. data/test/unit/parser/basic_test.rb +55 -0
  110. data/test/unit/parser/description_test.rb +34 -0
  111. data/test/unit/parser/documentation_test.rb +520 -0
  112. data/test/unit/parser/ebnf_arguments_test.rb +81 -0
  113. data/test/unit/parser/ebnf_expression_test.rb +382 -0
  114. data/test/unit/parser/ebnf_javascript_test.rb +37 -0
  115. data/test/unit/parser/events_test.rb +27 -0
  116. data/test/unit/parser/section_content_test.rb +44 -0
  117. data/test/unit/parser/tags_test.rb +39 -0
  118. data/test/unit/parser/test_fragment.rb +80 -0
  119. data/test/unit/parser_test_helper.rb +62 -0
  120. data/test/unit/runner/basic_test.rb +14 -0
  121. data/test/unit/templates/html_helpers_test.rb +25 -0
  122. metadata +222 -0
@@ -0,0 +1,71 @@
1
+ module PDoc
2
+ module Generators
3
+ module Html
4
+ class Page
5
+
6
+ include Helpers::BaseHelper
7
+ include Helpers::LinkHelper
8
+
9
+ def initialize(template, layout, variables = {})
10
+ @template = template
11
+ @layout = layout
12
+ assign_variables(variables)
13
+ end
14
+
15
+ # Renders the page as a string using the assigned layout.
16
+ def render
17
+ if @layout
18
+ @content_for_layout = Template.new(@template, @templates_directory).result(binding)
19
+ Template.new(@layout, @templates_directory).result(binding)
20
+ else
21
+ Template.new(@template, @templates_directory).result(binding)
22
+ end
23
+ end
24
+
25
+ # Creates a new file and renders the page to it
26
+ # using the assigned layout.
27
+ def render_to_file(filename)
28
+ filename ||= ""
29
+ FileUtils.mkdir_p(File.dirname(filename))
30
+ File.open(filename, "w+") { |f| f << render }
31
+ end
32
+
33
+ def include(path, options = {})
34
+ r = ''
35
+ options.each { |k, v| r << "#{k.to_s} = options[:#{k}];" }
36
+ eval(r)
37
+
38
+ if options[:collection]
39
+ options[:collection].map { |object| Template.new(path, @templates_directory).result(binding) }.join("\n")
40
+ else
41
+ Template.new(path, @templates_directory).result(binding)
42
+ end
43
+ end
44
+
45
+ private
46
+ def assign_variables(variables)
47
+ variables.each { |key, value| instance_variable_set("@#{key}", value) }
48
+ end
49
+ end
50
+
51
+ class DocPage < Page
52
+ include Helpers::LinkHelper, Helpers::CodeHelper, Helpers::MenuHelper
53
+
54
+ attr_reader :doc_instance, :depth, :root
55
+
56
+ def initialize(template, layout = "layout", variables = {})
57
+ if layout.is_a?(Hash)
58
+ variables = layout
59
+ layout = "layout"
60
+ end
61
+ super(template, layout, variables)
62
+ end
63
+
64
+ def htmlize(markdown)
65
+ super(auto_link_content(markdown))
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ module PDoc
2
+ module Generators
3
+ module Html
4
+ class SyntaxHighlighter
5
+ CODE_BLOCK_REGEXP = /(?:\n\n|\A)(?:\s{4,}lang(?:uage)?:\s*(\w+)\s*\n)?((?:\s{4}.*\n*)+)(^\s{0,3}\S|\z)?/
6
+
7
+ attr_reader :highlighter
8
+
9
+ def initialize(h = nil)
10
+ @highlighter = h.nil? ? :none : h.to_sym
11
+ end
12
+
13
+ def parse(input)
14
+ input.gsub(CODE_BLOCK_REGEXP) do |block|
15
+ language, codeblock, remainder = $1, $2, $3
16
+ codeblock = codeblock.gsub(/^\s{4}/, '').rstrip
17
+ "\n\n#{highlight_block(codeblock, language)}\n#{remainder}"
18
+ end
19
+ end
20
+
21
+ def highlight_block(code, language)
22
+ language = :javascript if language.nil?
23
+ case highlighter.to_sym
24
+ when :none
25
+ require 'cgi'
26
+ code = CGI.escapeHTML(code)
27
+ "<pre><code class=\"#{language}\">#{code}</code></pre>"
28
+ when :coderay
29
+ require 'coderay'
30
+ CodeRay.scan(code, language).div
31
+ when :pygments
32
+ require 'albino'
33
+ Albino.new(code, language).colorize
34
+ else
35
+ raise "Requested unsupported syntax highlighter: #{highlighter}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'erubis'
3
+ rescue LoadError
4
+ end
5
+
6
+ module PDoc
7
+ module Generators
8
+ module Html
9
+ class Template
10
+ def initialize(file_name = "layout.erb", templates_directory = nil)
11
+ @file_name = file_name
12
+ @templates_directory = templates_directory
13
+ @template = create_template(IO.read(file_path))
14
+ end
15
+
16
+ def result(binding)
17
+ @template.result(binding)
18
+ end
19
+
20
+ private
21
+ def file_path
22
+ @file_name << '.erb' unless @file_name =~ /\.erb$/
23
+ path = File.join(@templates_directory, @file_name.split("/"))
24
+ File.expand_path(path, DIR)
25
+ end
26
+
27
+ def create_template(input)
28
+ if defined?(Erubis)
29
+ Erubis::Eruby.new(input)
30
+ else
31
+ ERB.new(input, nil, '%')
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,194 @@
1
+ module PDoc
2
+ module Generators
3
+ module Html
4
+
5
+ unless defined? TEMPLATES_DIRECTORY
6
+ TEMPLATES_DIRECTORY = File.join(TEMPLATES_DIR, "html")
7
+ end
8
+
9
+ class Website < AbstractGenerator
10
+
11
+ include Helpers::BaseHelper
12
+ include Helpers::LinkHelper
13
+
14
+ class << Website
15
+ attr_accessor :syntax_highlighter
16
+ attr_accessor :markdown_parser
17
+ def pretty_urls?
18
+ !!@pretty_urls
19
+ end
20
+
21
+ def pretty_urls=(boolean)
22
+ @pretty_urls = boolean
23
+ end
24
+ end
25
+ attr_reader :templates_directory, :custom_assets, :index_page
26
+ def initialize(parser_output, options = {})
27
+ super
28
+ @templates_directory = File.expand_path(options[:templates] || TEMPLATES_DIRECTORY)
29
+ @index_page = options[:index_page] && File.expand_path(options[:index_page])
30
+ @custom_assets = @options[:assets] && File.expand_path(@options[:assets])
31
+ self.class.syntax_highlighter = SyntaxHighlighter.new(options[:syntax_highlighter])
32
+ self.class.pretty_urls = options[:pretty_urls]
33
+ set_markdown_parser(options[:markdown_parser])
34
+ load_custom_helpers
35
+ end
36
+
37
+ def set_markdown_parser(parser = nil)
38
+ parser = :rdiscount if parser.nil?
39
+ case parser.to_sym
40
+ when :rdiscount
41
+ require 'rdiscount'
42
+ self.class.markdown_parser = RDiscount
43
+ when :bluecloth
44
+ require 'bluecloth'
45
+ self.class.markdown_parser = BlueCloth
46
+ when :maruku
47
+ require 'maruku'
48
+ self.class.markdown_parser = Maruku
49
+ else
50
+ raise "Requested unsupported Markdown parser: #{parser}."
51
+ end
52
+ end
53
+
54
+ def load_custom_helpers
55
+ begin
56
+ require File.join(templates_directory, "helpers")
57
+ rescue LoadError => e
58
+ return nil
59
+ end
60
+ self.class.__send__(:include, Helpers::BaseHelper)
61
+ Page.__send__(:include, Helpers::BaseHelper)
62
+ Helpers.constants.map(&Helpers.method(:const_get)).each(&DocPage.method(:include))
63
+ end
64
+
65
+ # Generates the website to the specified directory.
66
+ def render(output)
67
+ @depth = 0
68
+ path = File.expand_path(output)
69
+ FileUtils.mkdir_p(path)
70
+ Dir.chdir(path) do
71
+
72
+ render_index
73
+ copy_assets
74
+ copy_custom_assets
75
+
76
+ render_children(root)
77
+ if root.sections?
78
+ root.sections.each do |section|
79
+ @depth = 0
80
+ render_template('section', { :doc_instance => section })
81
+ end
82
+ end
83
+
84
+ dest = File.join("javascripts", "pdoc", "item_index.js")
85
+ DocPage.new("item_index.js", false, variables).render_to_file(dest)
86
+ end
87
+ end
88
+
89
+ def render_index
90
+ vars = variables.merge(:index_page_content => index_page_content, :home => true)
91
+ DocPage.new('index', 'layout', vars).render_to_file('index.html')
92
+ end
93
+
94
+ def render_template(template, var = {})
95
+ @depth += 1
96
+ doc = var[:doc_instance]
97
+ dest = doc.url(File::SEPARATOR)
98
+ puts " Rendering #{dest}..."
99
+ FileUtils.mkdir_p(dest)
100
+ DocPage.new(template, variables.merge(var)).render_to_file(File.join(dest, 'index.html'))
101
+ render_json("#{dest}.json", doc) if json_api?
102
+ render_children(doc)
103
+ @depth -= 1
104
+ end
105
+
106
+ def render_json(dest, obj)
107
+ open(dest, 'w') { |file| file << obj.to_json }
108
+ end
109
+
110
+ def render_children(obj)
111
+ [:namespaces, :classes, :mixins].each do |prop|
112
+ obj.send(prop).each(&method(:render_node)) if obj.respond_to?(prop)
113
+ end
114
+
115
+ obj.utilities.each(&method(:render_leaf)) if obj.respond_to?(:utilities)
116
+ render_leaf(obj.constructor) if obj.respond_to?(:constructor) && obj.constructor
117
+
118
+ [:instance_methods, :instance_properties, :class_methods, :class_properties, :constants].each do |prop|
119
+ obj.send(prop).each(&method(:render_leaf)) if obj.respond_to?(prop)
120
+ end
121
+ end
122
+
123
+ # Copies the content of the assets folder to the generated website's
124
+ # root directory.
125
+ def copy_assets
126
+ FileUtils.cp_r(Dir.glob(File.join(templates_directory, "assets", "**")), '.')
127
+ end
128
+
129
+ def copy_custom_assets
130
+ if custom_assets
131
+ FileUtils.cp_r(Dir.glob(File.join(custom_assets, "**")), ".")
132
+ end
133
+ end
134
+
135
+ def render_leaf(object)
136
+ is_proto_prop = is_proto_prop?(object)
137
+ @depth += 1 if is_proto_prop
138
+ render_template('leaf', { :doc_instance => object })
139
+ @depth -= 1 if is_proto_prop
140
+ end
141
+
142
+ def render_node(object)
143
+ render_template('node', { :doc_instance => object })
144
+ end
145
+
146
+ private
147
+ def variables
148
+ {
149
+ :root => root,
150
+ :depth => @depth,
151
+ :templates_directory => templates_directory,
152
+ :name => @options[:name],
153
+ :short_name => @options[:short_name] || @options[:name],
154
+ :home_url => @options[:home_url],
155
+ :version => @options[:version],
156
+ :footer => footer,
157
+ :index_header => index_header,
158
+ :header => header,
159
+ :timestamp => timestamp
160
+ }
161
+ end
162
+
163
+ def header
164
+ @header ||= @options[:header] ? htmlize(@options[:header]) : ''
165
+ end
166
+
167
+ def index_header
168
+ @index_header ||= @options[:index_header] ? htmlize(@options[:index_header]) : ''
169
+ end
170
+
171
+ def footer
172
+ @footer ||= @options[:footer] ? htmlize(@options[:footer]) : ''
173
+ end
174
+
175
+ def timestamp
176
+ @timestamp ||= @options[:timestamp] == false ? nil : Time.now.utc
177
+ end
178
+
179
+ def json_api?
180
+ !!options[:json_api]
181
+ end
182
+
183
+ def is_proto_prop?(object)
184
+ object.is_a?(Models::InstanceMethod) ||
185
+ object.is_a?(Models::InstanceProperty)
186
+ end
187
+
188
+ def index_page_content
189
+ @index_page ? htmlize(File.read(@index_page)) : nil
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+ module PDoc
3
+ module Generators
4
+ class JSON < AbstractGenerator
5
+ def render(output)
6
+ open(output, "w+") do |file|
7
+ json = root.registry.map do |k, obj|
8
+ "#{k.inspect}: #{obj.to_json}"
9
+ end.join(",\n ")
10
+ file << "{\n #{json}\n}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,105 @@
1
+ module PDoc
2
+ module Generators
3
+ class Pythonesque < AbstractGenerator
4
+ def render(output)
5
+ open(output, "w+") do |file|
6
+ file << render_to_str
7
+ end
8
+ end
9
+
10
+ def render_to_str
11
+ selected_objects.map do |k, obj|
12
+ js_name = to_js_name(obj)
13
+ desc = Description.new(obj).to_escaped_str
14
+ " #{js_name}.__doc__ = '#{desc}';"
15
+ end.join("\n")
16
+ end
17
+
18
+ def to_js_name(obj)
19
+ if obj.is_a?(Models::InstanceMethod)
20
+ obj.full_name.sub('#', '.prototype.')
21
+ else
22
+ obj.full_name
23
+ end
24
+ end
25
+
26
+ def selected_objects
27
+ root.registry.select do |k, v|
28
+ (v.is_a?(Models::InstanceMethod) ||
29
+ v.is_a?(Models::ClassMethod) ||
30
+ v.is_a?(Models::Mixin) ||
31
+ v.is_a?(Models::Class) ||
32
+ v.is_a?(Models::Namespace) ||
33
+ v.is_a?(Models::Utility)) && !v.alias?
34
+ end
35
+ end
36
+
37
+ class Description
38
+ JS_ESCAPE_MAP = {
39
+ '\\' => '\\\\',
40
+ '</' => '<\/',
41
+ "\r\n" => '\n',
42
+ "\n" => '\n',
43
+ "\r" => '\n',
44
+ '"' => '\\"',
45
+ "'" => "\\'"
46
+ }
47
+
48
+ attr_reader :obj
49
+ def initialize(obj)
50
+ @obj = obj
51
+ end
52
+
53
+ def to_str
54
+ return "#{obj.full_name} has been deprecated." if obj.deprecated?
55
+ results = []
56
+ results << sig
57
+ results << args if obj.respond_to?(:arguments) && obj.arguments?
58
+ results << desc
59
+ results << aliases if obj.aliases?
60
+ if obj.respond_to?(:constructor) && obj.constructor
61
+ results << "\nWhen called as a constructor:\n"
62
+ results << Description.new(obj.constructor).to_str
63
+ end
64
+ results.join("\n")
65
+ end
66
+
67
+ def to_escaped_str
68
+ escape(to_str)
69
+ end
70
+
71
+ private
72
+ def escape(str)
73
+ str.gsub(/(\\|<\/|\r\n|[\n\r"'])/) {
74
+ JS_ESCAPE_MAP[$1]
75
+ }
76
+ end
77
+
78
+ def sig
79
+ if obj.signatures?
80
+ obj.signatures.map do |s|
81
+ s.return_value ? "#{s.name} -> #{s.return_value}" : s.name
82
+ end.join("\n")
83
+ else
84
+ obj.full_name
85
+ end
86
+ end
87
+
88
+ def aliases
89
+ aliases = obj.aliases.map { |a| a.full_name }.join(', ')
90
+ "Aliased as: #{aliases}."
91
+ end
92
+
93
+ def args
94
+ obj.arguments.map do |a|
95
+ " - #{a.name} (#{a.types.join(' | ')}): #{a.description.chomp}"
96
+ end.join("\n")
97
+ end
98
+
99
+ def desc
100
+ obj.short_description ? obj.short_description : ''
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end