nesta 0.14.0 → 0.15.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e14e3ed99700277225c068df787864f52f19385f08469d91bf1a8f4073e24568
4
- data.tar.gz: 84e9c0fb31ba8b380b0ce86f9edc6f660fd8a8f9474ad28400e7c921fd745c01
3
+ metadata.gz: 7080f8e4c8c7b993aaaf2f1f2ea59edbefd0b49ef73218a8f20d6877880b18c2
4
+ data.tar.gz: 806b5caf9fbfc69221087bd98254e282ddb5ba0e4288ee835d3af22fc879ab9f
5
5
  SHA512:
6
- metadata.gz: e39c4ada00a1ac3b4be18b6887240a670d4f1c050023771dc71fa6703bbb315bac54f959053788dc7018761a5bfc3e1fe387486e856629a785ae3098c8203704
7
- data.tar.gz: 4da9e195b6679df8c9f2c9ec3a07b780409b2a68f6cffe66feab9276a0ee90b987b7221a13d5f363c0b700c3d5b8d956e9b6a5753a0aa81058e1e10458ced75f
6
+ metadata.gz: 17a405a098acca204751044cff182ee6484c93c373b7266a2c194345736438b71366b05cd04ea8d64e7949c9ab071e01cde387a1c68d1190a2087d211fa45060
7
+ data.tar.gz: 8d37d4b2b1fb4df23d57403da16c865db06a4a899a942a594cfac47212f998c52ef330ae9091149e1858c06f390f2ed9e4ef94621b115c52f04ed821d0cc9b7d
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
- ruby: ["2.7", "3.0", "3.1", "3.2", "head"]
11
+ ruby: ["2.7", "3.0", "3.1", "3.2"]
12
12
  runs-on: ubuntu-latest
13
13
  steps:
14
14
  - uses: actions/checkout@v3
@@ -1,4 +1,26 @@
1
- = 0.14.0 / (23 March 2023)
1
+ # Changelog
2
+
3
+ ## 0.15.0 (11 July 2023)
4
+
5
+ * Upgrade to Sinatra 3. (Graham Ashton)
6
+
7
+ * The `models.rb` file has been long and unnecessarily hard to navigate
8
+ for too long. It has been split up into multiple files, one for each
9
+ class. (Graham Ashton, suggested by Lilith River)
10
+
11
+ * Remove support for the BlueCloth markdown library. Nesta uses Tilt
12
+ for rendering Markdown, and BlueCloth support has been removed in
13
+ Tilt 2.2.0. See rtomayko/tilt#382 for details.
14
+
15
+ If your site uses BlueCloth, remove your call to `Tilt.prefer`,
16
+ and Nesta will use its default Markdown processor. See Nesta's docs
17
+ on [configuring the Markdown processor] for more details.
18
+
19
+ [configuring the Markdown processor]: https://nestacms.com/docs/creating-content/changing-the-markdown-processor
20
+
21
+ (Graham Ashton)
22
+
23
+ ## 0.14.0 (23 March 2023)
2
24
 
3
25
  * Nesta can now be used as a Static Site Generator (SSG) with its new
4
26
  `nesta build` command! Server Side Rendering (SSR) is still supported,
@@ -71,7 +93,7 @@
71
93
  * The breadcrumb_label helper method (deprecated in 0.9.3) has been removed.
72
94
  (Graham Ashton)
73
95
 
74
- = 0.13.0 / (28 September 2022)
96
+ ## 0.13.0 (28 September 2022)
75
97
 
76
98
  * Update dependencies in order to support Ruby 3.0 and above.
77
99
  (Graham Ashton)
@@ -97,7 +119,7 @@
97
119
  * Stopped the test suite from executing external commands during tests.
98
120
  (Graham Ashton)
99
121
 
100
- = 0.12.0 / (30 June 2020)
122
+ ## 0.12.0 (30 June 2020)
101
123
 
102
124
  * Upgrade to Sinatra 2 and Rack 2. (Graham Ashton)
103
125
 
@@ -113,12 +135,12 @@
113
135
  * Stop running the test suite under Ruby 2.3 and 2.4, both of which
114
136
  have reached end-of-life. (Graham Ashton)
115
137
 
116
- = 0.11.1 / (26 March 2015)
138
+ ## 0.11.1 (26 March 2015)
117
139
 
118
140
  * Tighten dependency on Tilt, as version 2.x is incompatible.
119
141
  (Graham Ashton)
120
142
 
121
- = 0.11.0 / (16 March 2015)
143
+ ## 0.11.0 (16 March 2015)
122
144
 
123
145
  * Allow Haml pages to use the built-in Markdown filter again, by
124
146
  including the haml-contrib gem.
@@ -161,7 +183,7 @@
161
183
 
162
184
  * Support for Ruby 2.2. (Graham Ashton)
163
185
 
164
- = 0.10.0 / (25 April 2014)
186
+ ## 0.10.0 (25 April 2014)
165
187
 
166
188
  * Upgraded the default theme to a responsive design, using Google's
167
189
  Roboto Slab web font. (Graham Ashton)
@@ -285,7 +307,7 @@
285
307
  this list. See GitHub for the full list of commits.
286
308
  https://github.com/gma/nesta/compare/v0.9.13...v0.10.0
287
309
 
288
- = 0.9.13 / (3 March 2012)
310
+ ## 0.9.13 (3 March 2012)
289
311
 
290
312
  * The nesta script has a new command; edit. You can pass it the path
291
313
  to a file within your content/pages folder and it will open the file
@@ -343,9 +365,9 @@
343
365
  * Bug fix: Summaries on Haml pages were not marked up as paragraphs.
344
366
  See #75.
345
367
 
346
- = 0.9.12 / (Released then pulled, due to rubygems [still] being a total mess)
368
+ ## 0.9.12 (Released then pulled, due to rubygems [still] being a total mess)
347
369
 
348
- = 0.9.11 / (22 September 2011)
370
+ ## 0.9.11 (22 September 2011)
349
371
 
350
372
  * Use Tilt to render the Markdown, Textile and Haml in content/pages.
