yarrow 0.7.2 → 0.7.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/.gitignore +1 -0
- data/lib/yarrow/config.rb +5 -5
- data/lib/yarrow/content/expansion.rb +6 -41
- data/lib/yarrow/content/expansion_strategy.rb +51 -0
- data/lib/yarrow/content/graph.rb +2 -2
- data/lib/yarrow/content/manifest.rb +4 -4
- data/lib/yarrow/content/model.rb +46 -0
- data/lib/yarrow/content/policy.rb +48 -0
- data/lib/yarrow/content/resource.rb +29 -0
- data/lib/yarrow/content/source.rb +70 -3
- data/lib/yarrow/content/tree_expansion.rb +15 -6
- data/lib/yarrow/generator.rb +1 -1
- data/lib/yarrow/schema/definitions.rb +30 -0
- data/lib/yarrow/schema/dictionary.rb +49 -0
- data/lib/yarrow/schema/entity.rb +51 -0
- data/lib/yarrow/schema/types.rb +73 -0
- data/lib/yarrow/schema/value.rb +46 -0
- data/lib/yarrow/schema.rb +28 -119
- data/lib/yarrow/symbols.rb +15 -0
- data/lib/yarrow/version.rb +1 -1
- data/lib/yarrow.rb +10 -3
- data/yarrow.gemspec +1 -1
- metadata +18 -12
- data/lib/yarrow/content/collection_expander.rb +0 -241
- data/lib/yarrow/content/object_type.rb +0 -42
- data/lib/yarrow/content/source_collector.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c49c3ae295fd127f7533a0af4c543d5315a2929a580a21b6b4c26379e519490
|
4
|
+
data.tar.gz: 6867e3004c89dc7362a83378f3b0d55a14803d513cc77445c7893a31ffb4e4bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0056694b64f9ac4d000e85341570b4a5b630f27b5de15d5dcf12eeae1dc3625368abfd1d65a6c0a26f9793cbd6993e076c7e46d76ef97007874e8dddfd75c7af'
|
7
|
+
data.tar.gz: fa5469997f6e674f3f2b1daf8bbc525637b2cbb2db7b9a8a1d494c85b1ce4d2725753ece2a2d114a905b994fe96f99e9db09178fa6f92483d4ee5d6208a500ad
|
data/.gitignore
CHANGED
data/lib/yarrow/config.rb
CHANGED
@@ -52,11 +52,11 @@ module Yarrow
|
|
52
52
|
#
|
53
53
|
# TODO: meta should be union of Type::Optional and Config::Meta
|
54
54
|
Instance = Yarrow::Schema::Value.new(
|
55
|
-
project_dir:
|
56
|
-
content_dir:
|
57
|
-
output_dir:
|
58
|
-
meta:
|
59
|
-
server:
|
55
|
+
project_dir: :path,
|
56
|
+
content_dir: :path,
|
57
|
+
output_dir: :path,
|
58
|
+
meta: :any,
|
59
|
+
server: :any
|
60
60
|
)
|
61
61
|
end
|
62
62
|
end
|
@@ -1,49 +1,14 @@
|
|
1
1
|
module Yarrow
|
2
2
|
module Content
|
3
3
|
class Expansion
|
4
|
-
|
5
|
-
|
6
|
-
attr_reader :graph
|
7
|
-
|
8
|
-
def initialize(graph)
|
9
|
-
@graph = graph
|
10
|
-
end
|
11
|
-
|
12
|
-
# Extract collection level configuration/metadata from the root node for
|
13
|
-
# this content type.
|
14
|
-
def extract_metadata(node, type)
|
15
|
-
# TODO: support _index or _slug convention as well
|
16
|
-
meta_file = node.out(slug: type.to_s).first
|
17
|
-
|
18
|
-
if meta_file
|
19
|
-
# Process metadata and add it to the collection node
|
20
|
-
# TODO: pass in content converter object
|
21
|
-
# TODO: index/body content by default if extracted from frontmatter
|
22
|
-
body, data = process_content(meta_file.props[:entry])
|
23
|
-
else
|
24
|
-
# Otherwise, assume default collection behaviour
|
25
|
-
data = {}
|
26
|
-
end
|
27
|
-
|
28
|
-
# Generate a default title if not provided in metadata
|
29
|
-
unless data.key?(:title)
|
30
|
-
data[:title] = type.to_s.capitalize
|
31
|
-
end
|
32
|
-
|
33
|
-
data
|
4
|
+
def initialize(model)
|
5
|
+
@model = model
|
34
6
|
end
|
35
7
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
when '.htm', '.md'
|
41
|
-
read_split_content(path.to_s, symbolize_keys: true)
|
42
|
-
# when '.md'
|
43
|
-
# body, data = read_split_content(path.to_s, symbolize_keys: true)
|
44
|
-
# [Kramdown::Document.new(body).to_html, data]
|
45
|
-
when '.yml'
|
46
|
-
[nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
|
8
|
+
def expand(graph)
|
9
|
+
@model.each_policy do |policy|
|
10
|
+
strategy = TreeExpansion.new(graph)
|
11
|
+
strategy.expand(policy)
|
47
12
|
end
|
48
13
|
end
|
49
14
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
class ExpansionStrategy
|
4
|
+
include Yarrow::Tools::FrontMatter
|
5
|
+
|
6
|
+
attr_reader :graph
|
7
|
+
|
8
|
+
def initialize(graph)
|
9
|
+
@graph = graph
|
10
|
+
end
|
11
|
+
|
12
|
+
# Extract collection level configuration/metadata from the root node for
|
13
|
+
# this content type.
|
14
|
+
def extract_metadata(node, type)
|
15
|
+
# TODO: support _index or _slug convention as well
|
16
|
+
meta_file = node.out(slug: type.to_s).first
|
17
|
+
|
18
|
+
if meta_file
|
19
|
+
# Process metadata and add it to the collection node
|
20
|
+
# TODO: pass in content converter object
|
21
|
+
# TODO: index/body content by default if extracted from frontmatter
|
22
|
+
body, data = process_content(meta_file.props[:entry])
|
23
|
+
else
|
24
|
+
# Otherwise, assume default collection behaviour
|
25
|
+
data = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generate a default title if not provided in metadata
|
29
|
+
unless data.key?(:title)
|
30
|
+
data[:title] = type.to_s.capitalize
|
31
|
+
end
|
32
|
+
|
33
|
+
data
|
34
|
+
end
|
35
|
+
|
36
|
+
# Workaround for handling meta and content source in multiple files or a single
|
37
|
+
# file with front matter.
|
38
|
+
def process_content(path)
|
39
|
+
case path.extname
|
40
|
+
when '.htm', '.md'
|
41
|
+
read_split_content(path.to_s, symbolize_keys: true)
|
42
|
+
# when '.md'
|
43
|
+
# body, data = read_split_content(path.to_s, symbolize_keys: true)
|
44
|
+
# [Kramdown::Document.new(body).to_html, data]
|
45
|
+
when '.yml'
|
46
|
+
[nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/yarrow/content/graph.rb
CHANGED
@@ -7,7 +7,7 @@ module Yarrow
|
|
7
7
|
#
|
8
8
|
# @return [Yarrow::Content::Graph]
|
9
9
|
def self.from_source(config)
|
10
|
-
new(
|
10
|
+
new(Source.collect(config.content_dir), config)
|
11
11
|
end
|
12
12
|
|
13
13
|
attr_reader :graph, :config
|
@@ -18,7 +18,7 @@ module Yarrow
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def expand_pages
|
21
|
-
expander = Yarrow::Content::
|
21
|
+
expander = Yarrow::Content::Expansion.new(Yarrow::Content::Model.new)
|
22
22
|
expander.expand(graph)
|
23
23
|
end
|
24
24
|
|
@@ -22,11 +22,11 @@ module Yarrow
|
|
22
22
|
manifest
|
23
23
|
end
|
24
24
|
|
25
|
-
attr_reader :documents, :
|
25
|
+
attr_reader :documents, :assets
|
26
26
|
|
27
27
|
def initialize
|
28
28
|
@documents = []
|
29
|
-
@
|
29
|
+
@assets = []
|
30
30
|
end
|
31
31
|
|
32
32
|
def self.collection_context(collection)
|
@@ -53,8 +53,8 @@ module Yarrow
|
|
53
53
|
@documents << document
|
54
54
|
end
|
55
55
|
|
56
|
-
def
|
57
|
-
@
|
56
|
+
def add_asset(asset)
|
57
|
+
@assets << asset
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
ContentSpec = Yarrow::Schema::Value.new(:namespace, :model) do
|
4
|
+
def to_world
|
5
|
+
"world"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
ContentPolicy = Yarrow::Schema::Value.new(
|
10
|
+
:dir,
|
11
|
+
:file,
|
12
|
+
:expansion,
|
13
|
+
:container,
|
14
|
+
:record
|
15
|
+
)
|
16
|
+
|
17
|
+
class Model
|
18
|
+
def initialize(spec=nil, namespace=nil)
|
19
|
+
@namespace = []
|
20
|
+
@namespace << namespace unless namespace.nil?
|
21
|
+
|
22
|
+
@policies = if spec.nil?
|
23
|
+
spec = {
|
24
|
+
root: ContentPolicy.new(
|
25
|
+
expansion: :tree,
|
26
|
+
dir: "*",
|
27
|
+
file: "*.md",
|
28
|
+
:container => :pages,
|
29
|
+
:record => :page
|
30
|
+
)
|
31
|
+
}
|
32
|
+
else
|
33
|
+
spec.model
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def each_policy(&block)
|
38
|
+
@policies.each_value(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def policy_for(policy_key)
|
42
|
+
@policies[policy_key]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
class Policy
|
4
|
+
Options = Yarrow::Schema::Value.new(
|
5
|
+
:container,
|
6
|
+
:entity,
|
7
|
+
:extensions,
|
8
|
+
:match_path
|
9
|
+
)
|
10
|
+
|
11
|
+
DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
|
12
|
+
|
13
|
+
DEFAULT_MATCH_PATH = "."
|
14
|
+
|
15
|
+
def self.from_name(name)
|
16
|
+
new(Options.new(container: name.to_sym))
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(properties)
|
20
|
+
unless properties.respond_to?(:container) || properties.respond_to?(:entity)
|
21
|
+
raise "Must provide a container name or entity name"
|
22
|
+
end
|
23
|
+
|
24
|
+
@properties = properties
|
25
|
+
end
|
26
|
+
|
27
|
+
def container
|
28
|
+
return @properties.container if @properties.container
|
29
|
+
Yarrow::Symbols.to_plural(@properties.entity)
|
30
|
+
end
|
31
|
+
|
32
|
+
def entity
|
33
|
+
return @properties.entity if @properties.entity
|
34
|
+
Yarrow::Symbols.to_singular(@properties.container)
|
35
|
+
end
|
36
|
+
|
37
|
+
def extensions
|
38
|
+
return @properties.extensions if @properties.extensions
|
39
|
+
DEFAULT_EXTENSIONS
|
40
|
+
end
|
41
|
+
|
42
|
+
def match_path
|
43
|
+
return @properties.match_path if @properties.match_path
|
44
|
+
DEFAULT_MATCH_PATH
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
class Resource < Schema::Entity
|
4
|
+
attribute :id, :string
|
5
|
+
attribute :name, :string
|
6
|
+
attribute :title, :string
|
7
|
+
attribute :url, :string
|
8
|
+
attribute :content, :string
|
9
|
+
|
10
|
+
def self.from_frontmatter_url(data, url_field)
|
11
|
+
new({
|
12
|
+
id: data[:id],
|
13
|
+
name: data[:name],
|
14
|
+
title: data[:title],
|
15
|
+
content: data[:content],
|
16
|
+
url: data[url_field]
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_template_url(data, template, &block)
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_source_path(data, &block)
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,10 +1,77 @@
|
|
1
1
|
module Yarrow
|
2
2
|
module Content
|
3
|
+
# Collects a digraph of all directories and files underneath the given input
|
4
|
+
# directory.
|
3
5
|
class Source
|
4
|
-
|
6
|
+
def self.collect(input_dir)
|
7
|
+
Mementus::Graph.new(is_mutable: true) do
|
8
|
+
root = create_node do |root|
|
9
|
+
root.label = :root
|
10
|
+
end
|
5
11
|
|
6
|
-
|
7
|
-
|
12
|
+
root_dir_entry = Pathname.new(input_dir)
|
13
|
+
|
14
|
+
root_dir = create_node do |dir|
|
15
|
+
dir.label = :directory
|
16
|
+
dir.props = {
|
17
|
+
name: root_dir_entry.basename.to_s,
|
18
|
+
path: root_dir_entry.to_s,
|
19
|
+
entry: root_dir_entry
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
create_edge do |child|
|
24
|
+
child.label = :child
|
25
|
+
child.from = root.id
|
26
|
+
child.to = root_dir.id
|
27
|
+
end
|
28
|
+
|
29
|
+
directories = {
|
30
|
+
root_dir_entry.to_s => root_dir.id
|
31
|
+
}
|
32
|
+
|
33
|
+
Pathname.glob("#{input_dir}/**/**").each do |entry|
|
34
|
+
if entry.directory?
|
35
|
+
content_node = create_node do |dir|
|
36
|
+
dir.label = :directory
|
37
|
+
# dir.props[:name] = entry.basename.to_s
|
38
|
+
# dir.props[:slug] = entry.basename.to_s
|
39
|
+
# dir.props[:path] = entry.to_s
|
40
|
+
# dir.props[:entry] = entry
|
41
|
+
dir.props = {
|
42
|
+
name: entry.basename.to_s,
|
43
|
+
path: entry.to_s,
|
44
|
+
entry: entry
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
directories[entry.to_s] = content_node.id
|
49
|
+
else
|
50
|
+
content_node = create_node do |file|
|
51
|
+
file.label = :file
|
52
|
+
# file.props[:name] = entry.basename.to_s
|
53
|
+
# file.props[:slug] = entry.basename.sub_ext('').to_s
|
54
|
+
# file.props[:path] = entry.to_s
|
55
|
+
# file.props[:entry] = entry
|
56
|
+
|
57
|
+
file.props = {
|
58
|
+
name: entry.basename.to_s,
|
59
|
+
ext: entry.extname.to_s,
|
60
|
+
path: entry.to_s,
|
61
|
+
entry: entry
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if directories.key?(entry.dirname.to_s)
|
67
|
+
create_edge do |edge|
|
68
|
+
edge.label = :child
|
69
|
+
edge.from = directories[entry.dirname.to_s]
|
70
|
+
edge.to = content_node
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
8
75
|
end
|
9
76
|
end
|
10
77
|
end
|
@@ -1,14 +1,23 @@
|
|
1
1
|
module Yarrow
|
2
2
|
module Content
|
3
|
-
class TreeExpansion <
|
4
|
-
def expand(
|
5
|
-
|
6
|
-
|
3
|
+
class TreeExpansion < ExpansionStrategy
|
4
|
+
def expand(policy)
|
5
|
+
#p graph.n(:root).out(:directory).to_a.count
|
6
|
+
#policy.match()
|
7
|
+
|
8
|
+
#p graph.n(:root).out(:directory).first.props[:name]
|
9
|
+
|
10
|
+
expand_impl(policy)
|
11
|
+
end
|
12
|
+
|
13
|
+
def expand_impl(policy)
|
14
|
+
type = policy.container
|
7
15
|
|
8
16
|
# If match path represents entire content dir, then include the entire
|
9
17
|
# content dir instead of scanning from a subfolder matching the name of
|
10
18
|
# the collection.
|
11
|
-
start_node = if
|
19
|
+
#start_node = if policy.match_path == "."
|
20
|
+
start_node = if true
|
12
21
|
graph.n(:root)
|
13
22
|
else
|
14
23
|
graph.n(:root).out(name: type.to_s)
|
@@ -52,7 +61,7 @@ module Yarrow
|
|
52
61
|
# Create an item node representing a file mapped to a unique content object
|
53
62
|
item = graph.create_node do |item_node|
|
54
63
|
item_node.label = :item
|
55
|
-
item_node.props[:type] =
|
64
|
+
item_node.props[:type] = policy.record
|
56
65
|
item_node.props[:name] = node.props[:entry].basename(node.props[:entry].extname).to_s
|
57
66
|
item_node.props[:body] = body if body
|
58
67
|
item_node.props[:title] = meta[:title] if meta
|
data/lib/yarrow/generator.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Schema
|
3
|
+
module Definitions
|
4
|
+
DEFINED_TYPES = {
|
5
|
+
string: Type::Raw[String],
|
6
|
+
integer: Type::Raw[Integer],
|
7
|
+
path: Type::Raw[Pathname],
|
8
|
+
any: Type::Any
|
9
|
+
}
|
10
|
+
|
11
|
+
def self.register(identifier, type_class)
|
12
|
+
if DEFINED_TYPES.key?(identifier)
|
13
|
+
raise "#{identifier} is already defined"
|
14
|
+
end
|
15
|
+
|
16
|
+
DEFINED_TYPES[identifier] = type_class
|
17
|
+
end
|
18
|
+
|
19
|
+
def resolve_type(identifier)
|
20
|
+
#return identifier unless identifier.is_a?(Symbol)
|
21
|
+
|
22
|
+
unless DEFINED_TYPES.key?(identifier)
|
23
|
+
raise "#{identifier} is not defined"
|
24
|
+
end
|
25
|
+
|
26
|
+
return DEFINED_TYPES[identifier]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Schema
|
3
|
+
# Specifies types plugged into each attribute slot and runs any required
|
4
|
+
# validations and coercions.
|
5
|
+
#
|
6
|
+
# Current design throws on error rather than returns a boolean result.
|
7
|
+
class Dictionary
|
8
|
+
include Definitions
|
9
|
+
|
10
|
+
# @param attrs_spec [Hash] defines the slots in the schema to validate against
|
11
|
+
def initialize(attrs_spec={})
|
12
|
+
@attrs_spec = attrs_spec.reduce({}) do |spec, (name, type_identifier)|
|
13
|
+
spec[name] = resolve_type(type_identifier)
|
14
|
+
spec
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def define_attribute(name, type_identifier)
|
19
|
+
@attrs_spec[name] = resolve_type(type_identifier)
|
20
|
+
end
|
21
|
+
|
22
|
+
def attr_names
|
23
|
+
@attrs_spec.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def check(input)
|
27
|
+
missing_attrs = @attrs_spec.keys.difference(input.keys)
|
28
|
+
|
29
|
+
if missing_attrs.any?
|
30
|
+
missing_attrs.each do |name|
|
31
|
+
raise "wrong number of attributes" unless @attrs_spec[name].eql?(Type::Any)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
mismatching_attrs = input.keys.difference(@attrs_spec.keys)
|
36
|
+
|
37
|
+
raise "attribute does not exist" if mismatching_attrs.any?
|
38
|
+
|
39
|
+
input.each do |(name, value)|
|
40
|
+
unless value.is_a?(@attrs_spec[name]) || @attrs_spec[name].eql?(Type::Any)
|
41
|
+
raise "wrong data type"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Schema
|
3
|
+
# Entity with comparison by reference equality. Generates attribute helpers
|
4
|
+
# for a declared set of props. Used to replace Hashie::Mash without dragging
|
5
|
+
# in a whole new library.
|
6
|
+
class Entity
|
7
|
+
class << self
|
8
|
+
def attribute(name, value_type)
|
9
|
+
dictionary.define_attribute(name, value_type)
|
10
|
+
attr_reader(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def dictionary
|
14
|
+
@dictionary ||= Dictionary.new({})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(config)
|
19
|
+
dictionary.check(config)
|
20
|
+
# dictionary.each_key do |name|
|
21
|
+
# raise "missing declared attribute #{name}" unless config.key?(name)
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
config.each_pair do |key, value|
|
25
|
+
# raise "#{key} not a declared attribute" unless dictionary.key?(key)
|
26
|
+
#
|
27
|
+
# defined_type = dictionary[key]
|
28
|
+
#
|
29
|
+
# unless value.is_a?(defined_type)
|
30
|
+
# raise "#{key} accepts #{defined_type} but #{value.class} given"
|
31
|
+
# end
|
32
|
+
|
33
|
+
instance_variable_set("@#{key}", value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_h
|
38
|
+
dictionary.attr_names.reduce({}) do |attr_dict, name|
|
39
|
+
attr_dict[name] = instance_variable_get("@#{name}")
|
40
|
+
attr_dict
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def dictionary
|
47
|
+
self.class.dictionary
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Schema
|
3
|
+
module Types
|
4
|
+
class CastError < TypeError
|
5
|
+
def self.instance_of(t, u)
|
6
|
+
new("#{t} is not an instance of #{u}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.kind_of(t, u)
|
10
|
+
new("#{t} is not a subclass of #{u}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class TypeClass
|
15
|
+
attr_reader :unit
|
16
|
+
|
17
|
+
def initialize(unit=nil)
|
18
|
+
@unit = unit
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_instance_of!(input)
|
22
|
+
unless input.instance_of?(unit)
|
23
|
+
raise CastError.instance_of(input.class, unit)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_kind_of!(input)
|
28
|
+
unless input.kind_of?(unit)
|
29
|
+
raise CastError.kind_of(input.class, unit)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_respond_to!(input)
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def cast(input); end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Any < TypeClass
|
41
|
+
def cast(input)
|
42
|
+
input
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Instance < TypeClass
|
47
|
+
def cast(input)
|
48
|
+
check_instance_of!(input)
|
49
|
+
input
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Kind
|
54
|
+
def cast(input)
|
55
|
+
check_kind_of!(input)
|
56
|
+
input
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Interface
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class Optional
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
class Constrained
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|