brief 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/Gemfile +4 -0
- data/Gemfile.lock +81 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +11 -0
- data/bin/brief +35 -0
- data/brief-0.0.1.gem +0 -0
- data/brief.gemspec +33 -0
- data/examples/project_overview.md +23 -0
- data/lib/brief/cli/commands/config.rb +40 -0
- data/lib/brief/cli/commands/publish.rb +27 -0
- data/lib/brief/cli/commands/write.rb +26 -0
- data/lib/brief/configuration.rb +134 -0
- data/lib/brief/document.rb +68 -0
- data/lib/brief/dsl.rb +0 -0
- data/lib/brief/formatters/base.rb +12 -0
- data/lib/brief/formatters/github_milestone_rollup.rb +52 -0
- data/lib/brief/git.rb +19 -0
- data/lib/brief/github/wiki.rb +9 -0
- data/lib/brief/github.rb +78 -0
- data/lib/brief/github_client/authentication.rb +32 -0
- data/lib/brief/github_client/client.rb +86 -0
- data/lib/brief/github_client/commands.rb +5 -0
- data/lib/brief/github_client/issue_labels.rb +65 -0
- data/lib/brief/github_client/issues.rb +22 -0
- data/lib/brief/github_client/milestone_issues.rb +13 -0
- data/lib/brief/github_client/organization_activity.rb +9 -0
- data/lib/brief/github_client/organization_issues.rb +13 -0
- data/lib/brief/github_client/organization_repositories.rb +20 -0
- data/lib/brief/github_client/organization_users.rb +9 -0
- data/lib/brief/github_client/repository_events.rb +8 -0
- data/lib/brief/github_client/repository_issue_events.rb +9 -0
- data/lib/brief/github_client/repository_issues.rb +8 -0
- data/lib/brief/github_client/repository_labels.rb +18 -0
- data/lib/brief/github_client/repository_milestones.rb +9 -0
- data/lib/brief/github_client/request.rb +181 -0
- data/lib/brief/github_client/request_wrapper.rb +121 -0
- data/lib/brief/github_client/response_object.rb +50 -0
- data/lib/brief/github_client/single_repository.rb +9 -0
- data/lib/brief/github_client/user_activity.rb +16 -0
- data/lib/brief/github_client/user_gists.rb +9 -0
- data/lib/brief/github_client/user_info.rb +9 -0
- data/lib/brief/github_client/user_issues.rb +13 -0
- data/lib/brief/github_client/user_organizations.rb +9 -0
- data/lib/brief/github_client/user_repositories.rb +9 -0
- data/lib/brief/github_client.rb +43 -0
- data/lib/brief/handlers/base.rb +62 -0
- data/lib/brief/handlers/github_issue.rb +41 -0
- data/lib/brief/handlers/github_milestone.rb +37 -0
- data/lib/brief/handlers/github_wiki.rb +11 -0
- data/lib/brief/line.rb +69 -0
- data/lib/brief/parser.rb +354 -0
- data/lib/brief/publisher/handler_manager.rb +47 -0
- data/lib/brief/publisher.rb +142 -0
- data/lib/brief/tree.rb +42 -0
- data/lib/brief/version.rb +9 -0
- data/lib/brief.rb +56 -0
- data/lib/core_ext.rb +37 -0
- data/spec/fixtures/front_end_tutorial.md +33 -0
- data/spec/fixtures/generated/project_overview.json +0 -0
- data/spec/fixtures/generator_dsl_example.rb +22 -0
- data/spec/fixtures/project_overview.md +48 -0
- data/spec/fixtures/sample.md +19 -0
- data/spec/lib/brief/document_spec.rb +35 -0
- data/spec/lib/brief/dsl_spec.rb +21 -0
- data/spec/lib/brief/line_spec.rb +11 -0
- data/spec/lib/brief/parser_spec.rb +12 -0
- data/spec/spec_helper.rb +25 -0
- metadata +231 -9
data/lib/brief/parser.rb
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
# This is my first attempt at breaking down raw markdown
|
2
|
+
# into a tree / hierarchy, so that it can be used to do other
|
3
|
+
# things / create other entities.
|
4
|
+
#
|
5
|
+
module Brief
|
6
|
+
class Parser
|
7
|
+
attr_accessor :content, :options, :raw_tree, :checksum
|
8
|
+
|
9
|
+
# Regexes
|
10
|
+
HeadingRegex = /^#+/
|
11
|
+
|
12
|
+
# Exceptions
|
13
|
+
MissingHeadings = Class.new(Exception)
|
14
|
+
HeadingsNotUnique = Class.new(Exception)
|
15
|
+
|
16
|
+
def initialize(content="",options={})
|
17
|
+
@options = options.dup
|
18
|
+
@content = content
|
19
|
+
@checksum = options.fetch(:checksum, Digest::MD5.hexdigest(content))
|
20
|
+
|
21
|
+
scan
|
22
|
+
parse
|
23
|
+
end
|
24
|
+
|
25
|
+
def tree
|
26
|
+
@tree ||= Brief::Tree.new(tree_nodes.dup.freeze)
|
27
|
+
end
|
28
|
+
|
29
|
+
def items(sorted=true)
|
30
|
+
sorted ? tree.items.sort_by(&:sort_index) : tree.items
|
31
|
+
end
|
32
|
+
|
33
|
+
def headings_at_level level, options={}
|
34
|
+
as_objects = options.fetch(:as_objects, false)
|
35
|
+
items = heading_lines.select {|line| line.level == level }.sort_by(&:sort_index)
|
36
|
+
as_objects ? items : items.map(&:content)
|
37
|
+
end
|
38
|
+
|
39
|
+
def next_sibling_of(heading_line)
|
40
|
+
if heading_line.is_a?(String)
|
41
|
+
heading_line = heading_lines.detect {|h| h.content == heading_line }
|
42
|
+
end
|
43
|
+
|
44
|
+
heading_lines.detect {|l| l.level <= heading_line.level && l.number > heading_line.number }
|
45
|
+
end
|
46
|
+
|
47
|
+
def body_content_for(heading_line)
|
48
|
+
if heading_line.is_a?(String)
|
49
|
+
heading_line = heading_lines.detect {|h| h.content == heading_line }
|
50
|
+
end
|
51
|
+
|
52
|
+
min = heading_line.number + 1
|
53
|
+
max = next_sibling_of(heading_line).try(:number).try(:-, 1) || last_line_number
|
54
|
+
|
55
|
+
parsed_lines.select {|p| p.number.between?(min,max) }.map(&:raw).map(&:strip).join("")
|
56
|
+
end
|
57
|
+
|
58
|
+
def lines_between_boundaries *bounds
|
59
|
+
bounds.map do |range|
|
60
|
+
a = range.min - 1
|
61
|
+
b = range.max - a
|
62
|
+
parsed_lines.slice(a, b).map(&:raw)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_line_number
|
67
|
+
parsed_lines.last.try(:number) || raw_lines.length + 1
|
68
|
+
end
|
69
|
+
|
70
|
+
def parser
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def extract_frontmatter!
|
75
|
+
if raw_lines.any? {|l| l.match(/```settings/) }
|
76
|
+
markers = identify_codemarkers
|
77
|
+
|
78
|
+
if raw_lines[markers.first].to_s.match(/```settings/)
|
79
|
+
length = markers[1] - markers[0]
|
80
|
+
@front_matter = raw_lines.slice(markers[0] + 1, length - 1).join
|
81
|
+
remove_raw_lines(markers[0], markers[1])
|
82
|
+
@content = raw_lines.join.gsub(/^\n|\n$/,'')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def front_matter
|
88
|
+
YAML.load(@front_matter) rescue {}
|
89
|
+
end
|
90
|
+
|
91
|
+
def identify_codemarkers
|
92
|
+
markers = []
|
93
|
+
raw_lines.each_with_index do |line, index|
|
94
|
+
if line.match(Brief::Line::CodeBlockRegex)
|
95
|
+
markers << index
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
markers
|
100
|
+
end
|
101
|
+
|
102
|
+
def scan
|
103
|
+
extract_frontmatter!
|
104
|
+
|
105
|
+
@code_markers = identify_codemarkers
|
106
|
+
@heading_markers = identify_heading_markers
|
107
|
+
|
108
|
+
@scanned = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def identify_heading_markers
|
112
|
+
heading_markers = []
|
113
|
+
raw_lines.each_with_index do |line, index|
|
114
|
+
if line.match(Brief::Line::HeadingRegex) && !is_line_inside_code_region?(index)
|
115
|
+
heading_markers << index
|
116
|
+
end
|
117
|
+
end
|
118
|
+
heading_markers
|
119
|
+
end
|
120
|
+
|
121
|
+
def heading_boundaries
|
122
|
+
@heading_markers && @heading_markers.each_slice(2).map do |slice|
|
123
|
+
Range.new slice.first + 1, slice.last + 1
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def code_boundaries
|
128
|
+
@code_markers && @code_markers.each_slice(2).map do |slice|
|
129
|
+
Range.new slice.first + 1, slice.last + 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def valid?
|
134
|
+
@validated.presence || validate
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate
|
138
|
+
scan unless @scanned
|
139
|
+
|
140
|
+
unless heading_lines.length > 0
|
141
|
+
raise MissingHeadings, 'A Brief must include some headings'
|
142
|
+
end
|
143
|
+
|
144
|
+
if heading_lines.uniq.length < heading_lines.length
|
145
|
+
raise HeadingsNotUnique, 'Headings inside a brief must be unique'
|
146
|
+
end
|
147
|
+
|
148
|
+
@validated = true
|
149
|
+
end
|
150
|
+
|
151
|
+
# This syntax isn't right, but it works.
|
152
|
+
# TODO: Research the best way to do this.
|
153
|
+
# middleman had an example of using the &method(:method_name)
|
154
|
+
# to treat a method definition as a proc
|
155
|
+
def tree_visitor node
|
156
|
+
subheadings = headings_under(node, as_objects: true)
|
157
|
+
children = subheadings.map(&method(:tree_visitor))
|
158
|
+
id = "#{ checksum }_#{ node.sort_index.join('_') }"
|
159
|
+
|
160
|
+
children.each_with_index do |child, index|
|
161
|
+
child[:heading_index] = index
|
162
|
+
end
|
163
|
+
|
164
|
+
base = {
|
165
|
+
id: id,
|
166
|
+
level: node.level,
|
167
|
+
children: children,
|
168
|
+
title: node.content,
|
169
|
+
sort_index: node.sort_index,
|
170
|
+
line_number: node.line_number
|
171
|
+
}
|
172
|
+
|
173
|
+
base.merge! content: body_content_for(node).to_s.strip if node.heading?
|
174
|
+
|
175
|
+
base
|
176
|
+
end
|
177
|
+
|
178
|
+
def tree_nodes
|
179
|
+
@tree_nodes ||= begin
|
180
|
+
nodes = []
|
181
|
+
|
182
|
+
headings_at_level(highest_level, as_objects: true).each_with_index do |node, index|
|
183
|
+
element = tree_visitor(node)
|
184
|
+
element[:heading_index] = index
|
185
|
+
nodes << element
|
186
|
+
end
|
187
|
+
|
188
|
+
nodes.map {|node| Hashie::Mash.new(node) }.flatten
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def elements
|
193
|
+
@elements ||= tree.elements
|
194
|
+
end
|
195
|
+
|
196
|
+
def maximum_level
|
197
|
+
elements.map(&:level).max
|
198
|
+
end
|
199
|
+
|
200
|
+
def line_at(number)
|
201
|
+
parsed_lines.detect {|line| line.number == number }
|
202
|
+
end
|
203
|
+
|
204
|
+
def index_of(line)
|
205
|
+
lines.index(line)
|
206
|
+
end
|
207
|
+
|
208
|
+
def next_heading_after heading, options={}
|
209
|
+
heading_lines.detect {|line| line.number > heading.number }
|
210
|
+
end
|
211
|
+
|
212
|
+
def highest_level
|
213
|
+
level_boundaries.last
|
214
|
+
end
|
215
|
+
|
216
|
+
def lowest_level
|
217
|
+
level_boundaries.first
|
218
|
+
end
|
219
|
+
|
220
|
+
def level_boundaries
|
221
|
+
levels = heading_lines.map(&:level).uniq
|
222
|
+
[levels.max, levels.min]
|
223
|
+
end
|
224
|
+
|
225
|
+
def heading_lines
|
226
|
+
parsed_lines.select do |line|
|
227
|
+
line.heading?
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def is_line_inside_code_region?(line_number)
|
232
|
+
code_boundaries.any? {|range| range.include?(line_number) }
|
233
|
+
end
|
234
|
+
|
235
|
+
def headings_after(heading_line)
|
236
|
+
if heading_line.is_a?(String)
|
237
|
+
heading_line = heading_lines.detect {|h| h.content == heading_line }
|
238
|
+
end
|
239
|
+
|
240
|
+
heading_lines.select {|line| line.number > heading_line.number }
|
241
|
+
end
|
242
|
+
|
243
|
+
def headings_under(heading_line, options={})
|
244
|
+
matches = []
|
245
|
+
continue = true
|
246
|
+
|
247
|
+
include_subtree = options.fetch(:all, false)
|
248
|
+
as_objects = options.fetch(:as_objects, false)
|
249
|
+
|
250
|
+
if heading_line.is_a?(String)
|
251
|
+
heading_line = heading_lines.detect {|h| h.content == heading_line }
|
252
|
+
end
|
253
|
+
|
254
|
+
headings_after(heading_line).each do |line|
|
255
|
+
continue = false if line.level <= heading_line.level
|
256
|
+
if continue && line.level > heading_line.level
|
257
|
+
matches << line unless (!include_subtree && line.level - heading_line.level > 1)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
as_objects ? matches : matches.map(&:content)
|
262
|
+
end
|
263
|
+
|
264
|
+
def content_lines_under(heading_line, options={})
|
265
|
+
reject_blank = !options.fetch(:include_blank, true)
|
266
|
+
as_objects = options.fetch(:as_objects, false)
|
267
|
+
|
268
|
+
if heading_line.is_a?(String)
|
269
|
+
heading_line = heading_lines.detect {|h| h.content == heading_line }
|
270
|
+
end
|
271
|
+
|
272
|
+
return [] unless heading_line.respond_to?(:number)
|
273
|
+
|
274
|
+
min = heading_line.number
|
275
|
+
max = next_heading_after(heading_line).try(:number) || last_line_number
|
276
|
+
|
277
|
+
matches = content_lines.select do |line|
|
278
|
+
line.content? && line.number.between?(min, max)
|
279
|
+
end
|
280
|
+
|
281
|
+
matches.reject! {|m| m.content.blank? } if reject_blank
|
282
|
+
|
283
|
+
as_objects ? matches : matches.map(&:content)
|
284
|
+
end
|
285
|
+
|
286
|
+
def content_lines
|
287
|
+
parsed_lines.select(&:content?)
|
288
|
+
end
|
289
|
+
|
290
|
+
def code_block_markers
|
291
|
+
parsed_lines.select {|l| l.type == "code_block_marker" }
|
292
|
+
end
|
293
|
+
|
294
|
+
def raw_lines
|
295
|
+
@raw_lines ||= @content.lines.to_a
|
296
|
+
end
|
297
|
+
|
298
|
+
# I really need to get better with arrays
|
299
|
+
def remove_raw_lines start_index, end_index
|
300
|
+
original = @raw_lines.dup
|
301
|
+
copy = []
|
302
|
+
|
303
|
+
original.each_with_index do |line, index|
|
304
|
+
copy.push(line) unless index.between? start_index, end_index
|
305
|
+
end
|
306
|
+
|
307
|
+
@raw_lines = copy
|
308
|
+
end
|
309
|
+
|
310
|
+
def stripped_lines
|
311
|
+
@stripped ||= raw_lines.map(&:strip)
|
312
|
+
end
|
313
|
+
|
314
|
+
def parsed_lines
|
315
|
+
@parsed_lines
|
316
|
+
end
|
317
|
+
|
318
|
+
def parse clear=false
|
319
|
+
@parsed_lines = nil if clear
|
320
|
+
|
321
|
+
return @parsed_lines if @parsed_lines
|
322
|
+
|
323
|
+
parsed = []
|
324
|
+
|
325
|
+
raw = raw_lines
|
326
|
+
|
327
|
+
stripped_lines.each_with_index do |line,index|
|
328
|
+
is_code = code_boundaries.any? {|bounds| bounds.include?(index) }
|
329
|
+
line = Brief::Line.new(line, index, is_code)
|
330
|
+
line.raw = raw[index]
|
331
|
+
parsed << line
|
332
|
+
end
|
333
|
+
|
334
|
+
@parsed_lines = parsed
|
335
|
+
end
|
336
|
+
|
337
|
+
def code_blocks_by_language
|
338
|
+
return @code_samples if @code_samples
|
339
|
+
|
340
|
+
bounds = Array(parser && parser.send(:code_boundaries))
|
341
|
+
sections = parser.lines_between_boundaries(*bounds)
|
342
|
+
|
343
|
+
@code_samples = sections.inject({}) do |memo,section|
|
344
|
+
marker = section.first.split('```').last
|
345
|
+
language = marker.nil? ? :text : marker.strip
|
346
|
+
|
347
|
+
section.shift && section.pop
|
348
|
+
memo[language.to_sym] ||= []
|
349
|
+
memo[language.to_sym] << section.join("").sub(/^\s+/,'')
|
350
|
+
memo
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Brief
|
2
|
+
class Publisher::HandlerManager
|
3
|
+
|
4
|
+
attr_reader :publisher, :document
|
5
|
+
|
6
|
+
def initialize(publisher, document)
|
7
|
+
@publisher = publisher
|
8
|
+
@document = document
|
9
|
+
end
|
10
|
+
|
11
|
+
def parser
|
12
|
+
@parser ||= document.parser
|
13
|
+
end
|
14
|
+
|
15
|
+
def handlers
|
16
|
+
find_parent = lambda do |parent_id|
|
17
|
+
parser.tree.find(parent_id)
|
18
|
+
end
|
19
|
+
|
20
|
+
get_manager = -> { self }
|
21
|
+
|
22
|
+
@handlers ||= publisher.order.inject({}) do |memo, level|
|
23
|
+
config = publisher.config_for_level(level)
|
24
|
+
klass_name = config.publish.klass
|
25
|
+
klass = klass_name.nil? ? Brief::Handlers::Base : (klass_name.to_s.constantize rescue Brief::Handlers::Base)
|
26
|
+
|
27
|
+
memo[level] = parser.tree.level(level).map do |element|
|
28
|
+
obj = klass.new(element)
|
29
|
+
|
30
|
+
obj.parent(&find_parent)
|
31
|
+
obj.get_manager(&get_manager)
|
32
|
+
|
33
|
+
obj
|
34
|
+
end
|
35
|
+
|
36
|
+
memo
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
publisher.order.each do |level|
|
42
|
+
objects = handlers[level]
|
43
|
+
objects.each(&:handle!)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Brief
|
2
|
+
class Publisher
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :defined, :aliases
|
6
|
+
end
|
7
|
+
|
8
|
+
self.defined ||= {}
|
9
|
+
self.aliases ||= {}
|
10
|
+
|
11
|
+
def self.find query
|
12
|
+
query = query.join(" ") if query.respond_to?(:to_ary)
|
13
|
+
|
14
|
+
if defined[query].nil? && aliases.has_key?(query)
|
15
|
+
return find(aliases[query])
|
16
|
+
end
|
17
|
+
|
18
|
+
result = defined[query]
|
19
|
+
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.publisher publisher_name
|
24
|
+
defined[key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.define publisher_name, &config
|
28
|
+
obj = defined[publisher_name] ||= Brief::Publisher.new(publisher_name)
|
29
|
+
obj.instance_eval(&config) if block_given?
|
30
|
+
|
31
|
+
aliases[publisher_name.downcase] ||= obj.name
|
32
|
+
|
33
|
+
obj.aliases.each do |alias_name|
|
34
|
+
aliases[alias_name.to_s] ||= obj.name
|
35
|
+
end
|
36
|
+
|
37
|
+
obj
|
38
|
+
end
|
39
|
+
|
40
|
+
def process document
|
41
|
+
@manager = Brief::Publisher::HandlerManager.new(self, document).run
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_accessor :command_alias, :sample, :name, :options
|
45
|
+
|
46
|
+
def initialize(name, options={})
|
47
|
+
@name = name
|
48
|
+
@options = options
|
49
|
+
@command_alias = options[:command_alias]
|
50
|
+
|
51
|
+
@before_blocks = {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def before event_name, &block
|
55
|
+
(@before_blocks[event_name.to_sym] ||= []).push(block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_before_hooks event_name, &success
|
59
|
+
results = Array(@before_blocks[event_name.to_sym]).map do |hook|
|
60
|
+
hook.call if hook.respond_to?(:call)
|
61
|
+
end
|
62
|
+
|
63
|
+
unless results.any? {|r| r == false }
|
64
|
+
instance_eval(&success) if block_given?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def required_inputs
|
69
|
+
@options[:required_inputs] ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def requires_input field, options={}
|
73
|
+
cfg = required_inputs.fetch(field.to_sym) { options }
|
74
|
+
cfg.reverse_merge(options)
|
75
|
+
end
|
76
|
+
|
77
|
+
def levels &block
|
78
|
+
@state = :levels
|
79
|
+
instance_eval(&block) if block_given?
|
80
|
+
end
|
81
|
+
|
82
|
+
def sample sample_markdown=nil
|
83
|
+
options[:sample] = "#{sample_markdown}".gsub(/^\s+/,'') if sample_markdown
|
84
|
+
options[:sample]
|
85
|
+
end
|
86
|
+
|
87
|
+
def aliases *values
|
88
|
+
@command_aliases ||= []
|
89
|
+
@command_aliases += values
|
90
|
+
@command_aliases.uniq
|
91
|
+
end
|
92
|
+
|
93
|
+
def maximum_level
|
94
|
+
@maximum_level
|
95
|
+
end
|
96
|
+
|
97
|
+
def current_level
|
98
|
+
@current_level || 1
|
99
|
+
end
|
100
|
+
|
101
|
+
def order *levels
|
102
|
+
@order = levels if levels && levels.length > 0
|
103
|
+
@order || maximum_level.times.map {|n| n + 1 }
|
104
|
+
end
|
105
|
+
|
106
|
+
def level level_number, &block
|
107
|
+
@state = :level
|
108
|
+
@maximum_level = [@current_level || 0, level_number].compact.map(&:to_i).max
|
109
|
+
@current_level = level_number
|
110
|
+
instance_eval(&block) if block_given?
|
111
|
+
end
|
112
|
+
|
113
|
+
def level_config
|
114
|
+
@level_config ||= Hashie::Mash.new({})
|
115
|
+
end
|
116
|
+
|
117
|
+
def config_for_level level
|
118
|
+
level_config["level_#{level}".to_sym] ||= {}
|
119
|
+
end
|
120
|
+
|
121
|
+
def current_level_config
|
122
|
+
config_for_level(current_level)
|
123
|
+
end
|
124
|
+
|
125
|
+
def desc description
|
126
|
+
current_level_config[:description] = description
|
127
|
+
end
|
128
|
+
|
129
|
+
def define_handler handler, options={}
|
130
|
+
if options.is_a?(String)
|
131
|
+
options = {klass: options.classify}
|
132
|
+
end
|
133
|
+
|
134
|
+
current_level_config[handler] = options
|
135
|
+
end
|
136
|
+
|
137
|
+
def replaces_items_from_level n, options={}
|
138
|
+
current_level_config[:replace_options] = options
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
data/lib/brief/tree.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Brief
|
2
|
+
class Tree
|
3
|
+
def initialize(nodes)
|
4
|
+
@nodes = nodes
|
5
|
+
end
|
6
|
+
|
7
|
+
def find element_by_id
|
8
|
+
elements.detect {|el| el.id == element_by_id }
|
9
|
+
end
|
10
|
+
|
11
|
+
def elements
|
12
|
+
@elements ||= begin
|
13
|
+
@nodes.map do |node|
|
14
|
+
only = node.except(:children)
|
15
|
+
|
16
|
+
visitor = lambda do |child|
|
17
|
+
c_only = child.except(:children)
|
18
|
+
g_children = Array(child[:children])
|
19
|
+
g_children = nil if g_children.length == 0
|
20
|
+
|
21
|
+
g_children.each {|gchild| gchild.parent_id = c_only.id}
|
22
|
+
|
23
|
+
[c_only, g_children].compact
|
24
|
+
end
|
25
|
+
|
26
|
+
children = Array(node[:children]).map(&visitor).flatten
|
27
|
+
children.each {|child| child.parent_id ||= only.id }
|
28
|
+
|
29
|
+
children = nil if children.length == 0
|
30
|
+
|
31
|
+
[only, children].compact
|
32
|
+
end
|
33
|
+
|
34
|
+
end.flatten
|
35
|
+
end
|
36
|
+
|
37
|
+
def level level=1
|
38
|
+
elements.select {|el| el.level == level }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
data/lib/brief.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
lib = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
module Brief
|
5
|
+
# Haven't decided if the brief config system should support different profiles or not
|
6
|
+
def self.profile
|
7
|
+
configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.config
|
11
|
+
configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@configuration ||= Brief::Configuration.instance
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.root
|
19
|
+
Pathname(Dir.pwd())
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.gem_root
|
23
|
+
Pathname(File.dirname(__FILE__))
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.define publisher_name, &config
|
27
|
+
Brief::Publisher.define(publisher_name, &config)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'pathname'
|
32
|
+
require 'hashie'
|
33
|
+
require 'digest'
|
34
|
+
require 'yaml'
|
35
|
+
|
36
|
+
require 'active_support'
|
37
|
+
require 'active_support/core_ext'
|
38
|
+
|
39
|
+
require 'brief/line'
|
40
|
+
require 'brief/parser'
|
41
|
+
require 'brief/document'
|
42
|
+
require 'brief/tree'
|
43
|
+
require 'brief/version'
|
44
|
+
require 'brief/configuration'
|
45
|
+
|
46
|
+
require 'brief/publisher'
|
47
|
+
require 'brief/publisher/handler_manager'
|
48
|
+
# These should be able to be loaded separately
|
49
|
+
# some other way, but to help develoment..
|
50
|
+
require 'brief/handlers/base'
|
51
|
+
require 'brief/formatters/base'
|
52
|
+
|
53
|
+
# these may be optional one day
|
54
|
+
require 'brief/github'
|
55
|
+
require 'brief/git'
|
56
|
+
require 'brief/github/wiki'
|
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
class Object
|
2
|
+
def present?
|
3
|
+
!nil?
|
4
|
+
end
|
5
|
+
|
6
|
+
def try(*a, &b)
|
7
|
+
if a.empty? && block_given?
|
8
|
+
yield self
|
9
|
+
else
|
10
|
+
public_send(*a, &b) if respond_to?(a.first)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def try!(*a, &b)
|
15
|
+
if a.empty? && block_given?
|
16
|
+
yield self
|
17
|
+
else
|
18
|
+
public_send(*a, &b)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class NilClass
|
24
|
+
def present?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
def try(*args)
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def try!(*args)
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'core_ext/hash'
|
37
|
+
require 'core_ext/string'
|