batali 0.0.1 → 0.1.0
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/CHANGELOG.md +1 -1
- data/README.md +32 -1
- data/batali.gemspec +6 -0
- data/bin/batali +59 -0
- data/lib/batali/b_file.rb +61 -0
- data/lib/batali/command/configure.rb +12 -0
- data/lib/batali/command/install.rb +45 -0
- data/lib/batali/command/resolve.rb +71 -0
- data/lib/batali/command/update.rb +17 -0
- data/lib/batali/command.rb +48 -0
- data/lib/batali/config.rb +7 -0
- data/lib/batali/manifest.rb +42 -0
- data/lib/batali/monkey.rb +26 -0
- data/lib/batali/remote_site.rb +98 -0
- data/lib/batali/score_keeper.rb +18 -0
- data/lib/batali/source/path.rb +26 -0
- data/lib/batali/source/site.rb +70 -0
- data/lib/batali/source.rb +45 -0
- data/lib/batali/unit.rb +8 -0
- data/lib/batali/version.rb +1 -1
- data/lib/batali.rb +18 -0
- metadata +91 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12ae5060ad51ee573081c34f077e1242908dddd7
|
4
|
+
data.tar.gz: b569cf195a11b93a1b7c1ddf87df063c63df1693
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 909cfbcd842a0f2187efab1e4bf9453bc15234e38a0de13317b6ae1a5cf02b222cf499b4caf8ef6da814aa12c1cb74f2dbbde92089645ac9410e230f63f0d1e5
|
7
|
+
data.tar.gz: 1523d71a2af4dd85a487effeeb1bc967160f1093fec75925019afee3f3b128d0e32f3d07d983740d08f754e3732838ba909d672d2c565456466fbc660a333642
|
data/CHANGELOG.md
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# v0.1.0
|
2
|
-
* Initial
|
2
|
+
* Initial release
|
data/README.md
CHANGED
@@ -1,5 +1,36 @@
|
|
1
1
|
# Batali
|
2
2
|
|
3
|
-
|
3
|
+
Batali is a light weight cookbook resolver. It is currently
|
4
|
+
in an alpha state and should not be used with anything you
|
5
|
+
care about or love. There is a high chance it will burn it
|
6
|
+
all to the ground, and laugh.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
Provide a `Batali` file:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
Batali.define do
|
14
|
+
source 'https://supermarket.chef.io'
|
15
|
+
cookbook 'postgresql'
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
and then run:
|
20
|
+
|
21
|
+
```
|
22
|
+
$ batali update
|
23
|
+
```
|
24
|
+
|
25
|
+
in the same directory. It will destroy your `cookbooks` directory
|
26
|
+
by default.
|
27
|
+
|
28
|
+
_IT WILL DESTROY YOUR COOKBOOKS DIRECTORY BY DEFAULT_
|
29
|
+
|
30
|
+
There is other cool stuff too, to be documented later. Currently
|
31
|
+
only site sources can be defined (no path, or git, or anything else).
|
32
|
+
|
33
|
+
|
34
|
+
# Info
|
4
35
|
|
5
36
|
* Repository: https://github.com/hw-labs/batali
|
data/batali.gemspec
CHANGED
@@ -10,7 +10,13 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = 'Magic'
|
11
11
|
s.require_path = 'lib'
|
12
12
|
s.license = 'Apache 2.0'
|
13
|
+
s.add_runtime_dependency 'grimoire', '>= 0.1.2'
|
14
|
+
s.add_runtime_dependency 'bogo', '>= 0.1.12'
|
15
|
+
s.add_runtime_dependency 'bogo-cli', '>= 0.1.8'
|
16
|
+
s.add_runtime_dependency 'bogo-config', '>= 0.1.10'
|
17
|
+
s.add_runtime_dependency 'http'
|
13
18
|
s.add_development_dependency 'minitest'
|
14
19
|
s.add_development_dependency 'pry'
|
20
|
+
s.executables << 'batali'
|
15
21
|
s.files = Dir['{lib,bin}/**/**/*'] + %w(batali.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
|
16
22
|
end
|
data/bin/batali
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'batali'
|
4
|
+
|
5
|
+
Bogo::Cli::Setup.define do
|
6
|
+
|
7
|
+
on :v, :version, 'Print version' do
|
8
|
+
puts "batali - Cookbook Collection Manager - [Version: #{Batali::VERSION}]"
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
|
12
|
+
global_opts = lambda do
|
13
|
+
on :c, :config, 'Configuration file path'
|
14
|
+
on :V, :verbose, 'Enable verbose output'
|
15
|
+
on :D, :debug, 'Enable debug mode'
|
16
|
+
on :f, :file, 'Path to Batali file'
|
17
|
+
end
|
18
|
+
|
19
|
+
command 'install' do
|
20
|
+
description 'Install cookbooks from manifest'
|
21
|
+
self.instance_exec(&global_opts)
|
22
|
+
on :d, 'dry-run', 'Print changes'
|
23
|
+
on :p, 'path', 'Cookbook install path'
|
24
|
+
run do |opts, args|
|
25
|
+
Batali::Command::Install.new({:install => opts.to_hash}, args).execute!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
command 'update' do
|
30
|
+
description 'Update cookbooks manifest'
|
31
|
+
self.instance_exec(&global_opts)
|
32
|
+
on :d, 'dry-run', 'Print changes'
|
33
|
+
on :l, 'least-impact', 'Update cookbooks with minimal version impact', :default => true
|
34
|
+
on :i, 'install', 'Install cookbooks after update', :default => true
|
35
|
+
|
36
|
+
run do |opts, args|
|
37
|
+
Batali::Command::Update.new({:update => opts.to_hash}, args).execute!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
command 'resolve' do
|
42
|
+
description 'Build the cookbook manifest'
|
43
|
+
self.instance_exec(&global_opts)
|
44
|
+
on :d, 'dry-run', 'Print changes'
|
45
|
+
on :l, 'least-impact', 'Update cookbooks with minimal version impact', :default => true
|
46
|
+
|
47
|
+
run do |opts, args|
|
48
|
+
Batali::Command::Resolve.new({:resolve => opts.to_hash}, args).execute!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
command 'configure' do
|
53
|
+
self.instance_exec(&global_opts)
|
54
|
+
run do |opts, args|
|
55
|
+
Batali::Command::Configure.new(opts, args).execute!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
|
5
|
+
class Struct < AttributeStruct
|
6
|
+
|
7
|
+
def cookbook(*args)
|
8
|
+
set!(:cookbook, args)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
# Create a new file
|
15
|
+
#
|
16
|
+
# @param block [Proc]
|
17
|
+
# @return [AttributeStruct]
|
18
|
+
def self.define(&block)
|
19
|
+
struct = Struct.new
|
20
|
+
struct.set_state!(:value_collapse => true)
|
21
|
+
struct.build!(&block)
|
22
|
+
struct
|
23
|
+
end
|
24
|
+
|
25
|
+
class BFile < Bogo::Config
|
26
|
+
|
27
|
+
class Cookbook < Grimoire::Utility
|
28
|
+
attribute :name, String, :required => true
|
29
|
+
attribute :constraint, String, :multiple => true
|
30
|
+
attribute :git, Smash, :coerce => lambda{|v| v.to_smash}
|
31
|
+
attribute :path, String
|
32
|
+
end
|
33
|
+
|
34
|
+
attribute :source, RemoteSite, :multiple => true, :coerce => lambda{|v| RemoteSite.new(:endpoint => v)}
|
35
|
+
attribute :cookbook, Cookbook, :multiple => true, :coerce => lambda{|v|
|
36
|
+
case v
|
37
|
+
when Array
|
38
|
+
Cookbook.new(
|
39
|
+
:name => v.first,
|
40
|
+
:constraint => v.slice(1, v.size)
|
41
|
+
)
|
42
|
+
when String
|
43
|
+
Cookbook.new(:name => v)
|
44
|
+
when Hash
|
45
|
+
c_name = v.keys.first
|
46
|
+
constraints = v.values.first.to_a.flatten.find_all{|i| i.is_a?(String)}
|
47
|
+
Cookbook.new(
|
48
|
+
:name => c_name,
|
49
|
+
:constraint => constraints
|
50
|
+
)
|
51
|
+
else
|
52
|
+
raise ArgumentError.new "Unable to coerce given type `#{v.class}` to `Batali::BFile::Cookbook`!"
|
53
|
+
end
|
54
|
+
}
|
55
|
+
|
56
|
+
## TODO: supported values still required
|
57
|
+
# attribute :restrict -- restrict cookbooks of name `x` to source named `y`
|
58
|
+
# attribute :group -- cookbook grouping (i.e. :integration)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'batali'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Batali
|
5
|
+
class Command
|
6
|
+
|
7
|
+
# Install cookbooks based on manifest
|
8
|
+
class Install < Batali::Command
|
9
|
+
|
10
|
+
# Install cookbooks
|
11
|
+
def execute!
|
12
|
+
dry_run('Cookbook installation') do
|
13
|
+
install_path = opts.fetch(:path, 'cookbooks')
|
14
|
+
run_action('Readying installation destination') do
|
15
|
+
FileUtils.rm_rf(install_path)
|
16
|
+
FileUtils.mkdir_p(install_path)
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
run_action('Installing cookbooks') do
|
20
|
+
manifest.cookbook.each do |unit|
|
21
|
+
asset_path = unit.source.asset
|
22
|
+
begin
|
23
|
+
FileUtils.mv(
|
24
|
+
File.join(
|
25
|
+
asset_path,
|
26
|
+
unit.name
|
27
|
+
),
|
28
|
+
File.join(
|
29
|
+
install_path,
|
30
|
+
unit.name
|
31
|
+
)
|
32
|
+
)
|
33
|
+
ensure
|
34
|
+
FileUtils.rm_rf(asset_path)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
class Command
|
5
|
+
|
6
|
+
# Resolve cookbooks
|
7
|
+
class Resolve < Command
|
8
|
+
|
9
|
+
# Resolve dependencies and constraints. Output results to stdout
|
10
|
+
# and dump serialized manifest
|
11
|
+
def execute!
|
12
|
+
system = Grimoire::System.new
|
13
|
+
run_action 'Loading sources' do
|
14
|
+
batali_file.source.map(&:units).flatten.map do |unit|
|
15
|
+
system.add_unit(unit)
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
requirements = Grimoire::RequirementList.new(
|
20
|
+
:name => :batali_resolv,
|
21
|
+
:requirements => batali_file.cookbook.map{ |ckbk|
|
22
|
+
[ckbk.name, *(ckbk.constraint.empty? ? ['> 0'] : ckbk.constraint)]
|
23
|
+
}
|
24
|
+
)
|
25
|
+
solv = Grimoire::Solver.new(
|
26
|
+
:requirements => requirements,
|
27
|
+
:system => system,
|
28
|
+
:score_keeper => score_keeper
|
29
|
+
)
|
30
|
+
results = []
|
31
|
+
run_action 'Resolving dependency constraints' do
|
32
|
+
results = solv.generate!
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
if(results.empty?)
|
36
|
+
ui.error 'No solutions found defined requirements!'
|
37
|
+
else
|
38
|
+
ideal_solution = results.pop
|
39
|
+
dry_run('manifest file write') do
|
40
|
+
run_action 'Writing manifest' do
|
41
|
+
manifest = Manifest.new(:cookbook => ideal_solution.units)
|
42
|
+
File.open('batali.manifest', 'w') do |file|
|
43
|
+
file.write MultiJson.dump(manifest, :pretty => true)
|
44
|
+
end
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
ui.info "Found #{results.size} solutions for defined requirements."
|
49
|
+
ui.info 'Ideal solution:'
|
50
|
+
ui.puts ideal_solution.units.sort_by(&:name).map{|u| "#{u.name}<#{u.version}>"}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [ScoreKeeper]
|
55
|
+
def score_keeper
|
56
|
+
memoize(:score_keeper) do
|
57
|
+
sk_manifest = Manifest.new(:cookbook => manifest.cookbook)
|
58
|
+
unless(opts[:least_impact])
|
59
|
+
sk_manifest.cookbook.clear
|
60
|
+
end
|
61
|
+
sk_manifest.cookbook.delete_if do |unit|
|
62
|
+
arguments.include?(unit.name)
|
63
|
+
end
|
64
|
+
ScoreKeeper.new(:manifest => sk_manifest)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
class Command
|
5
|
+
|
6
|
+
# Update cookbook manifest
|
7
|
+
class Update < Batali::Command
|
8
|
+
|
9
|
+
def execute!
|
10
|
+
Resolve.new({:resolve => opts}, arguments).execute!
|
11
|
+
Install.new({:install => opts}, arguments).execute!
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
# Customized command base for Batali
|
5
|
+
class Command < Bogo::Cli::Command
|
6
|
+
|
7
|
+
include Bogo::Memoization
|
8
|
+
|
9
|
+
autoload :Configure, 'batali/command/configure'
|
10
|
+
autoload :Install, 'batali/command/install'
|
11
|
+
autoload :Resolve, 'batali/command/resolve'
|
12
|
+
autoload :Update, 'batali/command/update'
|
13
|
+
|
14
|
+
# @return [BFile]
|
15
|
+
def batali_file
|
16
|
+
memoize(:batali_file) do
|
17
|
+
# TODO: Add directory traverse searching
|
18
|
+
BFile.new(opts.fetch(:file, File.join(Dir.pwd, 'Batali')))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Manifest]
|
23
|
+
def manifest
|
24
|
+
memoize(:manifest) do
|
25
|
+
Manifest.build(
|
26
|
+
File.join(
|
27
|
+
File.dirname(
|
28
|
+
opts.fetch(:file, File.join(Dir.pwd, 'batali.manifest'))
|
29
|
+
), 'batali.manifest'
|
30
|
+
)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Do not execute block if dry run
|
36
|
+
#
|
37
|
+
# @param action [String] action to be performed
|
38
|
+
# @yield block to execute
|
39
|
+
def dry_run(action)
|
40
|
+
if(opts[:dry_run])
|
41
|
+
ui.warn "Dry run disabled: #{action}"
|
42
|
+
else
|
43
|
+
yield
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
# Collection of resolved units
|
5
|
+
class Manifest < Grimoire::Utility
|
6
|
+
|
7
|
+
include Bogo::Memoization
|
8
|
+
|
9
|
+
attribute :cookbook, Unit, :multiple => true, :coerce => lambda{|v| Unit.new(v)}
|
10
|
+
|
11
|
+
# Build manifest from given path. If no file exists, empty
|
12
|
+
# manifest will be provided.
|
13
|
+
#
|
14
|
+
# @param path [String] path to manifest
|
15
|
+
# @return [Manifest]
|
16
|
+
def self.build(path)
|
17
|
+
if(File.exists?(path))
|
18
|
+
self.new(Bogo::Config.new(path).data)
|
19
|
+
else
|
20
|
+
self.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check for unit within manifest
|
25
|
+
#
|
26
|
+
# @param unit [Unit]
|
27
|
+
# @return [TrueClass, FalseClass]
|
28
|
+
def include?(unit)
|
29
|
+
memoize(unit.inspect) do
|
30
|
+
if(cookbook)
|
31
|
+
!!cookbook.detect do |ckbk|
|
32
|
+
ckbk.name == unit.name &&
|
33
|
+
ckbk.version == unit.version
|
34
|
+
end
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
# Simple stubs mostly for naming
|
5
|
+
class UnitVersion < Grimoire::VERSION_CLASS; end
|
6
|
+
class UnitRequirement < Grimoire::REQUIREMENT_CLASS; end
|
7
|
+
class UnitDependency < Grimoire::DEPENDENCY_CLASS
|
8
|
+
def to_json(*args)
|
9
|
+
result = [
|
10
|
+
name,
|
11
|
+
*requirement.requirements.map do |req|
|
12
|
+
req.join(' ')
|
13
|
+
end
|
14
|
+
]
|
15
|
+
MultiJson.dump(result, *args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Grimoire.send(:remove_const, :VERSION_CLASS)
|
21
|
+
Grimoire.send(:remove_const, :DEPENDENCY_CLASS)
|
22
|
+
Grimoire.send(:remove_const, :REQUIREMENT_CLASS)
|
23
|
+
|
24
|
+
Grimoire.const_set(:VERSION_CLASS, Batali::UnitVersion)
|
25
|
+
Grimoire.const_set(:DEPENDENCY_CLASS, Batali::UnitDependency)
|
26
|
+
Grimoire.const_set(:REQUIREMENT_CLASS, Batali::UnitRequirement)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'batali'
|
2
|
+
require 'digest/sha2'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'http'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Batali
|
8
|
+
# Fetch unit information from remote site
|
9
|
+
class RemoteSite < Grimoire::Utility
|
10
|
+
|
11
|
+
# Site suffix for API endpoint
|
12
|
+
COOKBOOK_API_SUFFIX = 'api/v1/cookbooks'
|
13
|
+
|
14
|
+
include Bogo::Memoization
|
15
|
+
|
16
|
+
attribute :name, String
|
17
|
+
attribute :identifier, String
|
18
|
+
attribute :endpoint, String, :required => true
|
19
|
+
attribute :force_update, [TrueClass, FalseClass], :required => true, :default => false
|
20
|
+
attribute :update_interval, Integer, :required => true, :default => 10000 # NOTE: reset this default to 60/120 when ready
|
21
|
+
attribute :cache, String, :default => File.expand_path('~/.batali/cache/remote_site'), :required => true
|
22
|
+
|
23
|
+
def initialize(*_)
|
24
|
+
super
|
25
|
+
endpoint = URI.join(self.endpoint, COOKBOOK_API_SUFFIX).to_s
|
26
|
+
self.identifier = Digest::SHA256.hexdigest(endpoint)
|
27
|
+
unless(name?)
|
28
|
+
self.name = self.identifier
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] cache directory path
|
33
|
+
def cache_directory
|
34
|
+
memoize(:cache_directory) do
|
35
|
+
path = File.join(cache, identifier)
|
36
|
+
FileUtils.mkdir_p(path)
|
37
|
+
path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Array<Unit>] all units
|
42
|
+
def units
|
43
|
+
memoize(:units) do
|
44
|
+
items.map do |u_name, versions|
|
45
|
+
versions.map do |version, info|
|
46
|
+
Unit.new(
|
47
|
+
:name => u_name,
|
48
|
+
:version => version,
|
49
|
+
:dependencies => info[:dependencies].to_a,
|
50
|
+
:source => Smash.new(
|
51
|
+
:type => :site,
|
52
|
+
:url => info[:download_url],
|
53
|
+
:version => version,
|
54
|
+
:dependencies => info[:dependencies]
|
55
|
+
)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end.flatten
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# @return [Smash] all info
|
65
|
+
def items
|
66
|
+
memoize(:items) do
|
67
|
+
MultiJson.load(File.read(fetch)).to_smash
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fetch the universe
|
72
|
+
#
|
73
|
+
# @return [String] path to universe file
|
74
|
+
def fetch
|
75
|
+
do_fetch = true
|
76
|
+
if(File.exists?(universe_path))
|
77
|
+
age = Time.now - File.mtime(universe_path)
|
78
|
+
if(age < update_interval)
|
79
|
+
do_fetch = false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
if(do_fetch)
|
83
|
+
t_uni = "#{universe_path}.#{SecureRandom.urlsafe_base64}"
|
84
|
+
File.open(t_uni, 'w') do |file|
|
85
|
+
file.write HTTP.get(URI.join(endpoint, 'universe')).body.to_s
|
86
|
+
end
|
87
|
+
FileUtils.mv(t_uni, universe_path)
|
88
|
+
end
|
89
|
+
universe_path
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [String] path to universe file
|
93
|
+
def universe_path
|
94
|
+
File.join(cache_directory, 'universe.json')
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
# Provide scores for units
|
5
|
+
class ScoreKeeper < Grimoire::UnitScoreKeeper
|
6
|
+
|
7
|
+
attribute :manifest, Manifest, :required => true
|
8
|
+
|
9
|
+
# Provide score for given unit
|
10
|
+
#
|
11
|
+
# @param unit [Unit]
|
12
|
+
# @return [Numeric, NilClass]
|
13
|
+
def score_for(unit)
|
14
|
+
manifest.include?(unit) ? 0 : nil
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'batali'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module Batali
|
6
|
+
# Source of asset
|
7
|
+
class Source
|
8
|
+
# Path based source
|
9
|
+
class Path < Source
|
10
|
+
|
11
|
+
include Bogo::Memoization
|
12
|
+
|
13
|
+
attribute :path, String, :required => true
|
14
|
+
|
15
|
+
# @return [String] directory containing contents
|
16
|
+
def asset
|
17
|
+
memoize(:asset) do
|
18
|
+
dir = Dir.mktmpdir
|
19
|
+
FileUtils.cp_r(path, dir)
|
20
|
+
dir
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'batali'
|
2
|
+
require 'http'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'rubygems/package'
|
5
|
+
require 'zlib'
|
6
|
+
|
7
|
+
module Batali
|
8
|
+
class Source
|
9
|
+
# Site based source
|
10
|
+
class Site < Source
|
11
|
+
|
12
|
+
# @return [Array<Hash>] dependency strings
|
13
|
+
attr_reader :dependencies
|
14
|
+
# @return [String] version
|
15
|
+
attr_reader :version
|
16
|
+
|
17
|
+
attribute :url, String, :required => true
|
18
|
+
attribute :version, String, :required => true
|
19
|
+
|
20
|
+
# Extract extra info before allowing super to load data
|
21
|
+
#
|
22
|
+
# @param args [Hash]
|
23
|
+
# @return [self]
|
24
|
+
def initialize(args={})
|
25
|
+
@deps = args.delete(:dependencies) || {}
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String]
|
30
|
+
def unit_version
|
31
|
+
version
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Array<Array<name, constraints>>]
|
35
|
+
def unit_dependencies
|
36
|
+
deps.to_a
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String] directory
|
40
|
+
def asset
|
41
|
+
path = Dir.mktmpdir('batali')
|
42
|
+
result = HTTP.get(url)
|
43
|
+
while(result.code == 302)
|
44
|
+
result = HTTP.get(result.headers['Location'])
|
45
|
+
end
|
46
|
+
File.open(a_path = File.join(path, 'asset'), 'w') do |file|
|
47
|
+
while(content = result.body.readpartial(2048))
|
48
|
+
file.write content
|
49
|
+
end
|
50
|
+
end
|
51
|
+
ext = Gem::Package::TarReader.new(
|
52
|
+
Zlib::GzipReader.open(a_path)
|
53
|
+
)
|
54
|
+
ext.rewind
|
55
|
+
ext.each do |entry|
|
56
|
+
next unless entry.file?
|
57
|
+
n_path = File.join(path, entry.full_name)
|
58
|
+
FileUtils.mkdir_p(File.dirname(n_path))
|
59
|
+
File.open(n_path, 'w') do |file|
|
60
|
+
while(content = entry.read(2048))
|
61
|
+
file.write(content)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
path
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'batali'
|
2
|
+
|
3
|
+
module Batali
|
4
|
+
# Source of asset
|
5
|
+
class Source < Grimoire::Utility
|
6
|
+
|
7
|
+
autoload :Path, 'batali/source/path'
|
8
|
+
autoload :Site, 'batali/source/site'
|
9
|
+
autoload :Git, 'batali/source/git'
|
10
|
+
|
11
|
+
attribute :type, String, :required => true, :default => lambda{ self.name }
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
def unit_version
|
15
|
+
raise NotImplementedError.new 'Abstract class'
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Array<Array<name, constraints>>]
|
19
|
+
def unit_dependencies
|
20
|
+
raise NotImplementedError.new 'Abstract class'
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] directory containing contents
|
24
|
+
def asset
|
25
|
+
raise NotImplementedError.new 'Abstract class'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Build a source
|
29
|
+
#
|
30
|
+
# @param args [Hash]
|
31
|
+
# @return [Source]
|
32
|
+
# @note uses `:type` to build concrete source
|
33
|
+
def self.build(args)
|
34
|
+
type = args.delete(:type)
|
35
|
+
unless(type)
|
36
|
+
raise ArgumentError.new 'Missing required option `:type`!'
|
37
|
+
end
|
38
|
+
unless(type.to_s.include?('::'))
|
39
|
+
type = [self.name, Bogo::Utility.camel(type)].join('::')
|
40
|
+
end
|
41
|
+
Bogo::Utility.constantize(type).new(args.merge(:type => type))
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/batali/unit.rb
ADDED
data/lib/batali/version.rb
CHANGED
data/lib/batali.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bogo-cli'
|
2
|
+
require 'grimoire'
|
3
|
+
|
4
|
+
module Batali
|
5
|
+
|
6
|
+
autoload :Command, 'batali/command'
|
7
|
+
autoload :Config, 'batali/config'
|
8
|
+
autoload :Manifest, 'batali/manifest'
|
9
|
+
autoload :RemoteSite, 'batali/remote_site'
|
10
|
+
autoload :ScoreKeeper, 'batali/score_keeper'
|
11
|
+
autoload :Source, 'batali/source'
|
12
|
+
autoload :Unit, 'batali/unit'
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'batali/b_file'
|
17
|
+
require 'batali/monkey'
|
18
|
+
require 'batali/version'
|
metadata
CHANGED
@@ -1,15 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: batali
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Roberts
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: grimoire
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bogo
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.12
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.12
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bogo-cli
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.8
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.8
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bogo-config
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.10
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.1.10
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: http
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
13
83
|
- !ruby/object:Gem::Dependency
|
14
84
|
name: minitest
|
15
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -40,7 +110,8 @@ dependencies:
|
|
40
110
|
version: '0'
|
41
111
|
description: Magic
|
42
112
|
email: code@chrisroberts.org
|
43
|
-
executables:
|
113
|
+
executables:
|
114
|
+
- batali
|
44
115
|
extensions: []
|
45
116
|
extra_rdoc_files: []
|
46
117
|
files:
|
@@ -49,6 +120,23 @@ files:
|
|
49
120
|
- LICENSE
|
50
121
|
- README.md
|
51
122
|
- batali.gemspec
|
123
|
+
- bin/batali
|
124
|
+
- lib/batali.rb
|
125
|
+
- lib/batali/b_file.rb
|
126
|
+
- lib/batali/command.rb
|
127
|
+
- lib/batali/command/configure.rb
|
128
|
+
- lib/batali/command/install.rb
|
129
|
+
- lib/batali/command/resolve.rb
|
130
|
+
- lib/batali/command/update.rb
|
131
|
+
- lib/batali/config.rb
|
132
|
+
- lib/batali/manifest.rb
|
133
|
+
- lib/batali/monkey.rb
|
134
|
+
- lib/batali/remote_site.rb
|
135
|
+
- lib/batali/score_keeper.rb
|
136
|
+
- lib/batali/source.rb
|
137
|
+
- lib/batali/source/path.rb
|
138
|
+
- lib/batali/source/site.rb
|
139
|
+
- lib/batali/unit.rb
|
52
140
|
- lib/batali/version.rb
|
53
141
|
homepage: https://github.com/hw-labs/batali
|
54
142
|
licenses:
|