zwite 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.
@@ -0,0 +1,134 @@
1
+ module Zwite
2
+
3
+ class Site
4
+
5
+ ##
6
+ # Properties
7
+ ##
8
+
9
+ attr_accessor :path
10
+ attr_accessor :config_path
11
+ attr_accessor :apps_path
12
+ attr_accessor :output_path
13
+ attr_accessor :cache_path
14
+
15
+ attr_accessor :config
16
+ attr_accessor :plugins
17
+ attr_accessor :apps
18
+
19
+ attr_accessor :liquid_context
20
+
21
+ def files_changed?
22
+ @timestamps ||= {}
23
+ old_timestamps = @timestamps.dup
24
+
25
+ # save timestamps
26
+ @timestamps = {}
27
+ Dir[self.apps_path + "**/*"].each do |p|
28
+ path = Pathname.new(p)
29
+ @timestamps[path.to_s] = path.mtime
30
+ end
31
+
32
+ # if we have no old_timestamps probably a first generation
33
+ unless old_timestamps.any?
34
+ return true
35
+ end
36
+
37
+ # if we have no timestamps probably a bug
38
+ unless @timestamps.any?
39
+ return true
40
+ end
41
+
42
+ # check if we deleted any files
43
+ old_timestamps.each do |path, timestamp|
44
+ if @timestamps.fetch(path, nil).nil?
45
+ return true
46
+ end
47
+ end
48
+
49
+ # check if we added any files or modified any
50
+ @timestamps.each do |path, timestamp|
51
+ old_timestamp = old_timestamps.fetch(path, nil)
52
+ if old_timestamp.nil? || old_timestamp != timestamp
53
+ return true
54
+ end
55
+ end
56
+
57
+ return false
58
+ end
59
+
60
+ ##
61
+ # Initialization
62
+ ##
63
+
64
+ def initialize(path)
65
+ self.path = path
66
+ self.config_path = self.path + "config.yml"
67
+ self.apps_path = self.path + "apps"
68
+ self.output_path = self.path + "public"
69
+ self.cache_path = self.path + ".cache"
70
+ self.load
71
+ end
72
+
73
+ ##
74
+ # Actions
75
+ ##
76
+
77
+ def load
78
+ self.config = YAML.load_file(self.config_path)
79
+
80
+ self.plugins = Zwite::Plugin.subclasses
81
+
82
+ self.apps = []
83
+ self.config["apps"].each do |a|
84
+ self.apps << App.new(self, self.apps_path + a["name"], a["mount"])
85
+ end
86
+
87
+ end
88
+
89
+ def build
90
+ unless files_changed?
91
+ return
92
+ end
93
+
94
+ puts "Building site..."
95
+
96
+ begin
97
+ self.reset
98
+ self.preprocess
99
+ self.generate
100
+ rescue Exception => e
101
+ puts "ERROR BUILDING SITE"
102
+ puts "-------------------"
103
+ puts e
104
+ end
105
+
106
+ puts "Building site finished..."
107
+
108
+ end
109
+
110
+ def reset
111
+ if self.output_path.exist?
112
+ self.output_path.rmtree
113
+ end
114
+ self.output_path.mkpath
115
+ end
116
+
117
+ def preprocess
118
+ self.liquid_context = {}
119
+ self.apps.each do |app|
120
+ app.preprocess
121
+ self.liquid_context[app.name] = app
122
+ end
123
+ end
124
+
125
+ def generate
126
+ self.apps.each do |app|
127
+ app.generate
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,41 @@
1
+ module Zwite
2
+
3
+ module Utils
4
+
5
+ def self.markdownify(str)
6
+ return RDiscount.new(str, :smart).to_html
7
+ end
8
+
9
+ def self.parse_date_and_slug(str)
10
+ regex = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})-(?<hour>\d{2})(?<minute>\d{2})-(?<slug>.+)$/
11
+ matches = regex.match(str)
12
+ if matches.nil?
13
+ raise Exception::exception("error parsing date and slug #{str}")
14
+ end
15
+
16
+ date = DateTime.new(matches["year"].to_i, matches["month"].to_i, matches["day"].to_i, matches["hour"].to_i, matches["minute"].to_i, 0, DateTime.now.offset)
17
+ slug = matches["slug"]
18
+
19
+ yield date, slug
20
+ end
21
+
22
+ def self.parse_metadata_and_markdown(str)
23
+ metadata = nil
24
+ markdown = nil
25
+ if str =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
26
+ markdown = markdownify($')
27
+
28
+ unless $1.nil?
29
+ yaml_contents = $1.gsub(/\t/, " ")
30
+ unless yaml_contents.nil?
31
+ metadata = YAML.load(yaml_contents)
32
+ end
33
+ end
34
+
35
+ end
36
+ yield metadata, markdown
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,16 @@
1
+ class Object
2
+
3
+ def self.attr_accessor(*vars)
4
+ @attributes ||= []
5
+ @attributes.concat vars
6
+ super
7
+ end
8
+
9
+ def self.attributes
10
+ return @attributes
11
+ end
12
+ def attributes
13
+ return self.class.attributes
14
+ end
15
+
16
+ end
@@ -0,0 +1,16 @@
1
+ class Fixnum
2
+
3
+ def ordinalize
4
+ if (11..13).include?(self.abs % 100)
5
+ "#{self}th"
6
+ else
7
+ case self.abs % 10
8
+ when 1; "#{self}st"
9
+ when 2; "#{self}nd"
10
+ when 3; "#{self}rd"
11
+ else "#{self}th"
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,15 @@
1
+ class Hash
2
+
3
+ def merge_recursive(other_hash)
4
+ target = dup
5
+ other_hash.keys.each do |key|
6
+ if other_hash[key].is_a?(Hash) and self[key].is_a?(Hash)
7
+ target[key] = target[key].merge_recursive(other_hash[key])
8
+ next
9
+ end
10
+ target[key] = other_hash[key]
11
+ end
12
+ return target
13
+ end
14
+
15
+ end
@@ -0,0 +1,16 @@
1
+ class Integer
2
+
3
+ def ordinalize
4
+ if (11..13).include?(self.abs % 100)
5
+ "#{self}th"
6
+ else
7
+ case self.abs % 10
8
+ when 1; "#{self}st"
9
+ when 2; "#{self}nd"
10
+ when 3; "#{self}rd"
11
+ else "#{self}th"
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,19 @@
1
+ class String
2
+
3
+ SLUGIFY_SEPARATORS = %w[- _ +]
4
+
5
+ def slugify(separator = "-")
6
+ unless SLUGIFY_SEPARATORS.include?(separator)
7
+ raise "Word separator must be one of #{SLUGIFY_SEPARATORS}"
8
+ end
9
+ re_separator = Regexp.escape(separator)
10
+ result = self.dup
11
+ result.gsub!(/[^\x00-\x7F]+/, '') # Remove non-ASCII (e.g. diacritics).
12
+ result.gsub!(/[^a-z0-9\-_\+]+/i, separator) # Turn non-slug chars into the separator.
13
+ result.gsub!(/#{re_separator}{2,}/, separator) # No more than one of the separator in a row.
14
+ result.gsub!(/^#{re_separator}|#{re_separator}$/, '') # Remove leading/trailing separator.
15
+ result.downcase!
16
+ return result
17
+ end
18
+
19
+ end
@@ -0,0 +1,5 @@
1
+ class Time
2
+ def xmlschema
3
+ strftime("%Y-%m-%dT%H:%M:%S%Z")
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ module Zwite
2
+
3
+ module Liquid
4
+
5
+ class Block < ::Liquid::Block
6
+ Syntax = /(\w+)/
7
+
8
+ attr_reader :name
9
+
10
+ def initialize(tag_name, markup, tokens)
11
+ if markup =~ Syntax
12
+ @name = $1
13
+ else
14
+ raise SyntaxError.new("Syntax Error in 'block' - Valid syntax {% block [name] %}{% endblock %}")
15
+ end
16
+ super
17
+ end
18
+
19
+ def render(context)
20
+ context.stack do
21
+ render_all(@nodelist, context)
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ ::Liquid::Template.register_tag('block', Zwite::Liquid::Block)
@@ -0,0 +1,25 @@
1
+ module Liquid
2
+
3
+ module StandardFilters
4
+
5
+ def date(input, format)
6
+ if format.to_s.empty?
7
+ return input.to_s
8
+ end
9
+
10
+ if ((input.is_a?(String) && !/^\d+$/.match(input.to_s).nil?) || input.is_a?(Integer)) && input.to_i > 0
11
+ input = Time.at(input.to_i)
12
+ end
13
+
14
+ date = input.is_a?(String) ? Time.parse(input) : input
15
+ date = date.to_datetime
16
+
17
+ return Zwite::DateFormat::Formatter.new(date).format(format)
18
+
19
+ rescue => e
20
+ input
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,86 @@
1
+ module Liquid
2
+
3
+ class Template
4
+
5
+ attr_accessor :parent_template
6
+ attr_accessor :child_template
7
+
8
+ def parse(source)
9
+ # parse root normally
10
+ @root = Document.new(tokenize(source))
11
+
12
+ # check if root has any nodes otherwise skip early
13
+ unless @root.nodelist.nil?
14
+
15
+ # check if we have an "extends" node
16
+ extends = nil
17
+ @root.nodelist.each do |node|
18
+ if node.is_a?(::Zwite::Liquid::Extends)
19
+ extends = node
20
+ break
21
+ end
22
+ end
23
+
24
+ unless extends.nil?
25
+ @parent_template = Template.new
26
+ @parent_template.child_template = self
27
+ @parent_template.parse(Template.file_system.read_template_file(extends.template_name))
28
+ end
29
+
30
+ end
31
+
32
+ # perform merge if we're the last template in the tree
33
+ if @parent_template && @child_template.nil?
34
+
35
+ # get root template
36
+ template = self
37
+ while !template.parent_template.nil?
38
+ template = template.parent_template
39
+ end
40
+
41
+ # merge blocks starting from root down to bottom
42
+ template.merge_blocks(template.root)
43
+
44
+ # swap our root nodelist to the root templates nodelist
45
+ @root = template.root
46
+
47
+ # discard parent/child templates
48
+ @parent_template = nil
49
+ @child_template = nil
50
+
51
+ end
52
+
53
+ self
54
+ end
55
+
56
+ def find_blocks(root, stack = {})
57
+ if root.respond_to?(:nodelist) && !root.nodelist.nil?
58
+ root.nodelist.inject(stack) do |m, node|
59
+ if node.is_a?(Zwite::Liquid::Block)
60
+ m[node.name] = node
61
+ end
62
+ find_blocks(node, m)
63
+ m
64
+ end
65
+ end
66
+ return stack
67
+ end
68
+
69
+ def merge_blocks(root)
70
+ unless @child_template.nil?
71
+ blocks = find_blocks(root)
72
+ child_blocks = find_blocks(@child_template.root)
73
+
74
+ blocks.each do |name, block|
75
+ if child_block = child_blocks[name]
76
+ block.nodelist = child_block.nodelist
77
+ end
78
+ end
79
+
80
+ @child_template.merge_blocks(root)
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,40 @@
1
+ module Zwite
2
+
3
+ module Liquid
4
+
5
+ class Extends < ::Liquid::Block
6
+ Syntax = /(#{::Liquid::QuotedFragment})/
7
+
8
+ attr_accessor :template_name
9
+
10
+ def initialize(tag_name, markup, tokens)
11
+ if markup =~ Syntax
12
+ @template_name = $1
13
+ else
14
+ raise ::Liquid::SyntaxError.new("Syntax Error in 'extends' - Valid syntax: {% extends \"[template]\" %}")
15
+ end
16
+
17
+ case @template_name
18
+ when /^'(.*)'$/
19
+ @template_name = $1
20
+ when /^"(.*)"$/
21
+ @template_name = $1
22
+ else
23
+ raise ::Liquid::SyntaxError.new("Syntax Error in 'extends' - Valid syntax: {% extends \"[template]\" %}")
24
+ end
25
+
26
+ super
27
+
28
+ end
29
+
30
+ def assert_missing_delimitation!
31
+ # we cannot have a missing delimination on the extends block
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ ::Liquid::Template.register_tag('extends', Zwite::Liquid::Extends)
@@ -0,0 +1,35 @@
1
+ module Zwite
2
+
3
+ module Liquid
4
+
5
+ class FileSystem
6
+
7
+ attr_accessor :root
8
+
9
+ def initialize(root)
10
+ @root = root
11
+ end
12
+
13
+ def read_template_file(template_path)
14
+ full_path = full_path(template_path)
15
+ raise Exception::exception("No such template '#{template_path}'") unless File.exists?(full_path)
16
+ File.read(full_path)
17
+ end
18
+
19
+ def full_path(template_path)
20
+ full_path = if template_path.include?('/')
21
+ File.join(root, File.dirname(template_path), "#{File.basename(template_path)}")
22
+ else
23
+ File.join(root, "#{template_path}")
24
+ end
25
+
26
+ raise Exception::exception("Illegal template path '#{File.expand_path(full_path)}'") unless File.expand_path(full_path) =~ /^#{File.expand_path(root)}/
27
+
28
+ full_path
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,14 @@
1
+ module Zwite
2
+
3
+ class Template
4
+
5
+ def initialize(app, contents)
6
+ @app = app
7
+ end
8
+
9
+ def render(context)
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,173 @@
1
+ module Zwite
2
+
3
+ class AppcastRelease
4
+
5
+ ##
6
+ # Properties
7
+ ##
8
+
9
+ attr_accessor :app
10
+ attr_accessor :path
11
+
12
+ attr_accessor :date
13
+ attr_accessor :slug
14
+
15
+ attr_accessor :output_path
16
+ attr_accessor :url
17
+
18
+ attr_accessor :metadata
19
+ attr_accessor :notes
20
+
21
+ attr_accessor :name
22
+ attr_accessor :version
23
+ attr_accessor :build
24
+
25
+ attr_accessor :file_path
26
+ attr_accessor :file_name
27
+ attr_accessor :file_url
28
+ attr_accessor :file_size
29
+ attr_accessor :file_size_mb
30
+ attr_accessor :file_signature
31
+
32
+ def to_liquid
33
+ return {
34
+ "date" => self.date,
35
+ "slug" => self.slug,
36
+ "url" => self.url,
37
+ "metadata" => self.metadata,
38
+ "notes" => self.notes,
39
+ "name" => self.name,
40
+ "version" => self.version,
41
+ "file_name" => self.file_name,
42
+ "file_url" => self.file_url,
43
+ "file_size" => self.file_size,
44
+ "file_size_mb" => self.file_size_mb,
45
+ "file_signature" => self.file_signature
46
+ }
47
+ end
48
+
49
+ ##
50
+ # Initialization
51
+ ##
52
+
53
+ def initialize(app, path, signature_key)
54
+ self.app = app
55
+ self.path = path
56
+
57
+ Zwite::Utils.parse_date_and_slug(self.path.basename.to_s) do |date, slug|
58
+ self.date = date
59
+ self.slug = slug
60
+ end
61
+
62
+ rel_path = (self.path.parent + self.slug).relative_path_from(self.app.path).to_s
63
+ self.output_path = self.app.output_path + rel_path
64
+ self.url = self.app.url + rel_path + "/"
65
+
66
+ Zwite::Utils.parse_metadata_and_markdown((self.path + "release.md").read) do |metadata, markdown|
67
+ self.metadata = metadata
68
+ self.notes = markdown
69
+ end
70
+
71
+ self.name = self.metadata["name"]
72
+ self.version = self.metadata["version"]
73
+ self.build = self.metadata["build"]
74
+
75
+ self.file_path = self.path + self.metadata["file"]
76
+ self.file_name = self.file_path.basename.to_s
77
+ self.file_url = self.url + self.file_path.basename.to_s
78
+ self.file_size = self.file_path.size
79
+ self.file_size_mb = "%.2f" % (self.file_size / 1024.0 / 1024.0)
80
+
81
+ signature = OpenSSL::Digest::SHA1.new
82
+ self.file_path.open("r") do |h|
83
+ while buffer = h.read(1024)
84
+ signature << buffer
85
+ end
86
+ end
87
+
88
+ self.file_signature = Base64.strict_encode64(signature_key.syssign(signature.digest))
89
+
90
+ end
91
+
92
+ ##
93
+ # Actions
94
+ ##
95
+
96
+ def <=>(other)
97
+ if other.class == self.class
98
+ return other.date <=> self.date
99
+ end
100
+ return nil
101
+ end
102
+
103
+ end
104
+
105
+ class Appcast < Plugin
106
+
107
+ ##
108
+ # Properties
109
+ ##
110
+
111
+ attr_accessor :releases
112
+
113
+ def name
114
+ return "appcast"
115
+ end
116
+
117
+ def to_liquid
118
+ return {
119
+ "latest_release" => self.releases[0],
120
+ "releases" => self.releases
121
+ }
122
+ end
123
+
124
+ ##
125
+ # Actions
126
+ ##
127
+
128
+ def preprocess
129
+ super
130
+ self.releases = []
131
+ appcast_path = self.app.path + "appcast"
132
+ appcast_signature_key_path = appcast_path + "signature_key.pem"
133
+ appcast_signature_key = OpenSSL::PKey::DSA.new(appcast_signature_key_path.read)
134
+
135
+ releases_path = appcast_path + "releases"
136
+ Dir[appcast_path + "releases/*"].each do |p|
137
+ self.releases << AppcastRelease.new(self.app, Pathname.new(p), appcast_signature_key)
138
+ end
139
+
140
+ self.releases.sort!
141
+
142
+ end
143
+
144
+ def generate
145
+ appcast_output_path = self.app.output_path + "appcast"
146
+ appcast_output_path.mkpath
147
+
148
+ feed_path = appcast_output_path + "index.xml"
149
+ feed_path.parent.mkpath
150
+ feed_path.open("w") do |h|
151
+ h.write(self.app.render_template("appcast/appcast.xml", {"releases" => self.releases}))
152
+ end
153
+
154
+ index_path = appcast_output_path + "releases/index.html"
155
+ index_path.parent.mkpath
156
+ index_path.open("w") do |h|
157
+ h.write(self.app.render_template("appcast/notes.html", {"releases" => self.releases}))
158
+ end
159
+
160
+ self.releases.each do |release|
161
+ release_path = release.output_path + "index.html"
162
+ release_path.parent.mkpath
163
+ release_path.open("w") do |h|
164
+ h.write(self.app.render_template("appcast/notes.html", {"releases" => [release]}))
165
+ end
166
+ FileUtils.copy(release.file_path, release_path.parent)
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+
173
+ end