packager-dsl 0.0.1
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 +15 -0
- data/.rspec +5 -0
- data/Changes +4 -0
- data/Gemfile +12 -0
- data/LICENSE +340 -0
- data/README.md +48 -0
- data/Rakefile +37 -0
- data/bin/packager +8 -0
- data/lib/packager.rb +5 -0
- data/lib/packager/cli.rb +72 -0
- data/lib/packager/dsl.rb +55 -0
- data/lib/packager/executor.rb +63 -0
- data/lib/packager/struct.rb +80 -0
- data/lib/packager/version.rb +3 -0
- data/on_what.rb +15 -0
- data/packager.gemspec +44 -0
- data/spec/cli/context.rb +17 -0
- data/spec/cli/execute_spec.rb +79 -0
- data/spec/cli/validate_spec.rb +47 -0
- data/spec/cli/version_spec.rb +7 -0
- data/spec/dsl/args_spec.rb +16 -0
- data/spec/dsl/context.rb +5 -0
- data/spec/dsl/defaults_spec.rb +19 -0
- data/spec/dsl/dependency_spec.rb +62 -0
- data/spec/dsl/error_spec.rb +48 -0
- data/spec/dsl/files_spec.rb +46 -0
- data/spec/executor/basic_spec.rb +21 -0
- data/spec/executor/command_spec.rb +18 -0
- data/spec/executor/context.rb +4 -0
- data/spec/executor/dependency_spec.rb +46 -0
- data/spec/executor/execute_command_spec.rb +25 -0
- data/spec/executor/files_spec.rb +33 -0
- data/spec/fpm-test_spec.rb +98 -0
- data/spec/integration/context.rb +57 -0
- data/spec/integration/dependency_spec.rb +51 -0
- data/spec/integration/files_spec.rb +104 -0
- data/spec/lib/fpm/package/test.rb +27 -0
- data/spec/shared_context/workdir.rb +18 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/struct/command_spec.rb +67 -0
- data/spec/struct/error_spec.rb +12 -0
- metadata +268 -0
data/bin/packager
ADDED
data/lib/packager.rb
ADDED
data/lib/packager/cli.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'thor'
|
|
2
|
+
|
|
3
|
+
require 'packager'
|
|
4
|
+
|
|
5
|
+
class Packager::CLI < Thor
|
|
6
|
+
# Make sure to exit(1) on failure. This isn't covered in the specs.
|
|
7
|
+
# :nocov:
|
|
8
|
+
def self.exit_on_failure?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
# :nocov:
|
|
12
|
+
|
|
13
|
+
# Taken from http://stackoverflow.com/a/27804972/1732954
|
|
14
|
+
map %w[--version] => :__print_version
|
|
15
|
+
desc "--version", "Print the version"
|
|
16
|
+
def __print_version
|
|
17
|
+
puts Packager::VERSION
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc :execute, "Execute one or more package DSL definition(s)"
|
|
21
|
+
default_task :execute
|
|
22
|
+
def execute(*args)
|
|
23
|
+
if args.empty?
|
|
24
|
+
raise Thor::Error, "No filenames provided for execute"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
args.each do |filename|
|
|
28
|
+
unless File.exists? filename
|
|
29
|
+
raise Thor::Error, "'#{filename}' cannot be found"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
items = Packager::DSL.parse_dsl(IO.read(filename))
|
|
34
|
+
rescue Exception => e
|
|
35
|
+
raise Thor::Error, "'#{filename}' has the following errors:\n#{e}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if items.empty?
|
|
39
|
+
raise Thor::Error, "'#{filename}' produces nothing"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
packages = Packager::Executor.new.execute_on(items)
|
|
43
|
+
|
|
44
|
+
puts "'#{filename}' executed #{packages.join(', ')}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc :validate, "Validate one or more package DSL definition(s)"
|
|
49
|
+
def validate(*args)
|
|
50
|
+
if args.empty?
|
|
51
|
+
raise Thor::Error, "No filenames provided for validate"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
args.each do |filename|
|
|
55
|
+
unless File.exists? filename
|
|
56
|
+
raise Thor::Error, "'#{filename}' cannot be found"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
begin
|
|
60
|
+
items = Packager::DSL.parse_dsl(IO.read(filename))
|
|
61
|
+
rescue Exception => e
|
|
62
|
+
raise Thor::Error, "'#{filename}' has the following errors:\n#{e}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if items.empty?
|
|
66
|
+
raise Thor::Error, "'#{filename}' produces nothing"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
puts "'#{filename}' parses cleanly"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/packager/dsl.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'packager/version'
|
|
2
|
+
require 'packager/struct'
|
|
3
|
+
|
|
4
|
+
require 'dsl/maker'
|
|
5
|
+
|
|
6
|
+
class Packager::DSL < DSL::Maker
|
|
7
|
+
class << self
|
|
8
|
+
attr_writer :default_type
|
|
9
|
+
def default_type(*args)
|
|
10
|
+
@default_type = args[0] unless args.empty?
|
|
11
|
+
@default_type
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
add_type(VersionString = {}) do |attr, *args|
|
|
16
|
+
unless args.empty?
|
|
17
|
+
begin
|
|
18
|
+
___set(attr, Gem::Version.new(args[0]).to_s)
|
|
19
|
+
rescue ArgumentError
|
|
20
|
+
raise "'#{args[0]}' is not a legal version string"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
___get(attr)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
copy_file_dsl = generate_dsl({
|
|
27
|
+
:source => String,
|
|
28
|
+
:dest => String,
|
|
29
|
+
}) do
|
|
30
|
+
Packager::Struct::File.new(source, dest)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
add_entrypoint(:package, {
|
|
34
|
+
:name => String,
|
|
35
|
+
:version => VersionString,
|
|
36
|
+
:type => Any,
|
|
37
|
+
:files => ArrayOf[copy_file_dsl],
|
|
38
|
+
:file => AliasOf(:files),
|
|
39
|
+
:requires => ArrayOf[String],
|
|
40
|
+
:provides => ArrayOf[String],
|
|
41
|
+
}) do |*args|
|
|
42
|
+
type(Packager::DSL.default_type) unless type
|
|
43
|
+
default(:name, args, 0)
|
|
44
|
+
|
|
45
|
+
Packager::Struct::Package.new(
|
|
46
|
+
name, version, type, files, requires, provides,
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
add_verification(:package) do |item|
|
|
50
|
+
return 'Every package must have a name' unless item.name
|
|
51
|
+
return 'Every package must have a version' unless item.version
|
|
52
|
+
return 'Every package must have a type' unless item.type
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'packager/version'
|
|
2
|
+
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
class Packager
|
|
6
|
+
class Executor
|
|
7
|
+
attr_accessor :commands, :dryrun
|
|
8
|
+
|
|
9
|
+
def initialize(opts={})
|
|
10
|
+
self.dryrun = !!opts[:dryrun]
|
|
11
|
+
self.commands = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute_on(items)
|
|
15
|
+
curdir = Dir.pwd
|
|
16
|
+
items.collect do |item|
|
|
17
|
+
Dir.mktmpdir do |tempdir|
|
|
18
|
+
Dir.chdir(tempdir) do
|
|
19
|
+
path = create_package_for(item)
|
|
20
|
+
FileUtils.mv(path, curdir) if path
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_package_for(item)
|
|
27
|
+
unless item.files.empty?
|
|
28
|
+
item.files.each do |file|
|
|
29
|
+
dest = (file.dest || '').gsub /^\//, ''
|
|
30
|
+
FileUtils.mkdir_p File.dirname(dest)
|
|
31
|
+
FileUtils.cp_r(file.source, dest)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
cmd = Packager::Struct::Command.new(
|
|
36
|
+
:name => item.name,
|
|
37
|
+
:version => item.version,
|
|
38
|
+
:target => item.type,
|
|
39
|
+
:requires => item.requires,
|
|
40
|
+
:provides => item.provides,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
Dir.glob('*') do |entry|
|
|
44
|
+
if File.directory?(entry)
|
|
45
|
+
cmd.add_directory(entry)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
commands.push(cmd)
|
|
50
|
+
|
|
51
|
+
execute_command(cmd)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def execute_command(cmd)
|
|
55
|
+
return if dryrun
|
|
56
|
+
|
|
57
|
+
x = `#{cmd.to_system.join(' ')}`
|
|
58
|
+
rv = eval(x)
|
|
59
|
+
raise rv[:error] if rv[:error]
|
|
60
|
+
return rv[:path]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'packager/version'
|
|
2
|
+
|
|
3
|
+
# This exists so that you can pass in a Hash or an Array. While passing an Array
|
|
4
|
+
# can be useful, passing in a Hash is far more self-documenting.
|
|
5
|
+
class Packager::Struct < Struct
|
|
6
|
+
def initialize(*args)
|
|
7
|
+
if args.length == 1 and args[0].instance_of?(Hash)
|
|
8
|
+
difference = Set.new(args[0].keys) - Set.new(self.class.members)
|
|
9
|
+
unless difference.empty?
|
|
10
|
+
raise 'Passed in unknown params: ' + difference.to_a.sort.join(', ')
|
|
11
|
+
end
|
|
12
|
+
super(*args[0].values_at(*self.class.members))
|
|
13
|
+
else
|
|
14
|
+
super(*args)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Package < Packager::Struct.new(
|
|
19
|
+
:name, :version, :type, :files, :requires, :provides,
|
|
20
|
+
)
|
|
21
|
+
def initialize(*args)
|
|
22
|
+
super(*args)
|
|
23
|
+
self.files ||= []
|
|
24
|
+
self.requires ||= []
|
|
25
|
+
self.provides ||= []
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class File < Packager::Struct.new(
|
|
30
|
+
:source, :dest,
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Command < Packager::Struct.new(
|
|
35
|
+
:executable, :name, :version,
|
|
36
|
+
:source, :target, :directories, :requires, :provides,
|
|
37
|
+
)
|
|
38
|
+
class << self
|
|
39
|
+
attr_accessor :default_executable
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize(*args)
|
|
43
|
+
super(*args)
|
|
44
|
+
self.source ||= 'empty'
|
|
45
|
+
self.executable ||= self.class.default_executable || 'fpm'
|
|
46
|
+
self.directories ||= {}
|
|
47
|
+
self.requires ||= []
|
|
48
|
+
self.provides ||= []
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add_directory(*items)
|
|
52
|
+
self.source = 'dir'
|
|
53
|
+
items.each do |item|
|
|
54
|
+
directories[item] = true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_system
|
|
59
|
+
cmd = [
|
|
60
|
+
executable,
|
|
61
|
+
'--name', name,
|
|
62
|
+
'--version', version,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
self.requires.uniq.each do |req|
|
|
66
|
+
cmd.concat(['--depends', req])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
self.provides.uniq.each do |req|
|
|
70
|
+
cmd.concat(['--provides', req])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
cmd.concat(['-s', source, '-t', target])
|
|
74
|
+
cmd.concat(directories.keys)
|
|
75
|
+
|
|
76
|
+
return cmd
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
data/on_what.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# NOTE: Very simple tests for what system we are on, extracted for sharing
|
|
2
|
+
# between Rakefile, gemspec, and spec_helper. Not for use in actual library.
|
|
3
|
+
# This is copied from https://github.com/ms-ati/docile/blob/master/on_what.rb
|
|
4
|
+
|
|
5
|
+
def on_travis?
|
|
6
|
+
ENV['CI'] == 'true'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def on_jruby?
|
|
10
|
+
defined?(RUBY_ENGINE) && 'jruby' == RUBY_ENGINE
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def on_1_8?
|
|
14
|
+
RUBY_VERSION.start_with? '1.8'
|
|
15
|
+
end
|
data/packager.gemspec
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require File.expand_path('on_what', File.dirname(__FILE__))
|
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
|
3
|
+
require 'packager/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = 'packager-dsl'
|
|
7
|
+
s.version = Packager::VERSION
|
|
8
|
+
s.author = 'Rob Kinyon'
|
|
9
|
+
s.email = 'rob.kinyon@gmail.com'
|
|
10
|
+
s.summary = 'DSL for creating packages'
|
|
11
|
+
s.description = 'DSL for creating a package that is easy to work with.'
|
|
12
|
+
s.license = 'GPL2'
|
|
13
|
+
s.homepage = 'https://github.com/robkinyon/ruby-packager'
|
|
14
|
+
|
|
15
|
+
# Don't tramp along our dot-files, except for .rspec
|
|
16
|
+
s.files = `git ls-files`.split("\n").select { |filename|
|
|
17
|
+
!filename.match(/^\./) || filename == '.rspec'
|
|
18
|
+
}
|
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
20
|
+
s.executables = `git ls-files -- {bin}/*`.split("\n")
|
|
21
|
+
s.require_paths = %w(lib)
|
|
22
|
+
|
|
23
|
+
s.required_ruby_version = '>= 1.9.3'
|
|
24
|
+
|
|
25
|
+
s.add_dependency 'dsl_maker', '~> 0.1', '>= 0.1.1'
|
|
26
|
+
s.add_dependency 'fpm', '~> 1.4', '>= 1.1.0'
|
|
27
|
+
s.add_dependency 'thor', '~> 0.0', '>= 0.19.0'
|
|
28
|
+
|
|
29
|
+
s.add_development_dependency 'rake', '~> 10'
|
|
30
|
+
s.add_development_dependency 'rspec', '~> 3.0.0', '>= 3.0.0'
|
|
31
|
+
s.add_development_dependency 'simplecov', '~> 0'
|
|
32
|
+
s.add_development_dependency 'rubygems-tasks', '~> 0'
|
|
33
|
+
s.add_development_dependency 'json', '~> 1', '>=1.7.7'
|
|
34
|
+
|
|
35
|
+
# To limit needed compatibility with versions of dependencies, only configure
|
|
36
|
+
# yard doc generation when *not* on Travis, JRuby, or 1.8
|
|
37
|
+
if !on_travis? && !on_jruby? && !on_1_8?
|
|
38
|
+
# Github flavored markdown in YARD documentation
|
|
39
|
+
# http://blog.nikosd.com/2011/11/github-flavored-markdown-in-yard.html
|
|
40
|
+
s.add_development_dependency 'yard', '~> 0.8'
|
|
41
|
+
s.add_development_dependency 'redcarpet', '~> 3'
|
|
42
|
+
s.add_development_dependency 'github-markup', '~> 1.3'
|
|
43
|
+
end
|
|
44
|
+
end
|
data/spec/cli/context.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Examine:
|
|
2
|
+
# https://gabebw.wordpress.com/2011/03/21/temp-files-in-rspec/
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
RSpec.shared_context :cli do
|
|
8
|
+
subject(:cli) { Packager::CLI.new }
|
|
9
|
+
|
|
10
|
+
let(:workdir) { Dir.mktmpdir }
|
|
11
|
+
before(:all) { @dir = Dir.pwd }
|
|
12
|
+
before(:each) { FileUtils.chdir(workdir) }
|
|
13
|
+
after(:each) {
|
|
14
|
+
FileUtils.chdir @dir
|
|
15
|
+
FileUtils.remove_entry_secure(workdir)
|
|
16
|
+
}
|
|
17
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require './spec/cli/context.rb'
|
|
2
|
+
describe Packager::CLI do
|
|
3
|
+
context '#execute' do
|
|
4
|
+
include_context :cli
|
|
5
|
+
|
|
6
|
+
it "handles nothing passed" do
|
|
7
|
+
expect {
|
|
8
|
+
cli.execute
|
|
9
|
+
}.to raise_error(Thor::Error, "No filenames provided for execute")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "handles a non-existent filename" do
|
|
13
|
+
expect {
|
|
14
|
+
cli.execute('foo')
|
|
15
|
+
}.to raise_error(Thor::Error, "'foo' cannot be found")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "handles an empty file" do
|
|
19
|
+
FileUtils.touch('definition')
|
|
20
|
+
expect {
|
|
21
|
+
cli.execute('definition')
|
|
22
|
+
}.to raise_error(Thor::Error, "'definition' produces nothing")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "handles a bad file" do
|
|
26
|
+
append_to_file('definition', 'package {}')
|
|
27
|
+
|
|
28
|
+
expect {
|
|
29
|
+
cli.execute('definition')
|
|
30
|
+
}.to raise_error(Thor::Error, "'definition' has the following errors:\nEvery package must have a name")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "handles a file that works" do
|
|
34
|
+
before {
|
|
35
|
+
expect(Packager::DSL).to(
|
|
36
|
+
receive(:parse_dsl).
|
|
37
|
+
with('contents').
|
|
38
|
+
and_return(:stuff)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
expect_any_instance_of(Packager::Executor).to(
|
|
42
|
+
receive(:execute_on).
|
|
43
|
+
with(:stuff).
|
|
44
|
+
and_return(['foo'])
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
it {
|
|
49
|
+
append_to_file('definition', 'contents')
|
|
50
|
+
expect(
|
|
51
|
+
capture(:stdout) { cli.execute('definition') }
|
|
52
|
+
).to eq("'definition' executed foo\n")
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "handles a file that works with two packages" do
|
|
57
|
+
before {
|
|
58
|
+
expect(Packager::DSL).to(
|
|
59
|
+
receive(:parse_dsl).
|
|
60
|
+
with('contents').
|
|
61
|
+
and_return(:stuff)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
expect_any_instance_of(Packager::Executor).to(
|
|
65
|
+
receive(:execute_on).
|
|
66
|
+
with(:stuff).
|
|
67
|
+
and_return(['foo', 'bar'])
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
it {
|
|
72
|
+
append_to_file('definition', 'contents')
|
|
73
|
+
expect(
|
|
74
|
+
capture(:stdout) { cli.execute('definition') }
|
|
75
|
+
).to eq("'definition' executed foo, bar\n")
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|