brief 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|