darthapo-cumulus 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +45 -0
  3. data/PostInstall.txt +2 -0
  4. data/README.rdoc +56 -0
  5. data/Rakefile +39 -0
  6. data/bin/cumulus +11 -0
  7. data/bin/cumulus-gen +11 -0
  8. data/cumulus.gemspec +55 -0
  9. data/lib/cumulus.rb +29 -0
  10. data/lib/cumulus/builder.rb +121 -0
  11. data/lib/cumulus/cli.rb +64 -0
  12. data/lib/cumulus/hash_db.rb +137 -0
  13. data/lib/cumulus/resources.rb +46 -0
  14. data/lib/cumulus/resources/attachment.rb +35 -0
  15. data/lib/cumulus/resources/base_resource.rb +105 -0
  16. data/lib/cumulus/resources/content.rb +50 -0
  17. data/lib/cumulus/resources/layout.rb +29 -0
  18. data/lib/cumulus/resources/skin.rb +7 -0
  19. data/lib/cumulus/resources/template.rb +35 -0
  20. data/lib/cumulus/scanner.rb +65 -0
  21. data/lib/support/trollop.rb +694 -0
  22. data/script/console +10 -0
  23. data/script/destroy +14 -0
  24. data/script/generate +14 -0
  25. data/test/fixtures/my_site/Rakefile +27 -0
  26. data/test/fixtures/my_site/config/content.yml +3 -0
  27. data/test/fixtures/my_site/config/publishing.yml +0 -0
  28. data/test/fixtures/my_site/config/site.yml +3 -0
  29. data/test/fixtures/my_site/content/articles/about/index.html +14 -0
  30. data/test/fixtures/my_site/content/articles/about/test.rb +11 -0
  31. data/test/fixtures/my_site/content/posts/001-hello-world/index.html +20 -0
  32. data/test/fixtures/my_site/output/articles/about/index.html +31 -0
  33. data/test/fixtures/my_site/output/articles/about/test.rb +11 -0
  34. data/test/fixtures/my_site/output/posts/001-hello-world/index.html +40 -0
  35. data/test/fixtures/my_site/skin/styles/screen.css +3 -0
  36. data/test/fixtures/my_site/skin/templates/layouts/main.html +21 -0
  37. data/test/fixtures/my_site/skin/templates/objects/article.html +7 -0
  38. data/test/fixtures/my_site/skin/templates/objects/article.summary.html +7 -0
  39. data/test/fixtures/my_site/skin/templates/objects/post.html +10 -0
  40. data/test/fixtures/my_site/skin/templates/objects/post.summary.html +7 -0
  41. data/test/test_helper.rb +5 -0
  42. data/test/unit/cumulus_cli_test.rb +22 -0
  43. data/test/unit/cumulus_test.rb +15 -0
  44. data/test/unit/hash_db_test.rb +29 -0
  45. metadata +167 -0
