proscribe 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +4 -0
- data/README.md +7 -0
- data/bin/proscribe +5 -0
- data/data/default/Gemfile +5 -0
- data/data/default/Gemfile.lock +43 -0
- data/data/default/Protonfile +14 -0
- data/data/default/_extensions/manual/lib/cli.rb +18 -0
- data/data/default/_extensions/manual/lib/extractor.rb +206 -0
- data/data/default/_extensions/manual/lib/helpers.rb +32 -0
- data/data/default/_extensions/manual/manual.rb +2 -0
- data/data/default/_layouts/_nav.haml +8 -0
- data/data/default/_layouts/default.haml +123 -0
- data/data/default/style.scss +412 -0
- data/data/rack/Gemfile +2 -0
- data/data/rack/config.ru +8 -0
- data/lib/proscribe/cli.rb +45 -0
- data/lib/proscribe/extractor.rb +158 -0
- data/lib/proscribe/helpers.rb +42 -0
- data/lib/proscribe/project.rb +92 -0
- data/lib/proscribe/rack_app.rb +26 -0
- data/lib/proscribe/version.rb +11 -0
- data/lib/proscribe/watcher.rb +63 -0
- data/lib/proscribe.rb +40 -0
- metadata +137 -0
data/HISTORY.md
ADDED
data/README.md
ADDED
data/bin/proscribe
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
RedCloth (4.2.7)
|
5
|
+
chunky_png (1.2.0)
|
6
|
+
compass (0.11.5)
|
7
|
+
chunky_png (~> 1.2)
|
8
|
+
fssm (>= 0.2.7)
|
9
|
+
sass (~> 3.1)
|
10
|
+
cuba (2.0.0)
|
11
|
+
rack (~> 1.2)
|
12
|
+
tilt (~> 1.2)
|
13
|
+
fssm (0.2.7)
|
14
|
+
haml (3.1.2)
|
15
|
+
hashie (1.0.0)
|
16
|
+
maruku (0.6.0)
|
17
|
+
syntax (>= 1.0.0)
|
18
|
+
proton (0.3.3)
|
19
|
+
RedCloth (~> 4.2.3)
|
20
|
+
compass (~> 0.11.1)
|
21
|
+
cuba (~> 2.0.0)
|
22
|
+
haml (~> 3.1.1)
|
23
|
+
hashie (~> 1.0.0)
|
24
|
+
maruku (~> 0.6.0)
|
25
|
+
sass (~> 3.1.1)
|
26
|
+
shake (~> 0.1)
|
27
|
+
tilt (~> 1.2.2)
|
28
|
+
rack (1.3.0)
|
29
|
+
rack-cache (1.0.2)
|
30
|
+
rack (>= 0.4)
|
31
|
+
rdiscount (1.6.8)
|
32
|
+
sass (3.1.4)
|
33
|
+
shake (0.1.2)
|
34
|
+
syntax (1.0.0)
|
35
|
+
tilt (1.2.2)
|
36
|
+
|
37
|
+
PLATFORMS
|
38
|
+
ruby
|
39
|
+
|
40
|
+
DEPENDENCIES
|
41
|
+
proton (~> 0.3.2)
|
42
|
+
rack-cache (~> 1.0.0)
|
43
|
+
rdiscount
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Proton::CLI
|
2
|
+
task :update do
|
3
|
+
require File.expand_path('../extractor', __FILE__)
|
4
|
+
|
5
|
+
Dir.chdir(Hyde.project.root) {
|
6
|
+
Proton.project.config.extractor.files.each { |group|
|
7
|
+
FileUtils.rm_rf group.target
|
8
|
+
|
9
|
+
ex = Extractor.new Dir[group.source]
|
10
|
+
|
11
|
+
ex.write!(group.target) { |b| puts " update * #{File.join(group.target, b.file)}" }
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
task.description = "Extracts inline documentation."
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,206 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
# Extracts comments from list of files.
|
7
|
+
# Gets the ones with comment blocks starting with `[...]`
|
8
|
+
#
|
9
|
+
# == Common usage
|
10
|
+
#
|
11
|
+
# ex = Extractor.new('.')
|
12
|
+
# ex.blocks
|
13
|
+
#
|
14
|
+
# ex.blocks.map! { |b| b.file = "file: #{b.file}" }
|
15
|
+
#
|
16
|
+
# ex.write!('manual/') # Writes to manual/
|
17
|
+
#
|
18
|
+
class Extractor
|
19
|
+
def initialize(files, options={})
|
20
|
+
@files = files
|
21
|
+
end
|
22
|
+
|
23
|
+
def write!(output_path = '.', &blk)
|
24
|
+
blocks.each { |block|
|
25
|
+
path = File.join(output_path, block.file)
|
26
|
+
FileUtils.mkdir_p File.dirname(path)
|
27
|
+
File.open(path, 'w') { |f| f.write block.body }
|
28
|
+
yield block if block_given?
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns an array of Extractor::Blocks.
|
33
|
+
def blocks
|
34
|
+
@blocks ||= begin
|
35
|
+
@files.map { |file|
|
36
|
+
if File.file?(file)
|
37
|
+
input = File.read(file)
|
38
|
+
get_blocks input
|
39
|
+
end
|
40
|
+
}.compact.flatten
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
# Returns blocks that match a blah.
|
46
|
+
def get_blocks(str)
|
47
|
+
arr = get_comment_blocks(str)
|
48
|
+
arr.map { |block|
|
49
|
+
re = /^([A-Za-z ]*?): (.*?)(?: \((.*?)\))?$/
|
50
|
+
|
51
|
+
if block.last =~ re
|
52
|
+
Extractor::Block.new \
|
53
|
+
:type => $1,
|
54
|
+
:title => $2,
|
55
|
+
:parent => $3,
|
56
|
+
:body => (block[0..-2].join("\n") + "\n")
|
57
|
+
elsif block.first =~ re
|
58
|
+
Extractor::Block.new \
|
59
|
+
:type => $1,
|
60
|
+
:title => $2,
|
61
|
+
:parent => $3,
|
62
|
+
:body => (block[1..-1].join("\n") + "\n")
|
63
|
+
end
|
64
|
+
}.compact
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns contiguous comment blocks.
|
68
|
+
def get_comment_blocks(str)
|
69
|
+
chunks = Array.new
|
70
|
+
i = 0
|
71
|
+
|
72
|
+
str.split("\n").each { |s|
|
73
|
+
if s =~ /^\s*(?:\/\/\/?|##?) ?(.*)$/
|
74
|
+
chunks[i] ||= Array.new
|
75
|
+
chunks[i] << $1
|
76
|
+
else
|
77
|
+
i += 1 if chunks[i]
|
78
|
+
end
|
79
|
+
}
|
80
|
+
|
81
|
+
chunks
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Extractor::Block
|
86
|
+
attr_accessor :body
|
87
|
+
attr_accessor :file
|
88
|
+
|
89
|
+
def initialize(options)
|
90
|
+
title = options[:title]
|
91
|
+
parent = options[:parent]
|
92
|
+
body = options[:body]
|
93
|
+
type = options[:type].downcase
|
94
|
+
|
95
|
+
file = to_filename(title, parent)
|
96
|
+
brief, *body = body.split("\n\n")
|
97
|
+
body = "#{body.join("\n\n")}"
|
98
|
+
|
99
|
+
heading = "title: #{title}\npage_type: #{type}\nbrief: #{brief}\n"
|
100
|
+
heading += "--\n"
|
101
|
+
|
102
|
+
@file = file
|
103
|
+
body = Tilt.new(".md") { body }.render
|
104
|
+
body = fix_links(body, from: file)
|
105
|
+
@body = heading + body
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def fix_links(str, options={})
|
110
|
+
from = ("/" + options[:from].to_s).squeeze('/')
|
111
|
+
depth = from.to_s.count('/')
|
112
|
+
indent = (depth > 1 ? '../'*(depth-1) : './')[0..-2]
|
113
|
+
|
114
|
+
# First pass: {Helpers::content_for} to become links
|
115
|
+
str = str.gsub(/{([^}]*?)}/) { |s|
|
116
|
+
s = s.gsub(/{|}/, '')
|
117
|
+
|
118
|
+
m = s.match(/^(.*?)[:\.]+([A-Za-z_\(\)\!\?]+)$/)
|
119
|
+
if m
|
120
|
+
name, context = $2, $1
|
121
|
+
else
|
122
|
+
name, context = s, nil
|
123
|
+
end
|
124
|
+
|
125
|
+
s = "<a href='/#{to_filename(s, '', :ext => '.html')}'>#{name}</a>"
|
126
|
+
s += " <span class='context'>(#{context})</span>" if context
|
127
|
+
s
|
128
|
+
}
|
129
|
+
|
130
|
+
# Second pass: relativize
|
131
|
+
re = /href=['"](\/(?:.*?))['"]/
|
132
|
+
str.gsub(re) { |s|
|
133
|
+
url = s.match(re) && $1
|
134
|
+
url = "#{indent}/#{url}".squeeze('/')
|
135
|
+
"href=#{url.inspect}"
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_filename(title, parent='', options={})
|
140
|
+
extension = options[:ext] || '.erb'
|
141
|
+
pathify = lambda { |s|
|
142
|
+
s.to_s.scan(/[A-Za-z0-9_\!\?]+/).map { |chunk|
|
143
|
+
chunk = chunk.gsub('?', '_question')
|
144
|
+
chunk = chunk.gsub('!', '_bang')
|
145
|
+
|
146
|
+
if chunk[0].upcase == chunk[0]
|
147
|
+
chunk
|
148
|
+
else
|
149
|
+
"#{chunk}_"
|
150
|
+
end
|
151
|
+
}.join("/")
|
152
|
+
}
|
153
|
+
|
154
|
+
pathify["#{parent}/#{title}"] + extension
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
module Extractor::Command
|
159
|
+
module Params
|
160
|
+
def extract(what)
|
161
|
+
i = index(what) and slice!(i, 2)[1]
|
162
|
+
end
|
163
|
+
|
164
|
+
def extract_all(what)
|
165
|
+
re = Array.new
|
166
|
+
while true
|
167
|
+
x = extract(what) or return re
|
168
|
+
re << x
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.show_usage
|
174
|
+
puts "Usage: #{$0} <path> [-o <output_path>] [-i <ignore_spec>]"
|
175
|
+
puts " Extracts documentation comments from files in <path>, and places"
|
176
|
+
puts " them in <output_path>."
|
177
|
+
puts ""
|
178
|
+
puts "Example:"
|
179
|
+
puts " #{$0} **/*.rb -o manual/"
|
180
|
+
puts ""
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.run!
|
184
|
+
return show_usage if ARGV.empty?
|
185
|
+
|
186
|
+
ARGV.extend Params
|
187
|
+
|
188
|
+
glob = lambda { |s| Dir["#{s}/**/*"] + Dir[s] }
|
189
|
+
|
190
|
+
output = ARGV.extract('--output') || ARGV.extract('-o') || '.'
|
191
|
+
ignore = ARGV.extract_all('-i') + ARGV.extract_all('--ignore')
|
192
|
+
|
193
|
+
files = ARGV.map { |s| glob[s] }.flatten
|
194
|
+
files = Dir["**/*"] if ARGV.empty?
|
195
|
+
|
196
|
+
files -= ignore.map { |s| glob[s] }.flatten
|
197
|
+
|
198
|
+
ex = Extractor.new(files)
|
199
|
+
ex.blocks.map { |b| b.file }
|
200
|
+
ex.write!(output) { |blk|
|
201
|
+
puts "* #{blk.file}"
|
202
|
+
}
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
Extractor::Command.run! if $0 == __FILE__
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Hyde::Helpers
|
2
|
+
def page_children(page)
|
3
|
+
children = page.children
|
4
|
+
of_type = lambda { |str| children.select { |p| p.html? && p.meta.page_type == str } }
|
5
|
+
|
6
|
+
children.
|
7
|
+
select { |p| p.html? }.
|
8
|
+
group_by { |p|
|
9
|
+
type = p.meta.page_type
|
10
|
+
type.nil? ? nil : Inflector[type].pluralize.to_sym
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Inflector['hello'].pluralize
|
16
|
+
class Inflector < String
|
17
|
+
def self.[](str)
|
18
|
+
new str.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def pluralize
|
22
|
+
if self[-1] == 's'
|
23
|
+
"#{self}es"
|
24
|
+
else
|
25
|
+
"#{self}s"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sentencize
|
30
|
+
self.gsub('_', ' ').capitalize
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title= page.title
|
5
|
+
|
6
|
+
%meta(charset='UTF-8')
|
7
|
+
-# Use the latest IE engine, or Chrome frame.
|
8
|
+
%meta(http-equiv='X-UA-Compatible' content='IE=edge,chrome=1')
|
9
|
+
|
10
|
+
-# Mobile viewport optimization. j.mp/bplateviewport
|
11
|
+
%meta(name='viewport' content='width=device-width, initial-scale=1.0')
|
12
|
+
|
13
|
+
-# Standard SEO meta
|
14
|
+
- if page.meta.keywords
|
15
|
+
%meta{:name => 'keywords', :content => page.meta.keywords}
|
16
|
+
- if page.meta.description
|
17
|
+
%meta{:name => 'description', :content => page.meta.description}
|
18
|
+
|
19
|
+
%link{:rel => 'stylesheet', :href => rel('/style.css')+"?#{File.mtime(Proton::Page['/style.css'].file).to_i}"}
|
20
|
+
|
21
|
+
%body
|
22
|
+
#top
|
23
|
+
%a#logo{href: rel('/')}
|
24
|
+
= Hyde::Page['/'].title
|
25
|
+
|
26
|
+
#area
|
27
|
+
#content
|
28
|
+
%div.c
|
29
|
+
#crumbs
|
30
|
+
- page.breadcrumbs[0..-2].each do |p|
|
31
|
+
%a{href: rel(p.path)}= p
|
32
|
+
%span.gt!= "→"
|
33
|
+
|
34
|
+
%strong= page
|
35
|
+
|
36
|
+
%hgroup
|
37
|
+
- if page.meta.layout
|
38
|
+
%p.type= page.meta.layout.capitalize
|
39
|
+
%h1= page.title
|
40
|
+
- if page.meta.brief
|
41
|
+
%h5= page.meta.brief
|
42
|
+
|
43
|
+
.content
|
44
|
+
!= yield
|
45
|
+
|
46
|
+
- groups = page_children(page)
|
47
|
+
- groups.each do |type, children|
|
48
|
+
%h3= type.to_s.gsub('_', ' ').capitalize
|
49
|
+
|
50
|
+
%ul.section
|
51
|
+
- children.each do |method|
|
52
|
+
%li
|
53
|
+
%a{href: rel(method.path)}= method.title
|
54
|
+
- unless method.meta.brief.to_s.empty?
|
55
|
+
%span.brief= method.meta.brief
|
56
|
+
|
57
|
+
%nav#nav
|
58
|
+
- parent = (page.children.any? ? page : (page.parent || page))
|
59
|
+
- children = parent.children.select { |p| p.html? }
|
60
|
+
- groups = children.group_by { |p| p.meta.page_type }
|
61
|
+
|
62
|
+
- if parent && !parent.root?
|
63
|
+
%nav.parents
|
64
|
+
%ul
|
65
|
+
- parent.breadcrumbs.each do |pp|
|
66
|
+
%li
|
67
|
+
%a{href: rel(pp.path), class: ('active' if pp.path == page.path)}
|
68
|
+
- unless pp.path == page.path
|
69
|
+
%span.back!= "‹"
|
70
|
+
|
71
|
+
%em= pp.meta.page_type
|
72
|
+
= pp
|
73
|
+
|
74
|
+
- if groups.any?
|
75
|
+
- groups.each do |name, pages|
|
76
|
+
- name = name ? Inflector[name].pluralize.capitalize : parent.to_s
|
77
|
+
%nav
|
78
|
+
- if name
|
79
|
+
%h4= name
|
80
|
+
%ul
|
81
|
+
- pages.each do |pp|
|
82
|
+
%li
|
83
|
+
- classes = []
|
84
|
+
- classes << 'active' if pp.path == page.path
|
85
|
+
- classes << 'more' if pp.children.any?
|
86
|
+
|
87
|
+
%a{href: rel(pp.path), class: classes.join(' ')}
|
88
|
+
= pp
|
89
|
+
|
90
|
+
|
91
|
+
%script{src: 'http://cachedcommons.org/cache/prettify/1.0.0/javascripts/prettify-min.js', type: 'text/javascript'}
|
92
|
+
%script{src: 'http://cdnjs.cloudflare.com/ajax/libs/jquery/1.6.2/jquery.min.js', type: 'text/javascript'}
|
93
|
+
:javascript
|
94
|
+
$(function () {
|
95
|
+
$("pre").each(function() {
|
96
|
+
var r = /\[(.*?)\s*\((.*?)\)\]\n*/;
|
97
|
+
var m = $(this).text().match(r);
|
98
|
+
|
99
|
+
$(this).addClass('prettyprint');
|
100
|
+
|
101
|
+
if (m) {
|
102
|
+
var file = m[1];
|
103
|
+
var type = m[2];
|
104
|
+
$(this).addClass('lang-'+type);
|
105
|
+
|
106
|
+
if (file.length) {
|
107
|
+
$(this).addClass('has-caption');
|
108
|
+
$(this).prepend($("<h5 class='caption'>").text(file));
|
109
|
+
}
|
110
|
+
|
111
|
+
$(this).html($(this).html().replace(r, ''));
|
112
|
+
}
|
113
|
+
|
114
|
+
if ($(this).text().match(/^\s*([a-zA-Z_~\/]*)\$ /)) {
|
115
|
+
$(this).addClass('terminal');
|
116
|
+
$(this).removeClass('prettyprint');
|
117
|
+
$(this).html($(this).html().replace(/([a-zA-Z_~\/]*\$ )(.*?)[\r\n$]/g, "<strong><em>$1</em>$2</strong>\n"));
|
118
|
+
}
|
119
|
+
});
|
120
|
+
|
121
|
+
prettyPrint();
|
122
|
+
});
|
123
|
+
|