yarrow 0.9.1 → 0.9.3
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/.github/workflows/ruby.yml +2 -2
- data/lib/yarrow/config.rb +4 -4
- data/lib/yarrow/format/methods/front_matter.rb +1 -1
- data/lib/yarrow/schema/entity.rb +8 -10
- data/lib/yarrow/schema/value.rb +21 -12
- data/lib/yarrow/symbols.rb +14 -0
- data/lib/yarrow/version.rb +1 -1
- data/lib/yarrow.rb +1 -1
- data/yarrow.gemspec +3 -2
- metadata +33 -9
- data/lib/yarrow/content/expansion/directory_map.rb +0 -21
- data/lib/yarrow/content/expansion/file_list.rb +0 -15
- data/lib/yarrow/content/expansion/strategy.rb +0 -118
- data/lib/yarrow/content/expansion/tree.rb +0 -93
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a2ebdee5113cc218a9744d5da88b01cb0e7604b4ecfe0d133f776d48d2b4b97
|
4
|
+
data.tar.gz: 967ba36fec57253eea8069f903828b8529f43802578c8be009413e4a5373e1f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 982f7c783ed933419504e6722e9ffbacaf513468a16cdb07ca4ec7ffc859d5eaed1470948d8c1ca7ce5aa00f5e0fc05b9c29a38f0f0e2972aa6d2f348aec2c7b
|
7
|
+
data.tar.gz: 4debe486afb56d7f664a0dd730db05429b2d7256a5f2789f1987339b394f404ac0f59f67d9e2f25126ad946675a46b7f910a91881d447afed148cb56eb4b5317
|
data/.github/workflows/ruby.yml
CHANGED
@@ -10,10 +10,10 @@ jobs:
|
|
10
10
|
fail-fast: false
|
11
11
|
matrix:
|
12
12
|
os: [ubuntu-latest, macos-latest]
|
13
|
-
ruby: [
|
13
|
+
ruby: [3.1, 3.2, "3.3.0-preview1"]
|
14
14
|
runs-on: ${{ matrix.os }}
|
15
15
|
steps:
|
16
|
-
- uses: actions/checkout@
|
16
|
+
- uses: actions/checkout@v4
|
17
17
|
- name: Set up Ruby
|
18
18
|
uses: ruby/setup-ruby@v1
|
19
19
|
with:
|
data/lib/yarrow/config.rb
CHANGED
@@ -53,13 +53,13 @@ module Yarrow
|
|
53
53
|
# Yarrow::Schema::Types::Map.of(Symbol)
|
54
54
|
# )
|
55
55
|
|
56
|
-
class Content < Yarrow::Schema::Entity
|
56
|
+
class Content < Yarrow::Schema::Entity
|
57
57
|
attribute :module, :string
|
58
58
|
#attribute :source_map, :__config_source_map
|
59
59
|
attribute :source_map, :hash
|
60
60
|
end
|
61
61
|
|
62
|
-
class Output < Yarrow::Schema::Entity
|
62
|
+
class Output < Yarrow::Schema::Entity
|
63
63
|
attribute :generator, :string
|
64
64
|
attribute :template_dir, :path
|
65
65
|
#attribute :scripts, :array
|
@@ -71,8 +71,8 @@ module Yarrow
|
|
71
71
|
attribute :output_dir, :path
|
72
72
|
attribute :meta, :any
|
73
73
|
attribute :server, :any
|
74
|
-
attribute :content, :
|
75
|
-
attribute :output, :
|
74
|
+
attribute :content, :yarrow_config_content
|
75
|
+
attribute :output, :yarrow_config_output
|
76
76
|
end
|
77
77
|
#
|
78
78
|
# `content_dir` and `output_dir` are placeholders and should be overriden
|
data/lib/yarrow/schema/entity.rb
CHANGED
@@ -20,11 +20,16 @@ module Yarrow
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def inherited(class_name)
|
23
|
+
class_type = Yarrow::Schema::Types::Instance.of(class_name).accept(Hash)
|
24
|
+
|
23
25
|
if @label
|
24
|
-
|
25
|
-
Yarrow::Schema::Definitions.register(@label, class_type)
|
26
|
+
label = @label
|
26
27
|
@label = nil
|
28
|
+
else
|
29
|
+
label = Yarrow::Symbols.from_const(class_name)
|
27
30
|
end
|
31
|
+
|
32
|
+
Yarrow::Schema::Definitions.register(label, class_type)
|
28
33
|
end
|
29
34
|
end
|
30
35
|
|
@@ -32,14 +37,7 @@ module Yarrow
|
|
32
37
|
converted = dictionary.cast(config)
|
33
38
|
|
34
39
|
converted.each_pair do |key, value|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# defined_type = dictionary[key]
|
38
|
-
#
|
39
|
-
# unless value.is_a?(defined_type)
|
40
|
-
# raise "#{key} accepts #{defined_type} but #{value.class} given"
|
41
|
-
# end
|
42
|
-
|
40
|
+
# TODO: should we represent this as an attribute set rather than instance vars?
|
43
41
|
instance_variable_set("@#{key}", value)
|
44
42
|
end
|
45
43
|
end
|
data/lib/yarrow/schema/value.rb
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
module Yarrow
|
2
2
|
module Schema
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
class ValueType < Struct
|
4
|
+
def self.register(label)
|
5
|
+
class_type = Yarrow::Schema::Types::Instance.of(self).accept(Hash)
|
6
|
+
Yarrow::Schema::Definitions.register(label, class_type)
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
# Automatically register when struct is defined as a class extension
|
11
|
+
# rather than anonymous struct class.
|
12
|
+
def self.inherited(subclass)
|
13
|
+
if subclass.name
|
14
|
+
self.register(subclass.name.downcase.to_sym)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
13
18
|
|
14
19
|
# Value object (with comparison by value equality). This just chucks back a
|
15
20
|
# Ruby struct but wraps the constructor with method advice that handles
|
@@ -33,12 +38,16 @@ module Yarrow
|
|
33
38
|
|
34
39
|
validator = Dictionary.new(fields_spec)
|
35
40
|
|
36
|
-
struct =
|
41
|
+
struct = ValueType.new(*slots_spec, keyword_init: true, &block)
|
37
42
|
|
38
43
|
struct.define_method :initialize do |*args, **kwargs|
|
39
44
|
attr_values = if args.any?
|
40
45
|
raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
|
41
|
-
|
46
|
+
if args.first.instance_of?(Hash) and args.count == 1
|
47
|
+
args.first
|
48
|
+
else
|
49
|
+
Hash[slots.zip(args)]
|
50
|
+
end
|
42
51
|
else
|
43
52
|
kwargs
|
44
53
|
end
|
data/lib/yarrow/symbols.rb
CHANGED
@@ -19,6 +19,20 @@ module Yarrow
|
|
19
19
|
Object.const_get(Strings::Case.pascalcase(atom.to_s).to_sym)
|
20
20
|
end
|
21
21
|
|
22
|
+
# Converts a string name of class const to a symbol atom
|
23
|
+
#
|
24
|
+
# @param [Class, String, #to_s] const_obj
|
25
|
+
# @return [Symbol]
|
26
|
+
def self.from_const(const_obj)
|
27
|
+
const_lookup = if const_obj.respond_to?(:name)
|
28
|
+
const_obj.name
|
29
|
+
else
|
30
|
+
const_obj.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
Strings::Case.underscore(const_lookup).to_sym
|
34
|
+
end
|
35
|
+
|
22
36
|
# @param [Symbol, String] atom
|
23
37
|
# @return [Symbol]
|
24
38
|
def self.to_singular(atom)
|
data/lib/yarrow/version.rb
CHANGED
data/lib/yarrow.rb
CHANGED
data/yarrow.gemspec
CHANGED
@@ -24,8 +24,9 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_runtime_dependency 'parallel', '~> 1.22.1'
|
25
25
|
spec.add_runtime_dependency 'strings-inflection', '~> 0.1'
|
26
26
|
spec.add_runtime_dependency 'strings-case', '~> 0.3'
|
27
|
-
spec.add_runtime_dependency 'toml', '~>
|
28
|
-
|
27
|
+
spec.add_runtime_dependency 'toml-rb', '~> 2.2.0'
|
28
|
+
spec.add_runtime_dependency 'shale', '~> 1.0.0'
|
29
|
+
spec.add_runtime_dependency 'phlex', '~> 1.8.1'
|
29
30
|
spec.add_runtime_dependency 'kramdown', '~> 2.4.0'
|
30
31
|
spec.add_development_dependency 'rake', '~> 13.0'
|
31
32
|
spec.add_development_dependency 'rspec', '~> 3.11'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yarrow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Rickerby
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -165,19 +165,47 @@ dependencies:
|
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0.3'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
|
-
name: toml
|
168
|
+
name: toml-rb
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version:
|
173
|
+
version: 2.2.0
|
174
174
|
type: :runtime
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version:
|
180
|
+
version: 2.2.0
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: shale
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 1.0.0
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 1.0.0
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: phlex
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 1.8.1
|
202
|
+
type: :runtime
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 1.8.1
|
181
209
|
- !ruby/object:Gem::Dependency
|
182
210
|
name: kramdown
|
183
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -277,13 +305,9 @@ files:
|
|
277
305
|
- lib/yarrow/console_runner.rb
|
278
306
|
- lib/yarrow/content/expansion/aggregator.rb
|
279
307
|
- lib/yarrow/content/expansion/basename_merge.rb
|
280
|
-
- lib/yarrow/content/expansion/directory_map.rb
|
281
308
|
- lib/yarrow/content/expansion/directory_merge.rb
|
282
|
-
- lib/yarrow/content/expansion/file_list.rb
|
283
309
|
- lib/yarrow/content/expansion/filename_map.rb
|
284
|
-
- lib/yarrow/content/expansion/strategy.rb
|
285
310
|
- lib/yarrow/content/expansion/traversal.rb
|
286
|
-
- lib/yarrow/content/expansion/tree.rb
|
287
311
|
- lib/yarrow/content/graph.rb
|
288
312
|
- lib/yarrow/content/model.rb
|
289
313
|
- lib/yarrow/content/policy.rb
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Yarrow
|
2
|
-
module Content
|
3
|
-
module Expansion
|
4
|
-
class DirectoryMap < Aggregator
|
5
|
-
def expand_source(container, policy)
|
6
|
-
puts "create_node label=:collection type=:#{policy.container} name='#{container.props[:basename]}'"
|
7
|
-
@current_collection = container.props[:basename]
|
8
|
-
end
|
9
|
-
|
10
|
-
def expand_directory(collection, policy)
|
11
|
-
puts "create_node label=:collection type=:#{policy.collection} name='#{collection.props[:basename]}' collection=#{@current_collection}"
|
12
|
-
@current_collection = collection.props[:basename]
|
13
|
-
end
|
14
|
-
|
15
|
-
def expand_file(entity, policy)
|
16
|
-
puts "create_node label=:entity type=:#{policy.entity} name='#{entity.props[:basename]}' collection='#{@current_collection}"
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module Yarrow
|
2
|
-
module Content
|
3
|
-
module Expansion
|
4
|
-
class FileList < Strategy
|
5
|
-
def expand(policy)
|
6
|
-
start_node = graph.n(:root).out(name: policy.container.to_s)
|
7
|
-
|
8
|
-
start_node.out(:files).each do |file_node|
|
9
|
-
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,118 +0,0 @@
|
|
1
|
-
module Yarrow
|
2
|
-
module Content
|
3
|
-
module Expansion
|
4
|
-
class Strategy
|
5
|
-
include Yarrow::Tools::FrontMatter
|
6
|
-
|
7
|
-
attr_reader :graph
|
8
|
-
|
9
|
-
def initialize(graph)
|
10
|
-
@graph = graph
|
11
|
-
@subcollections = {}
|
12
|
-
@entity_links = []
|
13
|
-
@index_links = []
|
14
|
-
@index = nil
|
15
|
-
end
|
16
|
-
|
17
|
-
# Expand a directory to a collection node representing a collection of entities
|
18
|
-
def expand_directory(policy, node)
|
19
|
-
index = graph.create_node do |collection_node|
|
20
|
-
|
21
|
-
collection_attrs = {
|
22
|
-
name: node.props[:name],
|
23
|
-
title: node.props[:name].capitalize,
|
24
|
-
body: ""
|
25
|
-
}
|
26
|
-
|
27
|
-
populate_collection(collection_node, policy, collection_attrs)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Add this collection id to the lookup table for edge construction
|
31
|
-
@subcollections[node.props[:path]] = index
|
32
|
-
|
33
|
-
# Join the collection to its parent
|
34
|
-
unless node.props[:slug] == policy.collection.to_s || !@subcollections.key?(node.props[:entry].parent.to_s)
|
35
|
-
graph.create_edge do |edge|
|
36
|
-
edge.label = :child
|
37
|
-
edge.from = @subcollections[node.props[:entry].parent.to_s].id
|
38
|
-
edge.to = index.id
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Extract collection level configuration/metadata from the root node for
|
44
|
-
# this content type.
|
45
|
-
def extract_metadata(node, type)
|
46
|
-
# TODO: support _index or _slug convention as well
|
47
|
-
meta_file = node.out(slug: type.to_s).first
|
48
|
-
|
49
|
-
if meta_file
|
50
|
-
# Process metadata and add it to the collection node
|
51
|
-
# TODO: pass in content converter object
|
52
|
-
# TODO: index/body content by default if extracted from frontmatter
|
53
|
-
body, data = process_content(meta_file.props[:entry])
|
54
|
-
else
|
55
|
-
# Otherwise, assume default collection behaviour
|
56
|
-
data = {}
|
57
|
-
end
|
58
|
-
|
59
|
-
# Generate a default title if not provided in metadata
|
60
|
-
unless data.key?(:title)
|
61
|
-
data[:title] = type.to_s.capitalize
|
62
|
-
end
|
63
|
-
|
64
|
-
data
|
65
|
-
end
|
66
|
-
|
67
|
-
def populate_collection(node, policy, meta_attrs)
|
68
|
-
node.label = :collection
|
69
|
-
node.props[:type] = policy.collection
|
70
|
-
node.props[:resource] = policy.collection_const.new(meta_attrs)
|
71
|
-
end
|
72
|
-
|
73
|
-
def populate_entity(node, policy, meta_attrs)
|
74
|
-
node.label = :item
|
75
|
-
node.props[:type] = policy.entity
|
76
|
-
node.props[:resource] = policy.entity_const.new(meta_attrs)
|
77
|
-
end
|
78
|
-
|
79
|
-
def merge_collection_index(node, policy, meta_attrs)
|
80
|
-
props = { resource: node.props[:resource].merge(meta_attrs) }
|
81
|
-
node.merge_props(props)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Workaround for handling meta and content source in multiple files or a single
|
85
|
-
# file with front matter.
|
86
|
-
def process_content(path)
|
87
|
-
case path.extname
|
88
|
-
when '.htm', '.md'
|
89
|
-
read_split_content(path.to_s, symbolize_keys: true)
|
90
|
-
# when '.md'
|
91
|
-
# body, data = read_split_content(path.to_s, symbolize_keys: true)
|
92
|
-
# [Kramdown::Document.new(body).to_html, data]
|
93
|
-
when '.yml'
|
94
|
-
[nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
|
95
|
-
end
|
96
|
-
# TODO: Raise error if unsupported extname reaches here
|
97
|
-
end
|
98
|
-
|
99
|
-
def connect_expanded_entities
|
100
|
-
# Once all files and directories have been expanded, connect all the child
|
101
|
-
# edges between collections and entities
|
102
|
-
@entity_links.each do |entity_link|
|
103
|
-
graph.create_edge do |edge|
|
104
|
-
edge.label = :child
|
105
|
-
edge.from = entity_link[:parent_id].id
|
106
|
-
edge.to = entity_link[:child_id].id
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# Merge index page body and metadata with their parent collections
|
111
|
-
@index_links.each do |index_link|
|
112
|
-
merge_collection_index(index_link[:parent_id], policy, index_link[:index_attrs])
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
@@ -1,93 +0,0 @@
|
|
1
|
-
module Yarrow
|
2
|
-
module Content
|
3
|
-
module Expansion
|
4
|
-
class Tree < Strategy
|
5
|
-
def expand(policy)
|
6
|
-
# If match path represents entire content dir, then include the entire
|
7
|
-
# content dir instead of scanning from a subfolder matching the name of
|
8
|
-
# the collection.
|
9
|
-
#start_node = if policy.match_path == "."
|
10
|
-
start_node = if policy.match_path == "."
|
11
|
-
# TODO: match against source_dir
|
12
|
-
graph.n(:root).out(:directory)
|
13
|
-
else
|
14
|
-
graph.n(name: policy.match_path)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Collect all nested collections in the subgraph for this content type
|
18
|
-
@subcollections = {}
|
19
|
-
@entity_links = []
|
20
|
-
@index_links = []
|
21
|
-
@index = nil
|
22
|
-
|
23
|
-
# Scan and collect all nested files from the root
|
24
|
-
start_node.depth_first.each do |node|
|
25
|
-
if node.label == :directory
|
26
|
-
expand_directory(policy, node)
|
27
|
-
elsif node.label == :file
|
28
|
-
expand_file_by_extension(policy, node)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Once all files and directories have been expanded, connect all the child
|
33
|
-
# edges between collections and entities
|
34
|
-
@entity_links.each do |entity_link|
|
35
|
-
graph.create_edge do |edge|
|
36
|
-
edge.label = :child
|
37
|
-
edge.from = entity_link[:parent_id].id
|
38
|
-
edge.to = entity_link[:child_id].id
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# Merge index page body and metadata with their parent collections
|
43
|
-
@index_links.each do |index_link|
|
44
|
-
merge_collection_index(index_link[:parent_id], policy, index_link[:index_attrs])
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def expand_file_by_basename(policy, node)
|
49
|
-
body, meta = process_content(node.props[:entry])
|
50
|
-
meta = {} if !meta
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def expand_file_by_extension(policy, node)
|
56
|
-
body, meta = process_content(node.props[:entry])
|
57
|
-
meta = {} if !meta
|
58
|
-
|
59
|
-
# TODO: document mapping convention for index pages and collection metadata
|
60
|
-
# TODO: underscore _index pattern?
|
61
|
-
bare_basename = node.props[:entry].basename(node.props[:entry].extname)
|
62
|
-
if bare_basename.to_s == "index"
|
63
|
-
@index_links << {
|
64
|
-
parent_id: @subcollections[node.props[:entry].parent.to_s],
|
65
|
-
index_attrs: meta.merge({ body: body})
|
66
|
-
}
|
67
|
-
else
|
68
|
-
# Create an entity node representing a file mapped to a unique content object
|
69
|
-
entity = graph.create_node do |entity_node|
|
70
|
-
|
71
|
-
entity_slug = node.props[:entry].basename(node.props[:entry].extname).to_s
|
72
|
-
|
73
|
-
entity_attrs = {
|
74
|
-
name: entity_slug,
|
75
|
-
title: entity_slug.gsub("-", " ").capitalize,
|
76
|
-
body: body
|
77
|
-
}
|
78
|
-
|
79
|
-
populate_entity(entity_node, policy, entity_attrs.merge(meta || {}))
|
80
|
-
end
|
81
|
-
|
82
|
-
# We may not have an expanded node for the parent collection if this is a
|
83
|
-
# preorder traversal so save it for later
|
84
|
-
@entity_links << {
|
85
|
-
parent_id: @subcollections[node.props[:entry].parent.to_s],
|
86
|
-
child_id: entity
|
87
|
-
}
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|