brief 0.0.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +71 -55
  4. data/README.md +149 -48
  5. data/Rakefile +15 -5
  6. data/bin/brief +15 -28
  7. data/brief.gemspec +17 -11
  8. data/examples/blog/brief.rb +54 -0
  9. data/examples/blog/docs/an-intro-to-brief.html.md +9 -0
  10. data/lib/.DS_Store +0 -0
  11. data/lib/brief/briefcase.rb +78 -0
  12. data/lib/brief/cli/change.rb +14 -0
  13. data/lib/brief/cli/init.rb +66 -0
  14. data/{spec/fixtures/generated/project_overview.json → lib/brief/cli/publish.rb} +0 -0
  15. data/lib/brief/cli/write.rb +0 -0
  16. data/lib/brief/configuration.rb +19 -115
  17. data/lib/brief/core_ext.rb +11 -0
  18. data/lib/brief/document/content_extractor.rb +33 -0
  19. data/lib/brief/document/front_matter.rb +20 -0
  20. data/lib/brief/document/rendering.rb +37 -0
  21. data/lib/brief/document.rb +43 -38
  22. data/lib/brief/document_mapper.rb +158 -0
  23. data/lib/brief/dsl.rb +42 -0
  24. data/lib/brief/model/definition.rb +117 -0
  25. data/lib/brief/model.rb +177 -0
  26. data/lib/brief/repository.rb +57 -0
  27. data/lib/brief/version.rb +1 -7
  28. data/lib/brief.rb +35 -40
  29. data/spec/fixtures/example/brief.rb +27 -0
  30. data/spec/fixtures/example/docs/concept.html.md +5 -0
  31. data/spec/fixtures/example/docs/epic.html.md +19 -0
  32. data/spec/fixtures/example/docs/persona.html.md +5 -0
  33. data/spec/fixtures/example/docs/release.html.md +7 -0
  34. data/spec/fixtures/example/docs/resource.html.md +5 -0
  35. data/spec/fixtures/example/docs/user_story.html.md +24 -0
  36. data/spec/fixtures/example/docs/wireframe.html.md +5 -0
  37. data/spec/fixtures/example/models/epic.rb +16 -0
  38. data/spec/lib/brief/briefcase_spec.rb +27 -0
  39. data/spec/lib/brief/document_spec.rb +14 -22
  40. data/spec/lib/brief/dsl_spec.rb +4 -15
  41. data/spec/lib/brief/model_spec.rb +84 -0
  42. data/spec/lib/brief/repository_spec.rb +60 -0
  43. data/spec/spec_helper.rb +12 -14
  44. data/spec/support/test_helpers.rb +0 -0
  45. data/tasks/brief/release.rake +0 -0
  46. metadata +120 -110
  47. data/.gitignore +0 -1
  48. data/Guardfile +0 -5
  49. data/examples/project_overview.md +0 -23
  50. data/lib/brief/cli/commands/config.rb +0 -40
  51. data/lib/brief/cli/commands/publish.rb +0 -27
  52. data/lib/brief/cli/commands/write.rb +0 -27
  53. data/lib/brief/formatters/base.rb +0 -12
  54. data/lib/brief/formatters/github_milestone_rollup.rb +0 -52
  55. data/lib/brief/git.rb +0 -19
  56. data/lib/brief/github/wiki.rb +0 -9
  57. data/lib/brief/github.rb +0 -141
  58. data/lib/brief/github_client/authentication.rb +0 -32
  59. data/lib/brief/github_client/client.rb +0 -86
  60. data/lib/brief/github_client/commands.rb +0 -5
  61. data/lib/brief/github_client/issue_labels.rb +0 -65
  62. data/lib/brief/github_client/issues.rb +0 -22
  63. data/lib/brief/github_client/milestone_issues.rb +0 -13
  64. data/lib/brief/github_client/organization_activity.rb +0 -9
  65. data/lib/brief/github_client/organization_issues.rb +0 -13
  66. data/lib/brief/github_client/organization_repositories.rb +0 -20
  67. data/lib/brief/github_client/organization_users.rb +0 -9
  68. data/lib/brief/github_client/repository_events.rb +0 -8
  69. data/lib/brief/github_client/repository_issue_events.rb +0 -9
  70. data/lib/brief/github_client/repository_issues.rb +0 -8
  71. data/lib/brief/github_client/repository_labels.rb +0 -18
  72. data/lib/brief/github_client/repository_milestones.rb +0 -9
  73. data/lib/brief/github_client/request.rb +0 -181
  74. data/lib/brief/github_client/request_wrapper.rb +0 -121
  75. data/lib/brief/github_client/response_object.rb +0 -50
  76. data/lib/brief/github_client/single_repository.rb +0 -9
  77. data/lib/brief/github_client/user_activity.rb +0 -16
  78. data/lib/brief/github_client/user_gists.rb +0 -9
  79. data/lib/brief/github_client/user_info.rb +0 -9
  80. data/lib/brief/github_client/user_issues.rb +0 -13
  81. data/lib/brief/github_client/user_organizations.rb +0 -9
  82. data/lib/brief/github_client/user_repositories.rb +0 -9
  83. data/lib/brief/github_client.rb +0 -43
  84. data/lib/brief/handlers/base.rb +0 -62
  85. data/lib/brief/handlers/github_issue.rb +0 -41
  86. data/lib/brief/handlers/github_milestone.rb +0 -37
  87. data/lib/brief/handlers/github_wiki.rb +0 -11
  88. data/lib/brief/line.rb +0 -69
  89. data/lib/brief/parser.rb +0 -354
  90. data/lib/brief/publisher/handler_manager.rb +0 -47
  91. data/lib/brief/publisher.rb +0 -142
  92. data/lib/brief/tree.rb +0 -43
  93. data/lib/core_ext.rb +0 -37
  94. data/spec/fixtures/front_end_tutorial.md +0 -33
  95. data/spec/fixtures/generator_dsl_example.rb +0 -22
  96. data/spec/fixtures/project_overview.md +0 -48
  97. data/spec/fixtures/sample.md +0 -19
  98. data/spec/lib/brief/line_spec.rb +0 -11
  99. data/spec/lib/brief/parser_spec.rb +0 -12
