kojo 0.2.1 → 0.2.2

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: 66b59e59e2a7d874db8c30218544c194798aa8b0e0680dbc1a906f3f75f67e59
4
- data.tar.gz: 56ff38e17c7cedd6df3373275fdacc65e9bc7fe8f2df5bdf7bdd650edea5eb37
3
+ metadata.gz: 60d550cda8b281a7ecbac40c236b6446fbe6a4d0b2ed7cb261ec47abeb654cbc
4
+ data.tar.gz: a991496e62aa272ad939b5c663cec543ed5e49aa9dedff5f3505c579d50de8d5
5
5
  SHA512:
6
- metadata.gz: 1202ad70051723bcbeceaf4ef641704f133685177dc42f9479bb7d4391d69e0b1d6b80245b684c68788627933be382843865654a6b7766e4ad6379d5963d4c89
7
- data.tar.gz: 312a0a592b342cef71bbe08f5c38c3db2007047fa052ae3f34faa65910d4cb41af60deb9b241804290a2ab3d2e42c269e8581ece1c543fae3ddf9c2349d94387
6
+ metadata.gz: aa51aa1c425c7af70bd8fd12bed004c0472d290e96cfdb172e3222f76d994a671eba764b45baae57f1287ebb4f7ba27fa94f0fbf725edc714095740653c9507a
7
+ data.tar.gz: 58802308a1134db6abd3265b0f7ccacd854fe4cdef6468f6c15217799f75eaf03aae0e9761a2b990201ae6c50cb680f2bb026f1f8869be86930bbff4dce36779
data/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  ![kojo](images/kojo.png)
4
4
 
5
- Kojo - Configuration Ninja
5
+ Kojo Configuration Ninja
6
6
  ==================================================
7
7
 
