brief 1.16.2 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2ab0c3413dab15b505c92df133f1a4e144352dd9
4
- data.tar.gz: 4a613723060393c2b3338ad020cef71b06710ad9
3
+ metadata.gz: 379b1d59287fbfabb503d50cca774092722c3201
4
+ data.tar.gz: 76b5128f3003dd991804854e39c4b0c712fc7ca0
5
5
  SHA512:
6
- metadata.gz: 933b94b0c4b6d1b137eda4eb5c2f73ab6140943cb189fc85e752a9227613159b2df5706fe5f37d6065061da98bf00457311d58ec3083ee98874013d6e5fd91e4
7
- data.tar.gz: 3792781b3e13298ff9a6b2d0e5e0626f1c720afea837aa304553801251f15c10e6bdd0c3c4fa7b60120815c62998007b27688c2212c2cdbe613d1c7ab54f36bb
6
+ metadata.gz: e7a88ddd403c7db77efb356de2e1906f67e346eaa2708c9610228fdae3720f1e21147d33c6b52ccb6aa68d2fed69970f891fc8c807c911e0287b2bbda2c9ba76
7
+ data.tar.gz: e63bd5e5ea3157cb8367ca99d371f6da4371a05ad15cca6d2a00541b1cf3b4456ebccdb7950a14adf725fdabda01f21b6152767916207c063c688c07a4157546
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brief (1.16.2)
4
+ brief (1.16.3)
5
5
  activesupport (> 3.2)
6
6
  commander (~> 4.3)
7
7
  em-websocket (~> 0.5)
@@ -0,0 +1,28 @@
1
+ # The BlueprintEpicPublisher
2
+ # is an adapter class which will route
3
+ # the epic to an API integration capable of
4
+ # publishign the epic, such as pivotal tracker or github
5
+ class BlueprintEpicPublisher
6
+ def self.publish(epic, options={})
7
+ via = (options.fetch(:via, :github) || :github)
8
+
9
+ if respond_to?("publish_via_#{via}")
10
+ send("publish_via_#{via}", epic,options)
11
+ else
12
+ raise "Invalid publishing source. Need to implement publish_via_#{via} method"
13
+ end
14
+ end
15
+
16
+ # Epics get published to Pivotal
17
+ # directly as epics, since Epics have first class support in pivotal
18
+ def self.publish_via_pivotal(epic, options={})
19
+ raise 'This is not implemented in the app yet. You can implement it on your own by implementing BlueprintEpicPublisher.publish_via_pivotal'
20
+ end
21
+
22
+ # Epics get published to Github
23
+ # as issues, and then get labeled as epics.
24
+ # epics will reference other issues.
25
+ def self.publish_via_github(epic, options={})
26
+ raise 'This is not implemented in the app yet. You can implement it on your own by implementing BlueprintEpicPublisher.publish_via_github'
27
+ end
28
+ end
@@ -12,32 +12,6 @@ class Brief::Apps::Blueprint::Epic
12
12
  tags Array
13
13
  end
14
14
 
15
- example <<-EOF
16
- ---
17
- type: epic
18
- status: draft
19
- ---
20
-
21
- # Epic Title
22
-
23
- Write a description for your epic.
24
-
25
- # Features
26
-
27
- ## Feature Title
28
-
29
- As a **PERSONA** I would like to **BEHAVIOR** so that I can **GOAL**
30
- EOF
31
-
32
- template <<-EOF
33
- # <%= object.title %>
34
- # Features
35
- <% Array(object.features).each do |feature| %>
36
- ## <%= feature.title %>
37
- As a **User** I would like to **Do this** so that I can **succeed**
38
- <% end %>
39
- EOF
40
-
41
15
  content do
42
16
  title "h1:first-of-type"
43
17
  paragraph "p:first-of-type"
@@ -54,6 +28,34 @@ As a **User** I would like to **Do this** so that I can **succeed**
54
28
  end
55
29
  end
56
30
 
31
+ template <<-EOF
32
+ # <%= object.title %>
33
+ # Features
34
+ <% Array(object.features).each do |feature| %>
35
+ ## <%= feature.title %>
36
+ <%= feature.paragraph %>
37
+ <% end %>
38
+ EOF
39
+
40
+
41
+ actions do
42
+ def validate
43
+ $brief_cli ? validate_cli : true
44
+ end
45
+
46
+ def estimate
47
+ estimate_cli if $brief_cli
48
+ end
49
+
50
+ def activate
51
+ raise 'Need to implement this on your own'
52
+ end
53
+
54
+ def publish
55
+ BlueprintEpicPublisher.publish(self, via: briefcase.settings.try(:tracking_system))
56
+ end
57
+ end
58
+
57
59
  helpers do
58
60
  def features
59
61
  sections.features.items.map do |item|
