yarrow 0.7.1 → 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: 95557525739422605b678c010cad87b67b3e01e1382a9a7b65b032cd7ccfdceb
4
- data.tar.gz: 90bd1dfc26fc41c8aee75586f760b05d0b122fe1868cb061b29a7344a0627165
3
+ metadata.gz: 0c49c3ae295fd127f7533a0af4c543d5315a2929a580a21b6b4c26379e519490
4
+ data.tar.gz: 6867e3004c89dc7362a83378f3b0d55a14803d513cc77445c7893a31ffb4e4bd
5
5
  SHA512:
6
- metadata.gz: cb128a24a28ad30bef33b1db635e952a3c4930d75094ce5434fe3cbeb890644fb1704a4d619144a915b0e71cc41c333d8ce3186fb2b8d5e3eee334bb68aa8b9d
7
- data.tar.gz: ee6767873b4afa6270b7a8e21dc096eac492624d47063e7d742d81cdc3e5b2608595f8ce945f299e036cb19245b980df56322a768d225731f16275088bd54dd3
6
+ metadata.gz: '0056694b64f9ac4d000e85341570b4a5b630f27b5de15d5dcf12eeae1dc3625368abfd1d65a6c0a26f9793cbd6993e076c7e46d76ef97007874e8dddfd75c7af'
7
+ data.tar.gz: fa5469997f6e674f3f2b1daf8bbc525637b2cbb2db7b9a8a1d494c85b1ce4d2725753ece2a2d114a905b994fe96f99e9db09178fa6f92483d4ee5d6208a500ad
@@ -10,7 +10,7 @@ jobs:
10
10
  fail-fast: false
11
11
  matrix:
12
12
  os: [ubuntu-latest, macos-latest]
13
- ruby: [2.7, '3.0', jruby-head]
13
+ ruby: [2.7, '3.0', 3.1, head]
14
14
  runs-on: ${{ matrix.os }}
15
15
  steps:
16
16
  - uses: actions/checkout@v2
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
@@ -42,19 +42,21 @@ module Yarrow
42
42
  :root_dir
43
43
  )
44
44
 
45
- # Top level root config namespace. Source, content and output are directory
46
- # paths and should be the only required defaults for a complete batch run.
45
+ # Top level root config namespace.
46
+ #
47
+ # `content_dir` and `output_dir` are placeholders and should be overriden
48
+ # with more fine-grained config for web and book outputs in future.
47
49
  #
48
50
  # Additional server config is optional and only needed if running the dev
49
51
  # server locally.
50
52
  #
51
53
  # TODO: meta should be union of Type::Optional and Config::Meta
52
54
  Instance = Yarrow::Schema::Value.new(
53
- source: Pathname,
54
- content: Pathname,
55
- output_dir: Pathname,
56
- meta: Yarrow::Schema::Type::Any,
57
- server: Yarrow::Schema::Type::Any
55
+ project_dir: :path,
56
+ content_dir: :path,
57
+ output_dir: :path,
58
+ meta: :any,
59
+ server: :any
58
60
  )
59
61
  end
60
62
  end
@@ -52,13 +52,13 @@ module Yarrow
52
52
  # automated as part of the schema types or a default value should be
53
53
  # generated here (eg: `"#{Dir.pwd}/docs"`)
54
54
  out_dir_or_string = config[:output_dir] || ""
55
- source_dir_or_string = config[:source] || ""
56
- content_dir_or_string = config[:content] || ""
55
+ source_dir_or_string = config[:project_dir] || ""
56
+ content_dir_or_string = config[:content_dir] || ""
57
57
 
58
58
  Yarrow::Config::Instance.new(
59
59
  output_dir: Pathname.new(File.expand_path(out_dir_or_string)),
60
- source: Pathname.new(File.expand_path(source_dir_or_string)),
61
- content: Pathname.new(File.expand_path(content_dir_or_string)),
60
+ project_dir: Pathname.new(File.expand_path(source_dir_or_string)),
61
+ content_dir: Pathname.new(File.expand_path(content_dir_or_string)),
62
62
  meta: meta_obj,
63
63
  server: server_obj
64
64
  )
@@ -1,5 +1,4 @@
1
1
  module Yarrow
2
-
3
2
  class ConsoleRunner
4
3
 
5
4
  SUCCESS = 0
@@ -100,7 +99,6 @@ module Yarrow
100
99
  end
101
100
 
102
101
  def normalize_theme_path
103
-
104
102
  # noop
105
103
  end
106
104
 
@@ -139,12 +137,11 @@ module Yarrow
139
137
  @options.has_key? option
140
138
  end
141
139
 