351
373
  RDiscount is now the default Markdown processor. To continue using
@@ -372,7 +394,7 @@
372
394
  within the content folder by crafting a relative path containing
373
395
  the string '../' (Louis Nyffenegger).
374
396
 
375
- = 0.9.10 / (9 September 2011)
397
+ ## 0.9.10 (9 September 2011)
376
398
 
377
399
  * Load Nesta plugins from gems. Any gem whose name begins with
378
400
  nesta-plugin- can be used in a project by adding it to the project's
@@ -390,7 +412,7 @@
390
412
  the Sass rendering engine by default, which allows it to find .sass
391
413
  files within the gem if no matching files are found locally.
392
414
 
393
- = 0.9.9 / (24 August 2011)
415
+ ## 0.9.9 (24 August 2011)
394
416
 
395
417
  * Bug fix: What a debacle this is turning into. The new Nesta::Env
396
418
  class must be required before the code in 'nesta/app' is loaded.
@@ -398,7 +420,7 @@
398
420
  via config.ru. Running Nesta any other way lead to an immediate
399
421
  crash.
400
422
 
401
- = 0.9.8 / (22 August 2011)
423
+ ## 0.9.8 (22 August 2011)
402
424
 
403
425
  * Bug fix: The Sinatra app's root directory wasn't set which meant
404
426
  that Nesta couldn't always find the ./public directory (such as when
@@ -409,12 +431,12 @@
409
431
  Nesta::Env class. In 0.9.6 I forgot to actually set Nesta::App.root
410
432
  as well, which was a big mistake. Whoops.
411
433
 
412
- = 0.9.7 / (19 August 2011)
434
+ ## 0.9.7 (19 August 2011)
413
435
 
414
436
  * No code changes from 0.9.6; version number increased to allow new gem
415
437
  to be deployed to rubygems.org.
416
438
 
417
- = 0.9.6 / (18 August 2011) [never released due to packaging bug]
439
+ ## 0.9.6 (18 August 2011) [never released due to packaging bug]
418
440
 
419
441
  * Nesta no longer cares whether you write your Sass stylesheets in the
