sitepress-core 0.1.29 → 2.0.0.beta3

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: f8a17103f99df46b2b3c2e5abbb7b4211b60558361cfac8076b816230a21a16f
4
- data.tar.gz: 2d761e501a643705b0c1a0798c2a2ecdb9c3211de73e55614b9458508c6ff7dd
3
+ metadata.gz: 704ead2c0f9de6bb248d0ec498ec43e0a8be1b907e037fe6a31c0c12cc04805a
4
+ data.tar.gz: d720eddebcaf3066c2eae4e8c0efba237cf9156a751c4dbfef88608b333f72c8
5
5
  SHA512:
6
- metadata.gz: 0c2dd3b7f4040408a169b149a664da75184a56cba4b17af7ec1c2f3d597adda60254b36b3eb798b514b629d29c7f4f279884b513d40bb5528eac115c356ad2c2
7
- data.tar.gz: 167bb3220447c76a7365c22a8fb2ca5eb4c1ee4953a64c4d21cf5ce8989de9437f0ba85ccbd252e9ccdedd04cf250c7ccaeb004bf502c31c83a586800d1dc025
6
+ metadata.gz: 5c543445434532c284bef304d3a6dcc1eb54c714248818e37ae8415aa4c58a6bfbef5c836ed7b1fb0b47efa181d4745e04d7484b68b08af72c955b7c23b6ec74
7
+ data.tar.gz: 966740865aa555ea9ce5bbd2e4bd6a455cc0e071e6fbbe798db830c2086e2284faa5b6edc1cd1e98ea44a2121e1b6dc20dc19defdde9c62621749563b54c0e14
@@ -8,14 +8,15 @@ module Sitepress
8
8
  ExistingRequestPathError = Class.new(InvalidRequestPathError)
9
9
 
10
10
  autoload :Asset, "sitepress/asset"
11
- autoload :DirectoryCollection, "sitepress/directory_collection"
12
11
  autoload :Formats, "sitepress/formats"
13
12
  autoload :Frontmatter, "sitepress/frontmatter"
13
+ autoload :Node, "sitepress/node"
14
+ autoload :Path, "sitepress/path"
14
15
  autoload :Resource, "sitepress/resource"
15
16
  autoload :ResourceCollection, "sitepress/resource_collection"
16
17
  autoload :ResourcesPipeline, "sitepress/resources_pipeline"
17
- autoload :ResourcesNode, "sitepress/resources_node"
18
18
  autoload :Site, "sitepress/site"
19
+ autoload :SourceNodeMapper, "sitepress/source_node_mapper"
19
20
  module Middleware
20
21
  autoload :RequestCache, "sitepress/middleware/request_cache"
21
22
  end
@@ -30,10 +30,10 @@ module Sitepress
30
30
  path.basename.to_s.split(".").drop(1)
31
31
  end
32
32
 
33
- # TODO: This is really a "key" or "leafname".
34
33
  def basename
35
34
  path.basename.to_s.split(".").first
36
35
  end
36
+ alias :node_name :basename
37
37
 
38
38
  def format_basename
39
39
  [basename, format_extension].join(".")
@@ -64,17 +64,7 @@ module Sitepress
64
64
  end
65
65
 
66
66
  def exists?
67
- File.exists? path
68
- end
69
-
70
- # Spits out a reasonable default request path. This may be changed
71
- # via Resource#request_path.
72
- def to_request_path
73
- if ext = format_extension
74
- path.dirname.join(basename).sub_ext(".#{ext}").to_s
75
- else
76
- path.to_s
77
- end
67
+ File.exist? path
78
68
  end
79
69
 
80
70
  private
@@ -1,12 +1,13 @@
1
1
  module Sitepress
2
- # Manages collections of resources that share the same ResourceNode. Given the files `/a.html` and `/a.gif`,
3
- # both of these assets would be stored in the `ResourceNode#name = "a"` under `ResourceNode#formats` with
2
+ # Manages collections of resources that share the same Node. Given the files `/a.html` and `/a.gif`,
3
+ # both of these assets would be stored in the `Node#name = "a"` under `Node#formats` with
4
4
  # the extensions `.gif`, and `.html`.
