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 +4 -4
- data/.github/workflows/tests.yml +1 -1
- data/{CHANGES → CHANGELOG.md} +45 -22
- data/Gemfile.lock +9 -9
- data/LICENSE +1 -1
- data/README.md +42 -30
- data/RELEASING.md +1 -1
- data/lib/nesta/models/file_model.rb +191 -0
- data/lib/nesta/models/menu.rb +67 -0
- data/lib/nesta/models/page.rb +167 -0
- data/lib/nesta/models.rb +3 -421
- data/lib/nesta/version.rb +1 -1
- data/nesta.gemspec +1 -1
- metadata +12 -9
- /data/test/unit/{file_model_test.rb → models/file_model_test.rb} +0 -0
- /data/test/unit/{menu_test.rb → models/menu_test.rb} +0 -0
- /data/test/unit/{page_test.rb → models/page_test.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7080f8e4c8c7b993aaaf2f1f2ea59edbefd0b49ef73218a8f20d6877880b18c2
|
|
4
|
+
data.tar.gz: 806b5caf9fbfc69221087bd98254e282ddb5ba0e4288ee835d3af22fc879ab9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17a405a098acca204751044cff182ee6484c93c373b7266a2c194345736438b71366b05cd04ea8d64e7949c9ab071e01cde387a1c68d1190a2087d211fa45060
|
|
7
|
+
data.tar.gz: 8d37d4b2b1fb4df23d57403da16c865db06a4a899a942a594cfac47212f998c52ef330ae9091149e1858c06f390f2ed9e4ef94621b115c52f04ed821d0cc9b7d
|
data/.github/workflows/tests.yml
CHANGED
data/{CHANGES → CHANGELOG.md}
RENAMED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
368
|
+
## 0.9.12 (Released then pulled, due to rubygems [still] being a total mess)
|
|
347
369
|
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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 (~>
|
|
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 (
|
|
56
|
+
mustermann (3.0.0)
|
|
57
57
|
ruby2_keywords (~> 0.0.1)
|
|
58
|
-
nokogiri (1.14.
|
|
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 (
|
|
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 (
|
|
82
|
-
mustermann (~>
|
|
83
|
-
rack (~> 2.2)
|
|
84
|
-
rack-protection (=
|
|
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
data/README.md
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Lightweight file-based CMS and Static Site Generator
|
|
2
2
|
|
|
3
|
-
|
|
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]
|
|
6
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
30
|
-
|
|
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]
|
|
40
|
-
|
|
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
|
-
[
|
|
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
|
-
[@
|
|
54
|
-
[
|
|
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
|

|
|
57
69
|
|
|
58
70
|
## Contributing
|
|
59
71
|
|
|
60
|
-
If you want to add a new feature,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
[
|
|
77
|
+
[create an issue]: https://github.com/gma/nesta/issues/new
|
|
78
|
+
[plugin]: https://nestacms.com/docs/plugins
|
|
66
79
|
|
|
67
|
-
-- Graham
|
|
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 `
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
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', '~>
|
|
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.
|
|
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-
|
|
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: '
|
|
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: '
|
|
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
|
-
-
|
|
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.
|
|
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
|
|
File without changes
|