sitepress-core 4.1.1 → 5.0.0.beta2
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/lib/sitepress/data.rb +17 -14
- data/lib/sitepress/directory.rb +56 -0
- data/lib/sitepress/image.rb +53 -0
- data/lib/sitepress/node.rb +6 -0
- data/lib/sitepress/page.rb +107 -0
- data/lib/sitepress/parsers/base.rb +6 -0
- data/lib/sitepress/parsers/frontmatter.rb +9 -13
- data/lib/sitepress/parsers/notion.rb +8 -0
- data/lib/sitepress/path.rb +0 -8
- data/lib/sitepress/resource.rb +71 -13
- data/lib/sitepress/resource_indexer.rb +5 -2
- data/lib/sitepress/resources_pipeline.rb +0 -2
- data/lib/sitepress/site.rb +4 -4
- data/lib/sitepress/static.rb +45 -0
- data/lib/sitepress/version.rb +1 -1
- data/lib/sitepress-core.rb +9 -2
- data/sitepress-core.gemspec +1 -0
- metadata +20 -4
- data/lib/sitepress/asset.rb +0 -107
- data/lib/sitepress/asset_node_mapper.rb +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2fb8dab8d987a3743411bfbdfd9acb8311988c609f903558fa5f21f05008325
|
|
4
|
+
data.tar.gz: 9af2d1acb781fbbaa6ba63ff0b43a4eca27be8db94cde531689e573298b89c38
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 49778c133de40cd3a8f8aed1826ba742b1d6e4c7440b2076178ed18f6724cf1dbab3c78dd97e25536a547e0fecedf20233962a39198e962b7aac9a2cf8cd62b0
|
|
7
|
+
data.tar.gz: 523c0c1d7394fb75ff8864f206ca5c2891d712db1572f82bd8d722b0810fe3c910a346644817dba744d88621d513119f9cd7709b345095c10400b3c8934a6dd8
|
data/lib/sitepress/data.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
1
3
|
module Sitepress
|
|
2
4
|
module Data
|
|
3
5
|
def self.manage(value)
|
|
@@ -44,7 +46,7 @@ module Sitepress
|
|
|
44
46
|
def_delegators :@hash, :keys, :values, :key?
|
|
45
47
|
|
|
46
48
|
def initialize(hash)
|
|
47
|
-
@hash = hash
|
|
49
|
+
@hash = hash || {}
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def fetch(key, *args, &block)
|
|
@@ -70,22 +72,23 @@ module Sitepress
|
|
|
70
72
|
end
|
|
71
73
|
|
|
72
74
|
def method_missing(name, *args, **kwargs, &block)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
self.fetch(key, *args, &block)
|
|
83
|
-
when "?"
|
|
84
|
-
!!self[key]
|
|
85
|
-
end
|
|
75
|
+
key, modifier, _ = name.to_s.partition(/[!?]/)
|
|
76
|
+
|
|
77
|
+
case modifier
|
|
78
|
+
when ""
|
|
79
|
+
self[key]
|
|
80
|
+
when "!"
|
|
81
|
+
self.fetch(key, *args, &block)
|
|
82
|
+
when "?"
|
|
83
|
+
!!self[key]
|
|
86
84
|
end
|
|
87
85
|
end
|
|
88
86
|
|
|
87
|
+
def respond_to_missing?(name, include_private = false)
|
|
88
|
+
key, _, _ = name.to_s.partition(/[!?]/)
|
|
89
|
+
@hash.key?(key) || @hash.key?(key.to_sym) || super
|
|
90
|
+
end
|
|
91
|
+
|
|
89
92
|
def dig(*args, **kwargs, &block)
|
|
90
93
|
Data.manage @hash.dig(*args, **kwargs, &block)
|
|
91
94
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require "mime/types"
|
|
2
|
+
|
|
3
|
+
module Sitepress
|
|
4
|
+
# Maps a directory of files into a tree of nodes that form the navigational
|
|
5
|
+
# structure of a website. You can subclass this to handle different file
|
|
6
|
+
# types or directory structures.
|
|
7
|
+
class Directory
|
|
8
|
+
attr_reader :asset_paths
|
|
9
|
+
|
|
10
|
+
def initialize(path)
|
|
11
|
+
@asset_paths = AssetPaths.new(path: path)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Mounts the source files from the path into the given node.
|
|
15
|
+
def mount(node)
|
|
16
|
+
asset_paths.each do |path|
|
|
17
|
+
if path.directory?
|
|
18
|
+
process_directory path, node
|
|
19
|
+
else
|
|
20
|
+
process_asset path, node
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def process_directory(path, node)
|
|
28
|
+
node_name = File.basename path
|
|
29
|
+
self.class.new(path).mount(node.child(node_name))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def process_asset(path, node)
|
|
33
|
+
source = source_for(path)
|
|
34
|
+
# Parse the path to get node_name and format for tree building
|
|
35
|
+
parsed_path = Path.new(path.to_s)
|
|
36
|
+
node.child(parsed_path.node_name).resources.add_asset(source, format: parsed_path.format)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def source_for(path)
|
|
40
|
+
mime = MIME::Types.type_for(path.to_s).first&.content_type
|
|
41
|
+
|
|
42
|
+
case mime
|
|
43
|
+
when *Image.mime_types
|
|
44
|
+
Image.new(path: path)
|
|
45
|
+
when *Page.mime_types, nil
|
|
46
|
+
# nil handles template files like .erb that have no MIME type
|
|
47
|
+
Page.new(path: path)
|
|
48
|
+
else
|
|
49
|
+
Static.new(path: path)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Backwards compatibility
|
|
55
|
+
AssetNodeMapper = Directory
|
|
56
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require "fastimage"
|
|
2
|
+
|
|
3
|
+
module Sitepress
|
|
4
|
+
# A source for image files. Extracts dimensions via fastimage.
|
|
5
|
+
#
|
|
6
|
+
# Example:
|
|
7
|
+
# image = Image.new(path: "photos/sunset.jpg")
|
|
8
|
+
# image.width # => 1920
|
|
9
|
+
# image.height # => 1080
|
|
10
|
+
# image.data["width"] # => 1920
|
|
11
|
+
#
|
|
12
|
+
class Image < Static
|
|
13
|
+
MIME_TYPES = %w[image/png image/jpeg image/gif image/webp].freeze
|
|
14
|
+
|
|
15
|
+
def self.mime_types
|
|
16
|
+
MIME_TYPES
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def filename
|
|
20
|
+
path.basename.to_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def size
|
|
24
|
+
exists? ? File.size(path) : nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def width
|
|
28
|
+
dimensions[0]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def height
|
|
32
|
+
dimensions[1]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def data
|
|
36
|
+
@data ||= Data.manage({
|
|
37
|
+
"width" => width,
|
|
38
|
+
"height" => height
|
|
39
|
+
}.compact)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def dimensions
|
|
45
|
+
@dimensions ||= begin
|
|
46
|
+
return [nil, nil] unless exists?
|
|
47
|
+
FastImage.size(path.to_s) || [nil, nil]
|
|
48
|
+
rescue
|
|
49
|
+
[nil, nil]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/sitepress/node.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
1
3
|
module Sitepress
|
|
2
4
|
# Resource nodes give resources their parent/sibling/child relationships. The relationship are determined
|
|
3
5
|
# by the `request_path` given to an asset when its added to a node. Given the `request_path` `/foo/bar/biz/buz.html`,
|
|
@@ -96,6 +98,10 @@ module Sitepress
|
|
|
96
98
|
"<#{self.class}: name=#{name.inspect}, formats=#{formats.inspect}, children=#{children.map(&:name).inspect}, resource_request_paths=#{resources.map(&:request_path)}>"
|
|
97
99
|
end
|
|
98
100
|
|
|
101
|
+
def <<(mountable)
|
|
102
|
+
mountable.mount(self)
|
|
103
|
+
end
|
|
104
|
+
|
|
99
105
|
def dig(*args)
|
|
100
106
|
head, *tail = args
|
|
101
107
|
if (head.nil? or head.empty? or head == default_name) and tail.empty?
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "forwardable"
|
|
3
|
+
|
|
4
|
+
module Sitepress
|
|
5
|
+
# A source for text-based files that may have frontmatter.
|
|
6
|
+
# Handles parsing of YAML frontmatter and provides access to data and body.
|
|
7
|
+
class Page < Static
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
10
|
+
# MIME types that Page can handle - text-based content that may have frontmatter
|
|
11
|
+
MIME_TYPES = %w[
|
|
12
|
+
text/html
|
|
13
|
+
text/plain
|
|
14
|
+
text/markdown
|
|
15
|
+
text/x-web-markdown
|
|
16
|
+
text/css
|
|
17
|
+
text/javascript
|
|
18
|
+
application/json
|
|
19
|
+
application/xml
|
|
20
|
+
text/xml
|
|
21
|
+
image/svg+xml
|
|
22
|
+
text/x-haml
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
def self.mime_types
|
|
26
|
+
MIME_TYPES
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Parsers can be swapped out to deal with different types of resources, like Notion
|
|
30
|
+
# documents, JSON, exif data on images, etc.
|
|
31
|
+
DEFAULT_PARSER = Parsers::Frontmatter
|
|
32
|
+
|
|
33
|
+
attr_writer :body
|
|
34
|
+
|
|
35
|
+
def_delegator :renderer, :render, :serialize
|
|
36
|
+
|
|
37
|
+
def initialize(path:, parser: DEFAULT_PARSER)
|
|
38
|
+
super(path: path)
|
|
39
|
+
@parser_klass = parser
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def data
|
|
43
|
+
@data ||= Data.manage(exists? ? parse_error { parser.data } : {})
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def data=(data)
|
|
47
|
+
@data = Data.manage(data)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def body
|
|
51
|
+
@body ||= exists? ? parse_error { parser.body } : nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns the line number where the body starts in the original file.
|
|
55
|
+
# Used to adjust error line numbers when frontmatter is present.
|
|
56
|
+
def body_line_offset
|
|
57
|
+
exists? ? parser.body_line_offset : 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Treat sources with the same path as equal.
|
|
61
|
+
def ==(other)
|
|
62
|
+
path == other.path
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# When changing the parser, clear all cached parsed data.
|
|
66
|
+
def parser=(parser_klass)
|
|
67
|
+
@parser = nil
|
|
68
|
+
@data = nil
|
|
69
|
+
@body = nil
|
|
70
|
+
@parser_klass = parser_klass
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def updated_at
|
|
74
|
+
File.mtime path
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def created_at
|
|
78
|
+
File.ctime path
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def destroy
|
|
82
|
+
FileUtils.rm path
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def save
|
|
86
|
+
File.write path, serialize
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def renderer
|
|
90
|
+
@parser_klass::Renderer.new(data: data, body: body)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
def parse_error(&parse)
|
|
95
|
+
parse.call
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
raise ParseError, "Error parsing #{File.expand_path(path)}: #{e.class} - #{e.message}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parser
|
|
101
|
+
@parser ||= @parser_klass.new File.read path
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Backwards compatibility
|
|
106
|
+
Asset = Page
|
|
107
|
+
end
|
|
@@ -10,6 +10,12 @@ module Sitepress
|
|
|
10
10
|
@body = source
|
|
11
11
|
@data = {}
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
# Returns the line number where the body starts in the original file.
|
|
15
|
+
# Subclasses should override this if they strip content from the beginning.
|
|
16
|
+
def body_line_offset
|
|
17
|
+
1
|
|
18
|
+
end
|
|
13
19
|
end
|
|
14
20
|
end
|
|
15
21
|
end
|
|
@@ -14,12 +14,7 @@ module Sitepress
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def dump_yaml(data)
|
|
17
|
-
|
|
18
|
-
YAML.safe_dump data, permitted_classes: Frontmatter.permitted_classes
|
|
19
|
-
else
|
|
20
|
-
# Live dangerously, lol
|
|
21
|
-
YAML.dump data
|
|
22
|
-
end
|
|
17
|
+
YAML.safe_dump data, permitted_classes: Frontmatter.permitted_classes
|
|
23
18
|
end
|
|
24
19
|
|
|
25
20
|
def render
|
|
@@ -46,7 +41,13 @@ module Sitepress
|
|
|
46
41
|
PATTERN = /\A(#{DELIMITER}#{NEWLINE}(.+?)#{NEWLINE}#{DELIMITER}#{NEWLINE}*)?(.+)\Z/m
|
|
47
42
|
|
|
48
43
|
def initialize(content)
|
|
49
|
-
|
|
44
|
+
@frontmatter_block, @data, @body = content.match(PATTERN).captures
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the line number where the body starts in the original file.
|
|
48
|
+
def body_line_offset
|
|
49
|
+
return 1 unless @frontmatter_block
|
|
50
|
+
@frontmatter_block.count("\n") + 1
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def data
|
|
@@ -54,12 +55,7 @@ module Sitepress
|
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
def load_yaml(data)
|
|
57
|
-
|
|
58
|
-
YAML.safe_load data, permitted_classes: self.class.permitted_classes
|
|
59
|
-
else
|
|
60
|
-
# Live dangerously, lol
|
|
61
|
-
YAML.load data
|
|
62
|
-
end
|
|
58
|
+
YAML.safe_load data, permitted_classes: self.class.permitted_classes
|
|
63
59
|
end
|
|
64
60
|
|
|
65
61
|
class << self
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "yaml"
|
|
2
|
+
require "strscan"
|
|
2
3
|
|
|
3
4
|
module Sitepress
|
|
4
5
|
module Parsers
|
|
@@ -19,10 +20,17 @@ module Sitepress
|
|
|
19
20
|
@raw_data.append scanner.captures
|
|
20
21
|
end
|
|
21
22
|
scanner.scan(/\n/)
|
|
23
|
+
# Track where body starts for line number offset
|
|
24
|
+
@header_content = content[0, scanner.pos]
|
|
22
25
|
# Parse body
|
|
23
26
|
@body = scanner.rest
|
|
24
27
|
end
|
|
25
28
|
|
|
29
|
+
# Returns the line number where the body starts in the original file.
|
|
30
|
+
def body_line_offset
|
|
31
|
+
@header_content.count("\n") + 1
|
|
32
|
+
end
|
|
33
|
+
|
|
26
34
|
def data
|
|
27
35
|
Hash[@raw_data.prepend([TITLE_KEY, @title])]
|
|
28
36
|
end
|
data/lib/sitepress/path.rb
CHANGED
|
@@ -23,14 +23,6 @@ module Sitepress
|
|
|
23
23
|
action_view_template_handlers_extensions || HANDLER_EXTENSIONS
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# I tried to hook this into Rails engines in the `config.after_initialize` block,
|
|
27
|
-
# but the way template handlers register their extensions is across the board.
|
|
28
|
-
#
|
|
29
|
-
# config.after_initialize do
|
|
30
|
-
# Sitepress::Path.handler_extensions = ActionView::Template::Handlers.method(:extensions)
|
|
31
|
-
# ends
|
|
32
|
-
#
|
|
33
|
-
# I couldn't get that working, instead I do this check to find the handlers.
|
|
34
26
|
def action_view_template_handlers_extensions
|
|
35
27
|
ActionView::Template::Handlers.extensions if defined?(ActionView::Template::Handlers)
|
|
36
28
|
end
|
data/lib/sitepress/resource.rb
CHANGED
|
@@ -1,26 +1,70 @@
|
|
|
1
1
|
require "forwardable"
|
|
2
|
+
require "mime/types"
|
|
2
3
|
|
|
3
4
|
module Sitepress
|
|
4
|
-
# Represents the
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# Represents the web-facing view of a source file. A Resource wraps a source
|
|
6
|
+
# and adds web-serving concerns: request path, handler, format, and rendering.
|
|
7
|
+
#
|
|
8
|
+
# Source = file on disk (Pathname)
|
|
9
|
+
# Resource = web representation (Path parsing, rendering)
|
|
7
10
|
class Resource
|
|
8
11
|
extend Forwardable
|
|
9
|
-
def_delegators :
|
|
12
|
+
def_delegators :source, :body
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
# If we can't resolve a mime type for the resource, we'll fall
|
|
15
|
+
# back to this binary octet-stream type so the client can download
|
|
16
|
+
# the resource and figure out what to do with it.
|
|
17
|
+
DEFAULT_MIME_TYPE = MIME::Types["application/octet-stream"].first
|
|
18
|
+
|
|
19
|
+
attr_reader :node, :source, :source_path
|
|
20
|
+
|
|
21
|
+
alias :asset :source # Backwards compatibility
|
|
22
|
+
|
|
23
|
+
# Check if the source implements the data protocol.
|
|
24
|
+
def has_data?
|
|
25
|
+
source.respond_to?(:data)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Delegate to source.
|
|
29
|
+
def data
|
|
30
|
+
has_data? ? source.data : Data.manage({})
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Delegate to source.
|
|
34
|
+
def fetch_data(key, *args, &block)
|
|
35
|
+
source.fetch_data(key, *args, &block)
|
|
36
|
+
end
|
|
12
37
|
|
|
13
38
|
attr_accessor :format, :mime_type, :handler
|
|
14
39
|
|
|
15
40
|
# Default scope for querying parent/child/sibling resources.
|
|
16
41
|
DEFAULT_FILTER_SCOPE = :same
|
|
17
42
|
|
|
18
|
-
def initialize(asset
|
|
19
|
-
@
|
|
43
|
+
def initialize(asset: nil, source: nil, node:, format: nil, mime_type: nil, handler: nil)
|
|
44
|
+
@source = source || asset
|
|
45
|
+
raise ArgumentError, "Either asset: or source: must be provided" unless @source
|
|
20
46
|
@node = node
|
|
21
|
-
|
|
22
|
-
@
|
|
23
|
-
|
|
47
|
+
# Parse the source path to extract handler, format, node_name (if source has path)
|
|
48
|
+
if @source.respond_to?(:path)
|
|
49
|
+
@source_path = Path.new(@source.path.to_s)
|
|
50
|
+
@format = format || @source_path.format
|
|
51
|
+
@handler = handler || @source_path.handler
|
|
52
|
+
else
|
|
53
|
+
@source_path = nil
|
|
54
|
+
@format = format
|
|
55
|
+
@handler = handler
|
|
56
|
+
end
|
|
57
|
+
@mime_type = mime_type || inferred_mime_type || DEFAULT_MIME_TYPE
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The node name is derived from the source path
|
|
61
|
+
def node_name
|
|
62
|
+
@source_path&.node_name
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Whether this resource can be rendered (has a template handler)
|
|
66
|
+
def renderable?
|
|
67
|
+
!!handler
|
|
24
68
|
end
|
|
25
69
|
|
|
26
70
|
def request_path
|
|
@@ -71,7 +115,7 @@ module Sitepress
|
|
|
71
115
|
# Clones should be initialized with a nil node. Initializing with a node would mean that multiple resources
|
|
72
116
|
# are pointing to the same node, which shouldn't be possible.
|
|
73
117
|
def clone
|
|
74
|
-
self.class.new(
|
|
118
|
+
self.class.new(source: @source, node: nil, format: @format, mime_type: @mime_type, handler: @handler)
|
|
75
119
|
end
|
|
76
120
|
|
|
77
121
|
# Removes the resource from the node's resources list.
|
|
@@ -80,7 +124,7 @@ module Sitepress
|
|
|
80
124
|
end
|
|
81
125
|
|
|
82
126
|
def inspect
|
|
83
|
-
"
|
|
127
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} request_path=#{request_path.inspect} source=#{source.inspect}>"
|
|
84
128
|
end
|
|
85
129
|
|
|
86
130
|
def parent(**args)
|
|
@@ -108,8 +152,16 @@ module Sitepress
|
|
|
108
152
|
node.parents.reject(&:root?).reverse.map(&:name)
|
|
109
153
|
end
|
|
110
154
|
|
|
155
|
+
# Renders the resource in a view context using the appropriate template handler.
|
|
111
156
|
def render_in(view_context)
|
|
112
|
-
|
|
157
|
+
return nil unless renderable?
|
|
158
|
+
template = ActionView::Template.new(
|
|
159
|
+
body,
|
|
160
|
+
source.path.to_s,
|
|
161
|
+
ActionView::Template.handler_for_extension(handler),
|
|
162
|
+
locals: []
|
|
163
|
+
)
|
|
164
|
+
template.render(view_context, {})
|
|
113
165
|
end
|
|
114
166
|
|
|
115
167
|
private
|
|
@@ -135,6 +187,12 @@ module Sitepress
|
|
|
135
187
|
end
|
|
136
188
|
end
|
|
137
189
|
|
|
190
|
+
# Returns the mime type inferred from the format extension
|
|
191
|
+
def inferred_mime_type
|
|
192
|
+
format_extension = format&.to_s
|
|
193
|
+
MIME::Types.type_for(format_extension).first if format_extension
|
|
194
|
+
end
|
|
195
|
+
|
|
138
196
|
# Deals with situations, particularly in the root node and other "index" nodes, for the `request_path`
|
|
139
197
|
def request_filename
|
|
140
198
|
if node.root? and node.default_format == format
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
require "set"
|
|
3
|
+
|
|
1
4
|
module Sitepress
|
|
2
5
|
# Flattens a tree of Sitepress::Node and Sitepress:Resource classes into a collection of
|
|
3
6
|
# resources that can be quickly globbed, queried, or accessed.
|
|
@@ -17,8 +20,8 @@ module Sitepress
|
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def glob(pattern)
|
|
20
|
-
paths = Dir.glob
|
|
21
|
-
resources.select { |r| paths.include?
|
|
23
|
+
paths = Dir.glob(root_path.join(pattern)).to_set
|
|
24
|
+
resources.select { |r| paths.include?(r.asset.path.to_s) }
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def get(request_path)
|
data/lib/sitepress/site.rb
CHANGED
|
@@ -19,18 +19,18 @@ module Sitepress
|
|
|
19
19
|
self.root_path = root_path
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
# A tree representation of the
|
|
22
|
+
# A tree representation of the resources within the site. The root is a node that's
|
|
23
23
|
# processed by the `resources_pipeline`.
|
|
24
24
|
def root
|
|
25
25
|
@root ||= Node.new.tap do |root|
|
|
26
|
-
asset_node_mapper
|
|
26
|
+
root << asset_node_mapper
|
|
27
27
|
resources_pipeline.process root
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Maps a path of directories and files into the root node.
|
|
32
|
-
def asset_node_mapper
|
|
33
|
-
|
|
32
|
+
def asset_node_mapper
|
|
33
|
+
Directory.new(pages_path)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Returns a list of all the resources within #root.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require "mime/types"
|
|
2
|
+
|
|
3
|
+
module Sitepress
|
|
4
|
+
# Base class for source files. A source represents a file on disk
|
|
5
|
+
# without any web-serving concerns (handlers, formats, etc.).
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
# source = Static.new(path: "fonts/roboto.woff2")
|
|
9
|
+
# source.mime_type # => #<MIME::Type font/woff2>
|
|
10
|
+
# source.body # => binary content
|
|
11
|
+
#
|
|
12
|
+
class Static
|
|
13
|
+
attr_reader :path
|
|
14
|
+
|
|
15
|
+
def initialize(path:)
|
|
16
|
+
@path = Pathname.new(path)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def exists?
|
|
20
|
+
path.exist?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def mime_type
|
|
24
|
+
MIME::Types.type_for(path.to_s).first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def body
|
|
28
|
+
File.binread(path)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def data
|
|
32
|
+
@data ||= Data.manage({})
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fetch_data(key, *args, &block)
|
|
36
|
+
data.fetch(key, *args, &block)
|
|
37
|
+
rescue KeyError
|
|
38
|
+
raise KeyError, "key not found: #{key.inspect} in #{path}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inspect
|
|
42
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} path=#{path.to_s.inspect}>"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/sitepress/version.rb
CHANGED
data/lib/sitepress-core.rb
CHANGED
|
@@ -4,17 +4,23 @@ module Sitepress
|
|
|
4
4
|
# Errors raised by Sitepress
|
|
5
5
|
Error = Class.new(StandardError)
|
|
6
6
|
|
|
7
|
+
# Raised when an asset fails to parse (e.g., invalid YAML frontmatter)
|
|
8
|
+
ParseError = Class.new(Error)
|
|
9
|
+
|
|
7
10
|
# Raised by Resources if a path is added that's not a valid path.
|
|
8
11
|
InvalidRequestPathError = Class.new(RuntimeError)
|
|
9
12
|
|
|
10
13
|
# Raised by Resources if a path is already in its index
|
|
11
14
|
ExistingRequestPathError = Class.new(InvalidRequestPathError)
|
|
12
15
|
|
|
13
|
-
autoload :Asset, "sitepress/
|
|
14
|
-
autoload :AssetNodeMapper, "sitepress/
|
|
16
|
+
autoload :Asset, "sitepress/page" # Backwards compatibility
|
|
17
|
+
autoload :AssetNodeMapper, "sitepress/directory" # Backwards compatibility
|
|
15
18
|
autoload :AssetPaths, "sitepress/asset_paths"
|
|
19
|
+
autoload :Directory, "sitepress/directory"
|
|
16
20
|
autoload :Data, "sitepress/data"
|
|
21
|
+
autoload :Image, "sitepress/image"
|
|
17
22
|
autoload :Node, "sitepress/node"
|
|
23
|
+
autoload :Page, "sitepress/page"
|
|
18
24
|
autoload :Path, "sitepress/path"
|
|
19
25
|
autoload :Parsers, "sitepress/parsers"
|
|
20
26
|
autoload :Resource, "sitepress/resource"
|
|
@@ -22,4 +28,5 @@ module Sitepress
|
|
|
22
28
|
autoload :ResourceIndexer, "sitepress/resource_indexer"
|
|
23
29
|
autoload :ResourcesPipeline, "sitepress/resources_pipeline"
|
|
24
30
|
autoload :Site, "sitepress/site"
|
|
31
|
+
autoload :Static, "sitepress/static"
|
|
25
32
|
end
|
data/sitepress-core.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sitepress-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.0.beta2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brad Gessler
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-03-04 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: bundler
|
|
@@ -65,6 +65,20 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '2.99'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: fastimage
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '2.0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '2.0'
|
|
68
82
|
email:
|
|
69
83
|
- bradgessler@gmail.com
|
|
70
84
|
executables: []
|
|
@@ -72,13 +86,14 @@ extensions: []
|
|
|
72
86
|
extra_rdoc_files: []
|
|
73
87
|
files:
|
|
74
88
|
- lib/sitepress-core.rb
|
|
75
|
-
- lib/sitepress/asset.rb
|
|
76
|
-
- lib/sitepress/asset_node_mapper.rb
|
|
77
89
|
- lib/sitepress/asset_paths.rb
|
|
78
90
|
- lib/sitepress/data.rb
|
|
91
|
+
- lib/sitepress/directory.rb
|
|
79
92
|
- lib/sitepress/extensions/layouts.rb
|
|
80
93
|
- lib/sitepress/extensions/proc_manipulator.rb
|
|
94
|
+
- lib/sitepress/image.rb
|
|
81
95
|
- lib/sitepress/node.rb
|
|
96
|
+
- lib/sitepress/page.rb
|
|
82
97
|
- lib/sitepress/parsers.rb
|
|
83
98
|
- lib/sitepress/parsers/base.rb
|
|
84
99
|
- lib/sitepress/parsers/frontmatter.rb
|
|
@@ -89,6 +104,7 @@ files:
|
|
|
89
104
|
- lib/sitepress/resources.rb
|
|
90
105
|
- lib/sitepress/resources_pipeline.rb
|
|
91
106
|
- lib/sitepress/site.rb
|
|
107
|
+
- lib/sitepress/static.rb
|
|
92
108
|
- lib/sitepress/version.rb
|
|
93
109
|
- sitepress-core.gemspec
|
|
94
110
|
homepage: https://sitepress.cc/
|
data/lib/sitepress/asset.rb
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
require "mime/types"
|
|
2
|
-
require "forwardable"
|
|
3
|
-
require "fileutils"
|
|
4
|
-
|
|
5
|
-
module Sitepress
|
|
6
|
-
# Represents a file on a web server that may be parsed to extract
|
|
7
|
-
# metadata or be renderable via a template. Multiple resources
|
|
8
|
-
# may point to the same asset. Properties of an asset should be mutable.
|
|
9
|
-
# The Resource object is immutable and may be modified by the Resources proxy.
|
|
10
|
-
class Asset
|
|
11
|
-
# If we can't resolve a mime type for the resource, we'll fall
|
|
12
|
-
# back to this binary octet-stream type so the client can download
|
|
13
|
-
# the resource and figure out what to do with it.
|
|
14
|
-
DEFAULT_MIME_TYPE = MIME::Types["application/octet-stream"].first
|
|
15
|
-
|
|
16
|
-
# Parsers can be swapped out to deal with different types of resources, like Notion
|
|
17
|
-
# documents, JSON, exif data on images, etc.
|
|
18
|
-
DEFAULT_PARSER = Parsers::Frontmatter
|
|
19
|
-
|
|
20
|
-
attr_reader :path
|
|
21
|
-
attr_writer :body
|
|
22
|
-
|
|
23
|
-
extend Forwardable
|
|
24
|
-
def_delegators :renderer, :render
|
|
25
|
-
def_delegators :path, :handler, :node_name, :format, :exists?
|
|
26
|
-
|
|
27
|
-
def initialize(path:, mime_type: nil, parser: DEFAULT_PARSER)
|
|
28
|
-
# The MIME::Types gem returns an array when types are looked up.
|
|
29
|
-
# This grabs the first one, which is likely the intent on these lookups.
|
|
30
|
-
@mime_type = Array(mime_type).first
|
|
31
|
-
@path = Path.new path
|
|
32
|
-
@parser_klass = parser
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def data
|
|
36
|
-
@data ||= Data.manage(exists? ? parse_error { parser.data } : {})
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def data=(data)
|
|
40
|
-
@data = Data.manage(data)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def body
|
|
44
|
-
@body ||= exists? ? parse_error { parser.body } : nil
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Treat resources with the same request path as equal.
|
|
48
|
-
def ==(asset)
|
|
49
|
-
path == asset.path
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def mime_type
|
|
53
|
-
@mime_type ||= inferred_mime_type || DEFAULT_MIME_TYPE
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Certain files, like binary file types, aren't something that we should try to
|
|
57
|
-
# parse. When this returns true in some cases, a reference to the file will be
|
|
58
|
-
# passed and skip all the overhead of trying to parse and render.
|
|
59
|
-
def renderable?
|
|
60
|
-
!!handler
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Mmm.... that's the smell of cache busting, which means the hiearchy of this is wrong.
|
|
64
|
-
def parser=(parser_klass)
|
|
65
|
-
@parser = nil
|
|
66
|
-
@parser_klass = parser_klass
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def updated_at
|
|
70
|
-
File.mtime path
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def created_at
|
|
74
|
-
File.ctime path
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def destroy
|
|
78
|
-
FileUtils.rm path
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def save
|
|
82
|
-
File.write path, render
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def renderer
|
|
86
|
-
@parser_klass::Renderer.new(data: data, body: body)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
private
|
|
90
|
-
def parse_error(&parse)
|
|
91
|
-
parse.call
|
|
92
|
-
rescue StandardError => e
|
|
93
|
-
raise ParseError.new("Error parsing asset at #{path.to_s}: #{e}")
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def parser
|
|
97
|
-
@parser ||= @parser_klass.new File.read path
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Returns the mime type of the file extension. If a type can't
|
|
101
|
-
# be resolved then we'll just grab the first type.
|
|
102
|
-
def inferred_mime_type
|
|
103
|
-
format_extension = path.format&.to_s
|
|
104
|
-
MIME::Types.type_for(format_extension).first if format_extension
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
module Sitepress
|
|
2
|
-
# Maps a tree of Directory and Asset objects in a a tree of nodes that
|
|
3
|
-
# format the navigational structure of a website. You can override this
|
|
4
|
-
# this in a site to deal with different file systems. For example, Notion
|
|
5
|
-
# has a completely different file structure for its content than Rails, so
|
|
6
|
-
# we could extend this class to properly map those differences into a tree
|
|
7
|
-
# of nodes.
|
|
8
|
-
class AssetNodeMapper
|
|
9
|
-
attr_reader :node, :asset_paths
|
|
10
|
-
|
|
11
|
-
def initialize(path:, node:)
|
|
12
|
-
@asset_paths = AssetPaths.new(path: path)
|
|
13
|
-
@node = node
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Mounts the source files from the path to the given node.
|
|
17
|
-
def map
|
|
18
|
-
asset_paths.each do |path|
|
|
19
|
-
if path.directory?
|
|
20
|
-
process_directory path
|
|
21
|
-
else
|
|
22
|
-
process_asset path
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
protected
|
|
28
|
-
|
|
29
|
-
def process_directory(path)
|
|
30
|
-
node_name = File.basename path
|
|
31
|
-
node_mapper path: path, node: node.child(node_name)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def process_asset(path)
|
|
35
|
-
asset = Asset.new(path: path)
|
|
36
|
-
node.child(asset.node_name).resources.add_asset(asset, format: asset.format)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
def node_mapper(*args, **kwargs)
|
|
42
|
-
self.class.new(*args, **kwargs).map
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|