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.
- data/README.md +4 -0
- data/bin/zwite +36 -0
- data/lib/zwite/core/app.rb +122 -0
- data/lib/zwite/core/dateformat.rb +339 -0
- data/lib/zwite/core/pagination.rb +100 -0
- data/lib/zwite/core/plugin.rb +50 -0
- data/lib/zwite/core/site.rb +134 -0
- data/lib/zwite/core/utils.rb +41 -0
- data/lib/zwite/ext/class.rb +16 -0
- data/lib/zwite/ext/fixnum.rb +16 -0
- data/lib/zwite/ext/hash.rb +15 -0
- data/lib/zwite/ext/integer.rb +16 -0
- data/lib/zwite/ext/string.rb +19 -0
- data/lib/zwite/ext/time.rb +5 -0
- data/lib/zwite/liquid/block.rb +31 -0
- data/lib/zwite/liquid/ext/date.rb +25 -0
- data/lib/zwite/liquid/ext/template.rb +86 -0
- data/lib/zwite/liquid/extends.rb +40 -0
- data/lib/zwite/liquid/filesystem.rb +35 -0
- data/lib/zwite/models/Template.rb +14 -0
- data/lib/zwite/plugins/appcast.rb +173 -0
- data/lib/zwite/plugins/blog.rb +347 -0
- data/lib/zwite/plugins/compass.rb +58 -0
- data/lib/zwite/plugins/staticfiles.rb +53 -0
- data/lib/zwite.rb +122 -0
- data/zwite.gemspec +54 -0
- metadata +104 -0
@@ -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,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,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,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,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
|