brief 1.16.2 → 1.17.0

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.
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