5
5
  class Formats
6
6
  include Enumerable
7
7
 
8
8
  extend Forwardable
9
9
  def_delegators :@formats, :size, :clear
10
+ def_delegators :@node, :default_format
10
11
 
11
12
  def initialize(node: )
12
13
  @node = node
@@ -17,12 +18,12 @@ module Sitepress
17
18
  @formats.values.each(&block)
18
19
  end
19
20
 
20
- def remove(ext)
21
- @formats.delete(ext)
21
+ def remove(extension)
22
+ @formats.delete(symbolize(extension))
22
23
  end
23
24
 
24
- def ext(ext)
25
- @formats[ext]
25
+ def get(extension)
26
+ @formats[symbolize(extension || default_format)]
26
27
  end
27
28
 
28
29
  def extensions
@@ -33,17 +34,24 @@ module Sitepress
33
34
  find { |f| f.mime_type == mime_type }
34
35
  end
35
36
 
36
- def add(asset: , ext: )
37
- resource = Resource.new(asset: asset, node: @node, ext: ext)
38
- if @formats.has_key? ext
39
- raise Sitepress::ExistingRequestPathError, "Resource at #{resource.request_path} already set"
37
+ def add(asset:, format: nil)
38
+ format = symbolize(format || default_format)
39
+
40
+ resource = Resource.new(asset: asset, node: @node, format: format)
41
+ if @formats.has_key? format
42
+ raise Sitepress::ExistingRequestPathError, "Resource at #{resource.request_path} already set with format #{format.inspect}"
40
43
  else
41
- @formats[ext] = resource
44
+ @formats[format] = resource
42
45
  end
43
46
  end
44
47
 
45
48
  def inspect
46
49
  "<#{self.class}: resources=#{map(&:request_path)}>"
47
50
  end
51
+
52
+ private
53
+ def symbolize(format)
54
+ format&.to_sym
55
+ end
48
56
  end
49
57
  end
