frontmatter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b817bbc36796c281dfd5a562485af0bb45697f3acc7a0e6249877a7ba54e9dfe
4
+ data.tar.gz: 598e0e890161b35709e0b008aa336bbf70f4179fbb870d884e97d5cbb1a820c2
5
+ SHA512:
6
+ metadata.gz: c84eebe8e7bcc99be51ab952ea65ec6a68f43c75f762cd3fb13e702e6e903d67f15af0d70cf5f248b57acd9d77645fbbc99487528ff824e3bbaaaafd3dab6fd1
7
+ data.tar.gz: 04b496c4eb4319bb7d2e21c500312af903ee0d6f87b83347a581251c221ddb9380db5d44ffece3c8788d355d4542521bb3f07500ea5288312c4520052d374b01
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Caleb Hearth
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,118 @@
1
+ # Frontmatter
2
+
3
+ Frontmatter provides a Rails template handler for views ending in .yaml or .yml
4
+ which contain "frontmatter" that is valid YAML contained between two sets of
5
+ `---`, such as:
6
+
7
+ ---
8
+ author: Caleb Hearth
9
+ website: https://calebhearth.com
10
+ likes:
11
+ - Dogs
12
+ - Frontmatter
13
+ - Rails
14
+ dislikes:
15
+ - Beets
16
+ ---
17
+
18
+ This frontmatter is parsed and made available as getter methods in a model you
19
+ define per "collection" of views. This collection might be "posts" or "talks"
20
+ and conceptually it can be considered similarly to an ActiveRecord::Base
21
+ subclass, but with the view file as the data source rather than a database.
22
+
23
+ ## Usage
24
+
25
+ A simple use case would be to import blog posts into Rails from a Jekyll site.
26
+ The code to do that might look like the below. Note that the semantics are very
27
+ familiar to anyone who's used Rails applications for similar purposes.
28
+
29
+ ### app/models/post.rb
30
+
31
+ ```ruby
32
+ class Post < ActionPost::Base
33
+ end
34
+ ```
35
+
36
+ `ActionPost::Base` provides class-level "all" and "find(slug)" methods which
37
+ list all pages and finds the first page matching the slug. It also extracts all
38
+ frontmatter and provides it as getter methods such as `post.title`.
39
+
40
+ ### app/controllers/posts_controller.rb
41
+
42
+ A controller is required, but there is no need to define the default actions.
43
+ Default index and show actions are provided that list all posts (all files in
44
+ app/views/posts with a .yaml or .yml extension) and finds posts based on their
45
+ "slug" which is the filename without any extensions.
46
+
47
+ ```ruby
48
+ class PostsController < Frontmatter::BaseController
49
+ end
50
+ ```
51
+
52
+ The ActionPost::Base subclass is inferred to be the singular of the Controller's
53
+ class name without "Controller", so `PostsController` would render `Post`
54
+ objects and look for views in `app/views/posts`. The class-level `renders_page`
55
+ method overrides this:
56
+
57
+ ```
58
+ class PostsController < Frontmatter::BaseController
59
+ renders_page :blog_post # BlogPost objects / app/views/blog_posts views.
60
+ end
61
+ ```
62
+
63
+ ### config/routes.rb
64
+
65
+ ```ruby
66
+ resources :posts, only: %i(index show)
67
+ ```
68
+
69
+ ### app/views/posts/my-first-post.md.yaml
70
+
71
+ This original Jekyll file would be called `_posts/2020-05-30-my-first-post.md`
72
+ and would not have a date field in the frontmatter. That would need to be
73
+ migrated manually or scripted. Date extraction from filename is not a feature of
74
+ Frontmatter.
75
+
76
+ ```markdown
77
+
78
+ ---
79
+ title: My First Post
80
+ date: 2020-05-30
81
+ ---
82
+
83
+ Look at all the things I'm not doing!
84
+ ```
85
+
86
+ ### app/views/posts/show.html.erb
87
+
88
+ ```erb
89
+ <h1><%= @page.title %></h1>
90
+ <aside><%= time_tag @post.date %></aside>
91
+ <%= page_content @page %>
92
+ ```
93
+
94
+ ### app/views/posts/index.html.erb
95
+
96
+ ```erb
97
+ <h1>My Posts</h1>
98
+ <ul><% @posts.each do |post| %>
99
+ <li><%= link_to @post.title, post_path(@post) %></li>
100
+ <% end %></ul>
101
+ ```
102
+
103
+ ## Installation
104
+ Add this line to your application's Gemfile:
105
+
106
+ ```ruby
107
+ gem 'frontmatter'
108
+ ```
109
+
110
+ And then execute:
111
+ ```bash
112
+ $ bundle
113
+ ```
114
+
115
+ Or install it yourself as:
116
+ ```bash
117
+ $ gem install frontmatter
118
+ ```
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Frontmatter'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/frontmatter .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,36 @@
1
+ module Frontmatter
2
+ class PagesController < ::ApplicationController
3
+ cattr_reader :page_class
4
+ helper Frontmatter::ApplicationHelper
5
+
6
+ def self.renders_page(page)
7
+ @@page_class = page
8
+ end
9
+
10
+ def self.page_class
11
+ @@page_class ||= begin
12
+ page_class = name.sub(/Controller\z/, '')
13
+ page_class.singularize.constantize
14
+ rescue NameError
15
+ raise #Frontmatter::MissingPageClass.new(page: page_class)
16
+ end
17
+ end
18
+
19
+ def initialize(*args)
20
+ super
21
+ append_view_path("app/views/#{self.class.page_class.view_key}")
22
+ end
23
+
24
+ def index
25
+ @pages = page_class.all
26
+ end
27
+
28
+ def show
29
+ @page = page_class.find(params[:id])
30
+ if @page.nil?
31
+ raise ActionController::RoutingError.new("Not Found")
32
+ end
33
+ render
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ module Frontmatter
2
+ module ApplicationHelper
3
+ def page_content(page)
4
+ controller.render_to_string("#{page.class.view_key}/#{page.slug}", layout: false)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module Frontmatter
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Frontmatter
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Frontmatter
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,45 @@
1
+ module Frontmatter
2
+ class Page
3
+ extend ActiveModel::Naming
4
+ attr_reader :slug
5
+
6
+ def self.all
7
+ Dir[pages_directory]
8
+ .reject { %w(index show).include? File.basename(_1.split('.')[0]) }
9
+ .map { |f| new(f) }
10
+ end
11
+
12
+ def self.find(slug)
13
+ all.find { _1.slug == slug }
14
+ end
15
+
16
+ def self.pages_directory
17
+ Rails.root.join("app", "views", view_key, '*.yaml').freeze
18
+ end
19
+
20
+ def initialize(filename)
21
+ @slug = File.basename(filename.split('.')[0])
22
+ @frontmatter = YAML.load(File.read(filename)).with_indifferent_access
23
+ end
24
+
25
+ # TODO: Define attribute methods to speed this up
26
+ def method_missing(m, *args, &block)
27
+ if value = @frontmatter[m]
28
+ return value
29
+ end
30
+ super
31
+ end
32
+
33
+ def respond_to_missing(m)
34
+ @frontmatter.key(m)
35
+ end
36
+
37
+ def to_param
38
+ slug
39
+ end
40
+
41
+ def self.view_key
42
+ model_name.route_key
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Frontmatter</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "frontmatter/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
File without changes
@@ -0,0 +1,2 @@
1
+ Frontmatter::Engine.routes.draw do
2
+ end
@@ -0,0 +1,5 @@
1
+ require "frontmatter/engine"
2
+ require "frontmatter/template_handler"
3
+
4
+ module Frontmatter
5
+ end
@@ -0,0 +1,15 @@
1
+ module Frontmatter
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Frontmatter
4
+ initializer "frontmatter.load" do
5
+ ActiveSupport.on_load :action_view do
6
+ ActionView::Template.register_template_handler(
7
+ # Accept all existing handlers with additional .yaml or .yml extension
8
+ *ActionView::Template.template_handler_extensions.flat_map { %W(#{_1}.yaml #{_1}.yml) },
9
+ :yml, :yaml,
10
+ Frontmatter::TemplateHandler
11
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module Frontmatter
2
+ class TemplateHandler
3
+ def self.call(template, source)
4
+ new(template, source).call
5
+ end
6
+
7
+ def initialize(template, source)
8
+ @template = template
9
+ @source = source
10
+ end
11
+
12
+ def call
13
+ handler_for_template.call(@template, source_without_yaml)
14
+ end
15
+
16
+ def handler_for_template
17
+ ApplicationController.new.view_paths.paths.first
18
+ .extract_handler_and_format_and_variant(path_without_yaml)
19
+ .first
20
+ end
21
+
22
+ def source_without_yaml
23
+ @source.lines[last_line+1...].join
24
+ end
25
+
26
+ def path_without_yaml
27
+ @template.short_identifier.sub(/.ya?ml\z/, '')
28
+ end
29
+
30
+ def last_line
31
+ YAML.parse(@source).root.end_line
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Frontmatter
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :frontmatter do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: frontmatter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Caleb Hearth
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - caleb@calebhearth.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - app/assets/config/frontmatter_manifest.js
80
+ - app/assets/stylesheets/frontmatter/application.css
81
+ - app/controllers/frontmatter/pages_controller.rb
82
+ - app/helpers/frontmatter/application_helper.rb
83
+ - app/jobs/frontmatter/application_job.rb
84
+ - app/mailers/frontmatter/application_mailer.rb
85
+ - app/models/frontmatter/application_record.rb
86
+ - app/models/frontmatter/page.rb
87
+ - app/views/layouts/frontmatter/application.html.erb
88
+ - config/environment.rb
89
+ - config/routes.rb
90
+ - lib/frontmatter.rb
91
+ - lib/frontmatter/engine.rb
92
+ - lib/frontmatter/template_handler.rb
93
+ - lib/frontmatter/version.rb
94
+ - lib/tasks/frontmatter_tasks.rake
95
+ homepage: https://calebhearth.com
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.1.2
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Semi-static pages with data in Rails.
118
+ test_files: []