strelka-cms 0.0.1.pre.15
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/ChangeLog +33 -0
- data/History.rdoc +4 -0
- data/Manifest.txt +52 -0
- data/Procfile +4 -0
- data/README.rdoc +74 -0
- data/Rakefile +68 -0
- data/data/strelka-cms/apps/content-feeds +194 -0
- data/data/strelka-cms/apps/content-manager +159 -0
- data/data/strelka-cms/templates/autoindex/default.tmpl +11 -0
- data/data/strelka-cms/templates/layout.tmpl +58 -0
- data/data/strelka-cms/templates/page.tmpl +21 -0
- data/etc/mongrel.rb +34 -0
- data/lib/strelka/cms.rb +42 -0
- data/lib/strelka/cms/page.rb +322 -0
- data/lib/strelka/cms/pagecatalog.rb +121 -0
- data/lib/strelka/cms/pagefilter.rb +76 -0
- data/lib/strelka/cms/pagefilter/autoindex.rb +140 -0
- data/lib/strelka/cms/pagefilter/cleanup.rb +40 -0
- data/lib/strelka/cms/pagefilter/editorial.rb +55 -0
- data/lib/strelka/cms/pagefilter/example.rb +232 -0
- data/lib/strelka/cms/pagefilter/strip.rb +30 -0
- data/lib/strelka/cms/pagefilter/textile.rb +20 -0
- data/public/humans.txt +8 -0
- data/public/images/misc/button-gloss.png +0 -0
- data/public/images/misc/button-overlay.png +0 -0
- data/public/images/misc/custom-form-sprites.png +0 -0
- data/public/images/misc/input-bg-outset.png +0 -0
- data/public/images/misc/input-bg.png +0 -0
- data/public/images/misc/modal-gloss.png +0 -0
- data/public/images/misc/table-sorter.png +0 -0
- data/public/images/orbit/bullets.jpg +0 -0
- data/public/images/orbit/left-arrow.png +0 -0
- data/public/images/orbit/loading.gif +0 -0
- data/public/images/orbit/mask-black.png +0 -0
- data/public/images/orbit/pause-black.png +0 -0
- data/public/images/orbit/right-arrow.png +0 -0
- data/public/images/orbit/rotator-black.png +0 -0
- data/public/images/orbit/timer-black.png +0 -0
- data/public/index.page +46 -0
- data/public/javascripts/app.js +98 -0
- data/public/javascripts/foundation.js +12 -0
- data/public/javascripts/jquery.min.js +4 -0
- data/public/javascripts/modernizr.foundation.js +4 -0
- data/public/robots.txt +4 -0
- data/public/stylesheets/app.css +30 -0
- data/public/stylesheets/foundation.css +1424 -0
- data/public/stylesheets/ie.css +13 -0
- data/spec/data/test.page +22 -0
- data/spec/lib/helpers.rb +58 -0
- data/spec/strelka/cms/page_spec.rb +139 -0
- data/spec/strelka/cms/pagecatalog_spec.rb +94 -0
- data/spec/strelka/cms_spec.rb +27 -0
- metadata +346 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
<?import request ?>
|
2
|
+
|
3
|
+
<!-- Autoindex filter default template $Id$ -->
|
4
|
+
<div class="autoindex">
|
5
|
+
<ul>
|
6
|
+
<?for page in pages ?>
|
7
|
+
<li><a href="/[?call page.path.dirname
|
8
|
+
?]/[?call page.path.basename('.page') ?].html"><?escape page.title ?></a></li>
|
9
|
+
<?end for ?>
|
10
|
+
</ul>
|
11
|
+
</div>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
|
3
|
+
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
|
4
|
+
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
|
5
|
+
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
|
6
|
+
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
|
7
|
+
<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
|
8
|
+
<head>
|
9
|
+
<meta charset="utf-8" />
|
10
|
+
|
11
|
+
<!-- Set the viewport width to device width for mobile -->
|
12
|
+
<meta name="viewport" content="width=device-width" />
|
13
|
+
|
14
|
+
<title>Strelka CMS Demo: <?attr title ?></title>
|
15
|
+
|
16
|
+
<!-- Included CSS Files -->
|
17
|
+
<link rel="stylesheet" href="/stylesheets/foundation.css">
|
18
|
+
<link rel="stylesheet" href="/stylesheets/app.css">
|
19
|
+
|
20
|
+
<!--[if lt IE 9]>
|
21
|
+
<link rel="stylesheet" href="/stylesheets/ie.css">
|
22
|
+
<![endif]-->
|
23
|
+
|
24
|
+
<script src="/javascripts/modernizr.foundation.js"></script>
|
25
|
+
|
26
|
+
<!-- Included JS Files -->
|
27
|
+
<script src="/javascripts/jquery.min.js" defer="defer"></script>
|
28
|
+
<script src="/javascripts/foundation.js" defer="defer"></script>
|
29
|
+
<script src="/javascripts/app.js" defer="defer"></script>
|
30
|
+
|
31
|
+
<!-- IE Fix for HTML5 Tags -->
|
32
|
+
<!--[if lt IE 9]>
|
33
|
+
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
34
|
+
<![endif]-->
|
35
|
+
|
36
|
+
</head>
|
37
|
+
<body>
|
38
|
+
|
39
|
+
<!-- container -->
|
40
|
+
<div class="container">
|
41
|
+
|
42
|
+
<div class="row">
|
43
|
+
<div class="twelve columns">
|
44
|
+
<h2>Strelka CMS Demo</h2>
|
45
|
+
<p><?attr title ?></p>
|
46
|
+
<hr />
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<div class="row">
|
51
|
+
<?attr body ?>
|
52
|
+
</div>
|
53
|
+
|
54
|
+
</div>
|
55
|
+
<!-- container -->
|
56
|
+
|
57
|
+
</body>
|
58
|
+
</html>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
<section class="row">
|
3
|
+
<div class="eight columns">
|
4
|
+
<?attr page ?>
|
5
|
+
</div>
|
6
|
+
<div class="four columns">
|
7
|
+
<aside id="about">
|
8
|
+
<header>
|
9
|
+
<h2>Strelka CMS</h2>
|
10
|
+
</header>
|
11
|
+
|
12
|
+
<dl>
|
13
|
+
<dt>Home</dt>
|
14
|
+
<dd><a href="http://bitbucket.org/ged/strelka-cms">http://bitbucket.org/ged/strelka-cms</a></dd>
|
15
|
+
</dl>
|
16
|
+
|
17
|
+
</aside>
|
18
|
+
|
19
|
+
</div>
|
20
|
+
</section>
|
21
|
+
|
data/etc/mongrel.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'pathname'
|
5
|
+
require 'mongrel2'
|
6
|
+
require 'mongrel2/config'
|
7
|
+
|
8
|
+
server 'cms-demo' do
|
9
|
+
name 'Strelka CMS Demo'
|
10
|
+
|
11
|
+
chroot '.'
|
12
|
+
|
13
|
+
access_log '/logs/access.log'
|
14
|
+
error_log '/logs/error.log'
|
15
|
+
pid_file '/run/mongrel2.pid'
|
16
|
+
|
17
|
+
port 8080
|
18
|
+
|
19
|
+
default_host 'localhost'
|
20
|
+
|
21
|
+
host 'localhost' do
|
22
|
+
route '/stylesheets/(.)', directory( 'public/stylesheets/' )
|
23
|
+
route '/javascripts/(.)', directory( 'public/javascripts/' )
|
24
|
+
route '/images/(.)', directory( 'public/images/' )
|
25
|
+
|
26
|
+
route '/feed', handler( 'tcp://127.0.0.1:9946', 'content-feeds' )
|
27
|
+
route '/', handler( 'tcp://127.0.0.1:9948', 'content-manager' )
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
mkdir_p 'logs'
|
33
|
+
mkdir_p 'run'
|
34
|
+
|
data/lib/strelka/cms.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'loggability'
|
4
|
+
require 'configurability'
|
5
|
+
require 'strelka'
|
6
|
+
|
7
|
+
|
8
|
+
# Toplevel namespace of the Strelka CMS class library
|
9
|
+
module Strelka::CMS
|
10
|
+
extend Loggability,
|
11
|
+
Configurability
|
12
|
+
|
13
|
+
# Loggability API -- set up a logger that will serve DevEiate libraries
|
14
|
+
log_as :strelka_cms
|
15
|
+
|
16
|
+
# Configurability API -- get configuration values from the 'cms' section of the config
|
17
|
+
config_key :cms
|
18
|
+
|
19
|
+
|
20
|
+
# Version string
|
21
|
+
VERSION = '0.0.1'
|
22
|
+
|
23
|
+
# Version-control revision constant
|
24
|
+
REVISION = %q$Revision: e90e841548bc $
|
25
|
+
|
26
|
+
|
27
|
+
require 'strelka/cms/page'
|
28
|
+
require 'strelka/cms/pagefilter'
|
29
|
+
require 'strelka/cms/pagecatalog'
|
30
|
+
|
31
|
+
|
32
|
+
### Get the library version. If +include_buildnum+ is true, the version string will
|
33
|
+
### include the VCS rev ID.
|
34
|
+
def self::version_string( include_buildnum=false )
|
35
|
+
vstring = "%s %s" % [ self.name, VERSION ]
|
36
|
+
vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum
|
37
|
+
return vstring
|
38
|
+
end
|
39
|
+
|
40
|
+
end # module Strelka::CMS
|
41
|
+
|
42
|
+
|
@@ -0,0 +1,322 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'summarize'
|
6
|
+
require 'loggability'
|
7
|
+
require 'inversion'
|
8
|
+
|
9
|
+
require 'strelka/cms' unless defined?( Strelka::CMS )
|
10
|
+
|
11
|
+
# An abstraction of a page with markup. This class provides rendering
|
12
|
+
# and introspection facilities for page files on disk. A page file
|
13
|
+
# is a plain file with a .page extension that contains some content
|
14
|
+
# to be rendered for the docs site, and is of the form:
|
15
|
+
#
|
16
|
+
# --- | - YAML document separator
|
17
|
+
# title: My Brilliant Document | - An optional YAML header, see
|
18
|
+
# filters: |
|
19
|
+
# - apilinks |
|
20
|
+
# - links |
|
21
|
+
# - syntax |
|
22
|
+
# - editorial |
|
23
|
+
# - textile |
|
24
|
+
# |
|
25
|
+
# --- | - Document separator
|
26
|
+
# h1. My Brilliant Document | - Document contents which will
|
27
|
+
# | will be transformed by the
|
28
|
+
# This is a document with some stuff in | configured filters.
|
29
|
+
# it. It rules you. Like my teeth. |
|
30
|
+
# |
|
31
|
+
#
|
32
|
+
class Strelka::CMS::Page
|
33
|
+
extend Loggability
|
34
|
+
|
35
|
+
# Loggability API -- log to the strelka/cms logger
|
36
|
+
log_to :strelka_cms
|
37
|
+
|
38
|
+
|
39
|
+
require 'strelka/cms/pagefilter'
|
40
|
+
|
41
|
+
# Pattern to match a source page with a YAML header
|
42
|
+
PAGE_WITH_YAML_HEADER = /
|
43
|
+
\A---\s*$ # It should should start with three hyphens
|
44
|
+
(.*?) # ...have some YAML stuff
|
45
|
+
^---\s*$ # then have another three-hyphen line,
|
46
|
+
(.*)\Z # then the rest of the document
|
47
|
+
/xm
|
48
|
+
|
49
|
+
# The default page options.
|
50
|
+
DEFAULT_OPTIONS = {
|
51
|
+
'title' => '(Untitled)',
|
52
|
+
'filters' => %w[strip textile],
|
53
|
+
'tags' => [],
|
54
|
+
}
|
55
|
+
|
56
|
+
|
57
|
+
#################################################################
|
58
|
+
### C L A S S M E T H O D S
|
59
|
+
#################################################################
|
60
|
+
|
61
|
+
### Read page source from the file at the given +path+ and return a new Page object.
|
62
|
+
def self::load( path, catalog=nil )
|
63
|
+
page = File.open( path, 'r', encoding: 'utf-8' ) do |io|
|
64
|
+
self.read( io, catalog )
|
65
|
+
end
|
66
|
+
|
67
|
+
page.path = path
|
68
|
+
|
69
|
+
return page
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
### Read page source from the given +io+ and return a new Page object.
|
74
|
+
def self::read( io, catalog=nil )
|
75
|
+
source = io.read
|
76
|
+
self.log.debug "Read %d bytes from %p" % [ source.length, io ]
|
77
|
+
page = self.parse( source, catalog )
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
### Parse the given +source+ and return a new Page object.
|
82
|
+
def self::parse( source, catalog=nil )
|
83
|
+
options = nil
|
84
|
+
|
85
|
+
if source =~ PAGE_WITH_YAML_HEADER
|
86
|
+
self.log.debug "Parsing page with YAML header..."
|
87
|
+
options = YAML.load( $1 )
|
88
|
+
source = $2
|
89
|
+
self.log.debug " YAML header options: %p" % [ options ]
|
90
|
+
else
|
91
|
+
self.log.debug "Parsing page with no header."
|
92
|
+
options = {}
|
93
|
+
end
|
94
|
+
|
95
|
+
return new( source, catalog, options )
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
#################################################################
|
100
|
+
### I N S T A N C E M E T H O D S
|
101
|
+
#################################################################
|
102
|
+
|
103
|
+
### Create a new Page with the specified +content+ and +options+.
|
104
|
+
def initialize( content, catalog=nil, options={} )
|
105
|
+
# If there's no catalog, the options might be in its place
|
106
|
+
if catalog.is_a?( Hash )
|
107
|
+
options = catalog
|
108
|
+
catalog = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
@content = content
|
112
|
+
@catalog = catalog
|
113
|
+
|
114
|
+
options = DEFAULT_OPTIONS.merge( options )
|
115
|
+
@tags = options.delete( 'tags' ) || options.delete( 'keywords' )
|
116
|
+
@title = options.delete( 'title' )
|
117
|
+
@filters = options.delete( 'filters' )
|
118
|
+
@template = options.delete( 'template' )
|
119
|
+
@options = options
|
120
|
+
@created = Time.now
|
121
|
+
|
122
|
+
@summary = nil
|
123
|
+
@topics = nil
|
124
|
+
|
125
|
+
@path = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
######
|
130
|
+
public
|
131
|
+
######
|
132
|
+
|
133
|
+
# The page's content
|
134
|
+
attr_accessor :content
|
135
|
+
|
136
|
+
# The page's catalog (if it was loaded via one)
|
137
|
+
attr_accessor :catalog
|
138
|
+
|
139
|
+
# The Array of page tags specified in the page config
|
140
|
+
attr_reader :tags
|
141
|
+
alias_method :keywords, :tags
|
142
|
+
|
143
|
+
# The page title given in the page config
|
144
|
+
attr_accessor :title
|
145
|
+
|
146
|
+
# The Array of filter names that will be applied to the rendered output, in
|
147
|
+
# the order they will be applied.
|
148
|
+
attr_reader :filters
|
149
|
+
|
150
|
+
# The path to the Inversion template to wrap the page content in
|
151
|
+
attr_reader :template
|
152
|
+
|
153
|
+
# The path to the page (if loaded from a file, nil otherwise)
|
154
|
+
attr_reader :path
|
155
|
+
|
156
|
+
# Any additional options in the header
|
157
|
+
attr_reader :options
|
158
|
+
|
159
|
+
# The Time the page object was instantiated
|
160
|
+
attr_reader :created
|
161
|
+
|
162
|
+
|
163
|
+
### Set the path to the page.
|
164
|
+
def path=( newpath )
|
165
|
+
@path = Pathname( newpath )
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
### Return the page's path relative to its catalog.
|
170
|
+
def relative_path
|
171
|
+
catalog = self.catalog or return nil
|
172
|
+
path = self.path or return nil
|
173
|
+
|
174
|
+
return path.relative_path_from( catalog.basedir )
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
### Return the page's HTML path.
|
179
|
+
def relative_html_path
|
180
|
+
pagepath = self.relative_path or return nil
|
181
|
+
return pagepath.to_s.sub(/\.page$/, '.html')
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
### Apply the filters corresponding to the given +filter_names+ and return the modified
|
186
|
+
### +content+.
|
187
|
+
def apply_filters( content )
|
188
|
+
filters = self.filter_objects
|
189
|
+
self.log.debug "Applying %d filters to %d bytes of content: %p" %
|
190
|
+
[ filters.length, content.length, content[0,100] ]
|
191
|
+
|
192
|
+
return filters.inject( self.content.dup ) do |filtered_content, filter|
|
193
|
+
begin
|
194
|
+
filter.process( filtered_content, self )
|
195
|
+
rescue => err
|
196
|
+
self.log.error "%s while applying the %s filter: %s" %
|
197
|
+
[ err.class.name, filter.name, err.message ]
|
198
|
+
self.log.debug " " + err.backtrace.join("\n ")
|
199
|
+
filtered_content
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
### Return the page's filters as instances of Strelka::CMS::PageFilter.
|
206
|
+
def filter_objects
|
207
|
+
return self.filters.map do |filter_name|
|
208
|
+
filter_name.untaint
|
209
|
+
Strelka::CMS::PageFilter.create( filter_name )
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
### Return the contents of the first heading from the rendered page.
|
215
|
+
def first_heading
|
216
|
+
self.log.debug "Fetching page title from rendered page"
|
217
|
+
doc = Nokogiri::HTML( self.to_s )
|
218
|
+
if heading = doc.css( 'h1,h2,h3' ).first
|
219
|
+
self.log.debug " got first heading: %s" % [ heading ]
|
220
|
+
return heading.content
|
221
|
+
else
|
222
|
+
self.log.debug " couldn't find a heading."
|
223
|
+
return nil
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
### Return the page rendered as HTML after applying filters and wrapping
|
229
|
+
### it in its configured template if it has one.
|
230
|
+
def render( * )
|
231
|
+
if path = self.template
|
232
|
+
tmpl = Inversion::Template.load( path )
|
233
|
+
tmpl.page = self
|
234
|
+
return tmpl
|
235
|
+
else
|
236
|
+
return self.to_s
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
### Render the page content, filtering it with its configured filters. Note
|
242
|
+
### that this does not wrap it in its template.
|
243
|
+
def to_s
|
244
|
+
return self.apply_filters( self.content )
|
245
|
+
end
|
246
|
+
alias_method :to_html, :to_s
|
247
|
+
|
248
|
+
|
249
|
+
### Return the page content with tags stripped.
|
250
|
+
def stripped_content
|
251
|
+
return self.content.gsub( /<.*?>/, '' )
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
### Extract a Hash of fields to be indexed from the page and return it.
|
256
|
+
def index_fields
|
257
|
+
return {
|
258
|
+
:title => self.title,
|
259
|
+
:first_heading => self.first_heading,
|
260
|
+
:tags => self.tags.join( ', ' ),
|
261
|
+
:content => self.stripped_content,
|
262
|
+
:modified => self.modified,
|
263
|
+
:created => self.created,
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
### Return the Time the page was last modified. This is derived from its 'date' header
|
269
|
+
### if it has one, or its mtime if it was loaded from a file, or its #created date
|
270
|
+
### if neither of those are available.
|
271
|
+
def modified
|
272
|
+
if headerdate = self.options['date']
|
273
|
+
headerdate = Time.parse( headerdate ) unless headerdate.is_a?( Time )
|
274
|
+
return headerdate
|
275
|
+
elsif self.path
|
276
|
+
return self.path.mtime
|
277
|
+
else
|
278
|
+
return self.created
|
279
|
+
end
|
280
|
+
end
|
281
|
+
alias_method :date, :modified
|
282
|
+
|
283
|
+
|
284
|
+
### Return a summary of the page.
|
285
|
+
def summary
|
286
|
+
self.summarize unless @summary
|
287
|
+
return @summary
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
### Return an Array of topics for the page.
|
292
|
+
def summary_topics
|
293
|
+
self.summarize unless @topics
|
294
|
+
return @topics
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
### Return a human-readable representation of the receiving object.
|
299
|
+
def inspect
|
300
|
+
return %|#<%s:%#0x %p %p [%s] {filters: %s}>| % [
|
301
|
+
self.class.name,
|
302
|
+
self.object_id / 2,
|
303
|
+
self.path,
|
304
|
+
self.title,
|
305
|
+
self.tags.join(', '),
|
306
|
+
self.filters.join('+'),
|
307
|
+
]
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
#########
|
312
|
+
protected
|
313
|
+
#########
|
314
|
+
|
315
|
+
### Derive a summary and topics from the stripped contents of the page. This
|
316
|
+
### populates #summary and #summary_topics.
|
317
|
+
def summarize
|
318
|
+
@summary, @topics = self.stripped_content.summarize( :topics => true )
|
319
|
+
end
|
320
|
+
|
321
|
+
end # class Strelka::CMS::Page
|
322
|
+
|