142
- def run_input_process
143
- # noop
144
- end
145
-
146
- def run_output_process
147
- # noop
140
+ def run_generation_process
141
+ generator = Generator.new(@config)
142
+ generator.process do |manifest|
143
+ p manifest
144
+ end
148
145
  end
149
146
 
150
147
  def print_header
@@ -0,0 +1,16 @@
1
+ module Yarrow
2
+ module Content
3
+ class Expansion
4
+ def initialize(model)
5
+ @model = model
6
+ end
7
+
8
+ def expand(graph)
9
+ @model.each_policy do |policy|
10
+ strategy = TreeExpansion.new(graph)
11
+ strategy.expand(policy)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ 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
@@ -2,9 +2,12 @@ module Yarrow
2
2
  module Content
3
3
  # A directed graph of every element of content in the project.
4
4
  class Graph
5
- # Construct a graph collected from source content files.
5
+ # Construct a graph collected from files and directories in the configured
6
+ # content directory.
7
+ #
8
+ # @return [Yarrow::Content::Graph]
6
9
  def self.from_source(config)
7
- new(SourceCollector.collect(config.source), config)
10
+ new(Source.collect(config.content_dir), config)
8
11
  end
9
12
 
10
13
  attr_reader :graph, :config
@@ -15,7 +18,7 @@ module Yarrow
15
18
  end
16
19
 
17
20
  def expand_pages
18
- expander = Yarrow::Content::CollectionExpander.new
21
+ expander = Yarrow::Content::Expansion.new(Yarrow::Content::Model.new)
19
22
  expander.expand(graph)
20
23
  end
21
24
 
@@ -28,6 +31,16 @@ module Yarrow
28
31
  def directories
29
32
  graph.nodes(:directory)
30
33
  end
34
+
35
+ # List of mapped content object collections
36
+ def collections
37
+ graph.nodes(:collection)
38
+ end
39
+
40
+ # List of mapped content object items
41
+ def items
42
+ graph.nodes(:item)
43
+ end
31
44
  end
32
45
  end
33
46
  end
@@ -0,0 +1,61 @@
1
+ module Yarrow
2
+ module Content
3
+ class Manifest
4
+ def self.build(graph)
5
+ manifest = new
6
+
7
+ graph.n(:collection).each do |collection|
8
+
9
+ unless collection.props[:content_only]
10
+ manifest.add_document(collection_context(collection))
11
+ end
12
+
13
+ unless collection.props[:index_only]
14
+ collection.out(:item).each do |item|
15
+ #if item[:entity].status.to_sym == :published
16
+ manifest.add_document(item_context(item))
17
+ #end
18
+ end
19
+ end
20
+ end
21
+
22
+ manifest
23
+ end
24
+
25
+ attr_reader :documents, :assets
26
+
27
+ def initialize
28
+ @documents = []
29
+ @assets = []
30
+ end
31
+
32
+ def self.collection_context(collection)
33
+ Yarrow::Output::Context.new(
34
+ parent: collection.in(:collection).first,
35
+ name: collection.props[:name],
36
+ #url: collection.props[:url],
37
+ title: collection.props[:title],
38
+ type: collection.props[:type]
39
+ )
40
+ end
41
+
42
+ def self.item_context(item)
43
+ Yarrow::Output::Context.new(
44
+ parent: item.in(:collection).first,
45
+ name: item.props[:name],
46
+ #url: item.props[:url],
47
+ title: item.props[:title],
48
+ type: item.props[:type]
49
+ )
50
+ end
51
+
52
+ def add_document(document)
53
+ @documents << document
54
+ end
55
+
56
+ def add_asset(asset)
57
+ @assets << asset
58
+ end
59
+ end
60
+ end
61
+ 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
@@ -0,0 +1,92 @@
1
+ module Yarrow
2
+ module Content
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
15
+
16
+ # If match path represents entire content dir, then include the entire
17
+ # content dir instead of scanning from a subfolder matching the name of
18
+ # the collection.
19
+ #start_node = if policy.match_path == "."
20
+ start_node = if true
21
+ graph.n(:root)
22
+ else
23
+ graph.n(:root).out(name: type.to_s)
24
+ end
25
+
26
+ # Extract metadata from given start node
27
+ collection_metadata = extract_metadata(start_node, type)
28
+
29
+ # Collect all nested collections in the subgraph for this content type
30
+ subcollections = {}
31
+ item_links = []
32
+ index = nil
33
+
34
+ # Scan and collect all nested files from the root
35
+ start_node.depth_first.each do |node|
36
+ if node.label == :directory
37
+ # Create a collection node representing a collection of documents
38
+ index = graph.create_node do |collection_node|
39
+ collection_node.label = :collection
40
+ collection_node.props[:type] = type
41
+ collection_node.props[:name] = node.props[:name]
42
+
43
+ # TODO: title needs to be defined from metadata
44
+ collection_node.props[:title] = node.props[:name].capitalize
45
+ end
46
+
47
+ # Add this collection id to the lookup table for edge construction
48
+ subcollections[node.props[:path]] = index
49
+
50
+ # Join the collection to its parent
51
+ unless node.props[:slug] == type.to_s || !subcollections.key?(node.props[:entry].parent.to_s)
52
+ graph.create_edge do |edge|
53
+ edge.label = :child
54
+ edge.from = subcollections[node.props[:entry].parent.to_s].id
55
+ edge.to = index.id
56
+ end
57
+ end
58
+ elsif node.label == :file
59
+ body, meta = process_content(node.props[:entry])
60
+
61
+ # Create an item node representing a file mapped to a unique content object
62
+ item = graph.create_node do |item_node|
63
+ item_node.label = :item
64
+ item_node.props[:type] = policy.record
65
+ item_node.props[:name] = node.props[:entry].basename(node.props[:entry].extname).to_s
66
+ item_node.props[:body] = body if body
67
+ item_node.props[:title] = meta[:title] if meta
68
+ # TODO: better handling of metadata on node props
69
+ end
70
+
71
+ # We may not have an expanded node for the parent collection if this is a
72
+ # preorder traversal so save it for later
73
+ item_links << {
74
+ parent_path: node.props[:entry].parent.to_s,
75
+ item_id: item.id
76
+ }
77
+ end
78
+ end
79
+
80
+ # Once all files and directories have been expanded, connect all the child
81
+ # edges between collections and items
82
+ item_links.each do |item_link|
83
+ graph.create_edge do |edge|
84
+ edge.label = :child
85
+ edge.from = subcollections[item_link[:parent_path]].id
86
+ edge.to = item_link[:item_id]
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,27 +1,57 @@
1
1
  module Yarrow
