reacco 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /doc/
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/HISTORY.md ADDED
@@ -0,0 +1,4 @@
1
+ v0.0.2 - Sep 19, 2011
2
+ ---------------------
3
+
4
+ Initial semi-public version.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # Reacco
2
+ #### Readme documentation prettifier
3
+
4
+ In progress. Check out http://ricostacruz.com/sinatra-assetpack for an example.
5
+
6
+ # API reference
7
+
8
+ (API reference goes here)
9
+
10
+ Acknowledgements
11
+ ----------------
12
+
13
+ © 2011, Rico Sta. Cruz. Released under the [MIT
14
+ License](http://www.opensource.org/licenses/mit-license.php).
15
+
16
+ Reacco is authored and maintained by [Rico Sta. Cruz][rsc] with help from it's
17
+ [contributors][c]. It is sponsored by my startup, [Sinefunc, Inc][sf].
18
+
19
+ * [My website](http://ricostacruz.com) (ricostacruz.com)
20
+ * [Sinefunc, Inc.](http://sinefunc.com) (sinefunc.com)
21
+ * [Github](http://github.com/rstacruz) (@rstacruz)
22
+ * [Twitter](http://twitter.com/rstacruz) (@rstacruz)
23
+
24
+ [rsc]: http://ricostacruz.com
25
+ [c]: http://github.com/rstacruz/reacco/contributors
26
+ [sf]: http://sinefunc.com
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ desc "Invokes the test suite in multiple RVM environments"
2
+ task :'test!' do
3
+ # Override this by adding RVM_TEST_ENVS=".." in .rvmrc
4
+ envs = ENV['RVM_TEST_ENVS'] || '1.9.2@reacco,,1.8.7@reacco'
5
+ puts "* Testing in the following RVM environments: #{envs.gsub(',', ', ')}"
6
+ system "rvm #{envs} rake test" or abort
7
+ end
8
+
9
+ desc "Runs tests"
10
+ task :test do
11
+ Dir['test/*_test.rb'].each { |f| load f }
12
+ end
13
+
14
+ task :default => :test
15
+
16
+ gh = "rstacruz/reacco"
17
+ namespace :doc do
18
+ # http://github.com/rstacruz/reacco
19
+ desc "Builds the documentation into doc/"
20
+ task :build do
21
+ system "reacco -a --github #{gh} --api lib"
22
+ end
23
+
24
+ # http://github.com/rstacruz/git-update-ghpages
25
+ desc "Posts documentation to GitHub pages"
26
+ task :deploy => :build do
27
+ system "git update-ghpages #{gh} -i doc/"
28
+ end
29
+ end
30
+
31
+ task :doc => :'doc:build'
data/bin/reacco ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+ require 'reacco'
4
+
5
+ module Params
6
+ def extract(what) i = index(what) and slice!(i, 2)[1] end;
7
+ end
8
+ ARGV.extend Params
9
+
10
+ def tip(str)
11
+ $stderr.write "#{str}\n"
12
+ end
13
+
14
+ if ARGV == ['-h'] || ARGV == ['--help']
15
+ tip "Usage: reacco [options]"
16
+ tip "Generates nice HTML."
17
+ tip ""
18
+ tip "Options:"
19
+ tip " --literate Move <pre> blocks to the right."
20
+ tip " --toc Show a table of contents."
21
+ tip ""
22
+ tip "Theming:"
23
+ tip " --css FILE Append the CSS from that file to the generated CSS."
24
+ tip " -t, --template PATH The template to use, if you don't want the default."
25
+ tip ""
26
+ tip "API extraction:"
27
+ tip " --api PATH Where to get stuff."
28
+ tip ""
29
+ tip "More:"
30
+ tip " --github REPO Adds a 'Fork me on GitHub' badge. Repo should be in"
31
+ tip " 'username/reponame' format."
32
+ tip " -o, --output PATH Sets the path to put HTML files in. Defaults to 'doc/'."
33
+ tip ""
34
+ exit
35
+
36
+ else
37
+ # Build options from arguments.
38
+ switches = [:literate, :toc]
39
+ docpath = ARGV.extract('-o') || ARGV.extract('--output') || 'doc'
40
+ template = ARGV.extract('-t') || ARGV.extract('--template') || nil
41
+ github = ARGV.extract('--github') || nil
42
+ css = ARGV.extract('--css') || nil
43
+ awesome = ARGV.delete('-a') || ARGV.extract('--awesome')
44
+ options = Hash.new
45
+
46
+ extract = Array.new
47
+ while (x = ARGV.extract('--api'))
48
+ extract << x
49
+ end
50
+
51
+ switches.each do |opt|
52
+ options[opt] = true if ARGV.delete("--#{opt}")
53
+ end
54
+
55
+ switches.each { |opt| options[opt] = true } if awesome
56
+
57
+ options[:github] = github if github
58
+
59
+ if ARGV.any?
60
+ puts "Invalid usage. See `reacco --help` for help."
61
+ exit 60
62
+ end
63
+
64
+ # Lets find the readme.
65
+ readme = Reacco::Readme.new(options)
66
+ unless readme.exists?
67
+ tip "Readme file not found."
68
+ exit 60
69
+ end
70
+
71
+ # If we need to extract API, let's do it.
72
+ if extract.any?
73
+ blocks = extract.map { |path|
74
+ Reacco::Extractor.new(Dir["#{path}/**/*"]).blocks
75
+ }.flatten
76
+
77
+ blocks.each { |blk| readme.inject_api_block blk.to_html }
78
+ end
79
+
80
+ # Now let's generate it.
81
+ gen = Reacco::Generator.new(readme, docpath, template, css)
82
+ unless gen.template?
83
+ tip "Invalid template."
84
+ tip "A template must at least have an index.xxx page."
85
+ exit 61
86
+ end
87
+
88
+ gen.write! { |file| tip "* #{file}" }
89
+
90
+ # AHHH YYEAAAHHH!
91
+ tip "Construction complete."
92
+ end
93
+
94
+
95
+ #tip Reacco.html(:hgroup => true, :literate => true, :brief => true)
data/data/index.erb ADDED
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title><%= title %></title>
6
+ <link href="style.css" rel="stylesheet" />
7
+ <link href='http://fonts.googleapis.com/css?family=Shanti:400|PT+Sans:700,700italic' rel='stylesheet' type='text/css'>
8
+ </head>
9
+ <body class='<%= body_class %>'>
10
+ <% if github %>
11
+ <a href="<%= github %>"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://a248.e.akamai.net/assets.github.com/img/e6bef7a091f5f3138b8cd40bc3e114258dd68ddf/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub"></a>
12
+ <% end %>
13
+ <div id='all'><%= yield %></div>
14
+ <script type='text/javascript' src='http://cachedcommons.org/cache/prettify/1.0.0/javascripts/prettify-min.js'></script>
15
+ <script>prettyPrint();</script>
16
+ </body>
17
+ </html>
data/data/style.css ADDED
@@ -0,0 +1,341 @@
1
+ html, body {
2
+ margin: 0;
3
+ padding: 0; }
4
+
5
+ body {
6
+ background: #fcfcfa;
7
+ font-family: 'pt sans', sans-serif;
8
+ color: #444;
9
+ font-size: 11pt;
10
+ line-height: 1.5; }
11
+
12
+ ::-moz-selection { background: #88a; color: #fff; text-shadow: none; }
13
+ ::selection { background: #88a; color: #fff; text-shadow: none; }
14
+
15
+ h1, h2, h3, h4, h5, h6, p, ol, ul, pre, ol>li {
16
+ max-width: 500px;
17
+ margin: 20px 0; }
18
+
19
+ ol, ul {
20
+ padding-left: 0;
21
+ margin-left: 30px; }
22
+
23
+ #all {
24
+ min-width: 960px;
25
+ box-sizing: border-box;
26
+ -moz-box-sizing: border-box;
27
+ -webkit-box-sizing: border-box;
28
+ width: 100%;
29
+ overflow-x: hidden;
30
+ padding: 40px; }
31
+
32
+ a, a:visited {
33
+ text-decoration: none;
34
+ border-bottom: solid 1px #ccf;
35
+ color: #46b; }
36
+
37
+ a:hover {
38
+ background: #fafae0; }
39
+
40
+ /* List */
41
+ ul li > p > strong:first-child {
42
+ display: block;
43
+ font-size: 1.1em; }
44
+
45
+ /* Headings */
46
+ h1 {
47
+ font-family: palatino, serif; }
48
+
49
+ h1, h2, h3, h4, h5, h6 {
50
+ text-shadow: 2px 2px 0 rgba(55, 55, 55, 0.1);
51
+ color: #568;
52
+ font-weight: normal;
53
+ font-family: shanti, palatino, serif; }
54
+
55
+ h3 {
56
+ color: #77a; }
57
+
58
+ h4, h5, h6 {
59
+ color: #88a; }
60
+
61
+ /* The "(class method)" thing */
62
+ h1 .type, h2 .type, h3 .type, h4 .type, h5 .type, h6 .type {
63
+ font-size: 9pt;
64
+ color: #aaa;
65
+ text-transform: uppercase;
66
+ margin-left: 10px; }
67
+
68
+ h2 {
69
+ max-width: none;
70
+ padding-top: 30px;
71
+ margin-top: 40px; }
72
+
73
+ h3 {
74
+ max-width: none;
75
+ padding-top: 10px;
76
+ margin-top: 40px; }
77
+
78
+ h1 { font-size: 30pt; }
79
+ h2 { font-size: 23pt; }
80
+ h3 { font-size: 14pt; }
81
+ h4 { font-size: 14pt; }
82
+ h5 { font-size: 10pt; }
83
+ h6 { font-size: 10pt; }
84
+
85
+ h2+p { margin-top: 0; }
86
+ h2+pre.right+p { margin-top: 0; }
87
+
88
+ /* Code */
89
+ pre, code {
90
+ font-family: Monaco, monospace; }
91
+
92
+ code {
93
+ text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.99);
94
+ font-family: 'shanti', sans-serif;
95
+ color: #9090aa; }
96
+
97
+ pre > code {
98
+ color: #60606f;
99
+ text-shadow: none;
100
+ font-family: Monaco, monospace;
101
+ background: 0;
102
+ padding: 0;
103
+ border: 0; }
104
+
105
+ pre {
106
+ font-size: 9pt;
107
+ padding-top: 5px;
108
+ background: transparent; }
109
+
110
+ /* The header */
111
+ .header h1, .header h2, .header h3,
112
+ .header h4, .header h5, .header h6 {
113
+ text-shadow: none;
114
+ line-height: 1.35;
115
+ margin: 0;
116
+ padding: 0; }
117
+
118
+ .header h1 a,
119
+ .header h1 {
120
+ font-family: 'pt sans', sans-serif;
121
+ font-weight: bold;
122
+ color: #779; }
123
+
124
+ .header h1 a {
125
+ border-bottom: 0; }
126
+
127
+ .header h2, .header h3,
128
+ .header h4, .header h5, .header h6 {
129
+ color: #aab; } /* color: #78b; */
130
+
131
+ .header h4 {
132
+ font-size: 1.4em;
133
+ font-family: palatino, serif;
134
+ font-style: italic;
135
+ font-weight: normal; }
136
+
137
+ /* Literate */
138
+ body.literate h4, body.literate h5, body.literate h6, body.literate p,
139
+ body.literate ol, body.literate ul, body.literate pre {
140
+ max-width: 400px; }
141
+
142
+ body.literate pre.right {
143
+ clear: both;
144
+ max-width: none;
145
+ width: 490px;
146
+ margin-left: 50px;
147
+ float: right; }
148
+
149
+ body.literate pre.right code {
150
+ color: #78a; }
151
+
152
+ body.literate h1 + pre.right,
153
+ body.literate h2 + pre.right,
154
+ body.literate h3 + pre.right {
155
+ margin-top: 0; }
156
+
157
+ h1, h2, h3 {
158
+ clear: both; }
159
+
160
+ .clear {
161
+ clear: both; }
162
+
163
+ br.post-pre {
164
+ clear: both; }
165
+
166
+ h4 { margin-bottom: 5px; }
167
+ h4+p,
168
+ h4+pre.right,
169
+ h4+pre.right+p { margin-top: 0; }
170
+
171
+ /* Section-wrap */
172
+ section {
173
+ width: 960px;
174
+ margin-top: 0;
175
+ margin-left: 0;
176
+ padding-left: 40px;
177
+ margin-left: -40px;
178
+ padding-right: 9000px; }
179
+
180
+ section::after {
181
+ content: '';
182
+ display: table;
183
+ clear: both; }
184
+
185
+ section.h2:nth-child(even) {
186
+ box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
187
+ background: rgba(255, 255, 255, 0.8); }
188
+
189
+ section:first-of-type {
190
+ margin-top: 40px; }
191
+
192
+ section.h1, section.h2, section.h3 {
193
+ padding-top: 30px;
194
+ padding-bottom: 40px;
195
+ border-top: solid 1px #eee; }
196
+
197
+ section.h1 {
198
+ border-top: solid 10px #ddd; }
199
+
200
+ section.h3 {
201
+ padding-top: 20px;
202
+ margin-top: 40px;
203
+ padding-left: 30px;
204
+ border-left: solid 10px #e7e7f4;
205
+ border-top: dotted 1px #ddd; }
206
+
207
+ section.h3:nth-child(odd) {
208
+ border-left-color: #d0d0e8; }
209
+
210
+ section.h3 + section.h3 {
211
+ margin-top: -20px; }
212
+
213
+ section.h1>:first-child, section.h2>:first-child, section.h3>:first-child {
214
+ padding-top: 0;
215
+ margin-top: 0; }
216
+
217
+ /* API */
218
+ section.api h2,
219
+ section.api h3 {
220
+ margin-bottom: 0; }
221
+
222
+ section.api h2 + p,
223
+ section.api h2 + pre + p,
224
+ section.api h3 + p,
225
+ section.api h3 + pre + p {
226
+ margin-top: 0; }
227
+
228
+ h2 .args,
229
+ h3 .args {
230
+ margin-left: 3px;
231
+ font-size: 0.9em;
232
+ margin-right: 15px;
233
+ color: #aab; }
234
+
235
+ /* Pretty printing styles. Used with prettify.js. */
236
+ .str { color: #080; }
237
+ .kwd { color: #008; }
238
+ .com { color: #607077; background: rgba(0, 0, 0, 0.05); padding: 1px 3px; }
239
+ .typ { color: #606; }
240
+ .lit { color: #066; }
241
+ .pun { color: #660; }
242
+ .pln { color: #000; }
243
+ .tag { color: #008; }
244
+ .atn { color: #606; }
245
+ .atv { color: #080; }
246
+ .dec { color: #606; }
247
+
248
+ /* Specify class=linenums on a pre to get line numbering */
249
+ ol.linenums { margin-top: 0; margin-bottom: 0 } /* IE indents via margin-left */
250
+ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 { list-style-type: none }
251
+ /* Alternate shading for lines */
252
+ li.L1, li.L3, li.L5, li.L7, li.L9 { background: #eee }
253
+
254
+ @media print {
255
+ .str { color: #060; }
256
+ .kwd { color: #006; font-weight: bold; }
257
+ .com { color: #600; font-style: italic; }
258
+ .typ { color: #404; font-weight: bold; }
259
+ .lit { color: #044; }
260
+ .pun { color: #440; }
261
+ .pln { color: #000; }
262
+ .tag { color: #006; font-weight: bold; }
263
+ .atn { color: #404; }
264
+ .atv { color: #060; }
265
+ }
266
+
267
+ /* TOC */
268
+ body.toc {
269
+ margin-left: 200px; }
270
+
271
+ aside#toc {
272
+ font-size: 0.9em;
273
+ line-height: 1.7;
274
+ position: fixed;
275
+ top: 0;
276
+ left: 0;
277
+ bottom: 0;
278
+ padding: 20px;
279
+ width: 180px;
280
+ overflow-y: auto;
281
+ box-shadow: inset -2px 0 2px rgba(0, 0, 0, 0.1);
282
+ border-right: solid 1px #ccc;
283
+ background: #f4f4fa; }
284
+
285
+ aside#toc h1,
286
+ aside#toc h2,
287
+ aside#toc h3 {
288
+ overflow: hidden;
289
+ margin: 10px 0;
290
+ padding: 0;
291
+ font-size: 1.1em; }
292
+
293
+ aside#toc h1 {
294
+ color: #aaa;
295
+ text-shadow: none;
296
+ font-size: 1.3em; }
297
+
298
+ aside#toc h3 {
299
+ border-top: solid 1px #ddd;
300
+ padding-top: 10px; }
301
+
302
+ aside#toc h2 .type,
303
+ aside#toc h3 .type {
304
+ margin-left: 0; }
305
+
306
+ aside#toc ul,
307
+ aside#toc li {
308
+ margin: 0;
309
+ padding: 0;
310
+ list-style-type: none; }
311
+
312
+ aside#toc h1 a,
313
+ aside#toc h2 a,
314
+ aside#toc h3 a {
315
+ color: #333; }
316
+
317
+ aside#toc a {
318
+ overflow: hidden;
319
+ display: block;
320
+ border-bottom: 0;
321
+ color: #555; }
322
+
323
+ aside#toc a span.args {
324
+ display: none; }
325
+
326
+ aside#toc a span.type {
327
+ font-size: 0.8em;
328
+ text-transform: uppercase;
329
+ color: #999; }
330
+
331
+ aside#toc .type {
332
+ float: right; }
333
+
334
+ aside#toc::-webkit-scrollbar {
335
+ height: 15px; width: 15px;
336
+ }
337
+
338
+ aside#toc::-webkit-scrollbar-thumb {
339
+ border-width: 7px 7px 7px 7px;
340
+ -webkit-border-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDZEMERCMUZGRUI2MTFERkIwQ0RGMkFBNEE3NDlCOUMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDZEMERCMjBGRUI2MTFERkIwQ0RGMkFBNEE3NDlCOUMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0NkQwREIxREZFQjYxMURGQjBDREYyQUE0QTc0OUI5QyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0NkQwREIxRUZFQjYxMURGQjBDREYyQUE0QTc0OUI5QyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpHo8kwAAACBSURBVHjaYvz//z8DJYCJgUJAsQEsyBxGRkYwBcS6QKwBxLJQqcdAfAOILwPxf2RvM6JwICbYA7EDDgsPAPFBZBPQvaCLRzMDVE4XXxhoEOFtDXwGyBJhgCxNo/ExEXoe4zPgBhEG3MBnwGVoVDHgicbLKGkHLR1QlpCGZmYCCDAAxuMoE4WhhWUAAAAASUVORK5CYII=) 7 7 7 7 round round;
341
+ }
@@ -0,0 +1,84 @@
1
+ # Reacco::Extractor::Block [class]
2
+ # An extractor block.
3
+ #
4
+ module Reacco
5
+ class Extractor
6
+ class Block
7
+ # body [method]
8
+ # Returns the body text as a raw string.
9
+ attr_reader :body
10
+
11
+ # title [method]
12
+ # Returns the title of the block.
13
+ attr_reader :title
14
+ attr_reader :type
15
+ attr_reader :parent
16
+ attr_reader :args
17
+
18
+ def initialize(options)
19
+ @body = options[:body]
20
+ @type = options[:type] && options[:type].downcase
21
+ @args = options[:args]
22
+ @title = options[:title]
23
+ @parent = options[:parent]
24
+ @tag = options[:tag]
25
+ @source_line = options[:source_line]
26
+ @source_path = options[:source_path]
27
+ end
28
+
29
+ # children [method]
30
+ # Returns an array of child blocks.
31
+ def children
32
+ @children ||= Array.new
33
+ end
34
+
35
+ # << [method]
36
+ # Adds a block to it's children.
37
+ def <<(blk)
38
+ children << blk
39
+ end
40
+
41
+ def raw_html
42
+ Reacco.markdown.render(@body)
43
+ end
44
+
45
+ # Nokogiri node.
46
+ def doc
47
+ @doc ||= Nokogiri::HTML(raw_html)
48
+ end
49
+
50
+ def transform!
51
+ # Create heading.
52
+ name = @tag
53
+ node = Nokogiri::XML::Node.new(name, doc)
54
+ node['class'] = 'api'
55
+ node.content = title
56
+
57
+ # Add '(args)'.
58
+ if args
59
+ span = Nokogiri::XML::Node.new("span", doc)
60
+ span['class'] = 'args'
61
+ span.content = args
62
+ node.add_child span
63
+ end
64
+
65
+ # Add '(class method)'.
66
+ span = Nokogiri::XML::Node.new("span", doc)
67
+ span['class'] = 'type'
68
+ span.content = type
69
+ node.add_child Nokogiri::XML::Text.new(' ', doc)
70
+ node.add_child span
71
+
72
+ # Add heading.
73
+ doc.at_css('body>*:first-child').add_previous_sibling node
74
+ doc
75
+ end
76
+
77
+ # to_html [method]
78
+ # Returns the raw HTML to be included in the documentation.
79
+ def to_html
80
+ @to_html ||= transform!.at_css('body').inner_html
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ostruct'
4
+ require 'fileutils'
5
+ module Reacco
6
+ # Reacco::Extractor [class]
7
+ # Extracts comments from list of files.
8
+ #
9
+ # #### Instantiating
10
+ # Call the constructor with a list of files.
11
+ #
12
+ # ex = Extractor.new(Dir['./**/*.rb'])
13
+ #
14
+ class Extractor
15
+ autoload :Block, 'reacco/extractor/block'
16
+
17
+ def initialize(files, root=nil, options={})
18
+ @files = files.sort
19
+ @root = File.realpath(root || Dir.pwd)
20
+ end
21
+
22
+ # blocks [method]
23
+ # Returns an array of `Block` instances.
24
+ #
25
+ # ex.blocks
26
+ # ex.blocks.map { |b| puts "file: #{b.file}" }
27
+ #
28
+ def blocks
29
+ @blocks ||= begin
30
+ @files.map do |file|
31
+ if File.file?(file)
32
+ input = File.read(file)
33
+ get_blocks(input, unroot(file))
34
+ end
35
+ end.compact.flatten
36
+ end
37
+ end
38
+
39
+ private
40
+ def unroot(fn)
41
+ (File.realpath(fn))[@root.size..-1]
42
+ end
43
+
44
+ # get_blocks(str, [filename]) [private method]
45
+ # Returns the documentation blocks in a given `str`ing.
46
+ # If `filename` is given, it will be set as the *source_file*.
47
+ #
48
+ def get_blocks(str, filename=nil)
49
+ arr = get_comment_blocks(str)
50
+
51
+ arr.map do |hash|
52
+ block = hash[:block]
53
+
54
+ # Ensure the first line matches.
55
+ # This matches:
56
+ # "### name [type]"
57
+ # "## name(args) [type]"
58
+ re = /^(\#{1,6}) (.*?) ?(\(.*?\))? ?(?:\[([a-z ]+)\])?$/
59
+ block.first =~ re or next
60
+
61
+ blk = Extractor::Block.new \
62
+ :type => $4,
63
+ :tag => "h#{$1.strip.size}",
64
+ :title => $2,
65
+ :args => $3,
66
+ :source_line => hash[:line] + block.size + 1,
67
+ :source_file => filename,
68
+ :body => (block[1..-1].join("\n") + "\n")
69
+
70
+ blk
71
+ end.compact
72
+ end
73
+
74
+ # get_comment_blocks (private method)
75
+ # Returns contiguous comment blocks.
76
+ #
77
+ # Returns an array of hashes that look like
78
+ # `{ :block => [line1, line2...], :line => (line number) }`
79
+ #
80
+ def get_comment_blocks(str)
81
+ chunks = Array.new
82
+ i = 0
83
+
84
+ str.split("\n").each_with_index { |s, line|
85
+ if s =~ /^\s*(?:\/\/\/?|##?) ?(.*)$/
86
+ chunks[i] ||= { :block => Array.new, :line => line }
87
+ chunks[i][:block] << $1
88
+ else
89
+ i += 1 if chunks[i]
90
+ end
91
+ }
92
+
93
+ chunks.compact
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,12 @@
1
+ module Reacco
2
+ module Filters
3
+ module Brief
4
+ # Makes the first <p> a brief.
5
+ def brief_first_p(html)
6
+ p = html.at_css('body>p')
7
+ p['class'] = "#{p['class']} brief" if p
8
+ html
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module Reacco
2
+ module Filters
3
+ module HeadingID
4
+ def heading_id(html)
5
+ html.css('h1, h2, h3').each do |h|
6
+ h['id'] = slugify(h.content)
7
+ end
8
+
9
+ html
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module Reacco
2
+ module Filters
3
+ module Hgroup
4
+ # Wraps the first headings into an <hgroup>.
5
+ def wrap_hgroup(html)
6
+ nodes = Array.new
7
+ html.css('body>*').each { |node|
8
+ # Consume all headings.
9
+ if %w(h1 h2 h3 h4 h5 h6).include?(node.name)
10
+ nodes << node.to_s
11
+ node.remove
12
+
13
+ # Once the headings stop, dump them into an <hgroup>.
14
+ # Ah, and eat an <hr> if there is one.
15
+ else
16
+ node.before("<hgroup class='header'>#{nodes.join('')}</hgroup>") if nodes.any?
17
+ node.remove if node.name == 'hr'
18
+ break
19
+ end
20
+ }
21
+
22
+ html
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ module Reacco
2
+ module Filters
3
+ module Literate
4
+ # Moves pre's after headers.
5
+ def move_pre(html)
6
+ anchor = nil
7
+ position = nil
8
+
9
+ html.css('body>*').each_cons(2) { |(node, nxt)|
10
+ # Once we find the <pre>, move it.
11
+ if node.name == 'pre' && anchor && anchor != node && !(node['class'].to_s =~ /full/)
12
+ node.after "<br class='post-pre'>"
13
+
14
+ nxt['class'] = "#{nxt['class']} after-pre" if nxt
15
+ node['class'] = "#{node['class']} right"
16
+
17
+ anchor.send position, node
18
+ anchor = nil
19
+
20
+ # If we find one of these, put the next <pre> after it.
21
+ elsif %w(h1 h2 h3 h4 pre).include?(node.name)
22
+ anchor = node
23
+ position = :add_next_sibling
24
+
25
+ # If we find one of these, put the <pre> before it.
26
+ elsif node['class'].to_s.include?('after-pre')
27
+ anchor = node
28
+ position = :add_previous_sibling
29
+ end
30
+ }
31
+
32
+ html
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ module Reacco
2
+ module Filters
3
+ module PreLang
4
+ # Adds prettify classes.
5
+ def pre_lang(html)
6
+ html.css('pre').each { |pre| pre['class'] = "#{pre['class']} lang-#{pre['class']} prettyprint" }
7
+ html
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ module Reacco
2
+ module Filters
3
+ module Sections
4
+ # Wraps in sections.
5
+ def section_wrap(html)
6
+ %w(h1 h2 h3 h4 h5).each do |h|
7
+ nodes = html.css(h)
8
+ nodes.each do |alpha|
9
+ # For those affected by --hgroup, don't bother.
10
+ next if alpha.ancestors.any? { |tag| tag.name == 'hgroup' }
11
+ next unless alpha.parent
12
+
13
+ # Find the boundary, and get the nodes until that one.
14
+ omega = from_x_until(alpha, alpha.name)
15
+ section_nodes = between(alpha, omega)
16
+
17
+ # Create the <section>.
18
+ section = Nokogiri::XML::Node.new('section', html)
19
+ section['class'] = "#{alpha['class']} #{h} #{slugify alpha.content}"
20
+ alpha.add_previous_sibling(section)
21
+ section_nodes.each { |tag| section.add_child tag }
22
+ end
23
+ end
24
+
25
+ html
26
+ end
27
+
28
+ private
29
+ def from_x_until(alpha, name)
30
+ omega = nil
31
+ n = alpha
32
+
33
+ while true
34
+ n = n.next_sibling
35
+ break if n.nil? || n.name == name
36
+ omega = n
37
+ end
38
+
39
+ omega
40
+ end
41
+
42
+ def between(first, last)
43
+ nodes = Array.new
44
+ started = false
45
+
46
+ first.parent.children.each do |node|
47
+ started = true if node == first
48
+ nodes << node if started
49
+ break if node == last
50
+ end
51
+
52
+ nodes
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,54 @@
1
+ module Reacco
2
+ module Filters
3
+ module TOC
4
+ def make_toc(contents)
5
+ aside = Nokogiri::XML::Node.new('aside', contents)
6
+ aside['id'] = 'toc'
7
+
8
+ # Header
9
+ title = (h1 = contents.at_css('h1')) && h1.text || File.basename(Dir.pwd)
10
+ aside.inner_html = "#{aside.inner_html}<h1>#{title}</h1>"
11
+
12
+ contents.xpath('//body/section').each { |tag|
13
+ aside.inner_html = "#{aside.inner_html}#{make_section tag}"
14
+ }
15
+
16
+ contents.at_css('body').add_child aside
17
+
18
+ contents
19
+ end
20
+
21
+ def linkify(h)
22
+ href = slugify(h.content)
23
+ "<a href='##{href}'>#{h.inner_html}</a>"
24
+ end
25
+
26
+ def make_section(section)
27
+ h = section.at_css('h1, h2, h3')
28
+ return '' unless h
29
+ level = h.name[1] # 1 | 2 | 3
30
+ return '' unless %w(1 2 3).include?(level)
31
+ name = h.content.strip
32
+
33
+ out = case level
34
+ when "1"
35
+ [ "<h2>#{linkify h}</h2>",
36
+ section.css('section.h2').map { |s| make_section s }
37
+ ]
38
+ when "2"
39
+ [ "<nav class='level-#{level}'>",
40
+ "<h3>#{linkify h}</h3>",
41
+ "<ul>",
42
+ section.css('section.h3').map { |s| make_section s },
43
+ "</ul>",
44
+ "</nav>"
45
+ ]
46
+ when "3"
47
+ [ "<li>#{linkify h}</li>" ]
48
+ end
49
+
50
+ out.flatten.join "\n"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,68 @@
1
+ module Reacco
2
+ class Generator
3
+ def initialize(readme, dir, template=nil, css=nil)
4
+ @readme = readme
5
+ @dir = dir
6
+ @css = css
7
+ @template = template || Reacco.root('data')
8
+ end
9
+
10
+ # Returns the HTML contents.
11
+ def html
12
+ file = Dir["#{@template}/index.*"].first
13
+ raise "No index file found in template path." unless file
14
+
15
+ tpl = Tilt.new(file)
16
+ out = tpl.render({}, @readme.locals) { @readme.html }
17
+ end
18
+
19
+ # Writes to the output directory.
20
+ def write!(&blk)
21
+ yield "#{@dir}/index.html"
22
+ write_to "#{@dir}/index.html", html
23
+ copy_files &blk
24
+ append_css if @css
25
+ end
26
+
27
+ def template?
28
+ File.directory?(@template) && Dir["#{@template}/index.*"].first
29
+ end
30
+
31
+ # Adds the CSS file to the style.css file.
32
+ def append_css
33
+ contents = File.read(@css)
34
+ File.open("#{@dir}/style.css", 'a+') { |f| f.write "\n/* Custom */\n#{contents}" }
35
+ end
36
+
37
+ def copy_files(&blk)
38
+ files = Dir.chdir(@template) { Dir['**/*'] }
39
+
40
+ # For each of the template files...
41
+ files.each do |f|
42
+ next if File.basename(f)[0] == '_'
43
+ next if File.fnmatch('index.*', f)
44
+ ext = File.extname(f)[1..-1]
45
+
46
+ fullpath = File.join @template, f
47
+
48
+ # Try to render it with Tilt if possible.
49
+ if Tilt.mappings.keys.include?(ext)
50
+ contents = Tilt.new(fullpath).render
51
+ outfile = f.match(/^(.*)(\.[^\.]+)$/) && $1
52
+ else
53
+ contents = File.read(fullpath)
54
+ outfile = f
55
+ end
56
+
57
+ yield "#{@dir}/#{outfile}"
58
+ write_to "#{@dir}/#{outfile}", contents
59
+ end
60
+ end
61
+
62
+ private
63
+ def write_to(path, data)
64
+ FileUtils.mkdir_p File.dirname(path)
65
+ File.open(path, 'w') { |f| f.write data }
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,150 @@
1
+ # ## Reacco::Readme [class]
2
+ # A readme file.
3
+ module Reacco
4
+ class Readme
5
+ def initialize(options={})
6
+ @file = options.delete(:file) if options[:file]
7
+ @options = options
8
+ end
9
+
10
+ # ### file [method]
11
+ # The path to the README file. Returns nil if not available.
12
+ def file
13
+ @file ||= (Dir['README.*'].first || Dir['readme.*'].first || Dir['README'].first)
14
+ end
15
+
16
+ def file=(file)
17
+ @file = file
18
+ end
19
+
20
+ # ### switches [method]
21
+ # The switches, like 'literate' and 'hgroup'. Returns an array of strings.
22
+ def switches
23
+ @options.keys.map { |k| k.to_s }
24
+ end
25
+
26
+ # ### exists? [method]
27
+ # Returns true if the file (given in the constructor) exists.
28
+ def exists?
29
+ file && File.exists?(file)
30
+ end
31
+
32
+ # ### raw [method]
33
+ # Returns raw Markdown markup.
34
+ def raw
35
+ @raw ||= File.read(file)
36
+ end
37
+
38
+ # ### title [method]
39
+ # Returns a string of the title of the document (the first h1).
40
+ def title
41
+ @title ||= begin
42
+ h1 = (h1 = doc.at_css('h1')) && h1.text
43
+ h1 || File.basename(Dir.pwd)
44
+ end
45
+ end
46
+
47
+ def title?
48
+ title.to_s.size > 0
49
+ end
50
+
51
+ # ### raw_html [method]
52
+ # Raw HTML data.
53
+ def raw_html
54
+ @raw_html ||= markdown.render(raw)
55
+ end
56
+
57
+ def raw_html=(str)
58
+ @raw_html = str
59
+ end
60
+
61
+ # ### inject_api_block [method]
62
+ # Adds an API block. Takes an `html` argument.
63
+ def inject_api_block(html)
64
+ @api_blocks = "#{api_blocks}\n#{html}\n"
65
+ end
66
+
67
+ def api_blocks
68
+ @api_blocks ||= ""
69
+ end
70
+
71
+ # ### doc [method]
72
+ # Returns HTML as a Nokogiri document.
73
+ def doc(options={})
74
+ @doc ||= begin
75
+ add_api(api_blocks)
76
+ html = Nokogiri::HTML(raw_html)
77
+
78
+ html = pre_lang(html)
79
+ html = heading_id(html)
80
+ html = wrap_hgroup(html)
81
+ html = move_pre(html) if @options[:literate]
82
+ html = brief_first_p(html)
83
+ html = section_wrap(html)
84
+ html = make_toc(html) if @options[:toc]
85
+
86
+ html
87
+ end
88
+ end
89
+
90
+ # ### html [method]
91
+ # Returns body's inner HTML.
92
+ def html
93
+ doc.at_css('body').inner_html
94
+ end
95
+
96
+ # ### github [method]
97
+ # Returns the GitHub URL, or nil if not applicable.
98
+ def github
99
+ "https://github.com/#{@options[:github]}" if @options[:github]
100
+ end
101
+
102
+ # Returns locals for the template.
103
+ def locals
104
+ { :title => title,
105
+ :body_class => switches.join(' '),
106
+ :github => github }
107
+ end
108
+
109
+ private
110
+ include Filters::Brief
111
+ include Filters::Sections
112
+ include Filters::Hgroup
113
+ include Filters::Literate
114
+ include Filters::PreLang
115
+ include Filters::HeadingID
116
+ include Filters::TOC
117
+
118
+ # Puts `blocks` inside `raw_html`.
119
+ def add_api(blocks)
120
+ re1 = %r{^.*api reference goes here.*$}i
121
+ re2 = %r{^.*#api_reference.*$}i
122
+
123
+ if raw_html =~ re1
124
+ raw_html.gsub! re1, blocks
125
+ elsif raw_html =~ re2
126
+ raw_html.gsub! re2, blocks
127
+ else
128
+ self.raw_html = "#{raw_html}\n#{blocks}"
129
+ end
130
+ end
131
+
132
+ # ### markdown [private method]
133
+ # Returns the Markdown processor.
134
+ #
135
+ # markdown.render(md)
136
+ #
137
+ def markdown
138
+ Reacco.markdown
139
+ end
140
+
141
+ # ### slugify [private method]
142
+ # Turns text into a slug.
143
+ #
144
+ # "Install instructions" => "install_instructions"
145
+ #
146
+ def slugify(str)
147
+ str.downcase.scan(/[a-z0-9\-]+/).join('_')
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,5 @@
1
+ module Reacco
2
+ def self.version
3
+ "0.0.2"
4
+ end
5
+ end
data/lib/reacco.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'nokogiri'
2
+ require 'redcarpet'
3
+ require 'tilt'
4
+ require 'ostruct'
5
+ require 'fileutils'
6
+
7
+ # ## Reacco [module]
8
+ # This is the main module.
9
+ #
10
+ module Reacco
11
+ extend self
12
+
13
+ # ### root [class method]
14
+ # Returns the root path of the Reacco gem.
15
+ # You may pass additional parameters.
16
+ #
17
+ # Reacco.root #=> '/usr/local/ruby/gems/reacco-0.0.1'
18
+ #
19
+ def root(*a)
20
+ File.join File.expand_path('../../', __FILE__), *a
21
+ end
22
+
23
+ # ### markdown [class method]
24
+ # Returns the Redcarpet Markdown processor.
25
+ def markdown
26
+ Redcarpet::Markdown.new(Redcarpet::Render::HTML,
27
+ :fenced_code_blocks => true)
28
+ end
29
+
30
+ autoload :Readme, 'reacco/readme'
31
+ autoload :Generator, 'reacco/generator'
32
+ autoload :Extractor, 'reacco/extractor'
33
+
34
+ module Filters
35
+ autoload :Brief, 'reacco/filters/brief'
36
+ autoload :Sections, 'reacco/filters/sections'
37
+ autoload :Hgroup, 'reacco/filters/hgroup'
38
+ autoload :Literate, 'reacco/filters/literate'
39
+ autoload :PreLang, 'reacco/filters/prelang'
40
+ autoload :HeadingID, 'reacco/filters/headingid'
41
+ autoload :TOC, 'reacco/filters/toc'
42
+ end
43
+ end
44
+
data/reacco.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ require './lib/reacco/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "reacco"
5
+ s.version = Reacco.version
6
+ s.summary = %{Readme prettifier.}
7
+ s.description = %Q{Reacco makes your readme's pretty.}
8
+ s.authors = ["Rico Sta. Cruz"]
9
+ s.email = ["rico@sinefunc.com"]
10
+ s.homepage = "http://github.com/rstacruz/reacco"
11
+ s.files = `git ls-files`.strip.split("\n")
12
+ s.executables = Dir["bin/*"].map { |f| File.basename(f) }
13
+
14
+ s.add_dependency 'redcarpet', '~> 2.0.0b3'
15
+ s.add_dependency 'nokogiri', '~> 1.5'
16
+ s.add_dependency 'tilt'
17
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class BlocksTest < UnitTest
4
+ setup do
5
+ @ex = Reacco::Extractor.new(Dir[root 'lib/**/*.rb'])
6
+ end
7
+
8
+ should "extract comments" do
9
+ # From Reacco.root
10
+ block = @ex.blocks.detect { |blk| blk.title == "root" && blk.type == "class method" }
11
+ assert ! block.nil?
12
+ end
13
+
14
+ should "htmlize properly" do
15
+ block = @ex.blocks.detect { |blk| blk.title == "root" && blk.type == "class method" }
16
+ p block.to_html
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'contest'
3
+ require 'reacco'
4
+
5
+ class UnitTest < Test::Unit::TestCase
6
+ def root(*a)
7
+ Reacco.root *a
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reacco
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rico Sta. Cruz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-18 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redcarpet
16
+ requirement: &2156349300 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.0.0b3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2156349300
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ requirement: &2156348320 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '1.5'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2156348320
36
+ - !ruby/object:Gem::Dependency
37
+ name: tilt
38
+ requirement: &2156347220 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2156347220
47
+ description: Reacco makes your readme's pretty.
48
+ email:
49
+ - rico@sinefunc.com
50
+ executables:
51
+ - reacco
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - HISTORY.md
58
+ - README.md
59
+ - Rakefile
60
+ - bin/reacco
61
+ - data/index.erb
62
+ - data/style.css
63
+ - lib/reacco.rb
64
+ - lib/reacco/extractor.rb
65
+ - lib/reacco/extractor/block.rb
66
+ - lib/reacco/filters/brief.rb
67
+ - lib/reacco/filters/headingid.rb
68
+ - lib/reacco/filters/hgroup.rb
69
+ - lib/reacco/filters/literate.rb
70
+ - lib/reacco/filters/prelang.rb
71
+ - lib/reacco/filters/sections.rb
72
+ - lib/reacco/filters/toc.rb
73
+ - lib/reacco/generator.rb
74
+ - lib/reacco/readme.rb
75
+ - lib/reacco/version.rb
76
+ - reacco.gemspec
77
+ - test/blocks_test.rb
78
+ - test/test_helper.rb
79
+ homepage: http://github.com/rstacruz/reacco
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 1.8.10
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Readme prettifier.
103
+ test_files: []