@@ -0,0 +1,137 @@
1
+ module Cumulus
2
+
3
+ # A rudimentary "database" for holding resource objects and finding them.
4
+ # The database is held in a Ruby hash keyed by the collection/slug
5
+ # Based on Webby's DB class...
6
+ class HashDB
7
+ attr_reader :db
8
+
9
+ def initialize
10
+ @db = Hash.new {|h,k| h[k] = []}
11
+ end
12
+
13
+ def add( content )
14
+ ary = @db[content.content_path]
15
+
16
+ # make sure we don't duplicate contents
17
+ ary.delete content if ary.include? content
18
+ ary << content
19
+
20
+ content
21
+ end
22
+ alias :<< :add
23
+
24
+ def clear
25
+ @db.clear
26
+ self
27
+ end
28
+
29
+ def each( &block )
30
+ keys = @db.keys.sort
31
+ keys.each do |k|
32
+ @db[k].sort.each(&block)
33
+ end
34
+ self
35
+ end
36
+
37
+ # FIXME: Need to add support for:
38
+ # collection/*
39
+ # */*
40
+ def find( *args, &block )
41
+ opts = Hash === args.last ? args.pop : {}
42
+
43
+ limit = args.shift
44
+ limit = opts.delete(:limit) if opts.has_key?(:limit)
45
+ sort_by = opts.delete(:sort_by)
46
+ reverse = opts.delete(:reverse)
47
+
48
+ # figure out which collections to search through
49
+ search = self
50
+ # search = if (dir = opts.delete(:content_pat))
51
+ # dir = dir.sub(%r/^\//, '')
52
+ # strategy = lambda { |key| key == dir }
53
+ # matching_keys = @db.keys.select(&strategy)
54
+ # raise RuntimeError, "unknown collection '#{dir}'" if matching_keys.empty?
55
+ # matching_keys.map { |key| @db[key] }.flatten
56
+ # else
57
+ # self
58
+ # end
59
+
60
+ # construct a search block if one was not supplied by the user
61
+ block ||= lambda do |content|
62
+ found = true
63
+ opts.each do |key, value|
64
+ if key == :content_path
65
+ found &&= content.__send__(key.to_sym).match( Regexp.new( value.gsub('*', '.*'), 'g' ) )
66
+ else
67
+ found &&= content.__send__(key.to_sym) == value
68
+ end
69
+ break if not found
70
+ end
71
+ found
72
+ end
73
+
74
+ # search through the collections for the desired content objects
75
+ ary = []
76
+ search.each do |content|
77
+ ary << content if block.call(content)
78
+ end
79
+
80
+ # sort the search results if the user gave an attribute to sort by
81
+ if sort_by
82
+ m = sort_by.to_sym
83
+ ary.delete_if {|p| p.__send__(m).nil?}
84
+ reverse ?
85
+ ary.sort! {|a,b| b.__send__(m) <=> a.__send__(m)} :
86
+ ary.sort! {|a,b| a.__send__(m) <=> b.__send__(m)}
87
+ end
88
+
89
+ # limit the search results
90
+ case limit
91
+ when :all, 'all'
92
+ ary
93
+ when Integer
94
+ ary.slice(0,limit)
95
+ else
96
+ ary.first
97
+ end
98
+ end
99
+
100
+ def siblings( content, opts = {} )
101
+ ary = @db[content.content_path].dup
102
+ ary.delete content
103
+ return ary unless opts.has_key? :sort_by
104
+
105
+ m = opts[:sort_by]
106
+ ary.sort! {|a,b| a.__send__(m) <=> b.__send__(m)}
107
+ ary.reverse! if opts[:reverse]
108
+ ary
109
+ end
110
+
111
+ end # class
112
+ end # module
113
+
114
+ # EOF
115
+
116
+ # Webby's LICENCE:
117
+ # MIT License
118
+ # Copyright (c) 2007 - 2008
119
+ #
120
+ # Permission is hereby granted, free of charge, to any person obtaining
121
+ # a copy of this software and associated documentation files (the
122
+ # 'Software'), to deal in the Software without restriction, including
123
+ # without limitation the rights to use, copy, modify, merge, publish,
124
+ # distribute, sub-license, and/or sell copies of the Software, and to
125
+ # permit persons to whom the Software is furnished to do so, subject to
126
+ # the following conditions:
127
+ #
128
+ # The above copyright notice and this permission notice shall be
129
+ # included in all copies or substantial portions of the Software.
130
+ #
131
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
132
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
133
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
134
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
135
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
136
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
137
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,46 @@
1
+ require 'cumulus/hash_db'
2
+
3
+ module Cumulus::Resources
4
+ class << self
5
+
6
+ # Every source file: Content, Attachment, (¿Template?)
7
+ def all
8
+ @all ||= Cumulus::HashDB.new
9
+ end
10
+
11
+ # Only content attachments
12
+ def attachments(*args)
13
+ filter = args.extract_options!.merge! :content_type=>:attachment
14
+ all.find((args.first || :all), filter)
15
+ end
16
+
17
+ # Only content objects
18
+ def collections(*args)
19
+ filter = args.extract_options!.merge! :content_type=>:content
20
+ all.find((args.first || :all), filter)
21
+ end
22
+
23
+ # Only templates
24
+ def templates(*args)
25
+ filter = args.extract_options!.merge! :content_type=>:template
26
+ all.find((args.first || :all), filter)
27
+ end
28
+
29
+ # Only templates
30
+ def layouts(*args)
31
+ filter = args.extract_options!.merge! :content_type=>:layout
32
+ all.find((args.first || :all), filter)
33
+ end
34
+
35
+ def clear
36
+ all.clear
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ require 'cumulus/resources/base_resource'
43
+ require 'cumulus/resources/attachment'
44
+ require 'cumulus/resources/content'
45
+ require 'cumulus/resources/template'
46
+ require 'cumulus/resources/layout'
@@ -0,0 +1,35 @@
1
+ module Cumulus::Resources
2
+
3
+ # A attachment is any file that's not the index.html in a
4
+ # content object folder
5
+ class Attachment < BaseResource
6
+
7
+ attr_reader :parent
8
+
9
+ def initialize(filepath, content_object)
10
+ super(filepath)
11
+ @parent = content_object
12
+ @content_type = :attachment
13
+ @content_path = parent.content_path
14
+ @collection_type = parent.collection_type
15
+ parse_meta
16
+ end
17
+
18
+ def output_path
19
+ File.join(Cumulus.output_dir, @content_path, @slug)
20
+ end
21
+
22
+
23
+ protected
24
+
25
+ def parse_meta
26
+ metadata[:filename] = File.basename(source_path)
27
+ metadata[:ext] = File.extname(source_path)
28
+ metadata[:mtime] = File.mtime(source_path)
29
+ metadata[:ctime] = File.ctime(source_path)
30
+ metadata[:size] = File.size(source_path)
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,105 @@
1
+ module Cumulus::Resources
2
+
3
+ =begin
4
+
5
+ content_path:
6
+ - pages/about
7
+ - posts/20081105-my-slugline
8
+
9
+ content_type:
10
+ - content
11
+ - attachment
12
+
13
+ collection_type:
14
+ - pages
15
+ - posts
16
+
17
+ slug:
18
+ - about
19
+ - 20081105-my-slugline
20
+
21
+ =end
22
+
23
+ class BaseResource
24
+
25
+ attr_reader :slug, :content_path, :collection_type, :content_type, :source_path
26
+
27
+ # def guid
28
+ # @guid ||= `uuidgen` # Probably need a more portable solution than this...
29
+ # end
30
+
31
+ def initialize(fullpath)
32
+ @source_path = fullpath
33
+ @slug = File.basename(fullpath)
34
+ end
35
+
36
+ def metadata
37
+ @meta ||= {}
38
+ end
39
+
40
+ def includes
41
+ metadata.fetch(:include, [])
42
+ end
43
+
44
+ def output_path
45
+ #filename = File.extname(@slug).empty? ? "#{@slug}.html" : @slug
46
+ File.join(Cumulus.output_dir, @collection_type, @slug, 'index.html')
47
+ end
48
+
49
+ def method_missing(name, *args)
50
+ if name.to_s.ends_with? '='
51
+ metadata[name.to_s[0..-2].to_sym] = args.first
52
+ else
53
+ metadata[name]
54
+ end
55
+ end
56
+
57
+ def <=>( other )
58
+ return unless other.kind_of? ::Cumulus::Resources::BaseResource
59
+ self.content_path <=> other.content_path
60
+ end
61
+
62
+ def to_liquid
63
+ data = metadata.merge({
64
+ :slug => @slug,
65
+ :content_path => @content_path,
66
+ :collection_type => @collection_type,
67
+ :content_type => @content_type
68
+ }).stringify_keys
69
+ end
70
+
71
+ def [](key)
72
+ if self.respond_to? key.to_sym
73
+ self.send(key.to_sym)
74
+ else
75
+ metadata[name]
76
+ end
77
+ end
78
+
79
+ # def dirty?
80
+ # return metadata[:dirty] if metadata.has_key? :dirty
81
+ #
82
+ # # if the destination file does not exist, then we are dirty
83
+ # return true unless test(?e, destination)
84
+ #
85
+ # # if this file's mtime is larger than the destination file's
86
+ # # mtime, then we are dirty
87
+ # dirty = @mtime > ::File.mtime(destination)
88
+ # return dirty if dirty
89
+ #
90
+ # # check to see if the layout is dirty, and if it is then we
91
+ # # are dirty, too
92
+ # if _meta_data.has_key? 'layout'
93
+ # lyt = ::Webby::Resources.find_layout(_meta_data['layout'])
94
+ # unless lyt.nil?
95
+ # return true if lyt.dirty?
96
+ # end
97
+ # end
98
+ #
99
+ # # if we got here, then we are not dirty
100
+ # false
101
+ # end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,50 @@
1
+ require 'hpricot'
2
+
3
+ module Cumulus::Resources
4
+
5
+ # Content objects reside within collections, they include all meta data
6
+ # and attachments
7
+ class Content < BaseResource
8
+
9
+
10
+ def initialize(full_path, content_path)
11
+ super(full_path)
12
+ @content_type = :content
13
+ @content_path = content_path
14
+ @collection_type = content_path.split('/').first
15
+ parse_file
16
+ end
17
+
18
+ def attachments
19
+ Cumulus::Resources.attachments( :content_path=>content_path )
20
+ end
21
+
22
+ protected
23
+
24
+ def parse_file(filename='index.html')
25
+ doc = Hpricot(open(File.join(source_path, filename)))
26
+ metadata[:title] = doc.at("title").inner_html
27
+ doc.search("//title").remove
28
+
29
+ metadata[:content] = doc.at("body").inner_html #.squeeze
30
+ doc.search("//body").remove
31
+
32
+ doc.search("//meta").each do |meta_tag|
33
+ name = meta_tag[:name].gsub(' ', '_').gsub('-', '_').downcase.to_sym
34
+ if name == :include
35
+ metadata[name] = [] unless metadata.has_key? name
36
+ metadata[name] << meta_tag[:content]
37
+ else
38
+ metadata[name] = meta_tag[:content]
39
+ end
40
+
41
+ end
42
+ doc.search("//meta").remove
43
+
44
+ # Whatever's left in the header, we'll pull out here...
45
+ metadata[:head] = doc.at("head").inner_html.squeeze
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,29 @@
1
+ module Cumulus::Resources
2
+
3
+ =begin
4
+
5
+ Layouts could have meta tags, but only at the root level?
6
+
7
+ /meta
8
+
9
+ Should it use a decorator approach?
10
+
11
+ <meta name="decorates" content="page"/>
12
+
13
+ ???
14
+ =end
15
+
16
+
17
+ # A layout is any .html file that contains Liquid markup
18
+ # for use in wrapping content objects.
19
+ class Layout < Template
20
+
21
+ def initialize(fullpath)
22
+ super(fullpath)
23
+ @content_path = 'skin/layouts'
24
+ @content_type = :layout
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,7 @@
1
+ module Cumulus::Resources
2
+
3
+ class Skin
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,35 @@
1
+ module Cumulus::Resources
2
+
3
+ # A template is any .html file that contains Liquid markup
4
+ # for use in wrapping/rendering content objects
5
+ class Template < BaseResource
6
+
7
+ def initialize(fullpath)
8
+ super(fullpath)
9
+ @content_type = :template
10
+ @content_path = 'skin/templates'
11
+ parse_template
12
+ end
13
+
14
+ protected
15
+
16
+ def parse_template
17
+ doc = Hpricot(open(source_path))
18
+
19
+ doc.search("/meta").each do |meta_tag|
20
+ name = meta_tag[:name].gsub(' ', '_').gsub('-', '_').downcase.to_sym
21
+ if name == :include or metadata.has_key? name
22
+ metadata[name] = [ metadata[name] ] unless metadata[name].is_a? Array
23
+ metadata[name] << meta_tag[:content]
24
+ else
25
+ metadata[name] = meta_tag[:content]
26
+ end
27
+ end
28
+ doc.search("/meta").remove
29
+
30
+ metadata[:content] = doc.to_s
31
+ end
32
+
33
+ end
34
+
35
+ end