yarrow 0.8.2 → 0.8.5

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: 4ea37c60e3c8bfe51e6ceb9425f0d0c81b93757a681c7987f7852c3335644824
4
- data.tar.gz: 19cd090d3b3348f633f5d303d8b826fd2b8c054b790136355a41d9b4f0f098c4
3
+ metadata.gz: 33fcaf7b1d66e43c0bd0a83967d0cc8b4891b96ec652bbfd28e5e91e7991b7e2
4
+ data.tar.gz: ccf4513eee11929864f76386e22adf39224a1898f5b207a3cefb11a30a5ed6e7
5
5
  SHA512:
6
- metadata.gz: ebd0dd29f6d8800b20560c2e24fd2988caecf19e9ae5f3e44a3d0b427afc9201971be1b086705e72c874683d30625821b270e6ad944c5c28ca9b6cd9ecd65ea2
7
- data.tar.gz: 92d36891c8a270ff663a299b2a217ffe3bb6985200e70d84c4c9d29ee87bb00fb7b2e824e0d932d585b5f8b4a22d578748ee58ad3ae022aa8f1b8ab04117ac6f
6
+ metadata.gz: 20272c169c2dd1280c06b80277a981217ae08c77c80892b4c8d07bac0564099ea0cbc248d54ac3160522e7e38c7dbe129aa703e6cb9e9cb848619f2009e15273
7
+ data.tar.gz: 143a1bbb91909f1e8921b9b587f611293b2f20de9f50d66b18a7346c00e6ebc2078d98ac0e9abcb88cad258e2f3a2d902f1f2eda811d27abd0348ece8f7cf87e
@@ -21,4 +21,11 @@ module Mementus
21
21
  "nodes_count=#{nodes_count} edges_count=#{edges_count}>"
22
22
  end
23
23
  end
24
+
25
+ class Node
26
+ def merge_props(data)
27
+ next_props = props.merge(data)
28
+ @props = next_props.freeze
29
+ end
30
+ end
24
31
  end
data/lib/yarrow/config.rb CHANGED
@@ -62,7 +62,7 @@ module Yarrow
62
62
  attribute :meta, :any
63
63
  attribute :server, :any
64
64
  attribute :content, :__config_content
65
- #attribute :output, :__config_output
65
+ attribute :output, :__config_output
66
66
  end
67
67
  #
68
68
  # `content_dir` and `output_dir` are placeholders and should be overriden
@@ -59,6 +59,12 @@ module Yarrow
59
59
  })
60
60
  end
61
61
 
62
+ output_obj = if config.key?(:output)
63
+ Yarrow::Config::Output.new(config[:output])
64
+ else
65
+ Yarrow::Config::Output.new({ generator: "web", template_dir: "templates" })
66
+ end
67
+
62
68
  # TODO: messy hack to get rid of Hashie::Mash, this should either be
63
69
  # automated as part of the schema types or a default value should be
64
70
  # generated here (eg: `"#{Dir.pwd}/docs"`)
@@ -70,7 +76,8 @@ module Yarrow
70
76
  source_dir: Pathname.new(File.expand_path(source_dir_or_string)),
71
77
  meta: meta_obj,
72
78
  server: server_obj,
73
- content: content_obj
79
+ content: content_obj,
80
+ output: output_obj
74
81
  )
75
82
  end
76
83
  end
@@ -3,19 +3,19 @@ module Yarrow
3
3
  module Expansion
4
4
  class Strategy
5
5
  include Yarrow::Tools::FrontMatter
6
-
6
+
7
7
  attr_reader :graph
8
-
8
+
9
9
  def initialize(graph)
10
10
  @graph = graph
11
11
  end
12
-
12
+
13
13
  # Extract collection level configuration/metadata from the root node for
14
14
  # this content type.
15
15
  def extract_metadata(node, type)
16
16
  # TODO: support _index or _slug convention as well
17
17
  meta_file = node.out(slug: type.to_s).first
18
-
18
+
19
19
  if meta_file
20
20
  # Process metadata and add it to the collection node
21
21
  # TODO: pass in content converter object
@@ -25,15 +25,32 @@ module Yarrow
25
25
  # Otherwise, assume default collection behaviour