8
8
  [![Gem Version](https://badge.fury.io/rb/kojo.svg)](https://badge.fury.io/rb/kojo)
9
9
  [![Build Status](https://travis-ci.com/DannyBen/kojo.svg?branch=master)](https://travis-ci.com/DannyBen/kojo)
10
10
  [![Maintainability](https://api.codeclimate.com/v1/badges/f24566ad04b5054a2251/maintainability)](https://codeclimate.com/github/DannyBen/kojo/maintainability)
11
-
11
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/f24566ad04b5054a2251/test_coverage)](https://codeclimate.com/github/DannyBen/kojo/test_coverage)
12
12
 
13
13
  Kojo helps you generate configuration files from templates, using variables
14
14
  and definition files.
@@ -25,8 +25,8 @@ Table of Contents
25
25
  - [Usage](#usage)
26
26
  - [Variables](#variables)
27
27
  - [Import](#import)
28
- - [One to Many Generation](#one-to-many-generation)
29
28
  - [Compile an Entire Folder](#compile-an-entire-folder)
29
+ - [One to Many Generation](#one-to-many-generation)
30
30
  - [Conditions and Loops with ERB](#conditions-and-loops-with-erb)
31
31
 
32
32
  ---
@@ -41,7 +41,7 @@ Installation
41
41
  Usage
42
42
  --------------------------------------------------
43
43
 
44
- If you prefer to learn by example, see the [examples](examples) folder for
44
+ If you prefer to learn by example, see the [examples](examples#examples) folder for
45
45
  several use cases. Each example subfolder contains the command to run, the
46
46
  relevant files, and the expected output.
47
47
 
@@ -85,15 +85,28 @@ The space after `filename` is optional.
85
85
 
86
86
 
87
87
 
88
+ ### Compile an Entire Folder
89
+
90
+ ![kojo](images/features-dir.svg)
91
+
92
+ Process a folder containing templates and `@imports`, and generate a mirror
93
+ output folder, with all the variables and `@imports` evaluated.
94
+
95
+
96
+
88
97
  ### One to Many Generation
89
98
 
90
99
  ![kojo](images/features-config.svg)
91
100
 
92
- In order to generate several configuration files that are based on the same
93
- template, you should:
101
+ Using the `kojo config` command together with a simple definitions file, you
102
+ can:
103
+
104
+ 1. Generate multiple output files based on a single template file
105
+ 2. Generate multiple output directories, based on a single source directory.
106
+
107
+ To achieve this, you need to:
94
108
 
95
- 1. Create the configuration template, using `%{variables}` and `@imports`
96
- where appropriate.
109
+ 1. Create the configuration template or directory of templates.
97
110
  2. Create a configuration YAML file using this syntax:
98
111
 
99
112
  ```yaml
@@ -109,15 +122,22 @@ output:
109
122
  argument2: value
110
123
  ```
111
124
 
125
+ When using a folder as input, simply provide the folder name in the `input`
126
+ property, and instead of providing desired output filenames in the `output`
127
+ property, provide desired output directories:
112
128
 
129
+ ```yaml
130
+ input: base
113
131
 
114
- ### Compile an Entire Folder
115
-
116
- ![kojo](images/features-dir.svg)
117
-
118
- Process a folder containing templates and `@imports`, and generate a mirror
119
- output folder, with all the variables and `@imports` evaluated.
132
+ output:
133
+ app1:
134
+ argument1: value
135
+ argument2: value
120
136
 
137
+ app2:
138
+ argument1: value
139
+ argument2: value
140
+ ```
121
141
 
122
142
 
123
143
  ### Conditions and Loops with ERB
data/bin/kojo CHANGED
@@ -7,6 +7,6 @@ begin
7
7
  exit runner.run ARGV
8
8
  rescue => e
9
9
  puts e.backtrace.reverse if ENV['DEBUG']
10
- say! "!txtred!#{e.class}: #{e.message}"
10
+ say! "!undred!#{e.class}!txtrst!\n#{e.message}"
11
11
  exit 1
12
12
  end
data/lib/kojo.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'requires'
2
2
  require 'byebug' if ENV['BYEBUG']
3
- requires 'kojo'
3
+ requires 'kojo/exceptions', 'kojo'
4
4
 
5
5
  module Kojo
6
6
  module Commands
data/lib/kojo/cli.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'mister_bin'
2
2
 
3
3
  module Kojo
4
+ # The CLI class is used by the kojo binary and forwards incoming CLI
5
+ # commands to the relevant Kojo::Commands class
4
6
  class CLI
5
7
  def self.runner
6
8
  runner = MisterBin::Runner.new version: Kojo::VERSION
@@ -0,0 +1,38 @@
1
+ module Kojo
2
+ # The Collection class is a wrapper around the {Template} object. It
3
+ # provides a mechanism for processing an entire directory of templates.
4
+ class Collection
5
+ attr_reader :dir
6
+ attr_accessor :import_base
7
+
8
+ def initialize(dir)
9
+ @dir = dir
10
+ @import_base = dir
11
+ end
12
+
13
+ def render(args={}, &block)
14
+ files.each do |file|
15
+ handle file, args, &block
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def handle(file, args={})
22
+ template = Template.new file
23
+ template.import_base = import_base
24
+
25
+ path = file.sub(/#{dir}\//, '')
26
+
27
+ yield path, template.render(args)
28
+ end
29
+
30
+ def files
31
+ return @files if @files
32
+ raise Kojo::NotFoundError, "Directory not found: #{dir}" unless Dir.exist? dir
33
+ raise Kojo::NotFoundError, "Directory is empty: #{dir}" if Dir.empty? dir
34
+
35
+ @files = Dir["#{dir}/**/*"].reject { |f| File.directory? f }
36
+ end
37
+ end
38
+ end
@@ -2,15 +2,17 @@ require 'fileutils'
2
2
  require 'mister_bin'
3
3
 
4
4
  module Kojo::Commands
5
+ # Handle calls to the +kojo config+ command
5
6
  class ConfigCmd < MisterBin::Command
6
- attr_reader :gen, :outdir, :opts
7
+ attr_reader :gen, :outdir, :opts, :import_base, :config_file
7
8
 
8
9
  help "Generate based on instructions from a config file"
9
10
 
10
- usage "kojo config CONFIG [--save DIR --args FILE] [ARGS...]"
11
+ usage "kojo config CONFIG [--save DIR --imports DIR --args FILE] [ARGS...]"
11
12
  usage "kojo config (-h|--help)"
12
13
 
13
14
  option "-s --save DIR", "Save output to directory instead of printing"
15
+ option "-i --imports DIR", "Specify base directory for @import directives"
14
16
  option "-a --args FILE", "Load arguments from YAML file"
15
17
 
16
18
  param "ARGS", "Optional key=value pairs"
@@ -21,9 +23,10 @@ module Kojo::Commands
21
23
  example "kojo config config.yml -s output --args args.yml"
22
24
 
23
25
  def run(args)
24
- @gen = Kojo::Generator.new args['CONFIG']
26
+ @config_file = args['CONFIG']
25
27
  @outdir = args['--save']
26
28
  @opts = args['ARGS'].args_to_hash
29
+ @import_base = args['--imports']
27
30
  argfile = args['--args']
28
31
 
29
32
  if argfile
@@ -35,7 +38,10 @@ module Kojo::Commands
35
38
  end
36
39
 
37
40
  def run!
38
- gen.generate opts do |file, output|
41
+ config = Kojo::Config.new config_file
42
+ config.import_base = import_base if import_base
43
+
44
+ config.generate opts do |file, output|
39
45
  handle file, output
40
46
  end
41
47
  end
@@ -1,30 +1,31 @@
1
1
  require 'mister_bin'
2
2
 
3
3
  module Kojo::Commands
4
+ # Handle calls to the +kojo dir+ command
4
5
  class DirCmd < MisterBin::Command
5
6
  attr_reader :opts, :indir, :outdir, :import_base
6
7
 
7
8
  help "Compile a folder of templates to a similar output folder"
8
9
 
9
- usage "kojo dir INDIR [--save DIR --import DIR --args FILE] [ARGS...]"
10
+ usage "kojo dir INDIR [--save DIR --imports DIR --args FILE] [ARGS...]"
10
11
  usage "kojo dir (-h|--help)"
11
12
 
12
13
  option "-s --save DIR", "Save output to directory instead of printing"
13
- option "-i --import DIR", "Specify base directory for @import directives"
14
+ option "-i --imports DIR", "Specify base directory for @import directives"
14
15
  option "-a --args FILE", "Load arguments from YAML file"
15
16
 
16
17
  param "ARGS", "Optional key=value pairs"
17
18
 
18
19
  example "kojo dir indir"
19
20
  example "kojo dir in --save out env=production"
20
- example "kojo dir in --save out --import snippets env=production"
21
+ example "kojo dir in --save out --imports snippets env=production"
21
22
  example "kojo dir in -s out -i snippets -a args.yml"
22
23
 
23
24
  def run(args)
24
25
  @opts = args['ARGS'].args_to_hash
25
26
  @indir = args['INDIR']
26
27
  @outdir = args['--save']
27
- @import_base = args['--import']
28
+ @import_base = args['--imports']
28
29
  argfile = args['--args']
29
30
 
30
31
  if argfile
@@ -37,32 +38,32 @@ module Kojo::Commands
37
38
 
38
39
  private
39
40
 
40
- def files
41
- @files ||= Dir["#{indir}/**/*"].reject { |file| File.directory? file }
42
- end
43
-
44
41
  def run!
45
- files.each do |file|
46
- handle file
42
+ collection = Kojo::Collection.new @indir
43
+ collection.import_base = import_base if import_base
44
+
45
+ if outdir
46
+ write collection
47
+ else
48
+ show collection
47
49
  end
48
50
  end
49
51
 
50
- def handle(file)
51
- template = Kojo::Template.new(file, opts)
52
- template.import_base = import_base if import_base
53
- output = template.render
52
+ def show(collection)
53
+ collection.render @opts do |file, output|
54
+ say "\n!txtgrn!# #{file}"
55
+ say output
56
+ end
57
+ end
54
58
 
55
- if outdir
59
+ def write(collection)
60
+ collection.render @opts do |file, output|
56
61
  save file, output
57
- else
58
- outpath = file.sub(/#{indir}/, '')
59
- say "\n!txtgrn!# #{outpath}"
60
- say output
61
62
  end
62
63
  end
63
64
 
64
65
  def save(file, output)
65
- outpath = file.sub(/#{indir}/, outdir)
66
+ outpath = "#{outdir}/#{file}"
66
67
  dir = File.dirname outpath
67
68
  FileUtils.mkdir_p dir unless Dir.exist? dir
68
69
  File.write outpath, output
@@ -1,15 +1,17 @@
1
1
  require 'mister_bin'
2
2
 
3
3
  module Kojo::Commands
4
+ # Handle calls to the +kojo file+ command
4
5
  class FileCmd < MisterBin::Command
5
- attr_reader :opts, :outfile, :infile
6
+ attr_reader :opts, :outfile, :infile, :import_base
6
7
 
7
8
  help "Compile a file from a template"
8
9
 
9
- usage "kojo file INFILE [--save FILE --args FILE] [ARGS...]"
10
+ usage "kojo file INFILE [--save FILE --imports DIR --args FILE] [ARGS...]"
10
11
  usage "kojo file (-h|--help)"
11
12
 
12
13
  option "-s --save FILE", "Save to file instead of printing"
14
+ option "-i --imports DIR", "Specify base directory for @import directives"
13
15
  option "-a --args FILE", "Load arguments from YAML file"
14
16
 
15
17
  param "ARGS", "Optional key=value pairs"
@@ -23,6 +25,7 @@ module Kojo::Commands
23
25
  @opts = args['ARGS'].args_to_hash
24
26
  @outfile = args['--save']
25
27
  @infile = args['INFILE']
28
+ @import_base = args['--imports']
26
29
  argfile = args['--args']
27
30
 
28
31
  if argfile
@@ -34,7 +37,9 @@ module Kojo::Commands
34
37
  end
35
38
 
36
39
  def run!
37
- output = Kojo::Template.new(infile, opts).render
40
+ template = Kojo::Template.new infile
41
+ template.import_base = import_base if import_base
42
+ output = template.render(opts)
38
43
 
39
44
  if outfile
40
45
  File.write outfile, output
@@ -0,0 +1,66 @@
1
+ require 'yaml'
2
+
3
+ module Kojo
4
+ # The Config class handles multiple template generation from a
5
+ # definitions YAML file.
6
+ class Config
7
+ attr_reader :config_file, :outdir
8
+ attr_accessor :import_base
9
+
10
+ def initialize(config_file)
11
+ @config_file = config_file
12
+ @import_base = nil
13
+ end
14
+
15
+ def generate(opts={}, &block)
16
+ if directory_mode?
17
+ generate_from_dir opts, &block
18
+ else
19
+ generate_from_file opts, &block
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def generate_from_file(opts)
26
+ config['output'].each do |target, config_opts|
27
+ local_opts = opts.merge config_opts.symbolize_keys
28
+
29
+ template = Template.new source
30
+ template.import_base = import_base if import_base
31
+
32
+ yield target, template.render(local_opts)
33
+ end
34
+ end
35
+
36
+ def generate_from_dir(opts)
37
+ config['output'].each do |dir, config_opts|
38
+ local_opts = opts.merge config_opts.symbolize_keys
39
+
40
+ collection = Collection.new source
41
+ collection.import_base = import_base if import_base
42
+
43
+ collection.render(local_opts) do |file, output|
44
+ yield "#{dir}/#{file}", output
45
+ end
46
+ end
47
+ end
48
+
49
+ def base_dir
50
+ @base_dir ||= File.dirname config_file
51
+ end
52
+
53
+ def directory_mode?
54
+ File.directory? source
55
+ end
56
+
57
+ def source
58
+ "#{base_dir}/#{config['input']}"
59
+ end
60
+
61
+ def config
62
+ @config ||= YAML.load_file config_file
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,5 @@
1
+ module Kojo
2
+ class Error < StandardError; end
3
+ class TemplateError < Error; end
4
+ class NotFoundError < Error; end
5
+ end
@@ -1,6 +1,7 @@
1
1
  require 'kojo/extensions/hash'
2
2
 
3
3
  class Array
4
+ # Convert an array of +["key=value", "key=value"]+ pairs to a hash
4
5
  def args_to_hash
5
6
  collect { |a| k, v = a.split '=' }
6
7
  .to_h
@@ -1,4 +1,5 @@
1
1
  class String
2
+ # Convert a string to the most appropriate type
2
3
  def to_typed
3
4
  if self =~ /\A[+-]?\d+\Z/
4
5
  self.to_i
@@ -6,8 +7,8 @@ class String
6
7
  elsif self =~ /\A[+-]?\d+\.\d+\Z/
7
8
  self.to_f
8
9
 
9
- elsif %w[yes no true false].include? self.downcase
10
- %w[yes true].include? self.downcase
10
+ elsif %w[yes no true false].include? downcase
11
+ %w[yes true].include? downcase
11
12
 
12
13
  else
13
14
  self
data/lib/kojo/template.rb CHANGED
@@ -2,30 +2,53 @@ require 'erb'
2
2
  require 'ostruct'
3
3
 
4
4
  module Kojo
5
+ # The Template class handles a single template file, and processes it for:
6
+ # - Variables (using +%{var}+ syntax)
7
+ # - ERB
8
+ # - +@import+ statements
5
9
  class Template
6
- attr_reader :file, :extension, :args, :dir
10
+ attr_reader :file, :extension, :dir, :args
7
11
  attr_accessor :import_base
8
12
 
9
- def initialize(file, args={})
13
+ def initialize(file)
10
14
  @file = file
11
- @args = args
12
- @extension = file[/(\..*)$/]
15
+ @extension = File.extname file
13
16
  @dir = File.dirname file
14
17
  @import_base = dir
15
18
  end
16
19
 
17
- def render
18
- evaluate file, args
20
+ def render(args={})
21
+ @args = args
22
+ evaluate file
19
23
  end
20
24
 
21
25
  private
22
26
 
23
- def evaluate(file, args={})
24
- content = File.read(file)
27
+ def evaluate(file)
28
+ content = read_file file
29
+ content = eval_erb content
30
+ content = eval_vars content
31
+ content = eval_imports content
32
+ content
33
+ end
34
+
35
+ def read_file(file)
36
+ raise Kojo::NotFoundError, "File not found: #{file}" unless File.exist? file
37
+ File.read file
38
+ end
39
+
40
+ def eval_vars(content)
41
+ content % args
42
+ rescue ArgumentError, KeyError => e
43
+ raise Kojo::TemplateError, "#{e.message}\nin: #{file}"
44
+ end
25
45
 
26
- content = erb content, args
27
- content = content % args
28
- content = eval_imports content
46
+ def eval_erb(content)
47
+ erb content, args
48
+ rescue RuntimeError => e
49
+ raise Kojo::TemplateError, "Invalid Ruby code #{e.message}\nin: #{file}"
50
+ rescue SyntaxError => e
51
+ raise Kojo::TemplateError, "#{e.message}\nin: #{file}"
29
52
  end
30
53
 
31
54
  def eval_imports(content)
@@ -52,7 +75,7 @@ module Kojo
52
75
  def import(file, import_args={})
53
76
  filename = File.expand_path "#{file}#{extension}", import_base
54
77
  all_args = args.merge import_args
55
- self.class.new(filename, all_args).render
78
+ self.class.new(filename).render(all_args)
56
79
  end
57
80
 
58
81
  def erb(template, vars)
data/lib/kojo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kojo
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kojo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Ben Shitrit
@@ -134,13 +134,15 @@ files:
134
134
  - bin/kojo
135
135
  - lib/kojo.rb
136
136
  - lib/kojo/cli.rb
137
+ - lib/kojo/collection.rb
137
138
  - lib/kojo/commands/config.rb
138
139
  - lib/kojo/commands/dir.rb
139
140
  - lib/kojo/commands/file.rb
141
+ - lib/kojo/config.rb
142
+ - lib/kojo/exceptions.rb
140
143
  - lib/kojo/extensions/array.rb
141
144
  - lib/kojo/extensions/hash.rb
142
145
  - lib/kojo/extensions/string.rb
143
- - lib/kojo/generator.rb
144
146
  - lib/kojo/template.rb
145
147
  - lib/kojo/version.rb
146
148
  homepage: https://github.com/dannyben/kojo
@@ -1,33 +0,0 @@
1
- require 'yaml'
2
-
3
- module Kojo
4
- class Generator
5
- attr_reader :config_file, :outdir
6
-
7
- def initialize(config_file)
8
- @config_file = config_file
9
- end
10
-
11
- def generate(opts={})
12
- base_dir = File.dirname config_file
13
- infile = "#{base_dir}/#{config['input']}"
14
-
15
- config['output'].each do |outfile, config_opts|
16
- local_opts = opts.merge config_opts.symbolize_keys
17
- output = render infile, local_opts
18
- yield outfile, output
19
- end
20
- end
21
-
22
- private
23
-
24
- def config
25
- @config ||= YAML.load_file config_file
26
- end
27
-
28
- def render(infile, opts={})
29
- Template.new(infile, opts).render
30
- end
31
- end
32
-
33
- end