strelka-cms 0.0.1.pre.15

Sign up to get free protection for your applications and to get access to all the features.
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
+