26
26
  data = {}
27
27
  end
28
-
28
+
29
29
  # Generate a default title if not provided in metadata
30
30
  unless data.key?(:title)
31
31
  data[:title] = type.to_s.capitalize
32
32
  end
33
-
33
+
34
34
  data
35
35
  end
36
-
36
+
37
+ def populate_collection(node, policy, meta_attrs)
38
+ node.label = :collection
39
+ node.props[:type] = policy.collection
40
+ node.props[:resource] = policy.collection_const.new(meta_attrs)
41
+ end
42
+
43
+ def populate_entity(node, policy, meta_attrs)
44
+ node.label = :item
45
+ node.props[:type] = policy.entity
46
+ node.props[:resource] = policy.entity_const.new(meta_attrs)
47
+ end
48
+
49
+ def merge_collection_index(node, policy, meta_attrs)
50
+ props = { resource: node.props[:resource].merge(meta_attrs) }
51
+ node.merge_props(props)
52
+ end
53
+
37
54
  # Workaround for handling meta and content source in multiple files or a single
38
55
  # file with front matter.
39
56
  def process_content(path)
@@ -46,6 +63,7 @@ module Yarrow
46
63
  when '.yml'
47
64
  [nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
48
65
  end
66
+ # TODO: Raise error if unsupported extname reaches here
49
67
  end
50
68
  end
51
69
  end
@@ -21,11 +21,12 @@ module Yarrow
21
21
  end
22
22
 
23
23
  # Extract metadata from given start node
24
- collection_metadata = extract_metadata(start_node, policy.container)
24
+ #collection_metadata = extract_metadata(start_node, policy.container)
25
25
 
26
26
  # Collect all nested collections in the subgraph for this content type
27
27
  subcollections = {}
28
- item_links = []
28
+ entity_links = []
29
+ index_links = []
29
30
  index = nil
30
31
 
31
32
  # Scan and collect all nested files from the root
@@ -33,12 +34,14 @@ module Yarrow
33
34
  if node.label == :directory
34
35
  # Create a collection node representing a collection of documents
35
36
  index = graph.create_node do |collection_node|
36
- collection_node.label = :collection
37
- collection_node.props[:type] = policy.container
38
- collection_node.props[:name] = node.props[:name]
39
37
 
40
- # TODO: title needs to be defined from metadata
41
- collection_node.props[:title] = node.props[:name].capitalize
38
+ collection_attrs = {
39
+ name: node.props[:name],
40
+ title: node.props[:name].capitalize,
41
+ body: ""
42
+ }
43
+
44
+ populate_collection(collection_node, policy, collection_attrs)
42
45
  end
43
46
 
44
47
  # Add this collection id to the lookup table for edge construction
@@ -54,35 +57,55 @@ module Yarrow
54
57
  end
55
58
  elsif node.label == :file
56
59
  body, meta = process_content(node.props[:entry])
60
+ meta = {} if !meta
57
61
 
58
- # Create an item node representing a file mapped to a unique content object
59
- item = graph.create_node do |item_node|
60
- item_node.label = :item
61
- item_node.props[:type] = policy.entity
62
- item_node.props[:name] = node.props[:entry].basename(node.props[:entry].extname).to_s
63
- item_node.props[:body] = body if body
64
- item_node.props[:title] = meta[:title] if meta
65
- # TODO: better handling of metadata on node props
66
- end
62
+ # TODO: document mapping convention for index pages and collection metadata
63
+ # TODO: underscore _index pattern?
64
+ bare_basename = node.props[:entry].basename(node.props[:entry].extname)
65
+ if bare_basename.to_s == "index"
66
+ index_links << {
67
+ parent_id: subcollections[node.props[:entry].parent.to_s],
68
+ index_attrs: meta.merge({ body: body})
69
+ }
70
+ else
71
+ # Create an entity node representing a file mapped to a unique content object
72
+ entity = graph.create_node do |entity_node|
73
+
74
+ entity_slug = node.props[:entry].basename(node.props[:entry].extname).to_s
67
75
 
68
- # We may not have an expanded node for the parent collection if this is a
69
- # preorder traversal so save it for later
70
- item_links << {
71
- parent_path: node.props[:entry].parent.to_s,
72
- item_id: item.id
73
- }
76
+ entity_attrs = {
77
+ name: entity_slug,
78
+ title: entity_slug.gsub("-", " ").capitalize,
79
+ body: body
80
+ }
81
+
82
+ populate_entity(entity_node, policy, entity_attrs.merge(meta || {}))
83
+ end
84
+
85
+ # We may not have an expanded node for the parent collection if this is a
86
+ # preorder traversal so save it for later
87
+ entity_links << {
88
+ parent_id: subcollections[node.props[:entry].parent.to_s],
89
+ child_id: entity
90
+ }
91
+ end
74
92
  end
75
93
  end
76
94
 
77
95
  # Once all files and directories have been expanded, connect all the child
78
- # edges between collections and items
79
- item_links.each do |item_link|
96
+ # edges between collections and entities
97
+ entity_links.each do |entity_link|
80
98
  graph.create_edge do |edge|
81
99
  edge.label = :child
82
- edge.from = subcollections[item_link[:parent_path]].id
83
- edge.to = item_link[:item_id]
100
+ edge.from = entity_link[:parent_id].id
101
+ edge.to = entity_link[:child_id].id
84
102
  end
85
103
  end
104
+
105
+ # Merge index page body and metadata with their parent collections
106
+ index_links.each do |index_link|
107
+ merge_collection_index(index_link[:parent_id], policy, index_link[:index_attrs])
108
+ end
86
109
  end
87
110
  end
88
111
  end
@@ -4,7 +4,11 @@ module Yarrow
4
4
  def initialize(content_config)
5
5
  @policies = {}
6
6
  content_config.source_map.each_entry do |policy_label, policy_spec|
7
- @policies[policy_label] = Policy.from_spec(policy_label, policy_spec)
7
+ @policies[policy_label] = Policy.from_spec(
8
+ policy_label,
9
+ policy_spec,
10
+ content_config.module
11
+ )
8
12
  end
9
13
  end
10
14
 
@@ -9,6 +9,8 @@ module Yarrow
9
9
 
10
10
  DEFAULT_MATCH_PATH = "."
11
11
 
12
+ MODULE_SEPARATOR = "::"
13
+
12
14
  # Construct a content policy from the given source specification.
13
15
  def self.from_spec(policy_label, policy_props, module_prefix="")
14
16
  # TODO: validate length, structure etc
@@ -27,7 +29,7 @@ module Yarrow
27
29
  if policy_props.key?(:entity)
28
30
  Yarrow::Symbols.to_plural(policy_props[:entity])
29
31
  else
30
- Yarrow::Symbols.to_plural(label)
32
+ Yarrow::Symbols.to_plural(policy_label)
31
33
  end
32
34
  end
33
35
 
@@ -38,7 +40,7 @@ module Yarrow
38
40
  if policy_props.key?(:container)
39
41
  Yarrow::Symbols.to_singular(policy_props[:container])
40
42
  else
41
- Yarrow::Symbols.to_singular(label)
43
+ Yarrow::Symbols.to_singular(policy_label)
42
44
  end
43
45
  end
44
46
 
@@ -64,7 +66,7 @@ module Yarrow
64
66
  end
65
67
  end
66
68
 
67
- attr_reader :container, :entity, :expansion, :extensions, :match_path, :module
69
+ attr_reader :container, :entity, :expansion, :extensions, :match_path, :module_prefix
68
70
 
69
71
  def initialize(container, entity, expansion, extensions, match_path, module_prefix)
70
72
  @container = container
@@ -72,35 +74,18 @@ module Yarrow
72
74
  @expansion = expansion
73
75
  @extensions = extensions
74
76
  @match_path = match_path
75
- @module_prefix = module_prefix
77
+ @module_prefix = module_prefix.split(MODULE_SEPARATOR)
76
78
  end
77
79
 
78
80
  def container_const
79
- @container_const ||= Yarrow::Symbols.to_module_const([module_prefix, container])
80
- end
81
-
82
- def entity_const
83
- @entity_const ||= Yarrow::Symbols.to_module_const([module_prefix, entity])
84
- end
85
-
86
- def _container
87
- return @properties.container if @properties.container
88
- Yarrow::Symbols.to_plural(@properties.entity)
81
+ @container_const ||= Yarrow::Symbols.to_module_const([*module_prefix, container])
89
82
  end
90
83
 
91
- def _entity
92
- return @properties.entity if @properties.entity
93
- Yarrow::Symbols.to_singular(@properties.container)
94
- end
84
+ alias_method :collection, :container
85
+ alias_method :collection_const, :container_const
95
86
 
96
- def _extensions
97
- return @properties.extensions if @properties.extensions
98
- DEFAULT_EXTENSIONS
99
- end
100
-
101
- def _match_path
102
- return @properties.match_path if @properties.match_path
103
- DEFAULT_MATCH_PATH
87
+ def entity_const
88
+ @entity_const ||= Yarrow::Symbols.to_module_const([*module_prefix, entity])
104
89
  end
105
90
  end
106
91
  end
@@ -58,7 +58,7 @@ module Yarrow
58
58
  end
59
59
  end
60
60
 
61
- private
61
+ #private
62
62
 
63
63
  attr_reader :config, :workflow
64
64
 
@@ -8,7 +8,8 @@ module Yarrow
8
8
  path: Types::Instance.of(Pathname).accept(String),
9
9
  any: Types::Any.new,
10
10
  array: Types::List.of(Types::Any),
11
- hash: Types::Instance.of(Hash)
11
+ hash: Types::Instance.of(Hash),
12
+ markdown: Types::Instance.of(Kramdown::Document).accept(String)
12
13
  }
