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.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'rubygems' unless defined? Gem
6
+ require 'packager/cli'
7
+
8
+ Packager::CLI.start
@@ -0,0 +1,5 @@
1
+ require 'packager/version'
2
+
3
+ require 'packager/cli'
4
+ require 'packager/dsl'
5
+ require 'packager/executor'
@@ -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
@@ -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
+
@@ -0,0 +1,3 @@
1
+ class Packager
2
+ VERSION = '0.0.1'
3
+ end
@@ -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
@@ -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
@@ -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