@@ -62,6 +64,85 @@ As a **User** I would like to **Do this** so that I can **succeed**
62
64
  item.merge(goal: item.components[2],
63
65
  persona: item.components[0],
64
66
  behavior: item.components[1])
67
+
68
+ end
69
+ end
70
+
71
+ def raw_content_for_feature(feature_heading, include_heading=true)
72
+ document.content_under_heading(feature_heading, include_heading)
73
+ end
74
+
75
+ def estimate_cli
76
+ new_content = ask_editor("# Enter point values next to each feature title\n\n#{estimations_yaml}")
77
+ parsed = YAML.load(new_content) rescue nil
78
+
79
+ if parsed
80
+ estimations_yaml_path.open("w+") {|fh| fh.write(new_content) }
81
+ end
82
+ end
83
+
84
+ def estimations_yaml
85
+ estimations_data.to_yaml
86
+ end
87
+
88
+ def estimations_data
89
+ if estimations_yaml_path.exist?
90
+ YAML.load(estimations_yaml_path.read)
91
+ else
92
+ estimates = features.map(&:title).reduce({}) do |memo, feature_title|
93
+ memo[feature_title] = 0
94
+ memo
95
+ end
96
+
97
+ {
98
+ type: "epic_estimations",
99
+ document_path: document.path,
100
+ epic_title: title,
101
+ estimates: estimates
102
+ }.stringify_keys
103
+ end
104
+ end
105
+
106
+ def estimations_yaml_path
107
+ briefcase.data_path.join("estimations","#{title.parameterize}.yml")
108
+ end
109
+
110
+ # prints a validation report of the epic
111
+ def validation_report
112
+ warnings = []
113
+ errors = []
114
+
115
+ begin
116
+ features
117
+ rescue => e
118
+ errors.push "Error generating features: #{ e.message }"
119
+ end
120
+
121
+ if title.to_s.length == 0
122
+ errors.push "Missing epic title"
123
+ end
124
+
125
+ if project.to_s.length == 0
126
+ warnings.push "Missing project reference"
127
+ else
128
+ project_titles = Array((briefcase.projects.map(&:title) rescue nil))
129
+ warnings.push "Invalid project reference. #{ project } does not refer to a valid project. #{ project_titles }" unless project_titles.include?(project)
130
+ end
131
+
132
+ [warnings, errors]
133
+ end
134
+
135
+ def validate_cli
136
+ warnings, errors = validation_report
137
+
138
+ if !warnings.empty?
139
+ puts "== Epic Warnings. #{ document.path }".yellow
140
+ warnings.each {|w| puts " -- #{ w }" }
141
+ end
142
+
143
+ if !errors.empty?
144
+ puts "== Epic Errors. #{ document.path }".red
145
+ errors.each {|w| puts " -- #{ w }" }
65
146
  end
66
147
  end
67
148
  end
data/bin/brief CHANGED
@@ -5,6 +5,7 @@ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
5
5
  require 'rubygems'
6
6
  require 'brief'
7
7
  require 'brief/dsl'
8
+ require 'colored'
8
9
 
9
10
 
10
11
  if ARGV[0] == "console"
data/lib/brief.rb CHANGED
@@ -140,6 +140,7 @@ require 'brief/document/structure'
140
140
  require 'brief/document/section'
141
141
  require 'brief/document/section/mapping'
142
142
  require 'brief/document/section/builder'
143
+ require 'brief/document/source_map'
143
144
  require 'brief/document'
144
145
  require 'brief/document_mapper'
145
146
  require 'brief/repository'
@@ -26,10 +26,10 @@ module Brief
26
26
 
27
27
  Brief.cases[root.basename.to_s] ||= self
28
28
 
29
- load_lib_entries()
29
+ load_briefcase_lib_entries()
30
30
  end
31
31
 
32
- def load_lib_entries
32
+ def load_briefcase_lib_entries
33
33
  begin
34
34
  etc = Dir[briefcase_lib_path.join("**/*.rb")]
35
35
  etc.each {|f| require(f) } if briefcase_lib_path.exist?
@@ -254,6 +254,7 @@ module Brief
254
254
  def load_model_definitions
255
255
  if uses_app?
256
256
  Brief.load_modules_from(app_path.join("models"))
257
+ Dir[app_path.join("lib","**/*.rb")].each {|f| require(f) }
257
258
  end
258
259
 
259
260
  Brief.load_modules_from(models_path) if models_path.exist?
@@ -4,6 +4,7 @@ module Brief
4
4
  include Brief::Document::FrontMatter
5
5
  include Brief::Document::Templating
6
6
  include Brief::Document::Attachments
7
+ include Brief::Document::SourceMap
7
8
 
8
9
  def self.from_contents(content, frontmatter, &block)
9
10
  end
@@ -27,8 +27,12 @@ class Brief::Document::Section
27
27
  settings = config.selector_config[selector]
