brief 0.0.5 → 1.0.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 +1 -1
- data/Gemfile.lock +71 -55
- data/README.md +149 -48
- data/Rakefile +15 -5
- data/bin/brief +15 -28
- data/brief.gemspec +17 -11
- data/examples/blog/brief.rb +54 -0
- data/examples/blog/docs/an-intro-to-brief.html.md +9 -0
- data/lib/.DS_Store +0 -0
- data/lib/brief/briefcase.rb +78 -0
- data/lib/brief/cli/change.rb +14 -0
- data/lib/brief/cli/init.rb +66 -0
- data/{spec/fixtures/generated/project_overview.json → lib/brief/cli/publish.rb} +0 -0
- data/lib/brief/cli/write.rb +0 -0
- data/lib/brief/configuration.rb +19 -115
- data/lib/brief/core_ext.rb +11 -0
- data/lib/brief/document/content_extractor.rb +33 -0
- data/lib/brief/document/front_matter.rb +20 -0
- data/lib/brief/document/rendering.rb +37 -0
- data/lib/brief/document.rb +43 -38
- data/lib/brief/document_mapper.rb +158 -0
- data/lib/brief/dsl.rb +42 -0
- data/lib/brief/model/definition.rb +117 -0
- data/lib/brief/model.rb +177 -0
- data/lib/brief/repository.rb +57 -0
- data/lib/brief/version.rb +1 -7
- data/lib/brief.rb +35 -40
- data/spec/fixtures/example/brief.rb +27 -0
- data/spec/fixtures/example/docs/concept.html.md +5 -0
- data/spec/fixtures/example/docs/epic.html.md +19 -0
- data/spec/fixtures/example/docs/persona.html.md +5 -0
- data/spec/fixtures/example/docs/release.html.md +7 -0
- data/spec/fixtures/example/docs/resource.html.md +5 -0
- data/spec/fixtures/example/docs/user_story.html.md +24 -0
- data/spec/fixtures/example/docs/wireframe.html.md +5 -0
- data/spec/fixtures/example/models/epic.rb +16 -0
- data/spec/lib/brief/briefcase_spec.rb +27 -0
- data/spec/lib/brief/document_spec.rb +14 -22
- data/spec/lib/brief/dsl_spec.rb +4 -15
- data/spec/lib/brief/model_spec.rb +84 -0
- data/spec/lib/brief/repository_spec.rb +60 -0
- data/spec/spec_helper.rb +12 -14
- data/spec/support/test_helpers.rb +0 -0
- data/tasks/brief/release.rake +0 -0
- metadata +120 -110
- data/.gitignore +0 -1
- data/Guardfile +0 -5
- data/examples/project_overview.md +0 -23
- data/lib/brief/cli/commands/config.rb +0 -40
- data/lib/brief/cli/commands/publish.rb +0 -27
- data/lib/brief/cli/commands/write.rb +0 -27
- data/lib/brief/formatters/base.rb +0 -12
- data/lib/brief/formatters/github_milestone_rollup.rb +0 -52
- data/lib/brief/git.rb +0 -19
- data/lib/brief/github/wiki.rb +0 -9
- data/lib/brief/github.rb +0 -141
- data/lib/brief/github_client/authentication.rb +0 -32
- data/lib/brief/github_client/client.rb +0 -86
- data/lib/brief/github_client/commands.rb +0 -5
- data/lib/brief/github_client/issue_labels.rb +0 -65
- data/lib/brief/github_client/issues.rb +0 -22
- data/lib/brief/github_client/milestone_issues.rb +0 -13
- data/lib/brief/github_client/organization_activity.rb +0 -9
- data/lib/brief/github_client/organization_issues.rb +0 -13
- data/lib/brief/github_client/organization_repositories.rb +0 -20
- data/lib/brief/github_client/organization_users.rb +0 -9
- data/lib/brief/github_client/repository_events.rb +0 -8
- data/lib/brief/github_client/repository_issue_events.rb +0 -9
- data/lib/brief/github_client/repository_issues.rb +0 -8
- data/lib/brief/github_client/repository_labels.rb +0 -18
- data/lib/brief/github_client/repository_milestones.rb +0 -9
- data/lib/brief/github_client/request.rb +0 -181
- data/lib/brief/github_client/request_wrapper.rb +0 -121
- data/lib/brief/github_client/response_object.rb +0 -50
- data/lib/brief/github_client/single_repository.rb +0 -9
- data/lib/brief/github_client/user_activity.rb +0 -16
- data/lib/brief/github_client/user_gists.rb +0 -9
- data/lib/brief/github_client/user_info.rb +0 -9
- data/lib/brief/github_client/user_issues.rb +0 -13
- data/lib/brief/github_client/user_organizations.rb +0 -9
- data/lib/brief/github_client/user_repositories.rb +0 -9
- data/lib/brief/github_client.rb +0 -43
- data/lib/brief/handlers/base.rb +0 -62
- data/lib/brief/handlers/github_issue.rb +0 -41
- data/lib/brief/handlers/github_milestone.rb +0 -37
- data/lib/brief/handlers/github_wiki.rb +0 -11
- data/lib/brief/line.rb +0 -69
- data/lib/brief/parser.rb +0 -354
- data/lib/brief/publisher/handler_manager.rb +0 -47
- data/lib/brief/publisher.rb +0 -142
- data/lib/brief/tree.rb +0 -43
- data/lib/core_ext.rb +0 -37
- data/spec/fixtures/front_end_tutorial.md +0 -33
- data/spec/fixtures/generator_dsl_example.rb +0 -22
- data/spec/fixtures/project_overview.md +0 -48
- data/spec/fixtures/sample.md +0 -19
- data/spec/lib/brief/line_spec.rb +0 -11
- data/spec/lib/brief/parser_spec.rb +0 -12
@@ -0,0 +1,14 @@
|
|
1
|
+
command "change" do |c|
|
2
|
+
c.syntax = "brief change ATTRIBUTE [OPTIONS]"
|
3
|
+
c.description = "change attributes of brief documents"
|
4
|
+
|
5
|
+
c.option "--from", String, "Only apply when the attributes current value matches."
|
6
|
+
c.option "--to", String, "Only apply when the attributes current value matches."
|
7
|
+
c.option "--files", String, "The files you want to change"
|
8
|
+
c.option "--on", String, "alias for --files. The files you want to change"
|
9
|
+
|
10
|
+
c.action do |args, options|
|
11
|
+
puts "Args: #{ args.inspect }"
|
12
|
+
puts "Options: #{ options.inspect }"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
command "init" do |c|
|
2
|
+
c.syntax = "brief init NAME [OPTIONS]"
|
3
|
+
c.description = "Create a new brief project, aka a briefcase"
|
4
|
+
|
5
|
+
c.option "--root", String, "The root folder for the new project."
|
6
|
+
|
7
|
+
c.action do |args, options|
|
8
|
+
options.default(:root => Dir.pwd())
|
9
|
+
|
10
|
+
if path = args.first
|
11
|
+
root = Pathname(options.root).join(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
[root, root.join("models"), root.join("docs")].each do |p|
|
15
|
+
puts "== folder #{ p.basename } #{ '. exists' if p.exist? }"
|
16
|
+
FileUtils.mkdir_p(p) unless p.exist?
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
if root.join("brief.rb").exist?
|
21
|
+
say "== Briefcase config already exists. Skipping."
|
22
|
+
else
|
23
|
+
say "== Creating config file brief.rb"
|
24
|
+
root.join("brief.rb").open("w+") do |fh|
|
25
|
+
default_config = <<-EOF
|
26
|
+
|
27
|
+
config do
|
28
|
+
set(:models_path => Pathname(__FILE__).parent.join("models"))
|
29
|
+
end
|
30
|
+
|
31
|
+
define("Post") do
|
32
|
+
meta do
|
33
|
+
status
|
34
|
+
date DateTime, :default => lambda {|post, attr| post.document.created_at }
|
35
|
+
end
|
36
|
+
|
37
|
+
content do
|
38
|
+
title "h1"
|
39
|
+
has_many :subheadings, "h2"
|
40
|
+
end
|
41
|
+
|
42
|
+
helpers do
|
43
|
+
def publish(options={})
|
44
|
+
post.set(:status, "published")
|
45
|
+
post.save
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
on_status_change(:from => "draft", :to => "published") do |model|
|
50
|
+
# Do Something
|
51
|
+
# mail_service.send_html_email_campaign(model.to_html)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# brief publish posts /path/to/*.html.md
|
56
|
+
action "publish posts" do |briefcase, models, options|
|
57
|
+
models.each do |post|
|
58
|
+
post.publish(options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
EOF
|
62
|
+
fh.write(default_config)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
File without changes
|
File without changes
|
data/lib/brief/configuration.rb
CHANGED
@@ -1,134 +1,38 @@
|
|
1
|
-
require
|
2
|
-
require "json"
|
1
|
+
require 'singleton'
|
3
2
|
|
4
3
|
module Brief
|
5
4
|
class Configuration
|
6
5
|
include Singleton
|
7
6
|
|
8
|
-
|
9
|
-
github_username: "",
|
10
|
-
github_api_token: ""
|
11
|
-
}
|
12
|
-
|
13
|
-
def self.method_missing meth, *args, &block
|
7
|
+
def self.method_missing(meth, *args, &block)
|
14
8
|
if instance.respond_to?(meth)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def initialize!
|
22
|
-
FileUtils.mkdir_p home_config_path.dirname
|
23
|
-
end
|
24
|
-
|
25
|
-
def method_missing meth, *args, &block
|
26
|
-
if current.has_key?(meth.to_s)
|
27
|
-
return current.fetch(meth)
|
28
|
-
end
|
29
|
-
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
|
-
def current_github_repository
|
34
|
-
value = current.github_repository
|
35
|
-
|
36
|
-
return value if value
|
37
|
-
|
38
|
-
if value.nil?
|
39
|
-
if guess = Brief::Git.current_github_repository
|
40
|
-
set "github_repository", guess
|
41
|
-
return guess
|
42
|
-
end
|
9
|
+
instance.send(meth, *args, &block)
|
10
|
+
else
|
11
|
+
super
|
43
12
|
end
|
44
|
-
|
45
|
-
current.github_repository
|
46
13
|
end
|
47
14
|
|
48
15
|
def current
|
49
|
-
@current ||=
|
50
|
-
|
51
|
-
|
16
|
+
@current ||= {
|
17
|
+
docs_path: "docs",
|
18
|
+
models_path: "models"
|
19
|
+
}.to_mash
|
52
20
|
end
|
53
21
|
|
54
|
-
def
|
55
|
-
|
22
|
+
def set(attribute, value=nil)
|
23
|
+
current[attribute] = value
|
24
|
+
self
|
56
25
|
end
|
57
26
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
current_config.delete(setting)
|
66
|
-
save! if persist == true
|
67
|
-
end
|
68
|
-
|
69
|
-
def current
|
70
|
-
Hashie::Mash.new(home_config.merge(cwd_config).merge(applied_config))
|
71
|
-
end
|
72
|
-
|
73
|
-
def apply_config_from_path path
|
74
|
-
path = Pathname(path)
|
75
|
-
parsed = JSON.parse(path.read) rescue {}
|
76
|
-
applied_config.merge!(parsed)
|
77
|
-
nil
|
78
|
-
end
|
79
|
-
|
80
|
-
def save!
|
81
|
-
save_home_config
|
82
|
-
save_cwd_config
|
83
|
-
end
|
84
|
-
|
85
|
-
def save_cwd_config
|
86
|
-
return nil unless cwd_config_path.exist?
|
87
|
-
|
88
|
-
File.open(cwd_config_path,'w+') do |fh|
|
89
|
-
fh.write JSON.generate(cwd_config.to_hash)
|
27
|
+
def method_missing(meth, *args, &block)
|
28
|
+
if current.respond_to?(meth) && current.key?(meth)
|
29
|
+
current.send(meth, *args, &block)
|
30
|
+
else
|
31
|
+
# swallow invalid method calls in production
|
32
|
+
super if ENV['BRIEF_DEBUG_MODE']
|
33
|
+
nil
|
90
34
|
end
|
91
35
|
end
|
92
36
|
|
93
|
-
def save_home_config
|
94
|
-
File.open(home_config_path,'w+') do |fh|
|
95
|
-
fh.write JSON.generate(home_config.to_hash)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Applied config is configuration values passed in context
|
100
|
-
# usually from the cli, but also in the unit tests
|
101
|
-
def applied_config
|
102
|
-
@applied_config ||= {}
|
103
|
-
end
|
104
|
-
|
105
|
-
def cwd_config
|
106
|
-
@cwd_config ||= begin
|
107
|
-
(cwd_config_path.exist? rescue false) ? JSON.parse(cwd_config_path.read) : {}
|
108
|
-
rescue
|
109
|
-
{}
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def home_config
|
114
|
-
@home_config ||= begin
|
115
|
-
(home_config_path.exist? rescue false) ? JSON.parse(home_config_path.read) : {}
|
116
|
-
rescue
|
117
|
-
{}
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def home_folder
|
122
|
-
Pathname(ENV['HOME']).join('.brief')
|
123
|
-
end
|
124
|
-
|
125
|
-
def home_config_path
|
126
|
-
Pathname(ENV['HOME']).join('.brief','config.json')
|
127
|
-
end
|
128
|
-
|
129
|
-
def cwd_config_path
|
130
|
-
Pathname(Dir.pwd).join('.briefrc')
|
131
|
-
end
|
132
37
|
end
|
133
38
|
end
|
134
|
-
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Brief
|
2
|
+
class Document::ContentExtractor
|
3
|
+
def initialize(model_type, document)
|
4
|
+
@model_type = model_type
|
5
|
+
@document = document
|
6
|
+
end
|
7
|
+
|
8
|
+
def document
|
9
|
+
@document
|
10
|
+
end
|
11
|
+
|
12
|
+
def model_class
|
13
|
+
Brief::Model.for_type(@model_type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def attribute_set
|
17
|
+
model_class.definition.content_schema.attributes
|
18
|
+
end
|
19
|
+
|
20
|
+
def respond_to?(meth)
|
21
|
+
attribute_set.key?(meth) || super
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(meth, *args, &block)
|
25
|
+
if settings = attribute_set.fetch(meth, nil)
|
26
|
+
if settings.args.length == 1 && settings.args.first.is_a?(String)
|
27
|
+
selector = settings.args.first
|
28
|
+
document.css(selector).try(:text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Brief
|
2
|
+
class Document
|
3
|
+
module FrontMatter
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def frontmatter
|
7
|
+
@frontmatter || load_frontmatter
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def load_frontmatter
|
13
|
+
if content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
14
|
+
self.content = content[($1.size + $2.size)..-1]
|
15
|
+
@frontmatter = YAML.load($1).to_mash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Brief
|
2
|
+
class Document
|
3
|
+
module Rendering
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def to_html
|
7
|
+
self.class.renderer.render(content)
|
8
|
+
end
|
9
|
+
|
10
|
+
def css(*args, &block)
|
11
|
+
parser.send(:css, *args, &block)
|
12
|
+
end
|
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)
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def renderer
|
24
|
+
@renderer ||= begin
|
25
|
+
r = ::Redcarpet::Render::HTML.new(:tables => true,
|
26
|
+
:autolink => true,
|
27
|
+
:gh_blockcode => true,
|
28
|
+
:fenced_code_blocks => true,
|
29
|
+
:footnotes => true)
|
30
|
+
|
31
|
+
::Redcarpet::Markdown.new(r)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/brief/document.rb
CHANGED
@@ -1,68 +1,73 @@
|
|
1
|
-
|
2
1
|
module Brief
|
3
2
|
class Document
|
4
|
-
|
3
|
+
include Brief::Document::Rendering
|
4
|
+
include Brief::Document::FrontMatter
|
5
|
+
|
6
|
+
attr_accessor :path, :content, :frontmatter
|
5
7
|
|
6
|
-
def initialize(options)
|
8
|
+
def initialize(path, options={})
|
9
|
+
@path = Pathname(path)
|
7
10
|
@options = options
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
when options.is_a?(Pathname)
|
13
|
-
@content = options.read
|
14
|
-
when options.is_a?(Hash)
|
15
|
-
@content = options[:path].read
|
12
|
+
if self.path.exist?
|
13
|
+
content
|
14
|
+
load_frontmatter
|
16
15
|
end
|
16
|
+
|
17
|
+
self.model_class.try(:models).try(:<<, to_model) unless model_instance_registered?
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
-
|
21
|
-
write
|
22
|
-
end
|
20
|
+
def content
|
21
|
+
@content ||= path.read
|
23
22
|
end
|
24
23
|
|
25
|
-
def
|
26
|
-
|
24
|
+
def extract_content(*args)
|
25
|
+
options = args.extract_options!
|
26
|
+
args = options[:args] if options.is_a?(Hash) && options.key?(:args)
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
case
|
29
|
+
when args.length == 1 && args.first.is_a?(String)
|
30
|
+
css(args.first).try(:text).to_s
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
35
|
-
|
34
|
+
def data
|
35
|
+
frontmatter
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
|
40
|
-
return parser.send(meth, *args, &block)
|
41
|
-
end
|
42
|
-
|
43
|
-
super
|
38
|
+
def extension
|
39
|
+
path.extname
|
44
40
|
end
|
45
41
|
|
46
|
-
def
|
47
|
-
|
42
|
+
def to_model
|
43
|
+
model_class.new(data.to_hash.merge(path: path, document: self)) if model_class
|
48
44
|
end
|
49
45
|
|
50
|
-
def
|
51
|
-
@
|
46
|
+
def model_class
|
47
|
+
@model_class || ((data && data.type) && Brief::Model.for_type(data.type))
|
52
48
|
end
|
53
49
|
|
54
|
-
|
55
|
-
|
56
|
-
|
50
|
+
# Each model class tracks the instances of the models created
|
51
|
+
# and ensures that there is a 1-1 relationship between a document path
|
52
|
+
# and the model.
|
53
|
+
def model_instance_registered?
|
54
|
+
self.model_class && self.model_class.models.any? do |model|
|
55
|
+
model.path == self.path
|
56
|
+
end
|
57
57
|
end
|
58
58
|
|
59
|
-
def
|
60
|
-
|
59
|
+
def respond_to?(method)
|
60
|
+
super || data.respond_to?(method) || data.key?(method)
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
64
|
-
|
63
|
+
def method_missing(meth, *args, &block)
|
64
|
+
if data.respond_to?(meth)
|
65
|
+
data.send(meth, *args, &block)
|
66
|
+
else
|
67
|
+
super
|
68
|
+
end
|
65
69
|
end
|
66
70
|
|
67
71
|
end
|
68
72
|
end
|
73
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Brief::DocumentMapper
|
2
|
+
OPERATOR_MAPPING = {
|
3
|
+
'equal' => :==,
|
4
|
+
'eq' => :==,
|
5
|
+
'neq' => :!=,
|
6
|
+
'not_equal' => :!=,
|
7
|
+
'gt' => :>,
|
8
|
+
'gte' => :>=,
|
9
|
+
'in' => :in?,
|
10
|
+
'include' => :include?,
|
11
|
+
'lt' => :<,
|
12
|
+
'lte' => :<=
|
13
|
+
}
|
14
|
+
|
15
|
+
VALID_OPERATORS = OPERATOR_MAPPING.keys
|
16
|
+
|
17
|
+
class Selector
|
18
|
+
attr_reader :attribute, :operator
|
19
|
+
|
20
|
+
def initialize(opts = {})
|
21
|
+
unless Brief::DocumentMapper::VALID_OPERATORS.include?(opts[:operator])
|
22
|
+
raise 'Operator not supported'
|
23
|
+
end
|
24
|
+
|
25
|
+
@attribute, @operator = opts[:attribute], opts[:operator]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Query
|
30
|
+
attr_reader :model
|
31
|
+
|
32
|
+
def initialize(model)
|
33
|
+
@model = model
|
34
|
+
@where = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def where(constraints_hash)
|
38
|
+
selector_hash = constraints_hash.reject { |key, value| !key.is_a? Selector }
|
39
|
+
symbol_hash = constraints_hash.reject { |key, value| key.is_a? Selector }
|
40
|
+
symbol_hash.each do |attribute, value|
|
41
|
+
selector = Selector.new(:attribute => attribute, :operator => 'equal')
|
42
|
+
selector_hash.update({ selector => value })
|
43
|
+
end
|
44
|
+
@where.merge! selector_hash
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def order_by(field)
|
49
|
+
@order_by = field.is_a?(Symbol) ? {field => :asc} : field
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def offset(number)
|
54
|
+
@offset = number
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def limit(number)
|
59
|
+
@limit = number
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def first
|
64
|
+
self.all.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def last
|
68
|
+
self.all.last
|
69
|
+
end
|
70
|
+
|
71
|
+
def run_query
|
72
|
+
if query_is_empty?
|
73
|
+
select
|
74
|
+
else
|
75
|
+
model.select do |obj|
|
76
|
+
match = true
|
77
|
+
|
78
|
+
@where.each do |selector, value|
|
79
|
+
if obj.respond_to?(selector.attribute)
|
80
|
+
test_value = obj.send(selector.attribute)
|
81
|
+
operator = OPERATOR_MAPPING[selector.operator]
|
82
|
+
match = false unless test_value.send(operator, value)
|
83
|
+
else
|
84
|
+
match = false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
match
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def all
|
94
|
+
results = run_query
|
95
|
+
|
96
|
+
if @order_by
|
97
|
+
order_by_attr = @order_by.keys.first
|
98
|
+
direction = @order_by.values.first
|
99
|
+
|
100
|
+
results.select! do |result|
|
101
|
+
result.respond_to?(order_by_attr)
|
102
|
+
end
|
103
|
+
|
104
|
+
results.sort_by! do |result|
|
105
|
+
result.send(order_by_attr)
|
106
|
+
end
|
107
|
+
|
108
|
+
results.reverse! if direction == :desc
|
109
|
+
end
|
110
|
+
|
111
|
+
if @offset.present?
|
112
|
+
results = results.last([results.size - @offset, 0].max)
|
113
|
+
end
|
114
|
+
|
115
|
+
if @limit.present?
|
116
|
+
results = results.first(@limit)
|
117
|
+
end
|
118
|
+
|
119
|
+
results
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_a
|
123
|
+
all
|
124
|
+
end
|
125
|
+
|
126
|
+
def query_is_empty?
|
127
|
+
@where.empty? && @limit.nil? && @order_by.nil?
|
128
|
+
end
|
129
|
+
|
130
|
+
def inspect
|
131
|
+
"Query: #{ @where.map {|k,v| "#{k.attribute} #{k.operator} #{v}" }}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def method_missing(meth, *args, &block)
|
135
|
+
if all.respond_to?(meth)
|
136
|
+
all.send(meth, *args, &block)
|
137
|
+
else
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class Symbol
|
145
|
+
Brief::DocumentMapper::VALID_OPERATORS.each do |operator|
|
146
|
+
class_eval <<-OPERATORS
|
147
|
+
def #{operator}
|
148
|
+
Brief::DocumentMapper::Selector.new(:attribute => self, :operator => '#{operator}')
|
149
|
+
end
|
150
|
+
OPERATORS
|
151
|
+
end
|
152
|
+
|
153
|
+
unless method_defined?(:"<=>")
|
154
|
+
def <=>(other)
|
155
|
+
self.to_s <=> other.to_s
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/lib/brief/dsl.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Brief
|
2
|
+
module DSL
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def config(options={}, &block)
|
6
|
+
Brief::Configuration.instance.load_options(options) unless options.nil? || options.empty?
|
7
|
+
Brief::Configuration.instance.instance_eval(&block) if block_given?
|
8
|
+
end
|
9
|
+
|
10
|
+
def define(*args, &block)
|
11
|
+
definition = Brief::Model::Definition.new(args.first, args.extract_options!)
|
12
|
+
definition.instance_eval(&block) if block_given?
|
13
|
+
definition.validate!
|
14
|
+
end
|
15
|
+
|
16
|
+
def action(identifier, options={}, &block)
|
17
|
+
Object.class.class_eval do
|
18
|
+
command "#{identifier}" do |c|
|
19
|
+
c.syntax = "brief #{identifier}"
|
20
|
+
c.description = "run the #{identifier} command"
|
21
|
+
|
22
|
+
c.action do |args, opts|
|
23
|
+
briefcase = Brief.case
|
24
|
+
|
25
|
+
path_args = args.select {|arg| arg.is_a?(String) && arg.match(/\.md$/) }
|
26
|
+
|
27
|
+
path_args.select! do |arg|
|
28
|
+
path = briefcase.repository.root.join(arg)
|
29
|
+
path.exist?
|
30
|
+
end
|
31
|
+
|
32
|
+
path_args.map! {|p| briefcase.repository.root.join(p) }
|
33
|
+
|
34
|
+
models = path_args.map {|path| Brief::Document.new(path) }.map(&:to_model)
|
35
|
+
|
36
|
+
block.call(Brief.case, models, opts)
|
37
|
+
end
|
38
|
+
end rescue nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|