13
14
 
14
15
  TEMPLATE_TYPES = {
@@ -28,13 +28,13 @@ module Yarrow
28
28
 
29
29
  if missing_attrs.any?
30
30
  missing_attrs.each do |name|
31
- raise "wrong number of attributes" unless @attrs_spec[name].is_a?(Types::Any)
31
+ raise "#{missing_attrs} wrong number of attributes" unless @attrs_spec[name].is_a?(Types::Any)
32
32
  end
33
33
  end
34
34
 
35
35
  mismatching_attrs = input.keys.difference(@attrs_spec.keys)
36
36
 
37
- raise "attribute does not exist" if mismatching_attrs.any?
37
+ raise "attribute #{mismatching_attrs} does not exist" if mismatching_attrs.any?
38
38
 
39
39
  input.reduce({}) do |converted, (name, value)|
40
40
  converted[name] = @attrs_spec[name].cast(value)
@@ -59,7 +59,7 @@ module Yarrow
59
59
  end
60
60
 
61
61
  def merge(other)
62
- unless other.is_a?(self.class)
62
+ unless other.is_a?(self.class) || other.is_a?(Hash)
63
63
  raise ArgumentError.new("cannot merge entities that are not the same type")
64
64
  end
65
65
 
@@ -31,8 +31,13 @@ module Yarrow
31
31
  @accepts = {}
32
32
  end
33
33
 
34
- def accept(type, constructor=:new)
35
- accepts[type] = constructor
34
+ def accept(type, constructor=:new, options=nil)
35
+ accepts[type] = if options.nil?
36
+ [constructor]
37
+ else
38
+ [constructor, options]
39
+ end
40
+
36
41
  self
37
42
  end
38
43
 
@@ -41,8 +46,14 @@ module Yarrow
41
46
  end
42
47
 
43
48
  def coerce(input)
44
- constructor = accepts[input.class]
45
- unit.send(constructor, input)
49
+ constructor, options = accepts[input.class]
50
+
51
+ # TODO: should we clone all input so copy is stored rather than ref?
52
+ if options.nil?
53
+ unit.send(constructor, input)
54
+ else
55
+ unit.send(constructor, input, options.clone)
56
+ end
46
57
  end
47
58
 
48
59
  def check_instance_of!(input)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Yarrow
3
3
  APP_NAME = "Yarrow"
4
- VERSION = "0.8.2"
4
+ VERSION = "0.8.5"
5
5
  end
@@ -6,35 +6,75 @@ module Yarrow
6
6
  # too clever which has burned this lib in the past).
7
7
  def initialize(item, parent, is_index)
8
8
  @item = item
9
+ @resource = item.props[:resource]
9
10
  @parent = parent
10
11
  @is_index = is_index
11
12
  end
12
13
 
14
+ def resource
15
+ @resource
16
+ end
17
+
18
+ def index
19
+ _index = @item.out_e(:index)
20
+ unless _index.first.nil?
21
+ _index.first.to.props
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ def index_body
28
+ @item.props[:index_body]
29
+ end
30
+
31
+ # TODO: manage behaviour with and without current item
32
+ # TODO: link to manifest
33
+ def breadcrumbs
34
+ path = []
35
+
36
+ current_parent = @item.in(:collection)
37
+
38
+ while !current_parent.first.nil?
39
+ path << current_parent.first.props[:resource]
40
+ current_parent = current_parent.in(:collection)
41
+ end
42
+
43
+ path.reverse
44
+ end
45
+
13
46
  def name
14
- @item.props[:name]
47
+ @resource.name
15
48
  end
16
49
 
17
50
  def title
18
- @item.props[:title]
51
+ @resource.title
19
52
  end
20
53
 
21
54
  def type
22
55
  @item.props[:type]
23
56
  end
24
57
 
58
+ def body
59
+ return @resource.body.to_html if @resource.respond_to?(:body)
60
+ ""
61
+ end
62
+
25
63
  def url
26
64
  if @parent.nil?
27
65
  "/"
28
66
  else
29
- segments = [@item.props[:name]]
67
+ segments = [@resource.name]
30
68
  current = @parent
31
69
 
32
70
  until current.in(:collection).first.nil? do
33
- segments << current.props[:name]
71
+ segments << current.props[:resource].name
34
72
  current = current.in(:collection).first
35
73
  end
36
74
 
37
- "/" + segments.reverse.join("/")
75
+ suffix = @is_index ? "/" : ""
76
+
77
+ "/" + segments.reverse.join("/") + suffix
38
78
  end
39
79
  end
40
80
  end
@@ -18,7 +18,7 @@ module Yarrow
18
18
  end
19
19
 
20
20
  def write_document(document)
21
- template = Template.for_document(document)
21
+ template = Template.for_document(document, config)
22
22
  write_output_file(document.url, template.render(document))
23
23
  end
24
24
 
@@ -39,6 +39,8 @@ module Yarrow
39
39
  File.open(path.to_s, 'w+:UTF-8') do |file|
40
40
  file.puts(content)
41
41
  end
42
+
43
+ puts "[write] #{path} → #{url}"
42
44
  end
43
45
 
44
46
  def generate_sitemap(manifest)
@@ -12,7 +12,7 @@ module Yarrow
12
12
  unless collection.props[:index_only]
13
13
  collection.out(:item).each do |item|
14
14
  #if item[:entity].status.to_sym == :published
15
- if item.props[:name] == "index"
15
+ if item.props[:resource].name == "index"
16
16
  index = item
17
17
  else
18
18
  manifest.add_document(item_context(item))
@@ -25,7 +25,7 @@ module Yarrow
25
25
  unless collection.props[:content_only]
26
26
  if index
27
27
  manifest.add_document(collection_index_context(collection, index))
28
- else
28
+ else
29
29
  manifest.add_document(collection_context(collection))
30
30
  end
31
31
  end
@@ -1,14 +1,15 @@
1
1
  module Yarrow
2
2
  module Web
3
3
  class Template
4
- def self.for_document(document)
4
+ def self.for_document(document, config)
5
5
  layout_name = if document.respond_to?(:layout)
6
6
  document.layout || document.type
7
7
  else
8
8
  document.type
9
9
  end
10
10
 
11
- @template_dir = "./spec/fixtures/templates/doctest"
11
+ @template_dir = config.output.template_dir
12
+ #@template_dir = "./spec/fixtures/templates/doctest"
12
13
  @template_ext = ".html"
13
14
 
14
15
  template_file = "#{layout_name}#{@template_ext}"
data/lib/yarrow.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "pathname"
2
2
  require "yaml"
3
+ require "kramdown"
3
4
  require "mustache"
4
5
  require "parallel"
5
6
 
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.8.2
4
+ version: 0.8.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rickerby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-04 00:00:00.000000000 Z
11
+ date: 2022-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable