brief 1.1.0 → 1.2.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/CHANGELOG.md +38 -0
- data/Gemfile.lock +6 -6
- data/README.md +11 -2
- data/lib/brief/adapters/middleman.rb +18 -0
- data/lib/brief/briefcase.rb +9 -1
- data/lib/brief/document/content_extractor.rb +7 -1
- data/lib/brief/document/front_matter.rb +8 -2
- data/lib/brief/document/rendering.rb +45 -15
- data/lib/brief/document/section/builder.rb +89 -0
- data/lib/brief/document/section/mapping.rb +61 -0
- data/lib/brief/document/section.rb +41 -0
- data/lib/brief/document/structure.rb +146 -0
- data/lib/brief/document.rb +60 -10
- data/lib/brief/dsl.rb +10 -0
- data/lib/brief/model/definition.rb +14 -5
- data/lib/brief/model.rb +9 -1
- data/lib/brief/repository.rb +1 -1
- data/lib/brief/version.rb +1 -1
- data/lib/brief.rb +11 -0
- data/spec/fixtures/example/docs/epic.html.md +6 -0
- data/spec/fixtures/example/models/epic.rb +9 -2
- data/spec/fixtures/structures/one.html.md +37 -0
- data/spec/fixtures/structures/three.html.md +20 -0
- data/spec/fixtures/structures/two.html.md +40 -0
- data/spec/lib/brief/document_spec.rb +18 -2
- data/spec/lib/brief/model_spec.rb +7 -0
- data/spec/lib/brief/rendering_spec.rb +24 -0
- data/spec/lib/brief/section_builder_spec.rb +22 -0
- data/spec/lib/brief/structure_spec.rb +30 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f22a8f02957425fed25d8adfa60cd4b9aa2ced77
|
4
|
+
data.tar.gz: 5c6e4bbb9a213eeaeac820cef681f17fc308d08a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86ceea62469f2a210e595a03b056cda6297b0adbce06c90659f664b284e9d61a3b1f9be7d125ee759632bd08c8e585f2c8e3fdca74559f92675c9d56b486b067
|
7
|
+
data.tar.gz: 0f5fd7ccea4cd89b9243b6409297bc99525c37c2a6c5baca0f0b7aceb65f6ef067d042055f8e7915d28306c2c796f3307a35a4797c72593efedc63244af51c2c
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
### 1.1.0
|
2
|
+
|
3
|
+
- Introducing Briefcases
|
4
|
+
|
5
|
+
Briefcases are top level folders which contain model definition code
|
6
|
+
and configuration, and a hierarchy of subfolders each containing
|
7
|
+
markdown documents which will map to models.
|
8
|
+
|
9
|
+
- Model Definition DSL
|
10
|
+
|
11
|
+
Briefcases can have more than one model class. These models can have
|
12
|
+
attributes, and can specify structure rules which allow for the
|
13
|
+
document to be parsed in a semantic way.
|
14
|
+
|
15
|
+
### 1.2.0
|
16
|
+
|
17
|
+
- Introducing Document Sections
|
18
|
+
- Added `define_section` to the model definition DSL. This will allow certain headings to be used to access a
|
19
|
+
section of the document in isolation.
|
20
|
+
|
21
|
+
Document sections have their own mini-structure, and will
|
22
|
+
contain at least one, usually more than one repeatable pattern.
|
23
|
+
|
24
|
+
These repeatable patterns will usually contain short-hand
|
25
|
+
references to other documents, or key attributes that can be used to
|
26
|
+
create other documents.
|
27
|
+
|
28
|
+
Document sections are how one document can be broken apart into many
|
29
|
+
other documents.
|
30
|
+
|
31
|
+
- CLI Actions
|
32
|
+
- Added `actions` to the method definition DSL. This will allow a model instance to define a method, and
|
33
|
+
then dispatch calls to this method from the Brief CLI interface.
|
34
|
+
|
35
|
+
- Changed HTML rendering
|
36
|
+
- using a custom redcarpet markdown renderer in order to include
|
37
|
+
data-attributes on heading tags
|
38
|
+
- rendered html retains line number reference to the source markdown
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
brief (1.
|
4
|
+
brief (1.2.0)
|
5
5
|
activemodel
|
6
6
|
activesupport
|
7
7
|
commander
|
@@ -39,7 +39,7 @@ GEM
|
|
39
39
|
thread_safe (~> 0.3, >= 0.3.1)
|
40
40
|
diff-lcs (1.2.5)
|
41
41
|
equalizer (0.0.9)
|
42
|
-
faraday (0.9.
|
42
|
+
faraday (0.9.1)
|
43
43
|
multipart-post (>= 1.2, < 3)
|
44
44
|
github-fs (0.0.1)
|
45
45
|
activesupport (> 3.2.0)
|
@@ -50,10 +50,10 @@ GEM
|
|
50
50
|
i18n (0.7.0)
|
51
51
|
ice_nine (0.11.1)
|
52
52
|
inflecto (0.0.2)
|
53
|
-
json (1.8.
|
53
|
+
json (1.8.2)
|
54
54
|
method_source (0.8.2)
|
55
55
|
mini_portile (0.6.2)
|
56
|
-
minitest (5.5.
|
56
|
+
minitest (5.5.1)
|
57
57
|
multipart-post (2.0.0)
|
58
58
|
nokogiri (1.6.5)
|
59
59
|
mini_portile (~> 0.6.0)
|
@@ -67,7 +67,7 @@ GEM
|
|
67
67
|
pry (>= 0.9.10, < 0.11.0)
|
68
68
|
rack (1.6.0)
|
69
69
|
rake (10.4.2)
|
70
|
-
redcarpet (3.2.
|
70
|
+
redcarpet (3.2.2)
|
71
71
|
rspec (3.1.0)
|
72
72
|
rspec-core (~> 3.1.0)
|
73
73
|
rspec-expectations (~> 3.1.0)
|
@@ -87,7 +87,7 @@ GEM
|
|
87
87
|
thread_safe (0.3.4)
|
88
88
|
tzinfo (1.2.2)
|
89
89
|
thread_safe (~> 0.1)
|
90
|
-
virtus (1.0.
|
90
|
+
virtus (1.0.4)
|
91
91
|
axiom-types (~> 0.1)
|
92
92
|
coercible (~> 1.0)
|
93
93
|
descendants_tracker (~> 0.0, >= 0.0.3)
|
data/README.md
CHANGED
@@ -103,9 +103,18 @@ define "Post" do
|
|
103
103
|
end
|
104
104
|
```
|
105
105
|
|
106
|
-
you can either call that method as you normally would
|
107
|
-
|
106
|
+
you can either call that method as you normally would:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
post = Brief.case.posts.where(:status => "draft")
|
110
|
+
post.publish()
|
111
|
+
```
|
112
|
+
|
113
|
+
or you can run that action from the command line:
|
108
114
|
|
109
115
|
```bash
|
110
116
|
brief publish posts ./posts/*.html.md
|
111
117
|
```
|
118
|
+
|
119
|
+
this will find all of the post models matching the document, and then
|
120
|
+
call the publish method on them.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Brief::Adapters
|
2
|
+
class MiddlemanExtension
|
3
|
+
|
4
|
+
def self.activate_brief_extension
|
5
|
+
::Middleman::Extensions.register(:brief, Brief::Adapters::MiddlemanExtension)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(app, options_hash={}, &block)
|
9
|
+
super
|
10
|
+
|
11
|
+
app.include(ClassMethods)
|
12
|
+
|
13
|
+
options_hash.each do |key,value|
|
14
|
+
app.set(key, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/brief/briefcase.rb
CHANGED
@@ -60,7 +60,15 @@ module Brief
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def models_path
|
63
|
-
|
63
|
+
value = options.fetch(:models_path) { config.models_path }
|
64
|
+
|
65
|
+
if value.to_s.match(/\./)
|
66
|
+
Pathname(Dir.pwd).join(value)
|
67
|
+
elsif value.to_s.match(/\//)
|
68
|
+
Pathname(value)
|
69
|
+
else
|
70
|
+
root.join(value)
|
71
|
+
end
|
64
72
|
end
|
65
73
|
|
66
74
|
def repository
|
@@ -25,7 +25,13 @@ module Brief
|
|
25
25
|
if settings = attribute_set.fetch(meth, nil)
|
26
26
|
if settings.args.length == 1 && settings.args.first.is_a?(String)
|
27
27
|
selector = settings.args.first
|
28
|
-
document.css(selector)
|
28
|
+
matches = document.css(selector)
|
29
|
+
|
30
|
+
if matches.length > 1
|
31
|
+
selector.match(/first-of-type/) ? matches.first.text : matches.map(&:text)
|
32
|
+
else
|
33
|
+
matches.first.try(:text)
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -7,11 +7,17 @@ module Brief
|
|
7
7
|
@frontmatter || load_frontmatter
|
8
8
|
end
|
9
9
|
|
10
|
+
def frontmatter_line_count
|
11
|
+
(@raw_frontmatter && @raw_frontmatter.lines.count) || 0
|
12
|
+
end
|
13
|
+
|
10
14
|
protected
|
11
15
|
|
12
16
|
def load_frontmatter
|
13
|
-
if
|
14
|
-
self.content =
|
17
|
+
if raw_content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
18
|
+
self.content = raw_content[($1.size + $2.size)..-1]
|
19
|
+
@frontmatter_line_count = $1.lines.size
|
20
|
+
@raw_frontmatter = $1
|
15
21
|
@frontmatter = YAML.load($1).to_mash
|
16
22
|
end
|
17
23
|
end
|
@@ -3,26 +3,23 @@ module Brief
|
|
3
3
|
module Rendering
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def at(*args, &block)
|
15
|
-
parser.send(:at, *args, &block)
|
16
|
-
end
|
17
|
-
|
18
|
-
def parser
|
19
|
-
@parser ||= Nokogiri::HTML.fragment(to_html)
|
6
|
+
# Uses a custom Redcarpet::Render::HTML subclass
|
7
|
+
# which simply inserts data attributes on each heading element
|
8
|
+
# so that they can be queried with CSS more deliberately.
|
9
|
+
class HeadingWrapper < ::Redcarpet::Render::HTML
|
10
|
+
def header(text, level)
|
11
|
+
"<h#{level} data-level='#{level}' data-heading='#{ text }'>#{text}</h#{level}>"
|
12
|
+
end
|
20
13
|
end
|
21
14
|
|
22
15
|
module ClassMethods
|
16
|
+
def renderer_class
|
17
|
+
HeadingWrapper
|
18
|
+
end
|
19
|
+
|
23
20
|
def renderer
|
24
21
|
@renderer ||= begin
|
25
|
-
r =
|
22
|
+
r = renderer_class.new(:tables => true,
|
26
23
|
:autolink => true,
|
27
24
|
:gh_blockcode => true,
|
28
25
|
:fenced_code_blocks => true,
|
@@ -32,6 +29,39 @@ module Brief
|
|
32
29
|
end
|
33
30
|
end
|
34
31
|
end
|
32
|
+
|
33
|
+
# Documents can be rendered into HTML.
|
34
|
+
#
|
35
|
+
# They will first be put through a Nokogiri processor pipeline
|
36
|
+
# which allows us to wrap things in section containers, apply data
|
37
|
+
# attributes, and other things to the HTML so that the output HTML retains its
|
38
|
+
# relationship to the underlying data and document structure.
|
39
|
+
def to_html(options={})
|
40
|
+
if options[:wrap] == false
|
41
|
+
unwrapped_html
|
42
|
+
else
|
43
|
+
wrapper = options.fetch(:wrapper, 'div')
|
44
|
+
"<#{ wrapper } data-brief-model='#{ model_class.type_alias }' data-brief-path='#{ relative_path_identifier }'>#{ unwrapped_html }</#{wrapper}>"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def unwrapped_html
|
49
|
+
parser.to_html
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def to_raw_html
|
55
|
+
renderer.render(content)
|
56
|
+
end
|
57
|
+
|
58
|
+
def renderer
|
59
|
+
@renderer ||= self.class.renderer
|
60
|
+
end
|
61
|
+
|
62
|
+
def renderer=(value)
|
63
|
+
@renderer = value
|
64
|
+
end
|
35
65
|
end
|
36
66
|
end
|
37
67
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class Brief::Document::Section
|
2
|
+
class Builder
|
3
|
+
def self.run(source, options={})
|
4
|
+
new(source, options).to_fragment
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_accessor :source, :nodes, :low, :high
|
8
|
+
|
9
|
+
def initialize(source, options={})
|
10
|
+
@source = source.map do |item|
|
11
|
+
level, group = item
|
12
|
+
[level, group.map {|f| f.is_a?(String) ? Nokogiri::HTML.fragment(f) : f }]
|
13
|
+
end
|
14
|
+
|
15
|
+
@low = options.fetch(:low, 1)
|
16
|
+
@high = options.fetch(:high, 6)
|
17
|
+
@nodes = []
|
18
|
+
@cycles = 0
|
19
|
+
|
20
|
+
run
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
source.length.times do
|
25
|
+
source.each_with_index do |item, index|
|
26
|
+
n = index + 1
|
27
|
+
level, fragments = item
|
28
|
+
next_level, next_fragments = source[n]
|
29
|
+
|
30
|
+
if next_level && (next_level == level) && (level > low)
|
31
|
+
new_fragment = (fragments + next_fragments).map(&:to_html).join("")
|
32
|
+
source[index] = [level, [Nokogiri::HTML.fragment(new_fragment)]]
|
33
|
+
source[n] = nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
source.compact!
|
38
|
+
end
|
39
|
+
|
40
|
+
until even? || maxed_out?
|
41
|
+
source.map! do |item|
|
42
|
+
level, fragments = item
|
43
|
+
[level, fragments.first]
|
44
|
+
end
|
45
|
+
|
46
|
+
source.each_with_index do |item, index|
|
47
|
+
level, fragment = item
|
48
|
+
n = index + 1
|
49
|
+
next_level, next_fragment = source[n]
|
50
|
+
|
51
|
+
if fragment && next_level && (next_level > level)
|
52
|
+
parent = fragment.css("section, article").first
|
53
|
+
parent.add_child(next_fragment)
|
54
|
+
source[index] = [level, fragment]
|
55
|
+
source[n] = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
source.compact!
|
60
|
+
|
61
|
+
@cycles += 1
|
62
|
+
end
|
63
|
+
|
64
|
+
self.nodes = source.map(&:last)
|
65
|
+
|
66
|
+
self.nodes.each do |node|
|
67
|
+
parent = node.css("section, article").first
|
68
|
+
if %w(h1 h2 h3 h4 h5 h6).include?(parent.children.first.name)
|
69
|
+
parent['data-heading'] = parent.children.first.text
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
self.nodes.map!(&:to_html)
|
74
|
+
end
|
75
|
+
|
76
|
+
def maxed_out?
|
77
|
+
@cycles > source.length
|
78
|
+
end
|
79
|
+
|
80
|
+
def even?
|
81
|
+
source.map(&:first).uniq.length == 1
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_fragment
|
85
|
+
@html = nodes.join("") unless nodes.empty?
|
86
|
+
Nokogiri::HTML.fragment(@html || "<div/>")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Brief::Document::Section
|
2
|
+
class Mapping
|
3
|
+
def initialize(title, options={})
|
4
|
+
@title = title
|
5
|
+
@options = options
|
6
|
+
@config = {}.to_mash
|
7
|
+
end
|
8
|
+
|
9
|
+
def selectors
|
10
|
+
selector_config.keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def selector_config
|
14
|
+
config.selectors
|
15
|
+
end
|
16
|
+
|
17
|
+
def config
|
18
|
+
@config
|
19
|
+
end
|
20
|
+
|
21
|
+
def options
|
22
|
+
@options
|
23
|
+
end
|
24
|
+
|
25
|
+
def title
|
26
|
+
@title
|
27
|
+
end
|
28
|
+
|
29
|
+
def selector
|
30
|
+
@selector || :next
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(*args, &block)
|
34
|
+
@selector = args.first
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def heading(*args, &block)
|
39
|
+
send(:each, *args, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def has(*args)
|
43
|
+
options = args.extract_options!
|
44
|
+
|
45
|
+
unless options.empty?
|
46
|
+
config.selectors ||= {}
|
47
|
+
config.selectors.merge!(selector => options)
|
48
|
+
end
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def is_a(*args)
|
54
|
+
options = args.extract_options!
|
55
|
+
klass = args.first
|
56
|
+
options[:is_a] = klass if klass
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Brief::Document::Section
|
2
|
+
attr_accessor :title, :fragment
|
3
|
+
attr_reader :config
|
4
|
+
|
5
|
+
Headings = %w(h1 h2 h3 h4 h5 h6)
|
6
|
+
|
7
|
+
def initialize(title, fragment, config)
|
8
|
+
@title = title
|
9
|
+
@fragment = fragment
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def items
|
14
|
+
return @items if @items
|
15
|
+
|
16
|
+
data = []
|
17
|
+
|
18
|
+
config.selectors.each do |selector|
|
19
|
+
settings = config.selector_config[selector]
|
20
|
+
|
21
|
+
if Headings.include?(selector)
|
22
|
+
headings = fragment.css("article > h2")
|
23
|
+
articles = headings.map(&:parent)
|
24
|
+
|
25
|
+
if !settings.empty?
|
26
|
+
articles.compact.each do |article|
|
27
|
+
data.push(settings.inject({}.to_mash) do |memo, pair|
|
28
|
+
attribute, selector = pair
|
29
|
+
result = article.css(selector)
|
30
|
+
memo[attribute] = result.length > 1 ? result.map(&:text) : result.text
|
31
|
+
memo
|
32
|
+
end)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
@items = data
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Brief
|
2
|
+
class Document::Structure
|
3
|
+
attr_accessor :fragment, :content_lines
|
4
|
+
|
5
|
+
def initialize(fragment,content_lines=[])
|
6
|
+
@fragment = fragment
|
7
|
+
@content_lines = content_lines
|
8
|
+
end
|
9
|
+
|
10
|
+
def prescan
|
11
|
+
content_lines.each_with_index do |line, index|
|
12
|
+
if line.match(/^#/)
|
13
|
+
line = line.strip
|
14
|
+
level = line.count('#')
|
15
|
+
text = line.gsub('#','').strip
|
16
|
+
|
17
|
+
if level > 0 && text.length > 0
|
18
|
+
line_number = index + 1
|
19
|
+
heading = find_heading_by(level, text)
|
20
|
+
|
21
|
+
if heading
|
22
|
+
heading.element.set_attribute('data-line-number', line_number)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_wrappers
|
30
|
+
return if @elements_have_been_wrapped
|
31
|
+
|
32
|
+
elements = fragment.children
|
33
|
+
|
34
|
+
mapping = []
|
35
|
+
bucket = []
|
36
|
+
|
37
|
+
current_level = Util.level(elements.first)
|
38
|
+
|
39
|
+
elements.each_cons(2) do |element, next_element|
|
40
|
+
bucket << element
|
41
|
+
|
42
|
+
if Util.is_header?(next_element) && Util.level(next_element) >= current_level
|
43
|
+
mapping.push([current_level, bucket])
|
44
|
+
bucket = []
|
45
|
+
end
|
46
|
+
|
47
|
+
if Util.is_header?(element)
|
48
|
+
current_level = Util.level(element)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
mapping.push([current_level, bucket]) unless mapping.include?(bucket)
|
53
|
+
|
54
|
+
base_fragment = Nokogiri::HTML.fragment("<div class='brief top level' />")
|
55
|
+
|
56
|
+
mapping.map! do |item|
|
57
|
+
level, group = item
|
58
|
+
group.reject! {|i| i.text == "\n" }
|
59
|
+
|
60
|
+
if level == 0
|
61
|
+
base_fragment = fragment = Nokogiri::HTML.fragment("<div class='brief top level'>#{ group.map(&:to_html).join("") }</div>")
|
62
|
+
elsif level <= lowest_level
|
63
|
+
fragment = Nokogiri::HTML.fragment("<section>#{ group.map(&:to_html).join("") }</section>")
|
64
|
+
elsif level > lowest_level
|
65
|
+
# should be able to look at the document section mappings and
|
66
|
+
# apply custom css classes to these based on the name of the section
|
67
|
+
fragment = Nokogiri::HTML.fragment("<article>#{ group.map(&:to_html).join("") }</article>")
|
68
|
+
end
|
69
|
+
|
70
|
+
[level, [fragment]]
|
71
|
+
end
|
72
|
+
|
73
|
+
self.fragment = Brief::Document::Section::Builder.run(mapping, low: lowest_level, high: highest_level)
|
74
|
+
end
|
75
|
+
|
76
|
+
def levels
|
77
|
+
l = fragment.css("[data-level]").map {|el| el.attr('data-level').to_i }
|
78
|
+
l.reject!(&:nil?)
|
79
|
+
l.reject! {|v| v.to_i == 0 }
|
80
|
+
l.uniq!
|
81
|
+
l
|
82
|
+
end
|
83
|
+
|
84
|
+
def highest_level
|
85
|
+
levels.max
|
86
|
+
end
|
87
|
+
|
88
|
+
def lowest_level
|
89
|
+
levels.min
|
90
|
+
end
|
91
|
+
|
92
|
+
def headings_at_level(level, options={})
|
93
|
+
matches = heading_elements.select {|el| el.level.to_i == level.to_i }
|
94
|
+
|
95
|
+
if options[:text]
|
96
|
+
matches.map(&:text)
|
97
|
+
else
|
98
|
+
matches
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def heading_with_text(text)
|
103
|
+
headings_with_text(text).tap do |results|
|
104
|
+
raise 'no section found with content: ' + text if results.length == 0
|
105
|
+
raise 'more than one section found with content: ' + text if results.length >= 2
|
106
|
+
|
107
|
+
end.first
|
108
|
+
end
|
109
|
+
|
110
|
+
def headings_with_text(text)
|
111
|
+
heading_elements.select do |el|
|
112
|
+
el.heading.to_s.strip == text.to_s.strip
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def find_heading_by(level, heading)
|
117
|
+
heading_elements.find do |el|
|
118
|
+
el.level.to_s == level.to_s && heading.to_s.strip == el.heading.to_s.strip
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def heading_elements
|
123
|
+
@heading_elements ||= fragment.css("h1,h2,h3,h4,h5,h6").map do |el|
|
124
|
+
if el.attr('data-level').to_i > 0
|
125
|
+
{
|
126
|
+
level: el.attr('data-level'),
|
127
|
+
heading: el.attr('data-heading'),
|
128
|
+
element: el
|
129
|
+
}.to_mash
|
130
|
+
end
|
131
|
+
end.compact
|
132
|
+
end
|
133
|
+
|
134
|
+
class Util
|
135
|
+
class << self
|
136
|
+
def is_header?(element)
|
137
|
+
element.name.to_s.downcase.match(/^h\d$/)
|
138
|
+
end
|
139
|
+
|
140
|
+
def level(element)
|
141
|
+
element.name.to_s.gsub(/^h/i,'').to_i
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/brief/document.rb
CHANGED
@@ -3,36 +3,72 @@ module Brief
|
|
3
3
|
include Brief::Document::Rendering
|
4
4
|
include Brief::Document::FrontMatter
|
5
5
|
|
6
|
-
attr_accessor :path, :content, :frontmatter
|
6
|
+
attr_accessor :path, :content, :frontmatter, :raw_content
|
7
7
|
|
8
8
|
def initialize(path, options={})
|
9
9
|
@path = Pathname(path)
|
10
10
|
@options = options
|
11
11
|
|
12
12
|
if self.path.exist?
|
13
|
-
|
13
|
+
@raw_content = path.read
|
14
14
|
load_frontmatter
|
15
15
|
end
|
16
16
|
|
17
17
|
self.model_class.try(:models).try(:<<, to_model) unless model_instance_registered?
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
20
|
+
def data
|
21
|
+
frontmatter
|
22
|
+
end
|
23
|
+
|
24
|
+
def sections
|
25
|
+
mappings = model_class.section_mappings
|
26
|
+
|
27
|
+
@sections = {}.to_mash
|
28
|
+
|
29
|
+
mappings.each do |name, mapping|
|
30
|
+
fragment = css("section[data-heading='#{name}']").first
|
31
|
+
@sections[name.parameterize.downcase.underscore] = Brief::Document::Section.new(name, fragment, mapping)
|
32
|
+
end
|
33
|
+
|
34
|
+
@sections
|
35
|
+
end
|
36
|
+
|
37
|
+
# Shortcut for querying the rendered HTML by css selectors.
|
38
|
+
#
|
39
|
+
# This will allow for model data attributes to be pulled from the
|
40
|
+
# document contents.
|
41
|
+
#
|
42
|
+
# Returns a Nokogiri::HTML::Element
|
43
|
+
def css(*args, &block)
|
44
|
+
parser.send(:css, *args, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a Nokogiri::HTML::Element
|
48
|
+
def at(*args, &block)
|
49
|
+
parser.send(:at, *args, &block)
|
22
50
|
end
|
23
51
|
|
24
52
|
def extract_content(*args)
|
25
53
|
options = args.extract_options!
|
26
|
-
args = options
|
54
|
+
args = options.delete(:args) if options.is_a?(Hash) && options.key?(:args)
|
27
55
|
|
28
56
|
case
|
29
|
-
when args.length == 1 && args.first.is_a?(String)
|
30
|
-
css(args.first)
|
57
|
+
when options.empty? && args.length == 1 && args.first.is_a?(String)
|
58
|
+
results = css(args.first)
|
59
|
+
results = results.first if results.length > 1 && args.first.match(/:first-of-type/)
|
60
|
+
results.try(:text).to_s
|
61
|
+
else
|
62
|
+
binding.pry
|
31
63
|
end
|
32
64
|
end
|
33
65
|
|
34
|
-
def
|
35
|
-
|
66
|
+
def relative_path_identifier
|
67
|
+
if Brief.case
|
68
|
+
path.relative_path_from(Brief.case.root)
|
69
|
+
else
|
70
|
+
path.to_s
|
71
|
+
end
|
36
72
|
end
|
37
73
|
|
38
74
|
def extension
|
@@ -60,6 +96,21 @@ module Brief
|
|
60
96
|
super || data.respond_to?(method) || data.key?(method)
|
61
97
|
end
|
62
98
|
|
99
|
+
def structure
|
100
|
+
@structure_analyzer ||= Brief::Document::Structure.new(fragment, self.raw_content.lines.to_a)
|
101
|
+
end
|
102
|
+
|
103
|
+
def parser
|
104
|
+
@parser ||= begin
|
105
|
+
structure.prescan
|
106
|
+
structure.create_wrappers
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def fragment
|
111
|
+
@fragment ||= Nokogiri::HTML.fragment(to_raw_html)
|
112
|
+
end
|
113
|
+
|
63
114
|
def method_missing(meth, *args, &block)
|
64
115
|
if data.respond_to?(meth)
|
65
116
|
data.send(meth, *args, &block)
|
@@ -67,7 +118,6 @@ module Brief
|
|
67
118
|
super
|
68
119
|
end
|
69
120
|
end
|
70
|
-
|
71
121
|
end
|
72
122
|
end
|
73
123
|
|
data/lib/brief/dsl.rb
CHANGED
@@ -13,6 +13,16 @@ module Brief
|
|
13
13
|
definition.validate!
|
14
14
|
end
|
15
15
|
|
16
|
+
# defines a method on the model instance named after the identifier
|
17
|
+
# and then creates a CLI command matching that, so for example:
|
18
|
+
#
|
19
|
+
# given a model called 'Post' and an action named 'publish' the
|
20
|
+
# brief CLI executable will respond to:
|
21
|
+
#
|
22
|
+
# brief publish posts PATH_GLOB
|
23
|
+
#
|
24
|
+
# this will find all of the Post models from the documents matching PATH_GLOB
|
25
|
+
# and call the publish method on them
|
16
26
|
def action(identifier, options={}, &block)
|
17
27
|
Object.class.class_eval do
|
18
28
|
command "#{identifier}" do |c|
|
@@ -5,13 +5,15 @@ module Brief
|
|
5
5
|
:metadata_schema,
|
6
6
|
:content_schema,
|
7
7
|
:options,
|
8
|
-
:defined_helpers
|
8
|
+
:defined_helpers,
|
9
|
+
:section_mappings
|
9
10
|
|
10
11
|
def initialize(name, options={})
|
11
12
|
@name = name
|
12
13
|
@options = options
|
13
14
|
@type_alias = options.fetch(:type_alias) { name.downcase.parameterize.gsub(/-/,'_') }
|
14
15
|
@metadata_schema = {}.to_mash
|
16
|
+
@section_mappings = {}.to_mash
|
15
17
|
@content_schema = {attributes:{}}.to_mash
|
16
18
|
@model_class = options[:model_class]
|
17
19
|
end
|
@@ -107,15 +109,22 @@ module Brief
|
|
107
109
|
@current == :content
|
108
110
|
end
|
109
111
|
|
112
|
+
def section_mapping(identifier)
|
113
|
+
section_mappings.fetch(identifier)
|
114
|
+
end
|
115
|
+
|
110
116
|
def method_missing(meth, *args, &block)
|
111
117
|
args = args.dup
|
112
118
|
|
113
119
|
if inside_content?
|
114
|
-
if meth.
|
115
|
-
|
120
|
+
if meth.to_sym == :define_section
|
121
|
+
opts = args.extract_options!
|
122
|
+
identifier = args.first
|
123
|
+
self.section_mappings[identifier] ||= Brief::Document::Section::Mapping.new(identifier, opts)
|
124
|
+
section_mapping(identifier).instance_eval(&block) if block
|
125
|
+
else
|
126
|
+
self.content_schema.attributes[meth] = {args: args, block: block}
|
116
127
|
end
|
117
|
-
|
118
|
-
self.content_schema.attributes[meth] = {args: args, block: block}
|
119
128
|
elsif inside_meta?
|
120
129
|
if args.first.is_a?(Hash)
|
121
130
|
args.unshift(String)
|
data/lib/brief/model.rb
CHANGED
@@ -141,9 +141,17 @@ module Brief
|
|
141
141
|
@definition
|
142
142
|
end
|
143
143
|
|
144
|
+
def section_mapping(*args)
|
145
|
+
definition.send(:section_mapping, *args)
|
146
|
+
end
|
147
|
+
|
148
|
+
def section_mappings(*args)
|
149
|
+
definition.send(:section_mappings, *args)
|
150
|
+
end
|
151
|
+
|
144
152
|
def method_missing(meth, *args, &block)
|
145
153
|
if %w(meta content actions helpers).include?(meth.to_s)
|
146
|
-
definition.send(meth, &block)
|
154
|
+
definition.send(meth, *args, &block)
|
147
155
|
finalize
|
148
156
|
elsif meth.to_s.match(/^on_(.*)_change$/)
|
149
157
|
create_change_handler($1, *args, &block)
|
data/lib/brief/repository.rb
CHANGED
data/lib/brief/version.rb
CHANGED
data/lib/brief.rb
CHANGED
@@ -40,6 +40,13 @@ module Brief
|
|
40
40
|
def self.load_models(from_folder=nil)
|
41
41
|
Brief::Model.load_all(from_folder: from_folder)
|
42
42
|
end
|
43
|
+
|
44
|
+
# Adapters for Rails, Middleman, or Jekyll apps
|
45
|
+
def self.activate_adapter(identifier)
|
46
|
+
require "brief/adapters/#{ identifier }"
|
47
|
+
adapter = (Brief::Adapters.const_get(identifier.camelize) rescue nil)
|
48
|
+
adapter.try(:activate_adapter)
|
49
|
+
end
|
43
50
|
end
|
44
51
|
|
45
52
|
require "brief/core_ext"
|
@@ -49,6 +56,10 @@ require "brief/configuration"
|
|
49
56
|
require "brief/document/rendering"
|
50
57
|
require "brief/document/front_matter"
|
51
58
|
require "brief/document/content_extractor"
|
59
|
+
require "brief/document/structure"
|
60
|
+
require "brief/document/section"
|
61
|
+
require "brief/document/section/mapping"
|
62
|
+
require "brief/document/section/builder"
|
52
63
|
require "brief/document"
|
53
64
|
require "brief/document_mapper"
|
54
65
|
require "brief/repository"
|
@@ -14,6 +14,12 @@ which contains user stories and such.
|
|
14
14
|
|
15
15
|
## A user wants to write epics
|
16
16
|
|
17
|
+
As a **User** I want to **write epics** so that I can **write a bunch of user stories in one file**
|
18
|
+
|
17
19
|
## A user wants to annotate wireframes
|
18
20
|
|
21
|
+
As a **User** I want to **annotate wireframes** so that I can **augment diagrams with targeted explanations**
|
22
|
+
|
19
23
|
## A user wants to provide details about domain concepts
|
24
|
+
|
25
|
+
As a **User** I want to **provide some detailed explanations of domain concepts** so that I can **augment diagrams with targeted explanations**
|
@@ -8,10 +8,17 @@ class Brief::Epic
|
|
8
8
|
end
|
9
9
|
|
10
10
|
content do
|
11
|
-
|
11
|
+
# have to do this so that the user stories section h1 doesnt get confused
|
12
|
+
title "h1:first-of-type"
|
12
13
|
|
13
14
|
define_section "User Stories" do
|
14
|
-
|
15
|
+
# NOT YET Implemented
|
16
|
+
each("h2").is_a :user_story
|
17
|
+
|
18
|
+
each("h2").has(:title => "h2",
|
19
|
+
:paragraph => "p:first-of-type",
|
20
|
+
:components => "p:first-of-type strong"
|
21
|
+
)
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
---
|
2
|
+
type: epic
|
3
|
+
status: published
|
4
|
+
---
|
5
|
+
This is a paragraph
|
6
|
+
|
7
|
+
This is another paragraph
|
8
|
+
|
9
|
+
This is a list:
|
10
|
+
|
11
|
+
- list one
|
12
|
+
- list two
|
13
|
+
- list three
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
# SOME CODE
|
17
|
+
```
|
18
|
+
|
19
|
+
# User Stories
|
20
|
+
|
21
|
+
## Story Title One
|
22
|
+
|
23
|
+
As a **User** I would like to **Behavior** so that I can **goal**
|
24
|
+
|
25
|
+
## Story Title Two
|
26
|
+
|
27
|
+
As a **User** I would like to **Behavior** so that I can **goal**
|
28
|
+
|
29
|
+
## Story Title Three
|
30
|
+
|
31
|
+
As a **User** I would like to **Behavior** so that I can **goal**
|
32
|
+
|
33
|
+
# Diagrams
|
34
|
+
|
35
|
+
- diagram one
|
36
|
+
- diagram one
|
37
|
+
- diagram one
|
@@ -0,0 +1,40 @@
|
|
1
|
+
---
|
2
|
+
type: epic
|
3
|
+
status: published
|
4
|
+
---
|
5
|
+
|
6
|
+
# This is the title
|
7
|
+
|
8
|
+
This is a paragraph
|
9
|
+
|
10
|
+
This is another paragraph
|
11
|
+
|
12
|
+
This is a list:
|
13
|
+
|
14
|
+
- list one
|
15
|
+
- list two
|
16
|
+
- list three
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# SOME CODE
|
20
|
+
```
|
21
|
+
|
22
|
+
# User Stories
|
23
|
+
|
24
|
+
## Story Title One
|
25
|
+
|
26
|
+
As a **User** I would like to **Behavior** so that I can **goal**
|
27
|
+
|
28
|
+
## Story Title Two
|
29
|
+
|
30
|
+
As a **User** I would like to **Behavior** so that I can **goal**
|
31
|
+
|
32
|
+
## Story Title Three
|
33
|
+
|
34
|
+
As a **User** I would like to **Behavior** so that I can **goal**
|
35
|
+
|
36
|
+
# Diagrams
|
37
|
+
|
38
|
+
- diagram one
|
39
|
+
- diagram one
|
40
|
+
- diagram one
|
@@ -7,21 +7,37 @@ describe "The Brief Document" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it "renders html" do
|
10
|
-
expect(sample.to_html).to
|
10
|
+
expect(sample.to_html).to match(/h1.*User Stories.*h1\>/)
|
11
11
|
end
|
12
12
|
|
13
13
|
it "parses the html" do
|
14
14
|
expect(sample.css("h1").length).to eq(2)
|
15
15
|
end
|
16
16
|
|
17
|
+
|
17
18
|
it "deserializes YAML frontmatter into attributes" do
|
18
19
|
expect(sample.frontmatter.type).to eq("epic")
|
19
20
|
end
|
20
21
|
|
21
22
|
context "Content Extraction" do
|
22
23
|
it "extracts content from a css selector" do
|
23
|
-
extracted = sample.extract_content(:args => ["h1:first-
|
24
|
+
extracted = sample.extract_content(:args => ["h1:first-of-type"])
|
24
25
|
expect(extracted).to eq("Blueprint Epic Example")
|
25
26
|
end
|
26
27
|
end
|
28
|
+
|
29
|
+
context "defining sections" do
|
30
|
+
it "lets me define content sections" do
|
31
|
+
expect(sample.sections).not_to be_empty
|
32
|
+
expect(sample.sections.user_stories).to be_present
|
33
|
+
expect(sample.sections.user_stories.fragment.name).to eq("section")
|
34
|
+
expect(sample.sections.user_stories.fragment.css("article").length).to eq(3)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "gives me an array of items underneath the section filled with the key value mappings i laid out" do
|
38
|
+
items = sample.sections.user_stories.items
|
39
|
+
expect(items.length).to eq(3)
|
40
|
+
expect(items.map(&:components).map(&:first).uniq).to eq(["User"])
|
41
|
+
end
|
42
|
+
end
|
27
43
|
end
|
@@ -102,4 +102,11 @@ describe "The Brief Model" do
|
|
102
102
|
expect(user_story.class.defined_actions).to include(:custom_action)
|
103
103
|
end
|
104
104
|
end
|
105
|
+
|
106
|
+
context "Section Mappings" do
|
107
|
+
it "defines a section mapping for User Stories" do
|
108
|
+
mapping = epic.class.section_mapping("User Stories")
|
109
|
+
expect(mapping).to be_a(Brief::Document::Section::Mapping)
|
110
|
+
end
|
111
|
+
end
|
105
112
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Brief HTML Rendering" do
|
4
|
+
let(:sample) do
|
5
|
+
path = Brief.example_path.join("docs","epic.html.md")
|
6
|
+
Brief::Document.new(path)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "wraps the document with some identifying details" do
|
10
|
+
expect(sample.to_html).to include("docs/epic.html.md")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "wraps the higher level headings under section elements" do
|
14
|
+
expect(sample.css("section").length).to eq(2)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "wraps the lower level headings under article elements" do
|
18
|
+
expect(sample.css("article").length).to eq(3)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "nests the articles under the parent section" do
|
22
|
+
expect(sample.css("section article").length).to eq(3)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "The Section Builder" do
|
4
|
+
let(:builder) do
|
5
|
+
inputs = [
|
6
|
+
[1, ["<section><h1>Heading</h1></section>"]],
|
7
|
+
[1, ["<section><h1>Section Heading</h1></section>"]],
|
8
|
+
[2, ["<article><h2>a</h2></article>"]],
|
9
|
+
[2, ["<article><h2>b</h2></article>"]],
|
10
|
+
[2, ["<article><h2>c</h2></article>"]],
|
11
|
+
[1, ["<section><h1>Footer</h1></section>"]]
|
12
|
+
]
|
13
|
+
|
14
|
+
Brief::Document::Section::Builder.new(inputs)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "collapses the HTML into sections for us" do
|
18
|
+
expect(builder.to_fragment.css("section h1").count).to eq(3)
|
19
|
+
expect(builder.to_fragment.css("section article").count).to eq(3)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Document Structure Information" do
|
4
|
+
let(:sample) do
|
5
|
+
path = Brief.example_path.join("docs","epic.html.md")
|
6
|
+
Brief::Document.new(path)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:one) do
|
10
|
+
path = Brief.spec_root.join("fixtures","structures", "one.html.md")
|
11
|
+
Brief::Document.new(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:two) do
|
15
|
+
path = Brief.spec_root.join("fixtures","structures", "two.html.md")
|
16
|
+
Brief::Document.new(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:three) do
|
20
|
+
path = Brief.spec_root.join("fixtures","structures", "three.html.md")
|
21
|
+
Brief::Document.new(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "Mapping markdown source to output html" do
|
25
|
+
it "should put the line numbers on the headings" do
|
26
|
+
el = sample.css("h1").first
|
27
|
+
expect(el.attr('data-line-number')).to eq("8")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brief
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Soeder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
@@ -216,6 +216,7 @@ extensions: []
|
|
216
216
|
extra_rdoc_files: []
|
217
217
|
files:
|
218
218
|
- ".gitignore"
|
219
|
+
- CHANGELOG.md
|
219
220
|
- Gemfile
|
220
221
|
- Gemfile.lock
|
221
222
|
- LICENSE.txt
|
@@ -228,6 +229,7 @@ files:
|
|
228
229
|
- examples/blog/docs/an-intro-to-brief.html.md
|
229
230
|
- lib/.DS_Store
|
230
231
|
- lib/brief.rb
|
232
|
+
- lib/brief/adapters/middleman.rb
|
231
233
|
- lib/brief/briefcase.rb
|
232
234
|
- lib/brief/cli/change.rb
|
233
235
|
- lib/brief/cli/init.rb
|
@@ -238,6 +240,10 @@ files:
|
|
238
240
|
- lib/brief/document/content_extractor.rb
|
239
241
|
- lib/brief/document/front_matter.rb
|
240
242
|
- lib/brief/document/rendering.rb
|
243
|
+
- lib/brief/document/section.rb
|
244
|
+
- lib/brief/document/section/builder.rb
|
245
|
+
- lib/brief/document/section/mapping.rb
|
246
|
+
- lib/brief/document/structure.rb
|
241
247
|
- lib/brief/document_mapper.rb
|
242
248
|
- lib/brief/dsl.rb
|
243
249
|
- lib/brief/model.rb
|
@@ -255,12 +261,18 @@ files:
|
|
255
261
|
- spec/fixtures/example/docs/user_story.html.md
|
256
262
|
- spec/fixtures/example/docs/wireframe.html.md
|
257
263
|
- spec/fixtures/example/models/epic.rb
|
264
|
+
- spec/fixtures/structures/one.html.md
|
265
|
+
- spec/fixtures/structures/three.html.md
|
266
|
+
- spec/fixtures/structures/two.html.md
|
258
267
|
- spec/lib/brief/briefcase_spec.rb
|
259
268
|
- spec/lib/brief/document_spec.rb
|
260
269
|
- spec/lib/brief/dsl_spec.rb
|
261
270
|
- spec/lib/brief/model_spec.rb
|
262
271
|
- spec/lib/brief/persistence_spec.rb
|
272
|
+
- spec/lib/brief/rendering_spec.rb
|
263
273
|
- spec/lib/brief/repository_spec.rb
|
274
|
+
- spec/lib/brief/section_builder_spec.rb
|
275
|
+
- spec/lib/brief/structure_spec.rb
|
264
276
|
- spec/spec_helper.rb
|
265
277
|
- spec/support/test_helpers.rb
|
266
278
|
- tasks/brief/release.rake
|
@@ -298,12 +310,18 @@ test_files:
|
|
298
310
|
- spec/fixtures/example/docs/user_story.html.md
|
299
311
|
- spec/fixtures/example/docs/wireframe.html.md
|
300
312
|
- spec/fixtures/example/models/epic.rb
|
313
|
+
- spec/fixtures/structures/one.html.md
|
314
|
+
- spec/fixtures/structures/three.html.md
|
315
|
+
- spec/fixtures/structures/two.html.md
|
301
316
|
- spec/lib/brief/briefcase_spec.rb
|
302
317
|
- spec/lib/brief/document_spec.rb
|
303
318
|
- spec/lib/brief/dsl_spec.rb
|
304
319
|
- spec/lib/brief/model_spec.rb
|
305
320
|
- spec/lib/brief/persistence_spec.rb
|
321
|
+
- spec/lib/brief/rendering_spec.rb
|
306
322
|
- spec/lib/brief/repository_spec.rb
|
323
|
+
- spec/lib/brief/section_builder_spec.rb
|
324
|
+
- spec/lib/brief/structure_spec.rb
|
307
325
|
- spec/spec_helper.rb
|
308
326
|
- spec/support/test_helpers.rb
|
309
327
|
has_rdoc:
|