reacco 0.0.2

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.
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: []