pdoc 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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