@@ -0,0 +1,111 @@
1
+ module Sitepress
2
+ # Resource nodes give resources their parent/sibling/child relationships. The relationship are determined
3
+ # by the `request_path` given to an asset when its added to a node. Given the `request_path` `/foo/bar/biz/buz.html`,
4
+ # a tree of resource nodes would be built named `foo`, `bar`, `biz`, `buz`. `foo` would be the "root" node and `buz`
5
+ # a leaf node. The actual `buz.html` asset is then stored on the leaf node as a resource. This tree structure
6
+ # makes it possible to reason through path relationships from code to build out elements in a website like tree navigation.
7
+ class Node
8
+ attr_reader :parent, :name, :default_format, :default_name
9
+
10
+ # Default extension
11
+ DEFAULT_EXTENSION = :html
12
+
13
+ DEFAULT_NAME = "index".freeze
14
+
15
+ def initialize(parent: nil, name: nil, default_format: DEFAULT_EXTENSION, default_name: DEFAULT_NAME)
16
+ @parent = parent
17
+ @name = name.freeze
18
+ @default_format = default_format
19
+ @default_name = default_name
20
+ yield self if block_given?
21
+ end
22
+
23
+ def formats
24
+ @formats ||= Formats.new(node: self)
25
+ end
26
+
27
+ # Returns the immediate children nodes.
28
+ def children
29
+ child_nodes.values
30
+ end
31
+
32
+ # Returns sibling nodes and self.
33
+ def siblings
34
+ parent ? parent.children : []
35
+ end
36
+
37
+ # Returns all parents up to the root node.
38
+ def parents
39
+ parents = []
40
+ node = parent
41
+ while node do
42
+ parents << node
43
+ node = node.parent
44
+ end
45
+ parents
46
+ end
47
+
48
+ def root?
49
+ parent.nil?
50
+ end
51
+
52
+ def leaf?
53
+ child_nodes.empty?
54
+ end
55
+
56
+ def flatten(resources: [])
57
+ formats.each{ |resource| resources << resource }
58
+ children.each do |child|
59
+ child.flatten.each{ |resource| resources << resource }
60
+ end
61
+ resources
62
+ end
63
+
64
+ def remove
65
+ formats.clear
66
+ parent.remove_child(name) if leaf?
67
+ end
68
+
69
+ def get(path)
70
+ path = Path.new(path)
71
+ node = dig(*path.node_names)
72
+ node.formats.get(path.format) if node
73
+ end
74
+
75
+ def add_child(name)
76
+ return self if name == default_name
77
+ child_nodes[name].tap do |node|
78
+ yield node if block_given?
79
+ end
80
+ end
81
+
82
+ def inspect
83
+ "<#{self.class}: name=#{name.inspect} formats=#{formats.map(&:request_path)} children=#{children.map(&:name).inspect}>"
84
+ end
85
+
86
+ def dig(*args)
87
+ head, *tail = args
88
+ if (head.nil? or head.empty? or head == default_name) and tail.empty?
89
+ self
90
+ elsif child_nodes.has_key?(head)
91
+ child_nodes[head].dig(*tail)
92
+ else
93
+ nil
94
+ end
95
+ end
96
+
97
+ protected
98
+ def remove_child(name)
99
+ child_nodes.delete(name)
100
+ end
101
+
102
+ private
103
+ def build_child(name)
104
+ Node.new(parent: self, name: name, default_format: default_format, default_name: default_name)
105
+ end
106
+
107
+ def child_nodes
108
+ @child_nodes ||= Hash.new { |hash, key| hash[key] = build_child(key) }
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,31 @@
1
+ module Sitepress
2
+ class Path
3
+ DELIMITER = "/".freeze
4
+
5
+ attr_reader :node_names, :ext
6
+
7
+ def initialize(path, delimiter: DELIMITER)
8
+ @path = path
9
+ @delimiter = delimiter
10
+ parse_path
11
+ end
12
+
13
+ def format
14
+ format = @ext.partition(".").last
15
+ return nil if format.nil? or format.empty?
16
+ format.to_sym
17
+ end
18
+
19
+ private
20
+ def strip_leading_slash(path)
21
+ path.to_s.gsub(/^\//, "")
22
+ end
23
+
24
+ def parse_path
25
+ path, _, file = strip_leading_slash(@path).rpartition(@delimiter)
26
+ @ext = File.extname(file)
27
+ @file = File.basename(file, @ext)
28
+ @node_names = path.split(@delimiter).push(@file)
29
+ end
30
+ end
31
+ end
@@ -9,23 +9,21 @@ module Sitepress
9
9
  def_delegators :asset, :mime_type
10
10
 
11
11
  attr_writer :body, :data
12
- attr_reader :node, :asset, :ext
12
+ attr_reader :node, :asset, :format
13
13
 
14
14
  # Default scope for querying parent/child/sibling resources.
15
15
  DEFAULT_FILTER_SCOPE = :same
16
16
 
17
- def initialize(asset: , node: , ext: "")
17
+ RENDERABLE_MEDIA_TYPE = "text".freeze
18
+
19
+ def initialize(asset:, node:, format: nil)
18
20
  @asset = asset
19
21
  @node = node
20
- @ext = ext # TODO: Meh, feels dirty but I suppose the thingy has to drop it in.
22
+ @format = format
21
23
  end
22
24
 
23
25
  def request_path
24
- return unless node
25
- # TODO: This `compact` makes me nervous. How can we handle this better?
26
- lineage = node.parents.reverse.map(&:name).compact
27
- file_name = [node.name, @ext].join
28
- File.join("/", *lineage, file_name)
26
+ File.join("/", *lineage, request_filename)
29
27
  end
30
28
 
31
29
  def data
@@ -37,7 +35,7 @@ module Sitepress
37
35
  end
38
36
 
39
37
  def inspect
40
- "<#{self.class}:#{object_id} request_path=#{request_path.inspect} asset_path=#{@asset.path.to_s.inspect}>"
38
+ "<#{self.class}:#{object_id} request_path=#{request_path.inspect} asset_path=#{asset.path.to_s.inspect}>"
41
39
  end
42
40
 
43
41
  def parent(**args)
@@ -60,6 +58,18 @@ module Sitepress
60
58
  resource.request_path == request_path
61
59
  end
62
60
 
61
+ # Used internally to construct paths from the current node up to the root node.
62
+ def lineage
63
+ @lineage ||= node.parents.reject(&:root?).reverse.map(&:name)
64
+ end
65
+
66
+ # Certain files, like binary file types, aren't something that we should try to
67
+ # parse. When this returns true in some cases, a reference to the file will be
68
+ # passed and skip all the overhead of trying to parse and render.
69
+ def renderable?
70
+ asset.mime_type.media_type == RENDERABLE_MEDIA_TYPE
71
+ end
72
+
63
73
  private
64
74
  # Filters parent/child/sibling resources by a type. The default behavior is to only return
65
75
  # resources of the same type. For example given the pages `/a.html`, `/a.gif`, `/a/b.html`,
@@ -79,14 +89,29 @@ module Sitepress
79
89
  when :all
80
90
  nodes.map{ |node| node.formats }
81
91
  when :same
82
- nodes.map{ |n| n.formats.ext(ext) }.flatten
83
- when String
84
- nodes.map{ |n| n.formats.ext(type) }.flatten
92
+ nodes.map{ |n| n.formats.get(format) }.flatten
93
+ when String, Symbol, NilClass
94
+ nodes.map{ |n| n.formats.get(type) }.flatten
85
95
  when MIME::Type
86
96
  nodes.map{ |n| n.formats.mime_type(type) }.flatten
87
97
  else
88
98
  raise ArgumentError, "Invalid type argument #{type}. Must be either :same, :all, an extension string, or a Mime::Type"
89
99
  end
90
100
  end
101
+
102
+ # Deals with situations, particularly in the root node and other "index" nodes, for the `request_path`
103
+ def request_filename
104
+ if node.root? and node.default_format == format
105
+ ""
106
+ elsif node.root? and format
107
+ "#{node.default_name}.#{format}"
108
+ elsif node.root?
109
+ node.default_name
110
+ elsif format.nil? or node.default_format == format
111
+ node.name
112
+ else
113
+ "#{node.name}.#{format}"
114
+ end
115
+ end
91
116
  end
92
117
  end
@@ -4,7 +4,7 @@ module Sitepress
4
4
  # Processes a collection of resources
5
5
  class ResourcesPipeline < Array
6
6
  def process(resources)
7
- each{ |processor| processor.process_resources resources }
7
+ each { |processor| processor.process_resources resources }
8
8
  end
9
9
  end
10
10
  end
@@ -11,7 +11,8 @@ module Sitepress
11
11
  # Default root_path for site.
12
12
  DEFAULT_ROOT_PATH = Pathname.new(".").freeze
13
13
 
14
- attr_reader :root_path, :resources_pipeline
14
+ attr_reader :root_path
15
+ attr_writer :resources_pipeline
15
16
 
16
17
  # Cache resources for production runs of Sitepress. Development
17
18
  # modes don't cache to optimize for files reloading.
@@ -32,8 +33,8 @@ module Sitepress
32
33
  # A tree representation of the resourecs wthin the site. The root is a node that's
33
34
  # processed by the `resources_pipeline`.
34
35
  def root
35
- ResourcesNode.new.tap do |node|
36
- DirectoryCollection.new(assets: pages_assets, path: pages_path).mount(node)
36
+ Node.new.tap do |node|
37
+ source_node_mapper.mount node
37
38
  resources_pipeline.process node
38
39
  end
39
40
  end
@@ -41,12 +42,12 @@ module Sitepress
41
42
  # Returns a list of all the resources within #root.
42
43
  def resources
43
44
  with_resources_cache do
44
- ResourceCollection.new(node: root, root_path: root_path)
45
+ ResourceCollection.new(node: root, root_path: pages_path)
45
46
  end
46
47
  end
47
48
 
48
49
  def clear_resources_cache
49
- @_resources = nil
50
+ @resources = nil
50
51
  end
51
52
 
52
53
  # Root path to website project. Contains helpers, pages, and more.
@@ -63,6 +64,10 @@ module Sitepress
63
64
  root_path.join("helpers")
64
65
  end
65
66
 
67
+ def assets_path
68
+ root_path.join("assets")
69
+ end
70
+
66
71
  # Quick and dirty way to manipulate resources in the site without
67
72
  # creating classes that implement the #process_resources method.
68
73
  #
@@ -97,34 +102,19 @@ module Sitepress
97
102
  end
98
103
 
99
104
  # An array of procs that manipulate the tree and resources from the
100
- # ResourceNode returned by #root.
105
+ # Node returned by #root.
101
106
  def resources_pipeline
102
- @_resources_pipeline ||= ResourcesPipeline.new
107
+ @resources_pipeline ||= ResourcesPipeline.new
103
108
  end
104
109
 
105
110
  private
106
111
  def with_resources_cache
107
112
  clear_resources_cache unless cache_resources
108
- @_resources ||= yield
113
+ @resources ||= yield
109
114
  end
110
115
 
111
- # TODO: Move this into the DirectoryHandler class so that its not
112
- # a concern of site.rb
113
- # Exclude swap files created by Textmate and vim from being added
114
- # to the sitemap.
115
- SWAP_FILE_EXTENSIONS = [
116
- "~",
117
- ".swp"
118
- ]
119
-
120
- # Lazy stream of files that will be rendered by resources.
121
- def pages_assets(glob = DEFAULT_GLOB)
122
- # TODO: Move this into the DirectoryHandler class so that its not
123
- # a concern of site.rb
124
- paths = Dir.glob(pages_path.join(glob)).reject do |path|
125
- File.directory? path or SWAP_FILE_EXTENSIONS.any? { |ext| path.end_with? ext }
126
- end
127
- paths.lazy.map { |path| Asset.new(path: path) }
116
+ def source_node_mapper
117
+ @source_node_mapper ||= SourceNodeMapper.new(path: pages_path)
128
118
  end
129
119
  end
130
120
  end
@@ -0,0 +1,57 @@
1
+ module Sitepress
2
+ # Maps a directory of assets into a set of routes that correspond with
3
+ # the `path` root.
4
+ class SourceNodeMapper
5
+ # Exclude swap files created by Textmate and vim from being added
6
+ # to the sitemap.
7
+ SWAP_FILE_EXTENSIONS = [
8
+ "~",
9
+ ".swp",
10
+ ".DS_Store" # TODO: Not a swap file, but something that should be ignored.
11
+ ]
12
+
13
+ # Partial rails prefix.
14
+ PARTIAL_PREFIX = "_".freeze
15
+
16
+ attr_reader :assets, :path
17
+ alias :root :path
18
+
19
+ def initialize(path:)
20
+ @path = path
21
+ end
22
+
23
+ # Mounts the source files from the path to the given node.
24
+ def mount(node)
25
+ paths.each do |path, name, format|
26
+ if path.directory?
27
+ SourceNodeMapper.new(path: path).mount node.add_child(name)
28
+ else
29
+ asset = Asset.new(path: path)
30
+ node.add_child(name).formats.add(format: format, asset: asset)
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+ # Returns a list of files, paths, and node names to iterate through to build out nodes
37
+ def paths
38
+ Enumerator.new do |y|
39
+ root.each_child do |path|
40
+ next if is_swap_file? path
41
+ next if is_partial_file? path
42
+
43
+ node_name, node_format, template_handler = path.basename.to_s.split(".")
44
+ y << [ path, node_name, node_format&.to_sym ]
45
+ end
46
+ end
47
+ end
48
+
49
+ def is_partial_file?(path)
50
+ path.basename.to_s.start_with? PARTIAL_PREFIX
51
+ end
52
+
53
+ def is_swap_file?(path)
54
+ SWAP_FILE_EXTENSIONS.any? { |ext| path.to_s.end_with? ext }
55
+ end
56
+ end
57
+ end
@@ -1,3 +1,3 @@
1
1
  module Sitepress
2
- VERSION = "0.1.29".freeze
2
+ VERSION = "2.0.0.beta3".freeze
3
3
  end
@@ -17,8 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_development_dependency "bundler", "~> 1.11"
21
- spec.add_development_dependency "rake", "~> 10.0"
20
+ spec.add_development_dependency "bundler", ">= 1.11"
21
+ spec.add_development_dependency "rake", "~> 13.0"
22
22
  spec.add_development_dependency "rspec", "~> 3.0"
23
23
 
24
24
  spec.add_runtime_dependency "mime-types", ">= 2.99"
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sitepress-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.29
4
+ version: 2.0.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Gessler
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-20 00:00:00.000000000 Z
11
+ date: 2021-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.11'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.11'
27
27
  - !ruby/object:Gem::Dependency
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +66,7 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '2.99'
69
- description:
69
+ description:
70
70
  email:
71
71
  - bradgessler@gmail.com
72
72
  executables: []
@@ -75,23 +75,24 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - lib/sitepress-core.rb
77
77
  - lib/sitepress/asset.rb
78
- - lib/sitepress/directory_collection.rb
79
78
  - lib/sitepress/extensions/layouts.rb
80
79
  - lib/sitepress/extensions/proc_manipulator.rb
81
80
  - lib/sitepress/formats.rb
82
81
  - lib/sitepress/frontmatter.rb
83
82
  - lib/sitepress/middleware/request_cache.rb
83
+ - lib/sitepress/node.rb
84
+ - lib/sitepress/path.rb
84
85
  - lib/sitepress/resource.rb
85
86
  - lib/sitepress/resource_collection.rb
86
- - lib/sitepress/resources_node.rb
87
87
  - lib/sitepress/resources_pipeline.rb
88
88
  - lib/sitepress/site.rb
89
+ - lib/sitepress/source_node_mapper.rb
89
90
  - lib/sitepress/version.rb
90
91
  - sitepress-core.gemspec
91
92
  homepage: https://github.com/sitepress/sitepress
92
93
  licenses: []
93
94
  metadata: {}
94
- post_install_message:
95
+ post_install_message:
95
96
  rdoc_options: []
96
97
  require_paths:
97
98
  - lib
@@ -102,13 +103,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
102
103
  version: '0'
103
104
  required_rubygems_version: !ruby/object:Gem::Requirement
104
105
  requirements:
105
- - - ">="
106
+ - - ">"
106
107
  - !ruby/object:Gem::Version
107
- version: '0'
108
+ version: 1.3.1
108
109
  requirements: []
109
- rubyforge_project:
110
- rubygems_version: 2.7.3
111
- signing_key:
110
+ rubygems_version: 3.2.3
111
+ signing_key:
112
112
  specification_version: 4
113
113
  summary: An embeddable file-backed content management system.
114
114
  test_files: []
@@ -1,24 +0,0 @@
1
- module Sitepress
2
- # Maps a directory of assets into a set of routes that correspond with
3
- # the `path` root.
4
- class DirectoryCollection
5
- attr_reader :assets, :path
6
-
7
- def initialize(path: , assets:)
8
- @path = path
9
- @assets = assets
10
- end
11
-
12
- def mount(node)
13
- assets.each { |a| node.add path: asset_path_to_request_path(a), asset: a }
14
- end
15
-
16
- private
17
- # Given a @file_path of `/hi`, this method changes `/hi/there/friend.html.erb`
18
- # to an absolute `/there/friend` format by removing the file extensions
19
- def asset_path_to_request_path(asset)
20
- # Relative path of resource to the file_path of this project.
21
- asset.path.dirname.join(asset.format_basename).relative_path_from(path).to_s
22
- end
23
- end
24
- end
@@ -1,135 +0,0 @@
1
- module Sitepress
2
- # Resource nodes give resources their parent/sibling/child relationships. The relationship are determined
3
- # by the `request_path` given to an asset when its added to a node. Given the `request_path` `/foo/bar/biz/buz.html`,
4
- # a tree of resource nodes would be built named `foo`, `bar`, `biz`, `buz`. `foo` would be the "root" node and `buz`
5
- # a leaf node. The actual `buz.html` asset is then stored on the leaf node as a resource. This tree structure
6
- # makes it possible to reason through path relationships from code to build out elements in a website like tree navigation.
7
- class ResourcesNode
8
- attr_reader :parent, :name
9
-
10
- DELIMITER = "/".freeze
11
-
12
- def initialize(parent: nil, delimiter: ResourcesNode::DELIMITER, name: nil)
13
- @parent = parent
14
- @name = name.freeze
15
- @delimiter = delimiter.freeze
16
- end
17
-
18
- def formats
19
- @formats ||= Formats.new(node: self)
20
- end
21
-
22
- # Returns the immediate children nodes.
23
- def children
24
- child_nodes.values
25
- end
26
-
27
- # Returns sibling nodes.
28
- def siblings
29
- parent ? parent.children.reject{ |c| c == self } : []
30
- end
31
-
32
- # Returns all parents up to the root node.
33
- def parents
34
- parents = []
35
- node = parent
36
- while node do
37
- parents << node
38
- node = node.parent
39
- end
40
- parents
41
- end
42
-
43
- def root?
44
- parent.nil?
45
- end
46
-
47
- def leaf?
48
- child_nodes.empty?
49
- end
50
-
51
- def flatten(resources: [])
52
- formats.each{ |resource| resources << resource }
53
- children.each do |child|
54
- child.flatten.each{ |resource| resources << resource }
55
- end
56
- resources
57
- end
58
-
59
- def remove
60
- if leaf?
61
- # TODO: Check the parents to see if they also need to be removed if
62
- # this call orphans the tree up to a resource.
63
- parent.remove_child(name)
64
- else
65
- formats.clear
66
- end
67
- end
68
-
69
- def add(path: , asset: )
70
- head, *path = tokenize(path)
71
- if path.empty?
72
- # When there's no more paths, we're left with the format (e.g. ".html")
73
- formats.add(asset: asset, ext: head)
74
- else
75
- child_nodes[head].add(path: path, asset: asset)
76
- end
77
- end
78
- alias :[]= :add
79
-
80
- def get(path)
81
- *path, ext = tokenize(path)
82
- if node = dig(*path)
83
- node.formats.ext(ext)
84
- end
85
- end
86
-
87
- def get_node(path)
88
- *path, _ = tokenize(path)
89
- dig(*path)
90
- end
91
- alias :[] :get_node
92
-
93
- def inspect
94
- "<#{self.class}: formats=#{formats.map(&:request_path)} children=#{children.map(&:name).inspect}>"
95
- end
96
-
97
- # TODO: I don't really like how the path is broken up with the "ext" at the end.
98
- # It feels inconsistent. Either make an object/struct that encaspulates this or
99
- # just pass `index.html` through to the end.
100
- def dig(*args)
101
- head, *tail = args
102
- if head.nil? and tail.empty?
103
- self
104
- elsif child_nodes.has_key?(head)
105
- child_nodes[head].dig(*tail)
106
- else
107
- nil
108
- end
109
- end
110
-
111
- protected
112
- def remove_child(path)
113
- *_, segment, _ = tokenize(path)
114
- child_nodes.delete(segment)
115
- end
116
-
117
- private
118
- def add_child_node(name)
119
- ResourcesNode.new(parent: self, delimiter: @delimiter, name: name)
120
- end
121
-
122
- def child_nodes
123
- @child_nodes ||= Hash.new { |hash, key| hash[key] = add_child_node(key) }
124
- end
125
-
126
- # Returns all of the names for the path along with the format, if set.
127
- def tokenize(path)
128
- return path if path.respond_to? :to_a
129
- path, _, file = path.gsub(/^\//, "").rpartition(@delimiter)
130
- ext = File.extname(file)
131
- file = File.basename(file, ext)
132
- path.split(@delimiter).push(file).push(ext)
133
- end
134
- end
135
- end