soxer 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/soxer/main.rb +241 -0
- data/lib/soxer/views/atom.haml +32 -0
- data/lib/soxer/views/disqus.haml +26 -0
- data/lib/soxer/views/google_ads.haml +6 -0
- data/lib/soxer/views/google_analytics.haml +27 -0
- data/lib/soxer/views/recaptcha.haml +19 -0
- data/lib/soxer/views/sitemap.haml +10 -0
- data/lib/soxer.rb +7 -0
- data/soxer.gemspec +42 -0
- metadata +116 -0
data/lib/soxer/main.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'yaml'
|
3
|
+
require 'haml'
|
4
|
+
require 'uuid'
|
5
|
+
|
6
|
+
|
7
|
+
module Sinatra
|
8
|
+
|
9
|
+
#== Soxer, the web publishing tool.
|
10
|
+
#
|
11
|
+
# Soxer is a Sinatra module that adds additional methods to sinatra routes.
|
12
|
+
# These methods allow for route-to-yaml-file mapping in order to serve a set
|
13
|
+
# files from disk with minimal effort. By clever use of views and/or
|
14
|
+
# templating Soxer makes for a simple and effective web site creation tool.
|
15
|
+
# You can get more information about Soxer, including the latest version at
|
16
|
+
# http://soxer.mutsu.org
|
17
|
+
#
|
18
|
+
# Author: Toni Anzlovar (toni[at]formalibre.si)
|
19
|
+
# Copyright: Copyright (c) 1010 Toni Anzlovar, www.formalibre.si
|
20
|
+
# License: Distributed under the GPL licence. http://www.gnu.org/licenses/gpl.html
|
21
|
+
module Soxer
|
22
|
+
|
23
|
+
class Filereader
|
24
|
+
attr_accessor :filename
|
25
|
+
|
26
|
+
def settings
|
27
|
+
@s = Sinatra::Application
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_content
|
31
|
+
self.settings
|
32
|
+
out = YAML.load_file( @filename )
|
33
|
+
add_date unless out['date']
|
34
|
+
add_id unless out['uuid']
|
35
|
+
out['url'] = @filename.gsub(/#{@s.root}\/#{@s.origin}(.+)\.yaml$/, "\\1" ).gsub(/(.+)\/index$/, "\\1" )
|
36
|
+
out['mtime'] = File.mtime( @filename )
|
37
|
+
out
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def add_id
|
43
|
+
mtime = File.mtime( @filename )
|
44
|
+
File.open( @filename, 'r+' ) do |f|
|
45
|
+
out = "uuid: #{UUID.new.generate}\n"
|
46
|
+
out << f.read; f.pos = 0
|
47
|
+
f << out
|
48
|
+
end
|
49
|
+
File.utime( 0, mtime, @filename )
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_date
|
53
|
+
mtime = File.mtime( @filename )
|
54
|
+
File.open( @filename, 'r+' ) do |f|
|
55
|
+
out = "date: #{mtime.xmlschema}\n"
|
56
|
+
out << f.read; f.pos = 0
|
57
|
+
f << out
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Urlreader
|
63
|
+
attr_accessor :url
|
64
|
+
|
65
|
+
def settings
|
66
|
+
@s = Sinatra::Application
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_content
|
70
|
+
self.settings
|
71
|
+
fn = case true
|
72
|
+
when File.exist?( f = File.join( @s.root, @s.origin, @url+'.yaml' ) ) then f
|
73
|
+
when File.exist?( f = File.join( @s.root, @s.origin, @url+'/index.yaml' ) ) then f
|
74
|
+
else throw :halt, [404, "Document not found"]
|
75
|
+
end
|
76
|
+
out = Filereader.new
|
77
|
+
out.filename = fn
|
78
|
+
out.get_content
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# === The "get_page", the document reading function
|
83
|
+
#
|
84
|
+
# Read the document in yaml format (with .yaml) ending directly mapped from
|
85
|
+
# the given parameter. Sinatra's *settings.route* and Soxers
|
86
|
+
# *settings.origin* are prefixed to the path in order to get an absolute
|
87
|
+
# filename. If the filename cannot be found, a directory by that name and
|
88
|
+
# an index file within are probed and read.
|
89
|
+
#
|
90
|
+
# If no parameter is given, the entire route is taken as the argument.
|
91
|
+
def get_page url=params[:splat][0]
|
92
|
+
out = Urlreader.new
|
93
|
+
out.url = url
|
94
|
+
out.get_content
|
95
|
+
end
|
96
|
+
|
97
|
+
#=== The "get_list" the document listing function
|
98
|
+
#
|
99
|
+
# The get_list function is the aggregator function. It reads all available
|
100
|
+
# yaml files (that map to urls directly) and outputs them according to the
|
101
|
+
# arguments you send it. Get_list only accepts 1 argument and a block.
|
102
|
+
#
|
103
|
+
#==== sort='desc'
|
104
|
+
# Direction of output. 'desc' (descending) or 'asc' (ascending)The default
|
105
|
+
# is 'desc'. If :sort conatins anything else, the output is unsorted.
|
106
|
+
#
|
107
|
+
#==== &block
|
108
|
+
# Block is a callback. Every file (in hash form) is passed to block and
|
109
|
+
# the block acts as the filter. :sort only takes the result and sorts it
|
110
|
+
# according to the first key/value pair, so hash order IS signifficant.
|
111
|
+
def get_list sort='desc', &block
|
112
|
+
pattern = File.join( settings.root, settings.origin, "**", "*.yaml" )
|
113
|
+
output = Dir.glob(pattern).map! do |f|
|
114
|
+
file = Filereader.new
|
115
|
+
file.filename = f
|
116
|
+
if block_given?
|
117
|
+
f = block.call file.get_content
|
118
|
+
end
|
119
|
+
end.compact
|
120
|
+
case sort
|
121
|
+
when 'desc' then output.sort!{|b,a| a.to_a[0] <=> b.to_a[0] }
|
122
|
+
when 'asc' then output.sort!{|a,b| a.to_a[0] <=> b.to_a[0] }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
#=== The "sitemap" the sitemap generator
|
127
|
+
#
|
128
|
+
# This funnction accepts no arguments. It simply renders a sitemap file
|
129
|
+
# with all available urls from the site
|
130
|
+
def sitemap
|
131
|
+
template = File.read File.join( File.dirname(__FILE__), 'views', 'sitemap.haml' )
|
132
|
+
out = '<?xml version="1.0" encoding="UTF-8"?>'+"\n"
|
133
|
+
out << haml( template, :layout => false )
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
#=== The "atom" the atom feed generator
|
138
|
+
#
|
139
|
+
# This method accepts an author (which is the global feed's author)
|
140
|
+
# This is a required option, as the feed is only valid if it has at least
|
141
|
+
# the global author. If individual articles have a yaml field "author",
|
142
|
+
# the individual article's author is used for that article. In both cases,
|
143
|
+
# author is a hash consisting of values 'name', 'email', 'url', of which
|
144
|
+
# at least the 'name' should always be present.
|
145
|
+
#
|
146
|
+
#==== autor
|
147
|
+
# Hash of values as required by the Atom standard:
|
148
|
+
# 'name', 'email' and 'url'. Only name is reuired.
|
149
|
+
#
|
150
|
+
#==== &block
|
151
|
+
# Block is a callback. Every file (in hash form) is passed to block and
|
152
|
+
# the block acts as the filter. That way only pages which are returned by
|
153
|
+
# block are included in the feed
|
154
|
+
def atom author=author, &block
|
155
|
+
template = File.read File.join( File.dirname(__FILE__), 'views', 'atom.haml' )
|
156
|
+
pattern = File.join( settings.root, settings.origin, "**", "*.yaml" )
|
157
|
+
output = Dir.glob(pattern).map! do |f|
|
158
|
+
file = Filereader.new
|
159
|
+
file.filename = f
|
160
|
+
if block_given?
|
161
|
+
block.call file.get_content
|
162
|
+
end
|
163
|
+
end.compact!.sort!{|b,a| a.to_a[0] <=> b.to_a[0] }
|
164
|
+
out = '<?xml version="1.0" encoding="UTF-8"?>'+"\n"
|
165
|
+
out << haml( template, :layout => false, :locals => { :page=>get_page, :feed=>output, :author=>author } )
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def google_ads options={}
|
170
|
+
template = File.read File.join( File.dirname(__FILE__), 'views', 'google_ads.haml' )
|
171
|
+
pattern = File.join( settings.root, settings.origin, "**", "*.yaml" )
|
172
|
+
haml( template, options.merge!( :layout => false ) )
|
173
|
+
end
|
174
|
+
|
175
|
+
def disqus options={}
|
176
|
+
template = File.read File.join( File.dirname(__FILE__), 'views', 'disqus.haml' )
|
177
|
+
haml( template, options.merge!( :layout => false ) )
|
178
|
+
end
|
179
|
+
|
180
|
+
def google_analytics options={}
|
181
|
+
template = File.read File.join( File.dirname(__FILE__), 'views', 'google_analytics.haml' )
|
182
|
+
haml( template, options.merge!( :layout => false ) )
|
183
|
+
end
|
184
|
+
|
185
|
+
def recaptcha options={}
|
186
|
+
template = File.read File.join( File.dirname(__FILE__), 'views', 'recaptcha.haml' )
|
187
|
+
haml( template, options.merge!( :layout => false ) )
|
188
|
+
end
|
189
|
+
|
190
|
+
#=== "partial" rails like partial generator
|
191
|
+
#
|
192
|
+
# This funnction accepts a string and matches it to a haml layout (with a
|
193
|
+
# underscore prepended) Sinatra's layouts directory.
|
194
|
+
#
|
195
|
+
#==== snippet
|
196
|
+
# A string that maps to a haml view in the views directory
|
197
|
+
# "partial :example, :layout => false" would map to a views/_example.haml
|
198
|
+
#
|
199
|
+
#==== options={}
|
200
|
+
# Any options you pass to this partial ger merged and sent to haml as
|
201
|
+
# sinatra's haml options (this is usefull for passing sinatra's :layout,
|
202
|
+
# :locals and other variables)
|
203
|
+
def partial(snippet, options={})
|
204
|
+
haml ('_'+snippet).to_sym, options.merge!(:layout => false)
|
205
|
+
end
|
206
|
+
|
207
|
+
#=== "link_to" rails like link_to generator
|
208
|
+
#
|
209
|
+
# This funnction accepts a 1 or 2 strings.
|
210
|
+
#
|
211
|
+
#==== text
|
212
|
+
# A string that becomes the link text. If there is no second parameter,
|
213
|
+
# link_to converts the string into a local url by replacing all spaces with
|
214
|
+
# an underscore and downcasing the string.
|
215
|
+
#
|
216
|
+
#==== url
|
217
|
+
# This string is used for 'href' in a link
|
218
|
+
def link_to(text, url="/#{text.downcase.gsub(/\s/,'_')}")
|
219
|
+
url.gsub!(/^\//, '') if url =~ /.+:\/\//
|
220
|
+
"<a href=\"#{url}\"> #{text}</a>"
|
221
|
+
end
|
222
|
+
|
223
|
+
# A simple string obuscator.
|
224
|
+
# Useful for hiding emails and such
|
225
|
+
#=== "obfuscate" simple string obuscator.
|
226
|
+
#
|
227
|
+
# This funnction accepts a 1 or 2 strings.
|
228
|
+
#
|
229
|
+
#==== str=nil
|
230
|
+
# Obfuscates a string replacing characters with html entities.
|
231
|
+
# Useful for hiding emails and such
|
232
|
+
def obfuscate(str=nil)
|
233
|
+
out = []
|
234
|
+
str.each_byte {|c| out << "&##{c};" }
|
235
|
+
out.join
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
helpers Soxer
|
241
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
%feed(xmlns="http://www.w3.org/2005/Atom")
|
2
|
+
%title= page['title']
|
3
|
+
%link{:href=>request.url.split(request.fullpath)[0].gsub(/:\d+/, '')}
|
4
|
+
%link(rel="self"){:href=>request.url.gsub(/:\d+/, '')}
|
5
|
+
%id= "urn:uuid:"+page['uuid']
|
6
|
+
%updated= feed[0]['mtime'].xmlschema
|
7
|
+
%author
|
8
|
+
%name= author['name']
|
9
|
+
- if author['email'] and author['email'].length > 0
|
10
|
+
%email= author['email']
|
11
|
+
- if author['uri'] and author['uri'].length > 0
|
12
|
+
%uri= author['uri']
|
13
|
+
|
14
|
+
-feed.each do |f|
|
15
|
+
%entry
|
16
|
+
%title= f['title']
|
17
|
+
%link{:href=>f['url']}
|
18
|
+
%id= "urn:uuid:"+f['uuid']
|
19
|
+
%updated= f['mtime'].xmlschema
|
20
|
+
%published= f['date'].xmlschema
|
21
|
+
- if f['summary'] and f['summary'].length > 0
|
22
|
+
- s = haml f['summary'], :layout=>false
|
23
|
+
%summary= s.gsub(%r{</?[^>]+?>}, '')
|
24
|
+
- if f['author']
|
25
|
+
%author
|
26
|
+
- if f['author']['name'] and f['author']['name'].length > 0
|
27
|
+
%name= f['author']['name']
|
28
|
+
- if f['author']['email'] and f['author']['email'].length > 0
|
29
|
+
%email= f['author']['email']
|
30
|
+
- if f['author']['uri'] and f['author']['uri'].length > 0
|
31
|
+
%uri= f['author']['uri']
|
32
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
- if defined?(account)
|
2
|
+
#disqus_thread
|
3
|
+
:javascript
|
4
|
+
(function() {
|
5
|
+
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
|
6
|
+
dsq.src = 'http://#{account}.disqus.com/embed.js';
|
7
|
+
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
|
8
|
+
})();
|
9
|
+
%noscript Please enable JavaScript to view the #{link_to 'comments powered by Disqus', 'http://disqus.com/?ref_noscript=#{account}'}.
|
10
|
+
%a.dsq-brlink(href="http://disqus.com")
|
11
|
+
blog comments powered by
|
12
|
+
%span.logo-disqus Disqus
|
13
|
+
|
14
|
+
- if !defined?(account)
|
15
|
+
:javascript
|
16
|
+
(function() {
|
17
|
+
var links = document.getElementsByTagName('a');
|
18
|
+
var query = '?';
|
19
|
+
for(var i = 0; i < links.length; i++) {
|
20
|
+
if(links[i].href.indexOf('#disqus_thread') >= 0) {
|
21
|
+
query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&';
|
22
|
+
}
|
23
|
+
}
|
24
|
+
document.write('<script charset="utf-8" type="text/javascript" src="http://disqus.com/forums/soxer/get_num_replies.js' + query + '"></' + 'script>');
|
25
|
+
})();
|
26
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-#-----------------------------------------------------------------------------
|
2
|
+
-# OLD
|
3
|
+
-#-----------------------------------------------------------------------------
|
4
|
+
-#:javascript
|
5
|
+
-# var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
6
|
+
-# document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
|
7
|
+
-#
|
8
|
+
-#:javascript
|
9
|
+
-# try {
|
10
|
+
-# var pageTracker = _gat._getTracker("#{tracker}");
|
11
|
+
-# pageTracker._trackPageview();
|
12
|
+
-# } catch(err) {}
|
13
|
+
-#-----------------------------------------------------------------------------
|
14
|
+
|
15
|
+
-#-----------------------------------------------------------------------------
|
16
|
+
-# New, asynchronus
|
17
|
+
-#-----------------------------------------------------------------------------
|
18
|
+
:javascript
|
19
|
+
var _gaq = _gaq || [];
|
20
|
+
_gaq.push(['_setAccount', '#{tracker}']);
|
21
|
+
_gaq.push(['_trackPageview']);
|
22
|
+
(function() {
|
23
|
+
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
24
|
+
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
25
|
+
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
26
|
+
})();
|
27
|
+
-#-----------------------------------------------------------------------------
|
@@ -0,0 +1,19 @@
|
|
1
|
+
:ruby
|
2
|
+
width ||= 500;
|
3
|
+
height ||= 300;
|
4
|
+
theme ||= 'white'
|
5
|
+
lang ||= 'en'
|
6
|
+
|
7
|
+
%script(type="text/javascript" src="http://www.google.com/recaptcha/api/js/recaptcha_ajax.js")
|
8
|
+
:javascript
|
9
|
+
Recaptcha.create("#{key}", "recaptcha", {
|
10
|
+
theme: "#{theme}",
|
11
|
+
callback: Recaptcha.focus_response_field }
|
12
|
+
);
|
13
|
+
%noscript
|
14
|
+
%iframe(src="http://www.google.com/recaptcha/api/noscript?k=#{key}" height="#{height}" width="#{width}" frameborder="0")
|
15
|
+
%br
|
16
|
+
%textarea(name="recaptcha_challenge_field" rows="3" cols="40")
|
17
|
+
%input(type="hidden" name="recaptcha_response_field" value="manual_challenge")
|
18
|
+
|
19
|
+
#recaptcha
|
@@ -0,0 +1,10 @@
|
|
1
|
+
:ruby
|
2
|
+
site = env['HTTP_X_FORWARDED_HOST'] ? env['HTTP_X_FORWARDED_HOST'] : env['HTTP_HOST']
|
3
|
+
|
4
|
+
%urlset(xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
|
5
|
+
- pattern = File.join( settings.root, settings.origin, "**", "*.yaml" )
|
6
|
+
- Dir.glob(pattern).each do |file|
|
7
|
+
%url
|
8
|
+
%loc= "http://#{site}"+file.gsub(/#{settings.root}\/#{settings.origin}(.+)\.yaml$/, "\\1" ).gsub(/(.+)\/index$/, "\\1" )
|
9
|
+
%lastmod= File.mtime( file ).xmlschema
|
10
|
+
|
data/lib/soxer.rb
ADDED
data/soxer.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
s.rubygems_version = '1.3.7'
|
5
|
+
|
6
|
+
s.name = 'soxer'
|
7
|
+
s.version = '0.9.0'
|
8
|
+
s.date = '2010-10-25'
|
9
|
+
s.rubyforge_project = 'soxer'
|
10
|
+
|
11
|
+
s.summary = "Dynamic web site engine"
|
12
|
+
s.description = "Soxer is a file based dynamic web creation tool for Sinatra."
|
13
|
+
|
14
|
+
s.authors = ["Toni Anzlovar"]
|
15
|
+
s.email = 'toni@formalibre.si'
|
16
|
+
s.homepage = 'http://soxer.mutsu.org'
|
17
|
+
|
18
|
+
s.require_paths = %w[lib]
|
19
|
+
|
20
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
21
|
+
# s.extra_rdoc_files = %w[README.md LICENSE]
|
22
|
+
|
23
|
+
s.add_dependency('sinatra', "~> 1")
|
24
|
+
s.add_dependency('haml', "~> 3")
|
25
|
+
s.add_dependency('uuid', "~> 2")
|
26
|
+
|
27
|
+
# = MANIFEST =
|
28
|
+
s.files = %w[
|
29
|
+
soxer.gemspec
|
30
|
+
lib/soxer.rb
|
31
|
+
lib/soxer/main.rb
|
32
|
+
lib/soxer/views/sitemap.haml
|
33
|
+
lib/soxer/views/recaptcha.haml
|
34
|
+
lib/soxer/views/google_analytics.haml
|
35
|
+
lib/soxer/views/google_ads.haml
|
36
|
+
lib/soxer/views/disqus.haml
|
37
|
+
lib/soxer/views/atom.haml
|
38
|
+
]
|
39
|
+
# = MANIFEST =
|
40
|
+
|
41
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: soxer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Toni Anzlovar
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-25 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: sinatra
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 1
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
version: "1"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: haml
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 5
|
44
|
+
segments:
|
45
|
+
- 3
|
46
|
+
version: "3"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: uuid
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 7
|
58
|
+
segments:
|
59
|
+
- 2
|
60
|
+
version: "2"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
description: Soxer is a file based dynamic web creation tool for Sinatra.
|
64
|
+
email: toni@formalibre.si
|
65
|
+
executables: []
|
66
|
+
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files: []
|
70
|
+
|
71
|
+
files:
|
72
|
+
- soxer.gemspec
|
73
|
+
- lib/soxer.rb
|
74
|
+
- lib/soxer/main.rb
|
75
|
+
- lib/soxer/views/sitemap.haml
|
76
|
+
- lib/soxer/views/recaptcha.haml
|
77
|
+
- lib/soxer/views/google_analytics.haml
|
78
|
+
- lib/soxer/views/google_ads.haml
|
79
|
+
- lib/soxer/views/disqus.haml
|
80
|
+
- lib/soxer/views/atom.haml
|
81
|
+
has_rdoc: true
|
82
|
+
homepage: http://soxer.mutsu.org
|
83
|
+
licenses: []
|
84
|
+
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options:
|
87
|
+
- --charset=UTF-8
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project: soxer
|
111
|
+
rubygems_version: 1.3.7
|
112
|
+
signing_key:
|
113
|
+
specification_version: 2
|
114
|
+
summary: Dynamic web site engine
|
115
|
+
test_files: []
|
116
|
+
|