2
+ class ScanSource < Process::StepProcessor
3
+ accepts Config::Instance
4
+ provides Content::Graph
5
+
6
+ def step(config)
7
+ Yarrow::Content::Graph.from_source(config)
8
+ end
9
+ end
10
+
11
+ class ExpandCollections < Process::StepProcessor
12
+ accepts Content::Graph
13
+ provides Content::Graph
14
+
15
+ def step(content)
16
+ expander = Content::Expansion.new(Yarrow::Content::Model.new)
17
+ expander.expand(content.graph)
18
+ content
19
+ end
20
+ end
21
+
22
+ class FlattenManifest < Process::StepProcessor
23
+ accepts Content::Graph
24
+ provides Content::Manifest
25
+
26
+ def step(content)
27
+ Content::Manifest.build(content.graph)
28
+ end
29
+ end
30
+
31
+ class BuildOutput < Process::StepProcessor
32
+ accepts Content::Manifest
33
+ provides Output::Result
34
+ end
2
35
 
3
36
  # Generates documentation from a model.
4
- #
5
- # Subclasses of Generator need to override the template methods,
6
- # to specify a particular file structure to output.
7
37
  class Generator
8
-
9
- def initialize(target, site_tree)
10
- ensure_dir_exists! target
11
- @target = target
12
- @site_tree = site_tree
38
+ def initialize(config)
39
+ @config = config
40
+ @workflow = Process::Workflow.new(config)
13
41
  end
14
-
15
- def ensure_dir_exists!(target)
16
- unless File.directory? target
17
- Dir.mkdir target
42
+
43
+ def process(&block)
44
+ workflow.connect(ScanSource.new)
45
+ workflow.connect(ExpandCollections.new)
46
+ workflow.connect(FlattenManifest.new)
47
+
48
+ workflow.process do |result|
49
+ block.call(result)
18
50
  end
19
51
  end
20
-
21
- def build_docs
22
-
23
- end
24
-
25
- end
26
52
 
27
- end
53
+ private
54
+
55
+ attr_reader :config, :workflow
56
+ end
57
+ end
@@ -0,0 +1,13 @@
1
+ module Yarrow
2
+ module Output
3
+ class Log
4
+
5
+ end
6
+
7
+ class Result
8
+ def initialize
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -27,12 +27,18 @@ module Yarrow
27
27
 
28
28
  target_path = Pathname.new("#{docroot}#{path}")
29
29
 
30
- FileUtils.mkdir_p(target_path.dirname)
30
+ ensure_dir_exists!(target_path.dirname)
31
31
 
32
32
  File.open(target_path.to_s, WRITE_MODE) do |file|
33
33
  file.puts(content)
34
34
  end
35
35
  end
36
+
37
+ def ensure_dir_exists!(target)
38
+ unless File.directory?(target)
39
+ FileUtils.mkdir_p(target)
40
+ end
41
+ end
36
42
  end
37
43
  end
38
44
  end