proscribe 0.0.1
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/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
|
+
|