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.
Files changed (56) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog +33 -0
  4. data/History.rdoc +4 -0
  5. data/Manifest.txt +52 -0
  6. data/Procfile +4 -0
  7. data/README.rdoc +74 -0
  8. data/Rakefile +68 -0
  9. data/data/strelka-cms/apps/content-feeds +194 -0
  10. data/data/strelka-cms/apps/content-manager +159 -0
  11. data/data/strelka-cms/templates/autoindex/default.tmpl +11 -0
  12. data/data/strelka-cms/templates/layout.tmpl +58 -0
  13. data/data/strelka-cms/templates/page.tmpl +21 -0
  14. data/etc/mongrel.rb +34 -0
  15. data/lib/strelka/cms.rb +42 -0
  16. data/lib/strelka/cms/page.rb +322 -0
  17. data/lib/strelka/cms/pagecatalog.rb +121 -0
  18. data/lib/strelka/cms/pagefilter.rb +76 -0
  19. data/lib/strelka/cms/pagefilter/autoindex.rb +140 -0
  20. data/lib/strelka/cms/pagefilter/cleanup.rb +40 -0
  21. data/lib/strelka/cms/pagefilter/editorial.rb +55 -0
  22. data/lib/strelka/cms/pagefilter/example.rb +232 -0
  23. data/lib/strelka/cms/pagefilter/strip.rb +30 -0
  24. data/lib/strelka/cms/pagefilter/textile.rb +20 -0
  25. data/public/humans.txt +8 -0
  26. data/public/images/misc/button-gloss.png +0 -0
  27. data/public/images/misc/button-overlay.png +0 -0
  28. data/public/images/misc/custom-form-sprites.png +0 -0
  29. data/public/images/misc/input-bg-outset.png +0 -0
  30. data/public/images/misc/input-bg.png +0 -0
  31. data/public/images/misc/modal-gloss.png +0 -0
  32. data/public/images/misc/table-sorter.png +0 -0
  33. data/public/images/orbit/bullets.jpg +0 -0
  34. data/public/images/orbit/left-arrow.png +0 -0
  35. data/public/images/orbit/loading.gif +0 -0
  36. data/public/images/orbit/mask-black.png +0 -0
  37. data/public/images/orbit/pause-black.png +0 -0
  38. data/public/images/orbit/right-arrow.png +0 -0
  39. data/public/images/orbit/rotator-black.png +0 -0
  40. data/public/images/orbit/timer-black.png +0 -0
  41. data/public/index.page +46 -0
  42. data/public/javascripts/app.js +98 -0
  43. data/public/javascripts/foundation.js +12 -0
  44. data/public/javascripts/jquery.min.js +4 -0
  45. data/public/javascripts/modernizr.foundation.js +4 -0
  46. data/public/robots.txt +4 -0
  47. data/public/stylesheets/app.css +30 -0
  48. data/public/stylesheets/foundation.css +1424 -0
  49. data/public/stylesheets/ie.css +13 -0
  50. data/spec/data/test.page +22 -0
  51. data/spec/lib/helpers.rb +58 -0
  52. data/spec/strelka/cms/page_spec.rb +139 -0
  53. data/spec/strelka/cms/pagecatalog_spec.rb +94 -0
  54. data/spec/strelka/cms_spec.rb +27 -0
  55. metadata +346 -0
  56. 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
+
@@ -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
+