data/lib/brief/parser.rb DELETED
@@ -1,354 +0,0 @@
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).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
@@ -1,47 +0,0 @@
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
@@ -1,142 +0,0 @@
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 DELETED
@@ -1,43 +0,0 @@
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
-
20
- g_children.each {|gchild| gchild.parent_id = c_only.id}
21
-
22
- g_children = nil if g_children.length == 0
23
-
24
- [c_only, g_children].compact
25
- end
26
-
27
- children = Array(node[:children]).map(&visitor).flatten
28
- children.each {|child| child.parent_id ||= only.id }
29
-
30
- children = nil if children.length == 0
31
-
32
- [only, children].compact
33
- end
34
-
35
- end.flatten
36
- end
37
-
38
- def level level=1
39
- elements.select {|el| el.level == level }
40
- end
41
-
42
- end
43
- end
data/lib/core_ext.rb DELETED
@@ -1,37 +0,0 @@
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'
@@ -1,33 +0,0 @@
1
- # Backbone View Tutorial
2
-
3
- Below is an example of how you can style a backbone view.
4
-
5
- ## Template Content
6
-
7
- First write a basic template.
8
-
9
- ```slim
10
- nav.nav.navbar-inverse
11
- .container
12
- .row-fluid
13
- ```
14
-
15
- ## Style Content
16
-
17
- Make the text pink, because it is manly.
18
-
19
- ```scss
20
- nav.navbar-inverse {
21
- color: pink;
22
- }
23
- ```
24
-
25
- ## Script Content
26
-
27
- Then you'll wanna do some stuff with the coffeescript.
28
-
29
- ```coffeescript
30
- MyView = Backbone.View.extend
31
- className: "navbar-inverse"
32
- ```
33
-
@@ -1,22 +0,0 @@
1
- Brief.define "Project Overview" do
2
- aliases :project_overview
3
-
4
- levels do
5
- level(1) do
6
- name "Wiki Page"
7
- define_handler :create => "Brief::Handlers::GithubWiki"
8
- replaces_items_from_level 2, :with => "Brief::Formatters::GithubMilestoneRollup"
9
- end
10
-
11
- level(2) do
12
- name "Github Milestone"
13
- define_handler :create => "Brief::Handlers::GithubMilestone"
14
- replaces_items_from_level 3, :with => nil
15
- end
16
-
17
- level(3) do
18
- name "Github Issue"
19
- define_handler :create => "Brief::Handlers::GithubIssue"
20
- end
21
- end
22
- end