28
28
 
29
29
  if Headings.include?(selector)
30
- headings = fragment.css('article > h2')
31
- articles = headings.map(&:parent)
30
+ headings = fragment.css("article > #{selector}")
31
+
32
+ articles = headings.map do |el|
33
+ el.parent.set_attribute('data-parent-heading', el.text)
34
+ el.parent
35
+ end
32
36
 
33
37
  unless settings.empty?
34
38
  articles.compact.each do |article|
@@ -0,0 +1,55 @@
1
+ module Brief::Document::SourceMap
2
+ def heading_line_numbers
3
+ heading_element_tags.map {|el| el.attr('data-line-number') }
4
+ end
5
+
6
+ def heading_element_tags
7
+ css('h1,h2,h4,h5,h6')
8
+ end
9
+
10
+ def heading_details
11
+ @heading_details ||= structure.get_heading_details.tap do |list|
12
+ list.map! {|i| i.line_number = i.line_number.to_i; i.level = i.level.to_i; i }
13
+ list.sort_by! {|i| i.line_number }
14
+ end
15
+ end
16
+
17
+ def next_sibling_heading_for(heading_element)
18
+ if heading_element.is_a?(String) && heading_element.length > 1
19
+ heading_element = heading_element_tags.find do |el|
20
+ el.attr('data-heading').include?(heading_element) || el.text.to_s.include?(heading_element)
21
+ end
22
+ end
23
+
24
+ line = heading_element.attr('data-line-number').to_i
25
+ level = heading_element.attr('data-level').to_i
26
+
27
+ superior = heading_details.find do |next_element|
28
+ next_element.line_number.to_i > line && next_element.level.to_i >= level
29
+ end
30
+
31
+ superior && superior.element
32
+ end
33
+
34
+ def content_under_heading(heading_element, include_heading=true)
35
+ if heading_element.is_a?(String) && heading_element.length > 1
36
+ heading_element = heading_element_tags.find do |el|
37
+ el.attr('data-heading').include?(heading_element) || el.text.to_s.include?(heading_element)
38
+ end
39
+ end
40
+
41
+ start_index = heading_element.attr('data-line-number').to_i
42
+ end_index = content.lines.length
43
+
44
+ if next_heading = next_sibling_heading_for(heading_element)
45
+ end_index = next_heading.attr('data-line-number').to_i
46
+ end
47
+
48
+ end_index = end_index - start_index
49
+ start_index = 0 if start_index < 0
50
+
51
+ lines = raw_content.lines.dup.slice(start_index - 1, end_index)
52
+
53
+ (include_heading ? lines : lines.slice(1, lines.length)).join("")
54
+ end
55
+ end
@@ -89,6 +89,10 @@ module Brief
89
89
  end
90
90
  end
91
91
 
92
+ def heading_element_tags
93
+ css("h1,h2,h3,h4,h5,h6")
94
+ end
95
+
92
96
  # Markdown rendered HTML comes in the forms of a bunch of siblings,
93
97
  # and no parents. We need to introduce the concept of ownership of
94
98
  # sections of the document, by using the heading level (h1 - h6) as
@@ -180,7 +184,7 @@ module Brief
180
184
  end
181
185
 
182
186
  def headings_at_level(level, options = {})
183
- matches = heading_elements.select { |el| el.level.to_i == level.to_i }
187
+ matches = heading_details.select { |el| el.level.to_i == level.to_i }
184
188
 
185
189
  if options[:text]
186
190
  matches.map(&:text)
@@ -197,7 +201,7 @@ module Brief
197
201
  end
198
202
 
199
203
  def headings_with_text(text)
200
- heading_elements.select do |el|
204
+ heading_details.select do |el|
201
205
  el.heading.to_s.strip == text.to_s.strip
202
206
  end
203
207
  end
@@ -207,17 +211,22 @@ module Brief
207
211
  end
208
212
 
209
213
  def find_heading_by(level, heading)
210
- heading_elements.find do |el|
214
+ heading_details.find do |el|
211
215
  el.level.to_s == level.to_s && heading.to_s.strip == el.heading.to_s.strip
212
216
  end
213
217
  end
214
218
 
215
- def heading_elements
216
- @heading_elements ||= fragment.css('h1,h2,h3,h4,h5,h6').map do |el|
219
+ def heading_details
220
+ @heading_details ||= get_heading_details
221
+ end
222
+
223
+ def get_heading_details
224
+ fragment.css('h1,h2,h3,h4,h5,h6').map do |el|
217
225
  if el.attr('data-level').to_i > 0
218
226
  {
219
227
  level: el.attr('data-level'),
220
228
  heading: el.attr('data-heading'),
229
+ line_number: el.attr('data-line-number'),
221
230
  element: el
222
231
  }.to_mash
223
232
  end
