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 +4 -4
- data/Gemfile.lock +1 -1
- data/apps/blueprint/lib/epic_publisher.rb +28 -0
- data/apps/blueprint/models/epic.rb +107 -26
- data/bin/brief +1 -0
- data/lib/brief.rb +1 -0
- data/lib/brief/briefcase.rb +3 -2
- data/lib/brief/document.rb +1 -0
- data/lib/brief/document/section.rb +6 -2
- data/lib/brief/document/source_map.rb +55 -0
- data/lib/brief/document/structure.rb +14 -5
- data/lib/brief/util.rb +42 -2
- data/lib/brief/version.rb +1 -1
- data/spec/lib/brief/source_map_spec.rb +14 -0
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 379b1d59287fbfabb503d50cca774092722c3201
|
4
|
+
data.tar.gz: 76b5128f3003dd991804854e39c4b0c712fc7ca0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7a88ddd403c7db77efb356de2e1906f67e346eaa2708c9610228fdae3720f1e21147d33c6b52ccb6aa68d2fed69970f891fc8c807c911e0287b2bbda2c9ba76
|
7
|
+
data.tar.gz: e63bd5e5ea3157cb8367ca99d371f6da4371a05ad15cca6d2a00541b1cf3b4456ebccdb7950a14adf725fdabda01f21b6152767916207c063c688c07a4157546
|
data/Gemfile.lock
CHANGED
@@ -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
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'
|
data/lib/brief/briefcase.rb
CHANGED
@@ -26,10 +26,10 @@ module Brief
|
|
26
26
|
|
27
27
|
Brief.cases[root.basename.to_s] ||= self
|
28
28
|
|
29
|
-
|
29
|
+
load_briefcase_lib_entries()
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
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?
|
data/lib/brief/document.rb
CHANGED
@@ -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(
|
31
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
216
|
-
@
|
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
@@ -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.
|
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
|