420
442
  original indented Sass format or the default SCSS syntax (which is a
@@ -440,7 +462,7 @@
440
462
  Nesta::App.root would be before requiring nesta/app. Fixed by
441
463
  creating Nesta::Env and moving root to there instead.
442
464
 
443
- = 0.9.5 / (1 May 2011)
465
+ ## 0.9.5 (1 May 2011)
444
466
 
445
467
  * Added --version option to nesta command (Christopher Lindblom).
446
468
 
@@ -466,14 +488,14 @@
466
488
  within Haml templates. See https://github.com/gma/nesta/pull/18
467
489
  (Carl Furrow).
468
490
 
469
- = 0.9.4 / 18 February 2011
491
+ ## 0.9.4 (18 February 2011)
470
492
 
471
493
  * Updated the link colours in the default theme.
472
494
 
473
495
  * Set the default encoding to UTF-8. Without it Heroku would sometimes
474
496
  fail to render pages (issue 14).
475
497
 
476
- = 0.9.3 / 18 January 2011
498
+ ## 0.9.3 (18 January 2011)
477
499
 
478
500
  * The route and view for serving the home page (/) has been removed,
479
501
  and the home page must now be created as an index page in
@@ -515,7 +537,7 @@
515
537
  * Bug fix: Don't output empty <li> tags for nested submenus that are
516
538
  beneath the requested number of levels.
517
539
 
518
- = 0.9.2 / 10 January 2011
540
+ ## 0.9.2 (10 January 2011)
519
541
 
520
542
  * Made the FileModel.metadata method public, to allow for custom
521
543
  metadata at the top of each page. (Wynn Netherland)
@@ -525,7 +547,7 @@
525
547
 
526
548
  * Added templates required by `nesta theme:create`.
527
549
 
528
- = 0.9.1 / 31 December 2010
550
+ ## 0.9.1 (31 December 2010)
529
551
 
530
552
  * Re-implemented the default theme, using semantic HTML5 and
531
553
  Andy Clarke's Universal Internet Explorer 6 CSS. Named the previous
@@ -538,15 +560,16 @@
538
560
  * Moved the navigation helpers into the Nesta::Navigation::Renderers
539
561
  module.
540
562
 
541
- = 0.9.0 / 20 December 2010
563
+ ## 0.9.0 (20 December 2010)
542
564
 
543
565
  * Packaged Nesta as a gem for the first time, instead of as a template
544
566
  site that can be cloned, edited and deployed. Added the `nesta`
545
567
  command for generating new sites and managing themes.
546
568
 
547
- = Previous versions / from 26 November 2008
569
+ ## Previous versions (from 26 November 2008)
548
570
 
549
571
  * Prior to 0.9.0 Nesta was distributed as a git repository that could
550
572
  be forked, edited and deployed. The only change log for these
551
- versions is the Git commit history.
573
+ versions is the commit history:
574
+
552
575
  https://github.com/gma/nesta/commits/master
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nesta (0.14.0)
4
+ nesta (0.15.0)
5
5
  RedCloth (~> 4.2)
6
6
  haml (>= 3.1, < 6.0)
7
7
  haml-contrib (>= 1.0)
@@ -9,7 +9,7 @@ PATH
9
9
  rake
10
10
  rdiscount (~> 2.1)
11
11
  sass-embedded (~> 1.58)
12
- sinatra (~> 2.0)
12
+ sinatra (~> 3.0)
13
13
  tilt (~> 2.0)
14
14
 
15
15
  GEM
@@ -53,15 +53,15 @@ GEM
53
53
  rb-fsevent (>= 0.9)
54
54
  rb-inotify (>= 0.8)
55
55
  unicorn (>= 4.5)
56
- mustermann (2.0.2)
56
+ mustermann (3.0.0)
57
57
  ruby2_keywords (~> 0.0.1)
58
- nokogiri (1.14.2)
58
+ nokogiri (1.14.3)
59
59
  mini_portile2 (~> 2.8.0)
60
60
  racc (~> 1.4)
61
61
  public_suffix (5.0.1)
62
62
  racc (1.6.2)
63
63
  rack (2.2.6.4)
64
- rack-protection (2.2.4)
64
+ rack-protection (3.0.5)
65
65
  rack
66
66
  rack-test (2.0.2)
67
67
  rack (>= 1.3)
@@ -78,10 +78,10 @@ GEM
78
78
  sass-embedded (1.58.3)
79
79
  google-protobuf (~> 3.21)
80
80
  rake (>= 10.0.0)
81
- sinatra (2.2.4)
82
- mustermann (~> 2.0)
83
- rack (~> 2.2)
84
- rack-protection (= 2.2.4)
81
+ sinatra (3.0.5)
82
+ mustermann (~> 3.0)
83
+ rack (~> 2.2, >= 2.2.4)
84
+ rack-protection (= 3.0.5)
85
85
  tilt (~> 2.0)
86
86
  temple (0.10.0)
87
87
  tilt (2.1.0)
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008-2022 Graham Ashton
1
+ Copyright (c) 2008-2023 Graham Ashton
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,34 +1,37 @@
1
- # Nesta - a CMS for Ruby Developers
1
+ # Lightweight file-based CMS and Static Site Generator
2
2
 
3
- A file-based CMS for web sites and blogs, written in [Sinatra][frank].
3
+ Nesta is a lightweight CMS for building content sites and blogs, written in
4
+ [Sinatra].
4
5
 
5
- Content can be written in [Markdown][markdown] or [Textile][textile] and
6
- stored in text files (though you can also use Haml if you need to add
7
- some HTML to your pages). There's no database; write your content in
8
- your editor. Publish by pushing to a git repository.
6
+ Content can be written in [Markdown] or [Textile], stored in text files on your
7
+ computer. There is no database.
9
8
 
10
- [frank]: http://www.sinatrarb.com/ "Sinatra"
11
- [markdown]: http://daringfireball.net/projects/markdown/
12
- [textile]: http://textism.com/tools/textile/
9
+ You do your writing in your text editor.
10
+
11
+ Publish by pushing your changes to a git repository that's setup to deploy your
12
+ changes to the web.
13
+
14
+ [Sinatra]: http://www.sinatrarb.com/ "Sinatra"
15
+ [Markdown]: http://daringfireball.net/projects/markdown/
16
+ [Textile]: http://textism.com/tools/textile/
13
17
 
14
18
  ## Installation
15
19
 
16
- Begin by installing the gem:
20
+ Begin by [installing Ruby], then the Nesta gem:
17
21
 
18
22
  $ gem install nesta
19
23
 
20
- Then use the `nesta` command to generate a new site:
24
+ Use the `nesta` command to generate a new site:
21
25
 
22
- $ nesta new mysite.com --git
26
+ $ nesta new mysite.com --git # a git repo is optional, but recommended
23
27
 
24
28
  Install a few dependencies, and you're away:
25
29
 
26
30
  $ cd mysite.com
27
31
  $ bundle
28
32
 
29
- You'll find basic configuration options for your site in
30
- `config/config.yml`. The defaults will work, but you'll want to tweak it
31
- before you go very far.
33
+ You'll find configuration options for your site in `config/config.yml`. The
34
+ defaults will work, but you'll want to tweak it before you go very far.
32
35
 
33
36
  That's it - you can launch a local web server in development mode using
34
37
  mr-sparkle...
@@ -36,34 +39,43 @@ mr-sparkle...
36
39
  $ bundle exec mr-sparkle config.ru
37
40
 
38
41
  ...then point your web browser at http://localhost:8080. Start editing
39
- the files in `content/pages` (see the [docs on writing content][] for
40
- full instructions).
42
+ the files in `content/pages` (see the [docs on writing content] for full
43
+ instructions).
44
+
45
+ You can either [deploy it] behind a web server, or build a static version of
46
+ your site:
47
+
48
+ $ nesta build # but see config.yml for related settings
41
49
 
42
- [docs on writing content]: http://nestacms.com/docs/creating-content
50
+ [installing Ruby]: https://www.ruby-lang.org/en/documentation/installation/
51
+ [docs on writing content]: http://nestacms.com/docs/creating-content/
52
+ [deploy it]: https://nestacms.com/docs/deployment/
43
53
 
44
54
  ## Support
45
55
 
46
56
  There's plenty of information on <http://nestacms.com>. If you need some
47
57
  help with anything feel free to file an issue, or contact me on Mastodon
48
- (@gma@hachyderm.io) or Twitter (@grahamashton).
58
+ ([@gma@hachyderm.io]) or Twitter ([@grahamashton]).
49
59
 
50
- If you like Nesta you can keep up with developments by following [@nestacms][]
51
- on Twitter, and on [the blog][].
60
+ If you like Nesta you can keep up with developments by following [@nestacms]
61
+ on Twitter, and on [the blog].
52
62
 
53
- [@nestacms]: http://twitter.com/nestacms
54
- [the blog]: http://nestacms.com/blog
63
+ [@gma@hachyderm.io]: https://hachyderm.io/@gma
64
+ [@grahamashton]: https://twitter.com/grahamashton
65
+ [@nestacms]: https://twitter.com/nestacms
66
+ [the blog]: https://nestacms.com/blog
55
67
 
56
68
  ![Tests](https://github.com/gma/nesta/actions/workflows/tests.yml/badge.svg)
57
69
 
58
70
  ## Contributing
59
71
 
60
- If you want to add a new feature, I recommend that you file an issue to discuss
61
- it before you start coding. I'm likely to suggest that we implement it as a
62
- [plugin][] (to keep Nesta itself lean and simple), so you might save yourself
63
- some time if we chat about a good approach before you start.
72
+ If you want to add a new feature, please [create an issue] to discuss it before
73
+ you start coding. I might suggest that we implement it as a [plugin] (to keep
74
+ Nesta itself lean and simple), or be able to chip in with ideas on how to
75
+ approach it.
64
76
 
65
- [plugin]: http://nestacms.com/docs/plugins
77
+ [create an issue]: https://github.com/gma/nesta/issues/new
78
+ [plugin]: https://nestacms.com/docs/plugins
66
79
 
67
- -- Graham ([@grahamashton][] on Twitter).
80
+ -- Graham
68
81
 
69
- [@grahamashton]: http://twitter.com/grahamashton
data/RELEASING.md CHANGED
@@ -10,7 +10,7 @@ the steps:
10
10
  the version number in `Gemfile.lock` to be updated too, so regenerate
11
11
  it now and check them in together.
12
12
 
13
- 3. Update the `CHANGES` file with a summary of significant changes since
13
+ 3. Update the `CHANGLOG.md` file with a summary of significant changes since
14
14
  the previous release.
15
15
 
16
16
  4. Commit these changes with a commit message of 'Bump version to <version>'
@@ -0,0 +1,191 @@
1
+ def register_template_handler(class_name, *extensions)
2
+ Tilt.register Tilt.const_get(class_name), *extensions
3
+ rescue LoadError
4
+ # Only one of the Markdown processors needs to be available, so we can
5
+ # safely ignore these load errors.
6
+ end
7
+
8
+ register_template_handler :MarukuTemplate, 'mdown', 'md'
9
+ register_template_handler :KramdownTemplate, 'mdown', 'md'
10
+ register_template_handler :RDiscountTemplate, 'mdown', 'md'
11
+ register_template_handler :RedcarpetTemplate, 'mdown', 'md'
12
+
13
+
14
+ module Nesta
15
+ class MetadataParseError < RuntimeError; end
16
+
17
+ class FileModel
18
+ FORMATS = [:mdown, :md, :haml, :textile]
19
+ @@model_cache = {}
20
+ @@filename_cache = {}
21
+
22
+ attr_reader :filename, :mtime
23
+
24
+ class CaseInsensitiveHash < Hash
25
+ def [](key)
26
+ super(key.to_s.downcase)
27
+ end
28
+ end
29
+
30
+ def self.model_path(basename = nil)
31
+ Nesta::Config.content_path(basename)
32
+ end
33
+
34
+ def self.find_all
35
+ file_pattern = File.join(model_path, "**", "*.{#{FORMATS.join(',')}}")
36
+ Dir.glob(file_pattern).map do |path|
37
+ relative = path.sub("#{model_path}/", "")
38
+ load(relative.sub(/\.(#{FORMATS.join('|')})/, ""))
39
+ end
40
+ end
41
+
42
+ def self.find_file_for_path(path)
43
+ if ! @@filename_cache.has_key?(path)
44
+ FORMATS.each do |format|
45
+ [path, File.join(path, 'index')].each do |basename|
46
+ filename = model_path("#{basename}.#{format}")
47
+ if File.exist?(filename)
48
+ @@filename_cache[path] = filename
49
+ break
50
+ end
51
+ end
52
+ end
53
+ end
54
+ @@filename_cache[path]
55
+ end
56
+
57
+ def self.needs_loading?(path, filename)
58
+ @@model_cache[path].nil? || File.mtime(filename) > @@model_cache[path].mtime
59
+ end
60
+
61
+ def self.load(path)
62
+ if (filename = find_file_for_path(path)) && needs_loading?(path, filename)
63
+ @@model_cache[path] = self.new(filename)
64
+ end
65
+ @@model_cache[path]
66
+ end
67
+
68
+ def self.purge_cache
69
+ @@model_cache = {}
70
+ @@filename_cache = {}
71
+ end
72
+
73
+ def initialize(filename)
74
+ @filename = filename
75
+ @format = filename.split('.').last.to_sym
76
+ if File.zero?(filename)
77
+ @metadata = {}
78
+ @markup = ''
79
+ else
80
+ @metadata, @markup = parse_file
81
+ end
82
+ @mtime = File.mtime(filename)
83
+ end
84
+
85
+ def ==(other)
86
+ other.respond_to?(:path) && (self.path == other.path)
87
+ end
88
+
89
+ def index_page?
90
+ @filename =~ /\/?index\.\w+$/
91
+ end
92
+
93
+ def abspath
94
+ file_path = @filename.sub(self.class.model_path, '')
95
+ if index_page?
96
+ File.dirname(file_path)
97
+ else
98
+ File.join(File.dirname(file_path), File.basename(file_path, '.*'))
99
+ end
100
+ end
101
+
102
+ def path
103
+ abspath.sub(/^\//, '')
104
+ end
105
+
106
+ def permalink
107
+ File.basename(path)
108
+ end
109
+
110
+ def layout
111
+ (metadata('layout') || 'layout').to_sym
112
+ end
113
+
114
+ def template
115
+ (metadata('template') || 'page').to_sym
116
+ end
117
+
118
+ def to_html(scope = Object.new)
119
+ convert_to_html(@format, scope, markup)
120
+ end
121
+
122
+ def last_modified
123
+ @last_modified ||= File.stat(@filename).mtime
124
+ end
125
+
126
+ def description
127
+ metadata('description')
128
+ end
129
+
130
+ def keywords
131
+ metadata('keywords')
132
+ end
133
+
134
+ def metadata(key)
135
+ @metadata[key]
136
+ end
137
+
138
+ def flagged_as?(flag)
139
+ flags = metadata('flags')
140
+ flags && flags.split(',').map { |name| name.strip }.include?(flag)
141
+ end
142
+
143
+ def parse_metadata(first_paragraph)
144
+ is_metadata = first_paragraph.split("\n").first =~ /^[\w ]+:/
145
+ raise MetadataParseError unless is_metadata
146
+ metadata = CaseInsensitiveHash.new
147
+ first_paragraph.split("\n").each do |line|
148
+ key, value = line.split(/\s*:\s*/, 2)
149
+ next if value.nil?
150
+ metadata[key.downcase] = value.chomp
151
+ end
152
+ metadata
153
+ end
154
+
155
+ private
156
+
157
+ def markup
158
+ @markup
159
+ end
160
+
161
+ def parse_file
162
+ contents = File.open(@filename).read
163
+ rescue Errno::ENOENT
164
+ raise Sinatra::NotFound
165
+ else
166
+ first_paragraph, remaining = contents.split(/\r?\n\r?\n/, 2)
167
+ begin
168
+ return parse_metadata(first_paragraph), remaining
169
+ rescue MetadataParseError
170
+ return {}, contents
171
+ end
172
+ end
173
+
174
+ def add_p_tags_to_haml(text)
175
+ contains_tags = (text =~ /^\s*%/)
176
+ if contains_tags
177
+ text
178
+ else
179
+ text.split(/\r?\n/).inject('') do |accumulator, line|
180
+ accumulator << "%p #{line}\n"
181
+ end
182
+ end
183
+ end
184
+
185
+ def convert_to_html(format, scope, text)
186
+ text = add_p_tags_to_haml(text) if @format == :haml
187
+ template = Tilt[format].new { text }
188
+ template.render(scope)
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,67 @@
1
+ module Nesta
2
+ class Menu
3
+ INDENT = " " * 2
4
+
5
+ def self.full_menu
6
+ menu = []
7
+ menu_file = Nesta::Config.content_path('menu.txt')
8
+ if File.exist?(menu_file)
9
+ File.open(menu_file) { |file| append_menu_item(menu, file, 0) }
10
+ end
11
+ menu
12
+ end
13
+
14
+ def self.top_level
15
+ full_menu.reject { |item| item.is_a?(Array) }
16
+ end
17
+
18
+ def self.for_path(path)
19
+ path.sub!(Regexp.new('^/'), '')
20
+ if path.empty?
21
+ full_menu
22
+ else
23
+ find_menu_item_by_path(full_menu, path)
24
+ end
25
+ end
26
+
27
+ private_class_method def self.append_menu_item(menu, file, depth)
28
+ path = file.readline
29
+ rescue EOFError
30
+ else
31
+ page = Page.load(path.strip)
32
+ current_depth = path.scan(INDENT).size
33
+ if page
34
+ if current_depth > depth
35
+ sub_menu_for_depth(menu, depth) << [page]
36
+ else
37
+ sub_menu_for_depth(menu, current_depth) << page
38
+ end
39
+ end
40
+ append_menu_item(menu, file, current_depth)
41
+ end
42
+
43
+ private_class_method def self.sub_menu_for_depth(menu, depth)
44
+ sub_menu = menu
45
+ depth.times { sub_menu = sub_menu[-1] }
46
+ sub_menu
47
+ end
48
+
49
+ private_class_method def self.find_menu_item_by_path(menu, path)
50
+ item = menu.detect do |item|
51
+ item.respond_to?(:path) && (item.path == path)
52
+ end
53
+ if item
54
+ subsequent = menu[menu.index(item) + 1]
55
+ item = [item]
56
+ item << subsequent if subsequent.respond_to?(:each)
57
+ else
58
+ sub_menus = menu.select { |menu_item| menu_item.respond_to?(:each) }
59
+ sub_menus.each do |sub_menu|
60
+ item = find_menu_item_by_path(sub_menu, path)
61
+ break if item
62
+ end
63
+ end
64
+ item
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,167 @@
1
+ module Nesta
2
+ class HeadingNotSet < RuntimeError; end
3
+ class LinkTextNotSet < RuntimeError; end
4
+
5
+ class Page < FileModel
6
+ def self.model_path(basename = nil)
7
+ Nesta::Config.page_path(basename)
8
+ end
9
+
10
+ def self.find_by_path(path)
11
+ page = load(path)
12
+ page && page.hidden? ? nil : page
13
+ end
14
+
15
+ def self.find_all
16
+ super.select { |p| ! p.hidden? }
17
+ end
18
+
19
+ def self.find_articles
20
+ find_all.select do |page|
21
+ page.date && page.date < DateTime.now
22
+ end.sort { |x, y| y.date <=> x.date }
23
+ end
24
+
25
+ def draft?
26
+ flagged_as?('draft')
27
+ end
28
+
29
+ def hidden?
30
+ draft? && Nesta::App.production?
31
+ end
32
+
33
+ def heading
34
+ regex = case @format
35
+ when :mdown, :md
36
+ /^#\s*(.*?)(\s*#+|$)/
37
+ when :haml
38
+ /^\s*%h1\s+(.*)/
39
+ when :textile
40
+ /^\s*h1\.\s+(.*)/
41
+ end
42
+ markup =~ regex
43
+ Regexp.last_match(1) or raise HeadingNotSet, "#{abspath} needs a heading"
44
+ end
45
+
46
+ def link_text
47
+ metadata('link text') || heading
48
+ rescue HeadingNotSet
49
+ raise LinkTextNotSet, "Need to link to '#{abspath}' but can't get link text"
50
+ end
51
+
52
+ def title
53
+ metadata('title') || link_text
54
+ rescue LinkTextNotSet
55
+ return Nesta::Config.title if abspath == '/'
56
+ raise
57
+ end
58
+
59
+ def date(format = nil)
60
+ @date ||= if metadata("date")
61
+ if format == :xmlschema
62
+ Time.parse(metadata("date")).xmlschema
63
+ else
64
+ DateTime.parse(metadata("date"))
65
+ end
66
+ end
67
+ end
68
+
69
+ def atom_id
70
+ metadata('atom id')
71
+ end
72
+
73
+ def read_more
74
+ metadata('read more') || Nesta::Config.read_more
75
+ end
76
+
77
+ def summary
78
+ if summary_text = metadata("summary")
79
+ summary_text.gsub!('\n', "\n")
80
+ convert_to_html(@format, Object.new, summary_text)
81
+ end
82
+ end
83
+
84
+ def body_markup
85
+ case @format
86
+ when :mdown, :md
87
+ markup.sub(/^#[^#].*$\r?\n(\r?\n)?/, '')
88
+ when :haml
89
+ markup.sub(/^\s*%h1\s+.*$\r?\n(\r?\n)?/, '')
90
+ when :textile
91
+ markup.sub(/^\s*h1\.\s+.*$\r?\n(\r?\n)?/, '')
92
+ end
93
+ end
94
+
95
+ def body(scope = Object.new)
96
+ convert_to_html(@format, scope, body_markup)
97
+ end
98
+
99
+ def categories
100
+ paths = category_strings.map { |specifier| specifier.sub(/:-?\d+$/, '') }
101
+ valid_paths(paths).map { |p| Page.find_by_path(p) }
102
+ end
103
+
104
+ def priority(category)
105
+ category_string = category_strings.detect do |string|
106
+ string =~ /^#{category}([,:\s]|$)/
107
+ end
108
+ category_string && category_string.split(':', 2)[-1].to_i
109
+ end
110
+
111
+ def parent
112
+ if abspath == '/'
113
+ nil
114
+ else
115
+ parent_path = File.dirname(path)
116
+ while parent_path != '.' do
117
+ parent = Page.load(parent_path)
118
+ return parent unless parent.nil?
119
+ parent_path = File.dirname(parent_path)
120
+ end
121
+ return categories.first unless categories.empty?
122
+ Page.load('index')
123
+ end
124
+ end
125
+
126
+ def pages
127
+ in_category = Page.find_all.select do |page|
128
+ page.date.nil? && page.categories.include?(self)
129
+ end
130
+ in_category.sort do |x, y|
131
+ by_priority = y.priority(path) <=> x.priority(path)
132
+ if by_priority == 0
133
+ x.link_text.downcase <=> y.link_text.downcase
134
+ else
135
+ by_priority
136
+ end
137
+ end
138
+ end
139
+
140
+ def articles
141
+ Page.find_articles.select { |article| article.categories.include?(self) }
142
+ end
143
+
144
+ def receives_comments?
145
+ ! date.nil?
146
+ end
147
+
148
+ private
149
+
150
+ def category_strings
151
+ strings = metadata('categories')
152
+ strings.nil? ? [] : strings.split(',').map { |string| string.strip }
153
+ end
154
+
155
+ def valid_paths(paths)
156
+ page_dir = Nesta::Config.page_path
157
+ paths.select do |path|
158
+ FORMATS.detect do |format|
159
+ [path, File.join(path, 'index')].detect do |candidate|
160
+ File.exist?(File.join(page_dir, "#{candidate}.#{format}"))
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ end
data/lib/nesta/models.rb CHANGED
@@ -1,423 +1,5 @@
1
- require 'time'
2
-
3
1
  require 'rdiscount'
4
2
 
5
- def register_template_handler(class_name, *extensions)
6
- Tilt.register Tilt.const_get(class_name), *extensions
7
- rescue LoadError
8
- # Only one of the Markdown processors needs to be available, so we can
9
- # safely ignore these load errors.
10
- end
11
-
12
- register_template_handler :MarukuTemplate, 'mdown', 'md'
13
- register_template_handler :KramdownTemplate, 'mdown', 'md'
14
- register_template_handler :BlueClothTemplate, 'mdown', 'md'
15
- register_template_handler :RDiscountTemplate, 'mdown', 'md'
16
- register_template_handler :RedcarpetTemplate, 'mdown', 'md'
17
-
18
- module Nesta
19
- class HeadingNotSet < RuntimeError; end
20
- class LinkTextNotSet < RuntimeError; end
21
- class MetadataParseError < RuntimeError; end
22
-
23
- class FileModel
24
- FORMATS = [:mdown, :md, :haml, :textile]
25
- @@model_cache = {}
26
- @@filename_cache = {}
27
-
28
- attr_reader :filename, :mtime
29
-
30
- class CaseInsensitiveHash < Hash
31
- def [](key)
32
- super(key.to_s.downcase)
33
- end
34
- end
35
-
36
- def self.model_path(basename = nil)
37
- Nesta::Config.content_path(basename)
38
- end
39
-
40
- def self.find_all
41
- file_pattern = File.join(model_path, "**", "*.{#{FORMATS.join(',')}}")
42
- Dir.glob(file_pattern).map do |path|
43
- relative = path.sub("#{model_path}/", "")
44
- load(relative.sub(/\.(#{FORMATS.join('|')})/, ""))
45
- end
46
- end
47
-
48
- def self.find_file_for_path(path)
49
- if ! @@filename_cache.has_key?(path)
50
- FORMATS.each do |format|
51
- [path, File.join(path, 'index')].each do |basename|
52
- filename = model_path("#{basename}.#{format}")
53
- if File.exist?(filename)
54
- @@filename_cache[path] = filename
55
- break
56
- end
57
- end
58
- end
59
- end
60
- @@filename_cache[path]
61
- end
62
-
63
- def self.needs_loading?(path, filename)
64
- @@model_cache[path].nil? || File.mtime(filename) > @@model_cache[path].mtime
65
- end
66
-
67
- def self.load(path)
68
- if (filename = find_file_for_path(path)) && needs_loading?(path, filename)
69
- @@model_cache[path] = self.new(filename)
70
- end
71
- @@model_cache[path]
72
- end
73
-
74
- def self.purge_cache
75
- @@model_cache = {}
76
- @@filename_cache = {}
77
- end
78
-
79
- def initialize(filename)
80
- @filename = filename
81
- @format = filename.split('.').last.to_sym
82
- if File.zero?(filename)
83
- @metadata = {}
84
- @markup = ''
85
- else
86
- @metadata, @markup = parse_file
87
- end
88
- @mtime = File.mtime(filename)
89
- end
90
-
91
- def ==(other)
92
- other.respond_to?(:path) && (self.path == other.path)
93
- end
94
-
95
- def index_page?
96
- @filename =~ /\/?index\.\w+$/
97
- end
98
-
99
- def abspath
100
- file_path = @filename.sub(self.class.model_path, '')
101
- if index_page?
102
- File.dirname(file_path)
103
- else
104
- File.join(File.dirname(file_path), File.basename(file_path, '.*'))
105
- end
106
- end
107
-
108
- def path
109
- abspath.sub(/^\//, '')
110
- end
111
-
112
- def permalink
113
- File.basename(path)
114
- end
115
-
116
- def layout
117
- (metadata('layout') || 'layout').to_sym
118
- end
119
-
120
- def template
121
- (metadata('template') || 'page').to_sym
122
- end
123
-
124
- def to_html(scope = Object.new)
125
- convert_to_html(@format, scope, markup)
126
- end
127
-
128
- def last_modified
129
- @last_modified ||= File.stat(@filename).mtime
130
- end
131
-
132
- def description
133
- metadata('description')
134
- end
135
-
136
- def keywords
137
- metadata('keywords')
138
- end
139
-
140
- def metadata(key)
141
- @metadata[key]
142
- end
143
-
144
- def flagged_as?(flag)
145
- flags = metadata('flags')
146
- flags && flags.split(',').map { |name| name.strip }.include?(flag)
147
- end
148
-
149
- def parse_metadata(first_paragraph)
150
- is_metadata = first_paragraph.split("\n").first =~ /^[\w ]+:/
151
- raise MetadataParseError unless is_metadata
152
- metadata = CaseInsensitiveHash.new
153
- first_paragraph.split("\n").each do |line|
154
- key, value = line.split(/\s*:\s*/, 2)
155
- next if value.nil?
156
- metadata[key.downcase] = value.chomp
157
- end
158
- metadata
159
- end
160
-
161
- private
162
- def markup
163
- @markup
164
- end
165
-
166
- def parse_file
167
- contents = File.open(@filename).read
168
- rescue Errno::ENOENT
169
- raise Sinatra::NotFound
170
- else
171
- first_paragraph, remaining = contents.split(/\r?\n\r?\n/, 2)
172
- begin
173
- return parse_metadata(first_paragraph), remaining
174
- rescue MetadataParseError
175
- return {}, contents
176
- end
177
- end
178
-
179
- def add_p_tags_to_haml(text)
180
- contains_tags = (text =~ /^\s*%/)
181
- if contains_tags
182
- text
183
- else
184
- text.split(/\r?\n/).inject('') do |accumulator, line|
185
- accumulator << "%p #{line}\n"
186
- end
187
- end
188
- end
189
-
190
- def convert_to_html(format, scope, text)
191
- text = add_p_tags_to_haml(text) if @format == :haml
192
- template = Tilt[format].new { text }
193
- template.render(scope)
194
- end
195
- end
196
-
197
- class Page < FileModel
198
- def self.model_path(basename = nil)
199
- Nesta::Config.page_path(basename)
200
- end
201
-
202
- def self.find_by_path(path)
203
- page = load(path)
204
- page && page.hidden? ? nil : page
205
- end
206
-
207
- def self.find_all
208
- super.select { |p| ! p.hidden? }
209
- end
210
-
211
- def self.find_articles
212
- find_all.select do |page|
213
- page.date && page.date < DateTime.now
214
- end.sort { |x, y| y.date <=> x.date }
215
- end
216
-
217
- def draft?
218
- flagged_as?('draft')
219
- end
220
-
221
- def hidden?
222
- draft? && Nesta::App.production?
223
- end
224
-
225
- def heading
226
- regex = case @format
227
- when :mdown, :md
228
- /^#\s*(.*?)(\s*#+|$)/
229
- when :haml
230
- /^\s*%h1\s+(.*)/
231
- when :textile
232
- /^\s*h1\.\s+(.*)/
233
- end
234
- markup =~ regex
235
- Regexp.last_match(1) or raise HeadingNotSet, "#{abspath} needs a heading"
236
- end
237
-
238
- def link_text
239
- metadata('link text') || heading
240
- rescue HeadingNotSet
241
- raise LinkTextNotSet, "Need to link to '#{abspath}' but can't get link text"
242
- end
243
-
244
- def title
245
- metadata('title') || link_text
246
- rescue LinkTextNotSet
247
- return Nesta::Config.title if abspath == '/'
248
- raise
249
- end
250
-
251
- def date(format = nil)
252
- @date ||= if metadata("date")
253
- if format == :xmlschema
254
- Time.parse(metadata("date")).xmlschema
255
- else
256
- DateTime.parse(metadata("date"))
257
- end
258
- end
259
- end
260
-
261
- def atom_id
262
- metadata('atom id')
263
- end
264
-
265
- def read_more
266
- metadata('read more') || Nesta::Config.read_more
267
- end
268
-
269
- def summary
270
- if summary_text = metadata("summary")
271
- summary_text.gsub!('\n', "\n")
272
- convert_to_html(@format, Object.new, summary_text)
273
- end
274
- end
275
-
276
- def body_markup
277
- case @format
278
- when :mdown, :md
279
- markup.sub(/^#[^#].*$\r?\n(\r?\n)?/, '')
280
- when :haml
281
- markup.sub(/^\s*%h1\s+.*$\r?\n(\r?\n)?/, '')
282
- when :textile
283
- markup.sub(/^\s*h1\.\s+.*$\r?\n(\r?\n)?/, '')
284
- end
285
- end
286
-
287
- def body(scope = Object.new)
288
- convert_to_html(@format, scope, body_markup)
289
- end
290
-
291
- def categories
292
- paths = category_strings.map { |specifier| specifier.sub(/:-?\d+$/, '') }
293
- valid_paths(paths).map { |p| Page.find_by_path(p) }
294
- end
295
-
296
- def priority(category)
297
- category_string = category_strings.detect do |string|
298
- string =~ /^#{category}([,:\s]|$)/
299
- end
300
- category_string && category_string.split(':', 2)[-1].to_i
301
- end
302
-
303
- def parent
304
- if abspath == '/'
305
- nil
306
- else
307
- parent_path = File.dirname(path)
308
- while parent_path != '.' do
309
- parent = Page.load(parent_path)
310
- return parent unless parent.nil?
311
- parent_path = File.dirname(parent_path)
312
- end
313
- return categories.first unless categories.empty?
314
- Page.load('index')
315
- end
316
- end
317
-
318
- def pages
319
- in_category = Page.find_all.select do |page|
320
- page.date.nil? && page.categories.include?(self)
321
- end
322
- in_category.sort do |x, y|
323
- by_priority = y.priority(path) <=> x.priority(path)
324
- if by_priority == 0
325
- x.link_text.downcase <=> y.link_text.downcase
326
- else
327
- by_priority
328
- end
329
- end
330
- end
331
-
332
- def articles
333
- Page.find_articles.select { |article| article.categories.include?(self) }
334
- end
335
-
336
- def receives_comments?
337
- ! date.nil?
338
- end
339
-
340
- private
341
- def category_strings
342
- strings = metadata('categories')
343
- strings.nil? ? [] : strings.split(',').map { |string| string.strip }
344
- end
345
-
346
- def valid_paths(paths)
347
- page_dir = Nesta::Config.page_path
348
- paths.select do |path|
349
- FORMATS.detect do |format|
350
- [path, File.join(path, 'index')].detect do |candidate|
351
- File.exist?(File.join(page_dir, "#{candidate}.#{format}"))
352
- end
353
- end
354
- end
355
- end
356
- end
357
-
358
- class Menu
359
- INDENT = " " * 2
360
-
361
- def self.full_menu
362
- menu = []
363
- menu_file = Nesta::Config.content_path('menu.txt')
364
- if File.exist?(menu_file)
365
- File.open(menu_file) { |file| append_menu_item(menu, file, 0) }
366
- end
367
- menu
368
- end
369
-
370
- def self.top_level
371
- full_menu.reject { |item| item.is_a?(Array) }
372
- end
373
-
374
- def self.for_path(path)
375
- path.sub!(Regexp.new('^/'), '')
376
- if path.empty?
377
- full_menu
378
- else
379
- find_menu_item_by_path(full_menu, path)
380
- end
381
- end
382
-
383
- private_class_method def self.append_menu_item(menu, file, depth)
384
- path = file.readline
385
- rescue EOFError
386
- else
387
- page = Page.load(path.strip)
388
- current_depth = path.scan(INDENT).size
389
- if page
390
- if current_depth > depth
391
- sub_menu_for_depth(menu, depth) << [page]
392
- else
393
- sub_menu_for_depth(menu, current_depth) << page
394
- end
395
- end
396
- append_menu_item(menu, file, current_depth)
397
- end
398
-
399
- private_class_method def self.sub_menu_for_depth(menu, depth)
400
- sub_menu = menu
401
- depth.times { sub_menu = sub_menu[-1] }
402
- sub_menu
403
- end
404
-
405
- private_class_method def self.find_menu_item_by_path(menu, path)
406
- item = menu.detect do |item|
407
- item.respond_to?(:path) && (item.path == path)
408
- end
409
- if item
410
- subsequent = menu[menu.index(item) + 1]
411
- item = [item]
412
- item << subsequent if subsequent.respond_to?(:each)
413
- else
414
- sub_menus = menu.select { |menu_item| menu_item.respond_to?(:each) }
415
- sub_menus.each do |sub_menu|
416
- item = find_menu_item_by_path(sub_menu, path)
417
- break if item
418
- end
419
- end
420
- item
421
- end
422
- end
423
- end
3
+ require_relative './models/file_model'
4
+ require_relative './models/menu'
5
+ require_relative './models/page'
data/lib/nesta/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Nesta
2
- VERSION = '0.14.0'
2
+ VERSION = '0.15.0'
3
3
  end
data/nesta.gemspec CHANGED
@@ -36,7 +36,7 @@ EOF
36
36
  s.add_dependency('rdiscount', '~> 2.1')
37
37
  s.add_dependency('RedCloth', '~> 4.2')
38
38
  s.add_dependency('sass-embedded', '~> 1.58')
39
- s.add_dependency('sinatra', '~> 2.0')
39
+ s.add_dependency('sinatra', '~> 3.0')
40
40
  s.add_dependency('tilt', '~> 2.0')
41
41
 
42
42
  # Useful in development
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nesta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Graham Ashton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-23 00:00:00.000000000 Z
11
+ date: 2023-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: haml
@@ -120,14 +120,14 @@ dependencies:
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '2.0'
123
+ version: '3.0'
124
124
  type: :runtime
125
125
  prerelease: false
126
126
  version_requirements: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '2.0'
130
+ version: '3.0'
131
131
  - !ruby/object:Gem::Dependency
132
132
  name: tilt
133
133
  requirement: !ruby/object:Gem::Requirement
@@ -234,7 +234,7 @@ files:
234
234
  - ".github/workflows/tests.yml"
235
235
  - ".gitignore"
236
236
  - ".gitmodules"
237
- - CHANGES
237
+ - CHANGELOG.md
238
238
  - Gemfile
239
239
  - Gemfile.lock
240
240
  - Guardfile
@@ -265,6 +265,9 @@ files:
265
265
  - lib/nesta/env.rb
266
266
  - lib/nesta/helpers.rb
267
267
  - lib/nesta/models.rb
268
+ - lib/nesta/models/file_model.rb
269
+ - lib/nesta/models/menu.rb
270
+ - lib/nesta/models/page.rb
268
271
  - lib/nesta/navigation.rb
269
272
  - lib/nesta/overrides.rb
270
273
  - lib/nesta/path.rb
@@ -320,9 +323,9 @@ files:
320
323
  - test/support/test_configuration.rb
321
324
  - test/test_helper.rb
322
325
  - test/unit/config_test.rb
323
- - test/unit/file_model_test.rb
324
- - test/unit/menu_test.rb
325
- - test/unit/page_test.rb
326
+ - test/unit/models/file_model_test.rb
327
+ - test/unit/models/menu_test.rb
328
+ - test/unit/models/page_test.rb
326
329
  - test/unit/path_test.rb
327
330
  - test/unit/plugin_test.rb
328
331
  - test/unit/static/assets_test.rb
@@ -366,7 +369,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
366
369
  - !ruby/object:Gem::Version
367
370
  version: '0'
368
371
  requirements: []
369
- rubygems_version: 3.4.6
372
+ rubygems_version: 3.4.10
370
373
  signing_key:
371
374
  specification_version: 4
372
375
  summary: Ruby CMS, written in Sinatra
File without changes
File without changes