sitepress-core 1.0.1 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5edd37e3b6cbe8829fdf7e6d86863f51dab0985bd79ce3c6c6fc892b76cf43b6
4
- data.tar.gz: 8238ff5cdb9d205b095f85a7c5b863ceb995378c732be368b44bd936b302bc8b
3
+ metadata.gz: 6395983eac0134367f92c65aae2d13cc54fb1cd480bc062c00c24fcdd6575590
4
+ data.tar.gz: fd7db9816c3b8a6f3f6b9c6bf02e5a06e496209a8c006316f2cd85c9456fcdf2
5
5
  SHA512:
6
- metadata.gz: 53b2d7df10e9078f69a20e1dd13a219a42c4e9746d650902ba505838068769c5499b6dd09dd1a5d79aebd2728d0248c980f6373902bbac50b3975bea522bdbb0
7
- data.tar.gz: b67155cd7a4f8435f50a2fea2d944c14293fa90aaddd3ee7e27582b759a5e40e3938f36918d7833fa644ee10e68ea587c5fb7c91c10cbabd8380ea2f6c347a64
6
+ metadata.gz: 7430bd142a60bbbd3afbff40dce5e23741975241c9db38e97a258a85241aa191f55dbe89eaa6e08d0b5ee0c20c46fa01fda74fe24044f0f65e871944351ad85d
7
+ data.tar.gz: 286b316eccf042d8b83c1d231741d06d71a538aaf4cf311b322fe09bf6401e05413d74df1c872d0c6b0f2f64fe9a66e4ad37d041c8b66db249e5b80e4deac278
@@ -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(".")
@@ -67,18 +67,6 @@ module Sitepress
67
67
  File.exist? path
68
68
  end
69
69
 
70
- # Spits out a reasonable default request path. This may be changed
71
- # via Resource#request_path.
72
- def to_request_path
73
- ext = format_extension
74
-
75
- if ext
76
- path.dirname.join(basename).sub_ext(".#{ext}").to_s
77
- else
78
- path.to_s
79
- end
80
- end
81
-
82
70
  private
83
71
  def frontmatter
84
72
  Frontmatter.new File.read @path
@@ -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
@@ -33,8 +33,8 @@ module Sitepress
33
33
  # A tree representation of the resourecs wthin the site. The root is a node that's
34
34
  # processed by the `resources_pipeline`.
35
35
  def root
36
- ResourcesNode.new.tap do |node|
37
- DirectoryCollection.new(assets: pages_assets, path: pages_path).mount(node)
36
+ Node.new.tap do |node|
37
+ source_node_mapper.mount node
38
38
  resources_pipeline.process node
39
39
  end
40
40
  end
@@ -42,12 +42,12 @@ module Sitepress
42
42
  # Returns a list of all the resources within #root.
43
43
  def resources
44
44
  with_resources_cache do
45
- ResourceCollection.new(node: root, root_path: root_path)
45
+ ResourceCollection.new(node: root, root_path: pages_path)
46
46
  end
47
47
  end
48
48
 
49
49
  def clear_resources_cache
50
- @_resources = nil
50
+ @resources = nil
51
51
  end
52
52
 
53
53
  # Root path to website project. Contains helpers, pages, and more.
@@ -64,6 +64,10 @@ module Sitepress
64
64
  root_path.join("helpers")
65
65
  end
66
66
 
67
+ def assets_path
68
+ root_path.join("assets")
69
+ end
70
+
67
71
  # Quick and dirty way to manipulate resources in the site without
68
72
  # creating classes that implement the #process_resources method.
69
73
  #
@@ -98,7 +102,7 @@ module Sitepress
98
102
  end
99
103
 
100
104
  # An array of procs that manipulate the tree and resources from the
101
- # ResourceNode returned by #root.
105
+ # Node returned by #root.
102
106
  def resources_pipeline
103
107
  @resources_pipeline ||= ResourcesPipeline.new
104
108
  end
@@ -106,26 +110,11 @@ module Sitepress
106
110
  private
107
111
  def with_resources_cache
108
112
  clear_resources_cache unless cache_resources
109
- @_resources ||= yield
113
+ @resources ||= yield
110
114
  end
111
115
 
112
- # TODO: Move this into the DirectoryHandler class so that its not
113
- # a concern of site.rb
114
- # Exclude swap files created by Textmate and vim from being added
115
- # to the sitemap.
116
- SWAP_FILE_EXTENSIONS = [
117
- "~",
118
- ".swp"
119
- ]
120
-
121
- # Lazy stream of files that will be rendered by resources.
122
- def pages_assets(glob = DEFAULT_GLOB)
123
- # TODO: Move this into the DirectoryHandler class so that its not
124
- # a concern of site.rb
125
- paths = Dir.glob(pages_path.join(glob)).reject do |path|
126
- File.directory? path or SWAP_FILE_EXTENSIONS.any? { |ext| path.end_with? ext }
127
- end
128
- paths.lazy.map { |path| Asset.new(path: path) }
116
+ def source_node_mapper
117
+ @source_node_mapper ||= SourceNodeMapper.new(path: pages_path)
129
118
  end
130
119
  end
131
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 = "1.0.1".freeze
2
+ VERSION = "2.0.0.beta1".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sitepress-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0.beta1
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: 2020-10-22 00:00:00.000000000 Z
11
+ date: 2021-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -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,12 +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
- rubygems_version: 3.1.2
110
- signing_key:
110
+ rubygems_version: 3.2.3
111
+ signing_key:
111
112
  specification_version: 4
112
113
  summary: An embeddable file-backed content management system.
113
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,134 +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
- node = dig(*path)
83
- node.formats.ext(ext) if node
84
- end
85
-
86
- def get_node(path)
87
- *path, _ = tokenize(path)
88
- dig(*path)
89
- end
90
- alias :[] :get_node
91
-
92
- def inspect
93
- "<#{self.class}: formats=#{formats.map(&:request_path)} children=#{children.map(&:name).inspect}>"
94
- end
95
-
96
- # TODO: I don't really like how the path is broken up with the "ext" at the end.
97
- # It feels inconsistent. Either make an object/struct that encaspulates this or
98
- # just pass `index.html` through to the end.
99
- def dig(*args)
100
- head, *tail = args
101
- if head.nil? and tail.empty?
102
- self
103
- elsif child_nodes.has_key?(head)
104
- child_nodes[head].dig(*tail)
105
- else
106
- nil
107
- end
108
- end
109
-
110
- protected
111
- def remove_child(path)
112
- *_, segment, _ = tokenize(path)
113
- child_nodes.delete(segment)
114
- end
115
-
116
- private
117
- def add_child_node(name)
118
- ResourcesNode.new(parent: self, delimiter: @delimiter, name: name)
119
- end
120
-
121
- def child_nodes
122
- @child_nodes ||= Hash.new { |hash, key| hash[key] = add_child_node(key) }
123
- end
124
-
125
- # Returns all of the names for the path along with the format, if set.
126
- def tokenize(path)
127
- return path if path.respond_to? :to_a
128
- path, _, file = path.gsub(/^\//, "").rpartition(@delimiter)
129
- ext = File.extname(file)
130
- file = File.basename(file, ext)
131
- path.split(@delimiter).push(file).push(ext)
132
- end
133
- end
134
- end