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