data/lib/brief/util.rb CHANGED
@@ -22,13 +22,53 @@ module Brief::Util
22
22
  end
23
23
  end
24
24
 
25
+ def self.create_singular_method_dispatcher_command_for(action, klass)
26
+ identifier = "#{ action } #{ klass.type_alias }"
27
+
28
+ Object.class.class_eval do
29
+ command "#{identifier}" do |c|
30
+ c.syntax = "brief #{identifier} PATH"
31
+ c.description = "run the #{identifier} command on the model specified at path"
32
+
33
+ c.action do |args, _opts|
34
+ briefcase = $briefcase || Brief.case
35
+
36
+ path_args = args.select { |arg| arg.is_a?(String) && arg.match(/\.md$/) }
37
+
38
+ path_args.select! do |arg|
39
+ briefcase.root.join(arg).exist?
40
+ end
41
+
42
+ path_args.map! { |p| briefcase.root.join(p) }
43
+
44
+ models = briefcase.documents_at(*path_args).map(&:to_model)
45
+
46
+ if models.empty?
47
+ model_finder = c.name.to_s.split(' ').last
48
+ models = briefcase.send(model_finder)
49
+ elsif models.length > 1
50
+ puts "You passed more than one model. If this is what you want, use the pluralized version of this command to be safe"
51
+ else
52
+ model = models.first
53
+ model && model.send(action)
54
+ end
55
+ end
56
+ end rescue nil
57
+ end
58
+ end
59
+
60
+ # takes the actions from the models and creates a command
61
+ # that lets you dispatch the action to a group of models at the
62
+ # paths / directory / glob you pass
25
63
  def self.create_method_dispatcher_command_for(action, klass)
64
+ create_singular_method_dispatcher_command_for(action,klass)
65
+
26
66
  identifier = "#{ action } #{ klass.type_alias.to_s.pluralize }"
27
67
 
28
68
  Object.class.class_eval do
29
69
  command "#{identifier}" do |c|
30
- c.syntax = "brief #{identifier}"
31
- c.description = "run the #{identifier} command"
70
+ c.syntax = "brief #{identifier} PATHS"
71
+ c.description = "run the #{identifier} command on the models at PATHS"
32
72
 
33
73
  c.action do |args, _opts|
34
74
  briefcase = $briefcase || Brief.case
data/lib/brief/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Brief
2
- VERSION = '1.16.2'
2
+ VERSION = '1.17.0'
3
3
  end
@@ -0,0 +1,14 @@
1
+ require "spec_helper"
2
+
3
+ describe "Source Maps Feature" do
4
+ let(:example) { Brief.example_document }
5
+ let(:heading) { "A user wants to write epics" }
6
+
7
+ it "returns the raw content under a given heading" do
8
+ expect(example.content_under_heading(heading)).to include(heading)
9
+ end
10
+
11
+ it "returns the raw content under a given heading without the heading" do
12
+ expect(example.content_under_heading(heading, false)).not_to include(heading)
13
+ end
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brief
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.16.2
4
+ version: 1.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Soeder
@@ -343,6 +343,7 @@ files:
343
343
  - apps/blueprint/examples/sitemap.md
344
344
  - apps/blueprint/examples/wireframe.md
345
345
  - apps/blueprint/extensions/middleman.rb
346
+ - apps/blueprint/lib/epic_publisher.rb
346
347
  - apps/blueprint/models/concept.rb
347
348
  - apps/blueprint/models/diagram.rb
348
349
  - apps/blueprint/models/epic.rb
@@ -396,6 +397,7 @@ files:
396
397
  - lib/brief/document/section.rb
397
398
  - lib/brief/document/section/builder.rb
398
399
  - lib/brief/document/section/mapping.rb
400
+ - lib/brief/document/source_map.rb
399
401
  - lib/brief/document/structure.rb
400
402
  - lib/brief/document/templating.rb
401
403
  - lib/brief/document/transformer.rb
@@ -475,6 +477,7 @@ files:
475
477
  - spec/lib/brief/serializers_spec.rb
476
478
  - spec/lib/brief/server/gateway_spec.rb
477
479
  - spec/lib/brief/server/route_spec.rb
480
+ - spec/lib/brief/source_map_spec.rb
478
481
  - spec/lib/brief/structure_spec.rb
479
482
  - spec/lib/brief/template_spec.rb
480
483
  - spec/spec_helper.rb
@@ -578,6 +581,7 @@ test_files:
578
581
  - spec/lib/brief/serializers_spec.rb
579
582
  - spec/lib/brief/server/gateway_spec.rb
580
583
  - spec/lib/brief/server/route_spec.rb
584
+ - spec/lib/brief/source_map_spec.rb
581
585
  - spec/lib/brief/structure_spec.rb
582
586
  - spec/lib/brief/template_spec.rb
583
587
  - spec/spec_helper.rb