impression 0.3 → 0.7

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: 5644ebb122c166a8a5579a950b3e64fde5d3d332eea8299aa4c4023af8b85cb6
4
- data.tar.gz: b89065d1cd8c14d5ddad28f1fc3ff90cc48d43354698e339715f3b6d172af671
3
+ metadata.gz: 871b003a25f9dd74ea137c483c2c208bdad9cf505d544c085f1f84eff394a278
4
+ data.tar.gz: 55623ee42bbc7213ef37c6632b1d4807b882301d133bc324607878857b7110c4
5
5
  SHA512:
6
- metadata.gz: 45ab7c1f4e3c9f920175acc8bce2417932571ceca8946cf1fd0b784a6280413514587bc998d467808023c56e054a0edc2c821e6dc700289a4a02f4e2372dfe05
7
- data.tar.gz: d3b57e4089e0473e508f5a67c52db12be7a62850897bc79a27871b4ea7a3d2b3b3062f214980a98d5e842033b62496ae9024416c37b00ca05b527b01c088b53e
6
+ metadata.gz: 29437cfb294efff3d90c7bcfa82bf05bba184b0ea4f68f13097650c2af17440e2d1294d95e06c17b981a4066c58dde70ac77fd3b8fbacb9ca7a676d08dfc71be
7
+ data.tar.gz: a787832534b192c37bc936f96a0c7b92c97b516dc2857b49fbefedab3a152c73301b6ce09e6b1bf581a77dd14bd8a2fa455c84e693b1ac29d5eb74208058bf33
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ # 0.7 2022-01-23
2
+
3
+ - Update Papercraft, Refactor Jamstack resource
4
+
5
+ # 0.6 2022-01-22
6
+
7
+ - Unify page_list entries and page_info cache entries (#10)
8
+ - Refactor FileTree#path_info to return more information
9
+
10
+ # 0.5 2022-01-20
11
+
12
+ - Pass resource and request to rendered templates (#8)
13
+ - Implement Jamstack#page_list method (#7)
14
+
15
+ # 0.4 2022-01-19
16
+
17
+ - Remove deprecated Pages code (#6)
18
+
1
19
  # 0.3 2022-01-19
2
20
 
3
21
  - Implement basic Jamstack resource (#5)
data/Gemfile.lock CHANGED
@@ -1,14 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- impression (0.3)
5
- kramdown (~> 2.3.0)
6
- kramdown-parser-gfm (~> 1.1.0)
4
+ impression (0.7)
7
5
  modulation (~> 1.1)
8
- papercraft (~> 0.14)
6
+ papercraft (~> 0.17)
9
7
  polyphony (~> 0.73.1)
10
- qeweney (~> 0.15)
11
- rouge (~> 3.26.0)
8
+ qeweney (~> 0.16)
12
9
  tipi (~> 0.45)
13
10
 
14
11
  GEM
@@ -53,20 +50,20 @@ GEM
53
50
  localhost (1.1.9)
54
51
  minitest (5.11.3)
55
52
  modulation (1.1)
56
- msgpack (1.4.2)
53
+ msgpack (1.4.4)
57
54
  multipart-post (2.1.1)
58
- papercraft (0.14)
59
- escape_utils (= 1.2.1)
60
- kramdown (~> 2.3.0)
55
+ papercraft (0.17)
56
+ escape_utils (~> 1.2.1)
57
+ kramdown (~> 2.3.1)
61
58
  kramdown-parser-gfm (~> 1.1.0)
62
- rouge (~> 3.26.0)
59
+ rouge (~> 3.27.0)
63
60
  polyphony (0.73.1)
64
- qeweney (0.15)
61
+ qeweney (0.16)
65
62
  escape_utils (~> 1.2.1)
66
63
  rack (2.2.3)
67
64
  rake (12.3.3)
68
65
  rexml (3.2.5)
69
- rouge (3.26.1)
66
+ rouge (3.27.0)
70
67
  ruby2_keywords (0.0.5)
71
68
  simplecov (0.17.1)
72
69
  docile (~> 1.1)
data/examples/hello.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'impression'
2
+
3
+ app = Impression.app do
4
+ mount '/' => text_response('Hello, world!')
5
+ end
6
+
7
+ # class App < Impression::Resource
8
+ # def route(req)
9
+ # @response ||= text_response('Hello, world!')
10
+ # end
11
+ # end
12
+
13
+ # run { |req| req.respond_text('Hello, world!') }
data/impression.gemspec CHANGED
@@ -10,7 +10,10 @@ Gem::Specification.new do |s|
10
10
  s.files = `git ls-files`.split
11
11
  s.homepage = 'http://github.com/digital-fabric/impression'
12
12
  s.metadata = {
13
- "source_code_uri" => "https://github.com/digital-fabric/impression"
13
+ "source_code_uri" => "https://github.com/digital-fabric/impression",
14
+ "documentation_uri" => "https://www.rubydoc.info/gems/impression",
15
+ "homepage_uri" => "https://github.com/digital-fabric/impression",
16
+ "changelog_uri" => "https://github.com/digital-fabric/impression/blob/master/CHANGELOG.md"
14
17
  }
15
18
  s.rdoc_options = ["--title", "impression", "--main", "README.md"]
16
19
  s.extra_rdoc_files = ["README.md"]
@@ -21,14 +24,10 @@ Gem::Specification.new do |s|
21
24
 
22
25
  s.add_runtime_dependency 'polyphony', '~>0.73.1'
23
26
  s.add_runtime_dependency 'tipi', '~>0.45'
24
- s.add_runtime_dependency 'qeweney', '~>0.15'
27
+ s.add_runtime_dependency 'qeweney', '~>0.16'
25
28
 
26
- s.add_runtime_dependency 'kramdown', '~>2.3.0'
27
- s.add_runtime_dependency 'rouge', '~>3.26.0'
28
- s.add_runtime_dependency 'kramdown-parser-gfm', '~>1.1.0'
29
-
30
29
  # s.add_runtime_dependency 'rb-inotify', '~>0.10.1'
31
- s.add_runtime_dependency 'papercraft', '~>0.14'
30
+ s.add_runtime_dependency 'papercraft', '~>0.17'
32
31
  s.add_runtime_dependency 'modulation', '~>1.1'
33
32
 
34
33
 
@@ -27,8 +27,6 @@ module Impression
27
27
  render_from_path_info(req, path_info)
28
28
  end
29
29
 
30
- private
31
-
32
30
  # Renders a response from the given response kind and path.
33
31
  #
34
32
  # @param req [Qeweney::Request] request
@@ -47,6 +45,9 @@ module Impression
47
45
 
48
46
  private
49
47
 
48
+ PAGE_EXT_REGEXP = /^(.+)\.html$/.freeze
49
+ INDEX_PAGE_REGEXP = /^(.+)?\/index$/.freeze
50
+
50
51
  # Renders a file response for the given request and the given path info.
51
52
  #
52
53
  # @param req [Qeweney::Request] request
@@ -89,8 +90,37 @@ module Impression
89
90
  elsif stat.directory?
90
91
  return directory_path_info(path)
91
92
  else
92
- return { kind: :file, path: path, ext: File.extname(path) }
93
+ file_info(path)
94
+ end
95
+ end
96
+
97
+ # Returns the path info for the given file path.
98
+ #
99
+ # @param path [String] file path
100
+ # @return [Hash] path info
101
+ def file_info(path)
102
+ relative_path = path.gsub(/^#{@directory}/, '')
103
+ {
104
+ kind: :file,
105
+ path: path,
106
+ ext: File.extname(path),
107
+ url: pretty_url(relative_path)
108
+ }
109
+ end
110
+
111
+ # Returns the pretty URL for the given relative path. For pages, the
112
+ # extension is removed. For index pages, the index suffix is removed.
113
+ #
114
+ # @param relative_path [String] relative path
115
+ # @return [String] pretty URL
116
+ def pretty_url(relative_path)
117
+ if (m = relative_path.match(PAGE_EXT_REGEXP))
118
+ relative_path = m[1]
119
+ end
120
+ if (m = relative_path.match(INDEX_PAGE_REGEXP))
121
+ relative_path = m[1] || '/'
93
122
  end
123
+ relative_path == '/' ? absolute_path : File.join(absolute_path, relative_path)
94
124
  end
95
125
 
96
126
  # Calculates the path info for a directory. If an index file exists, its
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
+ require 'date'
4
5
  require 'yaml'
5
6
  require 'modulation'
6
7
  require 'papercraft'
@@ -17,8 +18,68 @@ module Impression
17
18
  @layouts = {}
18
19
  end
19
20
 
21
+ # Returns a list of pages found in the given directory (relative to the base
22
+ # directory). Each entry containins the absolute file path, the pretty URL,
23
+ # the possible date parsed from the file name, and any other front matter
24
+ # attributes (for .md files). This method will detect only pages with the
25
+ # extensions .html, .md, .rb. The returned entries are sorted by file path.
26
+ #
27
+ # @param dir [String] relative directory
28
+ # @return [Array<Hash>] array of page entries
29
+ def page_list(dir)
30
+ base = File.join(@directory, dir)
31
+ Dir.glob('*.{html,md}', base: base)
32
+ .map { |fn| get_path_info(File.join(dir, fn)) }# page_entry(fn, dir) }
33
+ .sort_by { |i| i[:path] }
34
+ end
35
+
20
36
  private
21
37
 
38
+ DATE_REGEXP = /(\d{4}\-\d{2}\-\d{2})/.freeze
39
+ FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m.freeze
40
+ MD_EXT_REGEXP = /\.md$/.freeze
41
+ PAGE_EXT_REGEXP = /^(.+)\.(md|html|rb)$/.freeze
42
+ INDEX_PAGE_REGEXP = /^(.+)?\/index$/.freeze
43
+ YAML_OPTS = {
44
+ permitted_classes: [Date]
45
+ }.freeze
46
+
47
+ # Returns the path info for the given file path.
48
+ #
49
+ # @param path [String] file path
50
+ # @return [Hash] path info
51
+ def file_info(path)
52
+ info = super
53
+ case info[:ext]
54
+ when '.md'
55
+ atts, content = parse_markdown_file(path)
56
+ info = info.merge(atts)
57
+ info[:html_content] = Papercraft.markdown(content)
58
+ when '.rb'
59
+ info[:module] = import(path)
60
+ end
61
+ if (m = path.match(DATE_REGEXP))
62
+ info[:date] ||= Date.parse(m[1])
63
+ end
64
+
65
+ info
66
+ end
67
+
68
+ # Returns the pretty URL for the given relative path. For pages, the
69
+ # extension is removed. For index pages, the index suffix is removed.
70
+ #
71
+ # @param relative_path [String] relative path
72
+ # @return [String] pretty URL
73
+ def pretty_url(relative_path)
74
+ if (m = relative_path.match(PAGE_EXT_REGEXP))
75
+ relative_path = m[1]
76
+ end
77
+ if (m = relative_path.match(INDEX_PAGE_REGEXP))
78
+ relative_path = m[1] || '/'
79
+ end
80
+ relative_path == '/' ? absolute_path : File.join(absolute_path, relative_path)
81
+ end
82
+
22
83
  # Renders a file response for the given request and the given path info.
23
84
  #
24
85
  # @param req [Qeweney::Request] request
@@ -27,9 +88,9 @@ module Impression
27
88
  def render_file(req, path_info)
28
89
  case path_info[:ext]
29
90
  when '.rb'
30
- render_papercraft_module(req, path_info[:path])
91
+ render_papercraft_module(req, path_info)
31
92
  when '.md'
32
- render_markdown_file(req, path_info[:path])
93
+ render_markdown_file(req, path_info)
33
94
  else
34
95
  req.serve_file(path_info[:path])
35
96
  end
@@ -38,26 +99,25 @@ module Impression
38
99
  # Renders a Papercraft module. The module is loaded using Modulation.
39
100
  #
40
101
  # @param req [Qeweney::Request] reqest
41
- # @param path [String] file path
102
+ # @param path_info [Hash] path info
42
103
  # @return [void]
43
- def render_papercraft_module(req, path)
44
- mod = import path
45
-
46
- html = H(mod).render
47
- req.respond(html, 'Content-Type' => Qeweney::MimeTypes[:html])
104
+ def render_papercraft_module(req, path_info)
105
+ template = Papercraft.html(path_info[:module])
106
+ body = template.render(request: req, resource: self)
107
+ req.respond(body, 'Content-Type' => template.mime_type)
48
108
  end
49
109
 
50
110
  # Renders a markdown file using a layout.
51
111
  #
52
112
  # @param req [Qeweney::Request] reqest
53
- # @param path [String] file path
113
+ # @param path_info [Hash] path info
54
114
  # @return [void]
55
- def render_markdown_file(req, path)
56
- attributes, markdown = parse_markdown_file(path)
115
+ def render_markdown_file(req, path_info)
116
+ layout = get_layout(path_info[:layout])
57
117
 
58
- layout = get_layout(attributes[:layout])
59
-
60
- html = layout.render(**attributes) { emit_markdown markdown }
118
+ html = layout.render(request: req, resource: self, **path_info) {
119
+ emit path_info[:html_content]
120
+ }
61
121
  req.respond(html, 'Content-Type' => Qeweney::MimeTypes[:html])
62
122
  end
63
123
 
@@ -73,30 +133,31 @@ module Impression
73
133
 
74
134
  import path
75
135
  end
76
-
77
- MARKDOWN_PAGE_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m.freeze
78
136
 
79
137
  # Parses the markdown file at the given path.
80
138
  #
81
139
  # @param path [String] file path
82
140
  # @return [Array] an tuple containing properties<Hash>, contents<String>
83
141
  def parse_markdown_file(path)
84
- data = IO.read(path) || ''
85
- if (m = data.match(MARKDOWN_PAGE_REGEXP))
86
- front_matter = m[1]
142
+ content = IO.read(path) || ''
143
+ atts = {}
87
144
 
88
- [symbolize_keys(YAML.load(front_matter)), m.post_match]
89
- else
90
- [{}, data]
145
+ # Parse date from file name
146
+ if (m = path.match(DATE_REGEXP))
147
+ atts[:date] ||= Date.parse(m[1])
91
148
  end
92
- end
93
149
 
94
- # Converts a hash with string keys to one with symbol keys.
95
- #
96
- # @param hash [Hash] input hash
97
- # @return [Hash] output hash
98
- def symbolize_keys(hash)
99
- hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
150
+ if (m = content.match(FRONT_MATTER_REGEXP))
151
+ front_matter = m[1]
152
+ content = m.post_match
153
+
154
+ yaml = YAML.load(front_matter, **YAML_OPTS)
155
+ yaml.each_with_object(atts) do |(k, v), h|
156
+ h[k.to_sym] = v
157
+ end
158
+ end
159
+
160
+ [atts, content]
100
161
  end
101
162
 
102
163
  # Returns the supported path extensions used for searching for files based
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Impression
4
- VERSION = '0.3'
4
+ VERSION = '0.7'
5
5
  end
@@ -1,6 +1,6 @@
1
1
  require 'papercraft'
2
2
 
3
- export_default H { |**props|
3
+ export_default Papercraft.html { |**props|
4
4
  html5 {
5
5
  head {
6
6
  title props[:title]
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: MMM
3
+ layout: article
4
+ ---
5
+
6
+ ## BBB
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: NNN
3
+ layout: article
4
+ ---
5
+
6
+ ## CCC
@@ -3,4 +3,4 @@ title: AAA
3
3
  layout: article
4
4
  ---
5
5
 
6
- ## BBB
6
+ ## ZZZ
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ layout = import('./_layouts/default')
4
+
5
+ export_default layout.apply(title: 'Foobar') { |resource:, request:, **props|
6
+ h1 request.query[:q]
7
+ resource.page_list('/articles').each do |i|
8
+ a i[:title], href: i[:url]
9
+ end
10
+ }
@@ -1,4 +1,5 @@
1
1
  ---
2
2
  title: Hello
3
+ foo: BarBar
3
4
  ---
4
5
  <h1>Index</h1>
@@ -113,4 +113,42 @@ class FileTreeTest < MiniTest::Test
113
113
  @file_tree.route_and_call(req)
114
114
  assert_response static('bar/index.html'), :html, req
115
115
  end
116
+
117
+ def path_info(path)
118
+ @file_tree.send(:get_path_info, path)
119
+ end
120
+
121
+ def test_path_info
122
+ assert_equal({
123
+ kind: :file,
124
+ path: File.join(__dir__, 'static/index.html'),
125
+ ext: '.html',
126
+ url: '/'
127
+ }, path_info('/index.html'))
128
+
129
+ assert_equal({
130
+ kind: :file,
131
+ path: File.join(__dir__, 'static/index.html'),
132
+ ext: '.html',
133
+ url: '/'
134
+ }, path_info('/index'))
135
+
136
+ assert_equal({
137
+ kind: :file,
138
+ path: File.join(__dir__, 'static/index.html'),
139
+ ext: '.html',
140
+ url: '/'
141
+ }, path_info('/'))
142
+
143
+ assert_equal({
144
+ kind: :file,
145
+ path: File.join(__dir__, 'static/js/a.js'),
146
+ ext: '.js',
147
+ url: '/js/a.js'
148
+ }, path_info('/js/a.js'))
149
+
150
+ assert_equal({
151
+ kind: :not_found,
152
+ }, path_info('/js/b.js'))
153
+ end
116
154
  end
@@ -48,7 +48,7 @@ class JamstackTest < MiniTest::Test
48
48
  req = mock_req(':method' => 'GET', ':path' => '/foo')
49
49
  @jamstack.route_and_call(req)
50
50
 
51
- foo = H {
51
+ foo = Papercraft.html {
52
52
  html5 {
53
53
  head {
54
54
  title 'Foo title'
@@ -63,7 +63,7 @@ class JamstackTest < MiniTest::Test
63
63
  req = mock_req(':method' => 'GET', ':path' => '/index')
64
64
  @jamstack.route_and_call(req)
65
65
 
66
- index = H {
66
+ index = Papercraft.html {
67
67
  html5 {
68
68
  head {
69
69
  title 'Hello'
@@ -86,7 +86,7 @@ class JamstackTest < MiniTest::Test
86
86
  req = mock_req(':method' => 'GET', ':path' => '/baz')
87
87
  @jamstack.route_and_call(req)
88
88
 
89
- baz_index = H {
89
+ baz_index = Papercraft.html {
90
90
  html5 {
91
91
  head {
92
92
  title 'BarBar'
@@ -101,14 +101,14 @@ class JamstackTest < MiniTest::Test
101
101
  req = mock_req(':method' => 'GET', ':path' => '/articles/a')
102
102
  @jamstack.route_and_call(req)
103
103
 
104
- a = H {
104
+ a = Papercraft.html {
105
105
  html5 {
106
106
  head {
107
107
  title 'AAA'
108
108
  }
109
109
  body {
110
110
  article {
111
- h2 'BBB', id: 'bbb'
111
+ h2 'ZZZ', id: 'zzz'
112
112
  }
113
113
  }
114
114
  }
@@ -116,7 +116,7 @@ class JamstackTest < MiniTest::Test
116
116
  assert_response a.render, :html, req
117
117
  end
118
118
 
119
- def test_non_root_file_tree_response
119
+ def test_non_root_jamstack_response
120
120
  @jamstack = Impression::Jamstack.new(path: '/app', directory: JAMSTACK_PATH)
121
121
 
122
122
  req = mock_req(':method' => 'GET', ':path' => '/app/roo')
@@ -138,7 +138,7 @@ class JamstackTest < MiniTest::Test
138
138
  req = mock_req(':method' => 'GET', ':path' => '/app/foo')
139
139
  @jamstack.route_and_call(req)
140
140
 
141
- foo = H {
141
+ foo = Papercraft.html {
142
142
  html5 {
143
143
  head {
144
144
  title 'Foo title'
@@ -153,7 +153,7 @@ class JamstackTest < MiniTest::Test
153
153
  req = mock_req(':method' => 'GET', ':path' => '/app/index')
154
154
  @jamstack.route_and_call(req)
155
155
 
156
- index = H {
156
+ index = Papercraft.html {
157
157
  html5 {
158
158
  head {
159
159
  title 'Hello'
@@ -176,7 +176,7 @@ class JamstackTest < MiniTest::Test
176
176
  req = mock_req(':method' => 'GET', ':path' => '/app/baz')
177
177
  @jamstack.route_and_call(req)
178
178
 
179
- baz_index = H {
179
+ baz_index = Papercraft.html {
180
180
  html5 {
181
181
  head {
182
182
  title 'BarBar'
@@ -191,18 +191,121 @@ class JamstackTest < MiniTest::Test
191
191
  req = mock_req(':method' => 'GET', ':path' => '/app/articles/a')
192
192
  @jamstack.route_and_call(req)
193
193
 
194
- a = H {
194
+ a = Papercraft.html {
195
195
  html5 {
196
196
  head {
197
197
  title 'AAA'
198
198
  }
199
199
  body {
200
200
  article {
201
- h2 'BBB', id: 'bbb'
201
+ h2 'ZZZ', id: 'zzz'
202
202
  }
203
203
  }
204
204
  }
205
205
  }
206
206
  assert_response a.render, :html, req
207
207
  end
208
+
209
+ def test_page_list
210
+ @jamstack = Impression::Jamstack.new(path: '/app', directory: JAMSTACK_PATH)
211
+
212
+ list = @jamstack.page_list('/')
213
+ assert_equal [
214
+ { kind: :file, path: File.join(JAMSTACK_PATH, 'bar.html'), ext: '.html', url: '/app/bar' },
215
+ { kind: :file, path: File.join(JAMSTACK_PATH, 'index.md'), ext: '.md', url: '/app',
216
+ title: 'Hello', foo: 'BarBar', html_content: "<h1>Index</h1>\n" },
217
+ ], list
218
+
219
+
220
+ list = @jamstack.page_list('/articles')
221
+
222
+ assert_equal [
223
+ {
224
+ kind: :file,
225
+ path: File.join(JAMSTACK_PATH, 'articles/2008-06-14-manu.md'),
226
+ url: '/app/articles/2008-06-14-manu',
227
+ ext: '.md',
228
+ title: 'MMM',
229
+ layout: 'article',
230
+ html_content: "<h2 id=\"bbb\">BBB</h2>\n",
231
+ date: Date.new(2008, 06, 14)
232
+ },
233
+ {
234
+ kind: :file,
235
+ path: File.join(JAMSTACK_PATH, 'articles/2009-06-12-noatche.md'),
236
+ url: '/app/articles/2009-06-12-noatche',
237
+ ext: '.md',
238
+ title: 'NNN',
239
+ layout: 'article',
240
+ html_content: "<h2 id=\"ccc\">CCC</h2>\n",
241
+ date: Date.new(2009, 06, 12)
242
+ },
243
+ {
244
+ kind: :file,
245
+ path: File.join(JAMSTACK_PATH, 'articles/a.md'),
246
+ url: '/app/articles/a',
247
+ ext: '.md',
248
+ title: 'AAA',
249
+ layout: 'article',
250
+ html_content: "<h2 id=\"zzz\">ZZZ</h2>\n",
251
+ },
252
+ ], list
253
+ end
254
+
255
+ def test_template_resource_and_request
256
+ req = mock_req(':method' => 'GET', ':path' => '/foobar?q=42')
257
+ @jamstack.route_and_call(req)
258
+
259
+ foo = Papercraft.html {
260
+ html5 {
261
+ head {
262
+ title 'Foobar'
263
+ }
264
+ body {
265
+ h1 '42'
266
+ a 'MMM', href: '/articles/2008-06-14-manu'
267
+ a 'NNN', href: '/articles/2009-06-12-noatche'
268
+ a 'AAA', href: '/articles/a'
269
+ }
270
+ }
271
+ }
272
+ assert_response foo.render, :html, req
273
+ end
274
+
275
+ def path_info(path)
276
+ @jamstack.send(:get_path_info, path)
277
+ end
278
+
279
+ def test_path_info
280
+ assert_equal({
281
+ kind: :file,
282
+ path: File.join(JAMSTACK_PATH, 'index.md'),
283
+ ext: '.md',
284
+ url: '/',
285
+ title: 'Hello',
286
+ foo: 'BarBar',
287
+ html_content: "<h1>Index</h1>\n"
288
+ }, path_info('/index'))
289
+
290
+ assert_equal({
291
+ kind: :file,
292
+ path: File.join(JAMSTACK_PATH, 'index.md'),
293
+ ext: '.md',
294
+ url: '/',
295
+ title: 'Hello',
296
+ foo: 'BarBar',
297
+ html_content: "<h1>Index</h1>\n"
298
+ }, path_info('/'))
299
+
300
+ assert_equal({
301
+ kind: :file,
302
+ path: File.join(JAMSTACK_PATH, 'assets/js/a.js'),
303
+ ext: '.js',
304
+ url: '/assets/js/a.js'
305
+ }, path_info('/assets/js/a.js'))
306
+
307
+ assert_equal({
308
+ kind: :not_found,
309
+ }, path_info('/js/b.js'))
310
+ end
208
311
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: impression
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: '0.7'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-19 00:00:00.000000000 Z
11
+ date: 2022-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: polyphony
@@ -44,70 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.15'
47
+ version: '0.16'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.15'
55
- - !ruby/object:Gem::Dependency
56
- name: kramdown
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 2.3.0
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 2.3.0
69
- - !ruby/object:Gem::Dependency
70
- name: rouge
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 3.26.0
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 3.26.0
83
- - !ruby/object:Gem::Dependency
84
- name: kramdown-parser-gfm
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: 1.1.0
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: 1.1.0
54
+ version: '0.16'
97
55
  - !ruby/object:Gem::Dependency
98
56
  name: papercraft
99
57
  requirement: !ruby/object:Gem::Requirement
100
58
  requirements:
101
59
  - - "~>"
102
60
  - !ruby/object:Gem::Version
103
- version: '0.14'
61
+ version: '0.17'
104
62
  type: :runtime
105
63
  prerelease: false
106
64
  version_requirements: !ruby/object:Gem::Requirement
107
65
  requirements:
108
66
  - - "~>"
109
67
  - !ruby/object:Gem::Version
110
- version: '0.14'
68
+ version: '0.17'
111
69
  - !ruby/object:Gem::Dependency
112
70
  name: modulation
113
71
  requirement: !ruby/object:Gem::Requirement
@@ -181,6 +139,7 @@ files:
181
139
  - README.md
182
140
  - Rakefile
183
141
  - TODO.md
142
+ - examples/hello.rb
184
143
  - examples/markdown/_assets/style.css
185
144
  - examples/markdown/app.rb
186
145
  - examples/markdown/docs/index.md
@@ -192,7 +151,6 @@ files:
192
151
  - lib/impression/file_tree.rb
193
152
  - lib/impression/file_watcher.rb
194
153
  - lib/impression/jamstack.rb
195
- - lib/impression/pages.rb
196
154
  - lib/impression/request_extensions.rb
197
155
  - lib/impression/request_extensions/responses.rb
198
156
  - lib/impression/request_extensions/routing.rb
@@ -201,11 +159,14 @@ files:
201
159
  - test/helper.rb
202
160
  - test/jamstack/_layouts/article.rb
203
161
  - test/jamstack/_layouts/default.rb
162
+ - test/jamstack/articles/2008-06-14-manu.md
163
+ - test/jamstack/articles/2009-06-12-noatche.md
204
164
  - test/jamstack/articles/a.md
205
165
  - test/jamstack/assets/js/a.js
206
166
  - test/jamstack/bar.html
207
167
  - test/jamstack/baz/index.md
208
168
  - test/jamstack/foo.rb
169
+ - test/jamstack/foobar.rb
209
170
  - test/jamstack/index.md
210
171
  - test/run.rb
211
172
  - test/static/bar/index.html
@@ -222,6 +183,9 @@ licenses:
222
183
  - MIT
223
184
  metadata:
224
185
  source_code_uri: https://github.com/digital-fabric/impression
186
+ documentation_uri: https://www.rubydoc.info/gems/impression
187
+ homepage_uri: https://github.com/digital-fabric/impression
188
+ changelog_uri: https://github.com/digital-fabric/impression/blob/master/CHANGELOG.md
225
189
  post_install_message:
226
190
  rdoc_options:
227
191
  - "--title"
@@ -1,239 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'kramdown'
4
- require 'rouge'
5
- require 'kramdown-parser-gfm'
6
- require 'yaml'
7
- require 'rb-inotify'
8
- require 'papercraft'
9
-
10
- require_relative './errors'
11
-
12
- module Impression
13
- class Pages
14
- module RequestMethods
15
- def serve_page(pages)
16
- pages.serve(self)
17
- end
18
- end
19
-
20
- def initialize(base_path, opts = {})
21
- @base_path = base_path
22
- @opts = opts
23
- @opts[:pages] = self
24
-
25
- load
26
-
27
- if opts[:auto_reload]
28
- start_automatic_reloader
29
- end
30
- end
31
-
32
- def start_automatic_reloader
33
- notifier = INotify::Notifier.new
34
- watched = {}
35
- @map.each_value do |entry|
36
- path = entry[:full_path]
37
- next if watched[path]
38
-
39
- notifier.watch(path, :modify, :delete_self) { |e| handle_changed_file(path) }
40
- watched[path] = true
41
- end
42
- notifier.watch(@base_path, :moved_to, :create) do |event|
43
- path = event.absolute_name
44
- if File.file?(path)
45
- notifier.watch(path, :modify, :delete_self) { |e| handle_changed_file(path) }
46
- end
47
- handle_changed_file(path)
48
- end
49
- @reloader = spin do
50
- notify_io = notifier.to_io
51
- loop do
52
- notify_io.wait_readable
53
- notifier.process
54
- end
55
- end
56
- end
57
-
58
- def handle_changed_file(full_path)
59
- p handle_changed_file: full_path
60
- if !File.file?(full_path)
61
- @map.reject! { |k, v| v[:full_path] == full_path }
62
- return
63
- end
64
-
65
- path = File.basename(full_path)
66
- page = Page.new(path, full_path, @opts)
67
- permalink = page.permalink
68
- @map[permalink] = { page: page, full_path: full_path }
69
- @map['/'] = @map[permalink] if permalink == '/index'
70
- end
71
-
72
- def load
73
- @map = {}
74
- Dir['**/*', base: @base_path].each do |path|
75
- next if path =~ /\/_.+/
76
-
77
- full_path = File.join(@base_path, path)
78
- next unless File.file?(full_path)
79
-
80
- page = Page.new(path, full_path, @opts)
81
- @map[page.permalink] = { page: page, full_path: full_path }
82
- end
83
- @map['/'] = @map['/index']
84
- end
85
- alias_method :reload, :load
86
-
87
- def prev_page(page)
88
- keys = @map.keys
89
- case idx = keys.index(page.permalink)
90
- when 0, nil
91
- nil
92
- else
93
- @map[keys[idx - 1]][:page]
94
- end
95
- end
96
-
97
- def next_page(page)
98
- keys = @map.keys
99
- case idx = keys.index(page.permalink)
100
- when keys.size - 1, nil
101
- nil
102
- else
103
- @map[keys[idx + 1]][:page]
104
- end
105
- end
106
-
107
- def load_file(path)
108
- content = IO.read(path)
109
- end
110
-
111
- def serve(req)
112
- entry = @map[req.route_relative_path]
113
- raise NotFoundError unless entry
114
-
115
- body = render_page(entry[:page])
116
- req.respond(body, 'Content-Type' => 'text/html')
117
- rescue NotFoundError => e
118
- req.respond('Not found.', ':status' => e.http_status)
119
- end
120
- alias_method :call, :serve
121
-
122
- def render_page(page)
123
- layout_proc(page.layout).().render(pages: self, page: page)
124
- end
125
-
126
- def layout_proc(layout)
127
- full_path = File.expand_path("../_layouts/#{layout}.rb", @base_path)
128
- instance_eval("->(&block) do; #{IO.read(full_path)}; end", full_path)
129
- end
130
-
131
- def select(selector)
132
- @map.inject([]) do |array, (permalink, entry)|
133
- array << entry[:page] if permalink =~ selector
134
- array
135
- end
136
- end
137
- end
138
-
139
- class Page
140
- attr_reader :attributes
141
-
142
- def initialize(path, full_path, opts = {})
143
- @path = path
144
- @full_path = full_path
145
- @opts = opts
146
- @kind = detect_page_kind(full_path)
147
- read_page
148
- end
149
-
150
- EXTNAME_REGEXP = /^\.(.+)$/.freeze
151
-
152
- def detect_page_kind(path)
153
- File.extname(path).match(EXTNAME_REGEXP)[1].to_sym
154
- end
155
-
156
- PAGE_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m.freeze
157
-
158
- def read_page
159
- data = IO.read(@full_path) || ''
160
- if data =~ PAGE_REGEXP
161
- front_matter = Regexp.last_match(1)
162
- @content_start_line = front_matter.lines.size + 2
163
- @attributes = YAML.load(front_matter)
164
- @content = Regexp.last_match.post_match
165
- else
166
- @attributes = {}
167
- @content_start_line = 1
168
- @content = data
169
- end
170
- end
171
-
172
- def permalink
173
- @permalink = @attributes[:permalink] || path_without_extension
174
- end
175
-
176
- def path_without_extension
177
- "/#{@path.delete_suffix(File.extname(@path))}"
178
- end
179
-
180
- def title
181
- @attributes['title'] || title_from_content
182
- end
183
-
184
- def prev_page
185
- @opts[:pages].prev_page(self)
186
- end
187
-
188
- def next_page
189
- @opts[:pages].next_page(self)
190
- end
191
-
192
- TITLE_REGEXP = /^#\s+([^\n]+)/.freeze
193
-
194
- def title_from_content
195
- (@content =~ TITLE_REGEXP) && Regexp.last_match(1)
196
- end
197
-
198
- def status
199
- @attributes['status'] || Qeweney::Status::OK
200
- end
201
-
202
- def layout
203
- layout = @attributes['layout'] || 'default'
204
- end
205
-
206
- def render
207
- case @kind
208
- when :md
209
- render_markdown
210
- when :rb
211
- render_papercraft
212
- else
213
- raise "Invalid page kind #{kind.inspect}"
214
- end
215
- end
216
-
217
- def render_markdown
218
- Kramdown::Document.new(@content, **kramdown_options).to_html
219
- end
220
-
221
- def kramdown_options
222
- {
223
- entity_output: :numeric,
224
- syntax_highlighter: :rouge,
225
- input: 'GFM',
226
- hard_wrap: false
227
- }
228
- end
229
-
230
- def render_papercraft
231
- proc = instance_eval("->(&block) do; #{@content}; end", @full_path, @content_start_line)
232
- proc.().render(page: self, pages: @opts[:pages])
233
- end
234
- end
235
- end
236
-
237
- class Qeweney::Request
238
- include Impression::Pages::RequestMethods
239
- end