strelka-cms 0.0.1.pre.15
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.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
|
+
|