yarrow 0.7.2 → 0.7.3

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: 395db81bd349ed8e1a6b5cdc67d0fd191f6ede369bd358483611e0b79fb27cec
4
- data.tar.gz: '028961b86756cfc6bc1453c6d3ad7789149e8921066fb5429358e1b3b08393eb'
3
+ metadata.gz: 0c49c3ae295fd127f7533a0af4c543d5315a2929a580a21b6b4c26379e519490
4
+ data.tar.gz: 6867e3004c89dc7362a83378f3b0d55a14803d513cc77445c7893a31ffb4e4bd
5
5
  SHA512:
6
- metadata.gz: f531ff89949b7d1b80e215529f2c6c29d812f958e9af4491d58b7a0f10f9419a9ee26385ac58891cc435053fff31123d2dcfed0e02c8dcd48927d0b47c312f9f
7
- data.tar.gz: 8a5bb918411a04ac7d5e8b3c649501346e94d3cc76c17097ec9eed436685b163a04294b40491b38f0ea8539a28722bc8e77043bdab81aec493e04d5ccc0b6e0e
6
+ metadata.gz: '0056694b64f9ac4d000e85341570b4a5b630f27b5de15d5dcf12eeae1dc3625368abfd1d65a6c0a26f9793cbd6993e076c7e46d76ef97007874e8dddfd75c7af'
7
+ data.tar.gz: fa5469997f6e674f3f2b1daf8bbc525637b2cbb2db7b9a8a1d494c85b1ce4d2725753ece2a2d114a905b994fe96f99e9db09178fa6f92483d4ee5d6208a500ad
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ Gemfile.lock
6
6
  yarrow-*.gem
7
7
  coverage
8
8
  bin/scripts
9
+ .DS_Store
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: Pathname,
56
- content_dir: Pathname,
57
- output_dir: Pathname,
58
- meta: Yarrow::Schema::Type::Any,
59
- server: Yarrow::Schema::Type::Any
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
- 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
4
+ def initialize(model)
5
+ @model = model
34
6
  end
35
7
 
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)]
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
@@ -7,7 +7,7 @@ module Yarrow
7
7
  #
8
8
  # @return [Yarrow::Content::Graph]
9
9
  def self.from_source(config)
10
- new(SourceCollector.collect(config.content_dir), config)
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::CollectionExpander.new
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, :resources
25
+ attr_reader :documents, :assets
26
26
 
27
27
  def initialize
28
28
  @documents = []
29
- @resources = []
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 add_resource(resource)
57
- @resources << resource
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
- attr_reader :input_dir
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
- def initialize(config)
7
- @input_dir = config[:input_dir]
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 < Expansion
4
- def expand(content_type)
5
- type = content_type.collection
6
- exts = content_type.extensions
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 content_type.match_path == "."
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] = content_type.entity
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
@@ -13,7 +13,7 @@ module Yarrow
13
13
  provides Content::Graph
14
14
 
15
15
  def step(content)
16
- expander = Content::CollectionExpander.new
16
+ expander = Content::Expansion.new(Yarrow::Content::Model.new)
17
17
  expander.expand(content.graph)
18
18
  content
19
19
  end
@@ -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