amiba 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,199 @@
1
+ require 'rexml/document'
2
+ include REXML
3
+
4
+ # reverse markdown for ruby
5
+ # author: JO
6
+ # e-mail: xijo@gmx.de
7
+ # date: 14.7.2009
8
+ # version: 0.1
9
+ # license: GPL
10
+
11
+ # TODO
12
+ # - ol numbering is buggy, in fact doesn't matter for markdown code
13
+ # -
14
+
15
+ module Amiba
16
+ class ReverseMarkdown
17
+
18
+ # set basic variables:
19
+ # - @li_counter: numbering list item (li) tags in an ordered list (ol)
20
+ # - @links: hold the links for adding them to the bottom of the @output
21
+ # this means 'reference style', please take a look at http://daringfireball.net/projects/markdown/syntax#link
22
+ # - @outout: fancy markdown code in here!
23
+ # - @indent: control indention level for nested lists
24
+ # - @errors: appearing errors, like unknown tags, go into this array
25
+ def initialize()
26
+ @li_counter = 0
27
+ @links = []
28
+ @output = ""
29
+ @indent = 0
30
+ @errors = []
31
+ end
32
+
33
+ # Invokes the HTML parsing by using a string. Returns the markdown code in @output.
34
+ # To garantuee well-formed xml for REXML a <root> element will be added, but has no effect.
35
+ # After parsing all elements, the 'reference style'-links will be inserted.
36
+ def parse_string(string)
37
+ doc = Document.new("<root>\n"+string+"\n</root>")
38
+ root = doc.root
39
+ root.elements.each do |element|
40
+ parse_element(element, :root)
41
+ end
42
+ insert_links()
43
+ @output
44
+ end
45
+
46
+ # Parsing an element and its children (recursive) and writing its markdown code to @output
47
+ # 1. do indent for nested list items
48
+ # 2. add the markdown opening tag for this element
49
+ # 3a. if element only contains text, handle it like a text node
50
+ # 3b. if element is a container handle its children, which may be text- or element nodes
51
+ # 4. finally add the markdown ending tag for this element
52
+ def parse_element(element, parent)
53
+ name = element.name.to_sym
54
+ # 1.
55
+ @output << indent() if name.eql?(:li)
56
+ # 2.
57
+ @output << opening(element, parent)
58
+
59
+ # 3a.
60
+ if (element.has_text? and element.children.size < 2)
61
+ @output << text_node(element, parent)
62
+ end
63
+
64
+ # 3b.
65
+ if element.has_elements?
66
+ element.children.each do |child|
67
+ # increase indent if nested list
68
+ @indent += 1 if element.name=~/(ul|ol)/ and parent.eql?(:li)
69
+
70
+ if child.node_type.eql?(:element)
71
+ parse_element(child, element.name.to_sym)
72
+ else
73
+ if parent.eql?(:blockquote)
74
+ @output << child.to_s.gsub("\n ", "\n>")
75
+ else
76
+ @output << child.to_s
77
+ end
78
+ end
79
+
80
+ # decrease indent if end of nested list
81
+ @indent -= 1 if element.name=~/(ul|ol)/ and parent.eql?(:li)
82
+ end
83
+ end
84
+
85
+ # 4.
86
+ @output << ending(element, parent)
87
+ end
88
+
89
+ # Returns opening markdown tag for the element. Its parent matters sometimes!
90
+ def opening(type, parent)
91
+ case type.name.to_sym
92
+ when :h1
93
+ "# "
94
+ when :li
95
+ parent.eql?(:ul) ? " - " : " "+(@li_counter+=1).to_s+". "
96
+ when :ol
97
+ @li_counter = 0
98
+ ""
99
+ when :ul
100
+ ""
101
+ when :h2
102
+ "## "
103
+ when :em
104
+ "*"
105
+ when :strong
106
+ "**"
107
+ when :blockquote
108
+ # remove leading newline
109
+ type.children.first.value = ""
110
+ "> "
111
+ when :code
112
+ parent.eql?(:pre) ? " " : "`"
113
+ when :a
114
+ "["
115
+ when :img
116
+ "!["
117
+ when :hr
118
+ "----------\n\n"
119
+ else
120
+ @errors << "unknown start tag: "+type.name.to_s
121
+ ""
122
+ end
123
+ end
124
+
125
+ # Returns the closing markdown tag, like opening()
126
+ def ending(type, parent)
127
+ case type.name.to_sym
128
+ when :h1
129
+ " #\n\n"
130
+ when :h2
131
+ " ##\n\n"
132
+ when :p
133
+ parent.eql?(:root) ? "\n\n" : "\n"
134
+ when :ol
135
+ parent.eql?(:li) ? "" : "\n"
136
+ when :ul
137
+ parent.eql?(:li) ? "" : "\n"
138
+ when :em
139
+ "*"
140
+ when :strong
141
+ "**"
142
+ when :li
143
+ ""
144
+ when :blockquote
145
+ ""
146
+ when :code
147
+ parent.eql?(:pre) ? "" : "`"
148
+ when :a
149
+ @links << type.attribute('href').to_s
150
+ "][" + @links.size.to_s + "] "
151
+ when :img
152
+ @links << type.attribute('src').to_s
153
+ "" + type.attribute('alt').to_s + "][" + @links.size.to_s + "] "
154
+ "#{type.attribute('alt')}][#{@links.size}] "
155
+ else
156
+ @errors << " unknown end tag: "+type.name.to_s
157
+ ""
158
+ end
159
+ end
160
+
161
+ # Perform indent: two space, @indent times - quite simple! :)
162
+ def indent
163
+ str = ""
164
+ @indent.times do
165
+ str << " "
166
+ end
167
+ str
168
+ end
169
+
170
+ # Return the content of element, which should be just text.
171
+ # If its a code block to indent of 4 spaces.
172
+ # For block quotation add a leading '>'
173
+ def text_node(element, parent)
174
+ if element.name.to_sym.eql?(:code) and parent.eql?(:pre)
175
+ element.text.gsub("\n","\n ") << "\n"
176
+ elsif parent.eql?(:blockquote)
177
+ element.text.gsub!("\n ","\n>")
178
+ else
179
+ element.text
180
+ end
181
+ end
182
+
183
+ # Insert the mentioned reference style links.
184
+ def insert_links
185
+ @output << "\n"
186
+ @links.each_index do |index|
187
+ @output << " [#{index+1}]: #{@links[index]}\n"
188
+ end
189
+ end
190
+
191
+ # Print out all errors, that occured and have been written to @errors.
192
+ def print_errors
193
+ @errors.each do |error|
194
+ puts error
195
+ end
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,49 @@
1
+ module Amiba
2
+ class Scope
3
+
4
+ attr_reader :page
5
+
6
+ def initialize(page)
7
+ @page = page
8
+ end
9
+
10
+ def title
11
+ page.title
12
+ end
13
+
14
+ def description
15
+ page.description
16
+ end
17
+
18
+ def content
19
+ page_renderer.render(self)
20
+ end
21
+
22
+ def entries
23
+ Amiba::Source::Entry
24
+ end
25
+
26
+ def partial(path, locals={})
27
+ p = Amiba::Source::Partial.new path
28
+ Tilt.new(p.filename).render(Amiba::Scope.new(p), locals)
29
+ end
30
+
31
+ def site_name
32
+ Amiba::Configuration.site_name.nil? ? "" : "http://#{Amiba::Configuration.site_name}/"
33
+ end
34
+
35
+ def full_url(frag)
36
+ if site_name.empty?
37
+ frag
38
+ else
39
+ URI.join(site_name, frag).to_s
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def page_renderer
46
+ Tilt.new page.staged_filename
47
+ end
48
+ end
49
+ end
data/lib/amiba/site.rb ADDED
@@ -0,0 +1,166 @@
1
+ module Amiba
2
+ module Site
3
+
4
+ class S3Upload < Thor::Group
5
+ include Amiba::Generator
6
+
7
+ namespace :"site:upload:s3"
8
+
9
+ class_option :credentials, :default => :default
10
+
11
+ def init_s3
12
+ Fog.credential = options[:credentials].to_sym
13
+ @s3 ||= Fog::Storage.new(:provider=>'AWS')
14
+ end
15
+
16
+ def create
17
+ invoke Amiba::Site::Generate
18
+ end
19
+
20
+ def configure_s3
21
+ if ! @s3.directories.get bucket
22
+ @bucket = @s3.directories.create(:key=>bucket, :public=>true, :location=>location)
23
+ say_status "Created", @bucket.key, :green
24
+ @s3.put_bucket_website(bucket,"index.html")
25
+ say_status "Configured", @bucket.key, :green
26
+ else
27
+ @bucket = @s3.directories.get bucket
28
+ end
29
+ end
30
+
31
+ def upload_files
32
+ Dir[File.join(Amiba::Configuration.site_dir, "public", "**/*")].each do |ent|
33
+ next if File.directory? ent
34
+ path = File.expand_path ent
35
+ name = File.relpath(path, File.join(Amiba::Configuration.site_dir, "public"))
36
+ data = File.open path
37
+ file = @bucket.files.create(:key=>name, :body=>data, :public=>true)
38
+ say_status "Uploaded", name, :green
39
+ end
40
+ end
41
+
42
+ def complete
43
+ host = "http://#{bucket}.s3-website-#{Fog.credentials[:region]}.amazonaws.com/"
44
+ say_status "Available at", host, :green
45
+ end
46
+
47
+ private
48
+ def bucket
49
+ Amiba::Configuration.site_name
50
+ end
51
+
52
+ def location
53
+ Amiba::Configuration.s3_location || "EU"
54
+ end
55
+
56
+ end
57
+
58
+ class Generate < Thor::Group
59
+ include Amiba::Generator
60
+
61
+ namespace :"site:generate"
62
+
63
+ def self.source_root
64
+ Dir.pwd
65
+ end
66
+
67
+ def cleardown
68
+ remove_dir Amiba::Configuration.site_dir
69
+ remove_dir Amiba::Configuration.staged_dir
70
+ end
71
+
72
+ def create_site_structure
73
+ empty_directory Amiba::Configuration.site_dir
74
+ end
75
+
76
+ def copy_favicon
77
+ if File.exists? "public/images/favicon.ico"
78
+ copy_file "public/images/favicon.ico", File.join(Amiba::Configuration.site_dir, "public/favicon.ico")
79
+ end
80
+ end
81
+
82
+ def copy_xdomain
83
+ if File.exists? "public/crossdomain.xml"
84
+ copy_file "public/crossdomain.xml", File.join(Amiba::Configuration.site_dir, "public/crossdomain.xml")
85
+ end
86
+ end
87
+
88
+ def copy_javascript
89
+ directory "public/js", File.join(Amiba::Configuration.site_dir, "public/js")
90
+ end
91
+
92
+ def copy_images
93
+ directory "public/images", File.join(Amiba::Configuration.site_dir, "public/images")
94
+ end
95
+
96
+ def copy_css
97
+ Dir.glob('public/css/*.css').each do |css_file|
98
+ copy_file css_file, File.join(Amiba::Configuration.site_dir, "public/css/", File.basename(css_file))
99
+ end
100
+ end
101
+
102
+ def process_and_copy_sass
103
+ Dir.glob('public/css/[^_]*.scss').each do |scss_file|
104
+ create_file File.join(Amiba::Configuration.site_dir,"public/css/", File.basename(scss_file).gsub('scss', 'css')) do
105
+ Tilt.new(scss_file).render
106
+ end
107
+ end
108
+ end
109
+
110
+ def build_pages
111
+ Dir.glob('pages/**/[^_]*').each do |page_file|
112
+ next if File.directory? page_file
113
+ page = Amiba::Source::Page.new(File.relpath(page_file, "pages"))
114
+ next unless page.state == "published"
115
+ build_page page
116
+ end
117
+ end
118
+
119
+ def build_entries
120
+ Amiba::Source::Entry.all.each do |entry|
121
+ build_page entry
122
+ end
123
+ end
124
+
125
+ def build_json
126
+ Dir.glob('entries/*').each do |cat|
127
+ c = File.basename cat
128
+ create_file(File.join(Amiba::Configuration.site_dir, "public", c, "latest.json")) do
129
+ Amiba::Source::Entry.send(c.to_sym.pluralize).limit(20).each.inject([]) do |acc, ent|
130
+ a = ent.metadata
131
+ a["content"] = ent.render
132
+ acc << a
133
+ end.to_json
134
+ end
135
+ end
136
+ end
137
+
138
+ def build_feeds
139
+ Dir.glob('feeds/*.builder').each do |feed_file|
140
+ feed = Amiba::Source::Feed.new(feed_file)
141
+ create_file(feed.output_filename) do
142
+ Tilt.new(feed.filename).render(Amiba::Scope.new(feed), :xml => Builder::XmlMarkup.new)
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def build_layout(page)
150
+ layout = Amiba::Source::Layout.new(page.layout)
151
+ return layout if File.exists? layout.staged_filename
152
+ create_file(layout.staged_filename) do layout.content end
153
+ layout
154
+ end
155
+
156
+ def build_page(page)
157
+ layout = build_layout(page)
158
+ create_file(page.staged_filename) do page.content end
159
+ create_file(page.output_filename) do
160
+ Tilt.new(layout.staged_filename).render(Amiba::Scope.new(page))
161
+ end
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,42 @@
1
+ module Amiba
2
+ module Source
3
+ class Entry
4
+ extend Amiba::Source::EntryFinder
5
+ include Amiba::Source
6
+
7
+ attr_accessor :category
8
+ metadata_fields :title, :slug, :state, :layout
9
+
10
+ validates_presence_of :title, :state, :layout
11
+
12
+ def initialize(category, name, format, metadata = nil, content = nil)
13
+ self.category = category
14
+ self.name = name
15
+ self.format = format
16
+ self.metadata = metadata
17
+ self.content = content
18
+ end
19
+
20
+ def filename
21
+ File.join("entries", category.to_s.downcase.pluralize, name + ".#{format.to_s}")
22
+ end
23
+
24
+ def staged_filename
25
+ File.join(Amiba::Configuration.staged_dir, filename)
26
+ end
27
+
28
+ def output_filename
29
+ File.join(Amiba::Configuration.site_dir, 'public', category.to_s.downcase.pluralize, "#{name}.html")
30
+ end
31
+
32
+ def link
33
+ URI.escape( ["", category.to_s.downcase.pluralize, "#{name}.html"].join("/") )
34
+ end
35
+
36
+ def render
37
+ Tilt.new(self.staged_filename).render(Amiba::Scope.new(self))
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,136 @@
1
+ module Amiba
2
+ module Source
3
+ module EntryFinder
4
+
5
+ def method_missing(method_name, *args, &block)
6
+ proxy = FinderProxy.new
7
+ proxy.send(method_name, *args, &block)
8
+ rescue
9
+ raise ArgumentError.new("#{method_name} does not exist")
10
+ end
11
+
12
+ end
13
+
14
+ class FinderProxy
15
+ include Amiba::Repo
16
+ include Enumerable
17
+
18
+ def each(&blk)
19
+ entries.each(&blk)
20
+ end
21
+
22
+ def first
23
+ entries.first
24
+ end
25
+
26
+ def last
27
+ entries.last
28
+ end
29
+
30
+ def count
31
+ entries.count
32
+ end
33
+
34
+ def [](index)
35
+ entries[index]
36
+ end
37
+
38
+ def entries
39
+ result = (scopes[:category] || CategoryScope.new).apply
40
+ result = (scopes[:state] || StateScope.new).apply(result)
41
+ # reverse sorting is a more natural approach
42
+ result.sort! do |a, b|
43
+ last_commit_date(b.filename) <=> last_commit_date(a.filename)
44
+ end
45
+ result = scopes[:offset].apply(result) if scopes[:offset]
46
+ result = scopes[:limit].apply(result) if scopes[:limit]
47
+ result
48
+ end
49
+
50
+ [:draft, :published, :any].each do |state|
51
+ define_method state do
52
+ self[:state] = StateScope.new(state)
53
+ self
54
+ end
55
+ end
56
+
57
+ def method_missing(method_name, *args, &block)
58
+ entry_types = (Dir.glob('entries/*') << "all").map {|c| File.basename(c).to_sym}
59
+ if entry_types.include?(method_name)
60
+ self[:category] = CategoryScope.new(method_name)
61
+ else
62
+ raise ArgumentError
63
+ end
64
+ self
65
+ end
66
+
67
+ def offset(index)
68
+ self[:offset] = OffsetScope.new(index)
69
+ self
70
+ end
71
+
72
+ def limit(count)
73
+ self[:limit] = LimitScope.new(count)
74
+ self
75
+ end
76
+
77
+ protected
78
+
79
+ def []=(key, val)
80
+ scopes[key] = val
81
+ self
82
+ end
83
+
84
+ def scopes
85
+ @scopes ||= {}
86
+ end
87
+
88
+ end
89
+
90
+ class CategoryScope
91
+ include Amiba::Repo
92
+ def initialize(category = :all)
93
+ @category = category
94
+ end
95
+ def apply
96
+ entry_files.map do |ef|
97
+ _, category, filename = ef.split('/')
98
+ name, format = filename.split('.')
99
+ Amiba::Source::Entry.new(category, name, format)
100
+ end
101
+ end
102
+ def entry_files
103
+ globstring = "entries/#{@category == :all ? '*' : @category.to_s}/*"
104
+ Dir.glob(globstring).select {|e| !repo.log(e).empty?}
105
+ end
106
+ end
107
+
108
+ class StateScope
109
+ def initialize(state = :published)
110
+ @state = state
111
+ end
112
+ def apply(entries)
113
+ return entries if @state == :any
114
+ entries.select {|e| e.state.to_sym == @state}
115
+ end
116
+ end
117
+
118
+ class OffsetScope
119
+ def initialize(offset = 0)
120
+ @offset = offset
121
+ end
122
+ def apply(entries)
123
+ entries[@offset..-1]
124
+ end
125
+ end
126
+
127
+ class LimitScope
128
+ def initialize(limit = -1)
129
+ @limit = (limit == -1 ? -1 : limit - 1)
130
+ end
131
+ def apply(entries)
132
+ entries[0..@limit]
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module Amiba
3
+ module Source
4
+ class Feed
5
+ include Amiba::Source
6
+
7
+ attr_accessor :type, :name
8
+ def initialize(fn)
9
+ self.name, self.type = File.basename(fn, ".builder").split(".")
10
+ end
11
+
12
+ def filename
13
+ @filename ||= File.join("feeds", "#{@name}.#{@type}.builder")
14
+ end
15
+
16
+ def content=(c)
17
+ @content ||= self.new? ? c : File.read(filename)
18
+ end
19
+
20
+ def staged_filename
21
+ File.join(Amiba::Configuration.staged_dir, filename)
22
+ end
23
+
24
+ def output_filename
25
+ File.join(Amiba::Configuration.site_dir, "public/#{name}.#{type}")
26
+ end
27
+
28
+ def link
29
+ URI.escape "/#{name}.#{type}"
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module Amiba
2
+ module Source
3
+ class Partial
4
+ include Amiba::Source
5
+
6
+ attr_accessor :dir, :name
7
+ def initialize(path)
8
+ self.dir, self.name = File.split path
9
+ end
10
+
11
+ def filename
12
+ @filename ||= File.join("pages", @dir, "_#{@name}.haml")
13
+ end
14
+
15
+ def staged_filename
16
+ File.join(Amiba::Configuration.staged_dir, filename)
17
+ end
18
+ alias_method :output_filename, :staged_filename
19
+
20
+ end
21
+ end
22
+ end