rascal 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +110 -0
- data/Guardfile +25 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/Rakefile +12 -0
- data/bin/_guard-core +29 -0
- data/bin/console +14 -0
- data/bin/cucumber +29 -0
- data/bin/guard +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/exe/rascal +13 -0
- data/lib/rascal/cli/base.rb +39 -0
- data/lib/rascal/cli/clean.rb +16 -0
- data/lib/rascal/cli/main.rb +45 -0
- data/lib/rascal/cli/shell.rb +16 -0
- data/lib/rascal/cli.rb +10 -0
- data/lib/rascal/docker/container.rb +121 -0
- data/lib/rascal/docker/interface.rb +111 -0
- data/lib/rascal/docker/network.rb +42 -0
- data/lib/rascal/docker/volume.rb +42 -0
- data/lib/rascal/docker.rb +18 -0
- data/lib/rascal/environment.rb +47 -0
- data/lib/rascal/environments_definition/gitlab.rb +117 -0
- data/lib/rascal/environments_definition.rb +23 -0
- data/lib/rascal/io_helper.rb +30 -0
- data/lib/rascal/service.rb +25 -0
- data/lib/rascal/version.rb +3 -0
- data/lib/rascal.rb +11 -0
- data/rascal.gemspec +37 -0
- metadata +97 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
module Rascal
|
2
|
+
module Docker
|
3
|
+
class Container
|
4
|
+
include IOHelper
|
5
|
+
|
6
|
+
def initialize(name, image)
|
7
|
+
@name = name
|
8
|
+
@prefixed_name = "#{NAME_PREFIX}#{name}"
|
9
|
+
@image = image
|
10
|
+
end
|
11
|
+
|
12
|
+
def download_missing
|
13
|
+
unless image_exists?
|
14
|
+
say "Downloading image for #{@name}"
|
15
|
+
Docker.interface.run(
|
16
|
+
'pull',
|
17
|
+
@image,
|
18
|
+
stdout: stdout,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def running?
|
24
|
+
if id
|
25
|
+
container_info = Docker.interface.run(
|
26
|
+
'container',
|
27
|
+
'inspect',
|
28
|
+
id,
|
29
|
+
output: :json
|
30
|
+
).first
|
31
|
+
!!container_info.dig('State', 'Running')
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def exists?
|
38
|
+
!!id
|
39
|
+
end
|
40
|
+
|
41
|
+
def start(network: nil, network_alias: nil)
|
42
|
+
say "Starting container for #{@name}"
|
43
|
+
create(network: network, network_alias: network_alias) unless exists?
|
44
|
+
Docker.interface.run(
|
45
|
+
'container',
|
46
|
+
'start',
|
47
|
+
id,
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create(network: nil, network_alias: nil)
|
52
|
+
@id = Docker.interface.run(
|
53
|
+
'container',
|
54
|
+
'create',
|
55
|
+
'--name', @prefixed_name,
|
56
|
+
*(['--network', network.id] if network),
|
57
|
+
*(['--network-alias', network_alias] if network_alias),
|
58
|
+
@image,
|
59
|
+
output: :id
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_and_attach(*command, env: {}, network: nil, volumes: [], working_dir: nil, allow_failure: false)
|
64
|
+
Docker.interface.run_and_attach(@image, *command,
|
65
|
+
env: env,
|
66
|
+
stdout: stdout,
|
67
|
+
stderr: stderr,
|
68
|
+
stdin: stdin,
|
69
|
+
network: network&.id,
|
70
|
+
volumes: volumes,
|
71
|
+
working_dir: working_dir,
|
72
|
+
allow_failure: allow_failure
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def clean
|
77
|
+
stop_container if running?
|
78
|
+
remove_container if exists?
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def id
|
84
|
+
@id ||= Docker.interface.run(
|
85
|
+
'container',
|
86
|
+
'ps',
|
87
|
+
'--all',
|
88
|
+
'--quiet',
|
89
|
+
'--filter', "name=^/#{@prefixed_name}$",
|
90
|
+
output: :id
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def image_exists?
|
95
|
+
Docker.interface.run(
|
96
|
+
'image',
|
97
|
+
'inspect',
|
98
|
+
@image,
|
99
|
+
output: :json,
|
100
|
+
allow_failure: true,
|
101
|
+
).first
|
102
|
+
end
|
103
|
+
|
104
|
+
def stop_container
|
105
|
+
Docker.interface.run(
|
106
|
+
'container',
|
107
|
+
'stop',
|
108
|
+
id,
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def remove_container
|
113
|
+
Docker.interface.run(
|
114
|
+
'container',
|
115
|
+
'rm',
|
116
|
+
id,
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Rascal
|
5
|
+
module Docker
|
6
|
+
class Interface
|
7
|
+
class Error < Rascal::Error; end
|
8
|
+
|
9
|
+
def run(*command, output: :ignore, stdout: nil, redirect_io: {}, allow_failure: false)
|
10
|
+
save_stdout = ''
|
11
|
+
save_stderr = ''
|
12
|
+
exit_status = nil
|
13
|
+
popen3('docker', *stringify_command(command)) do |docker_stdin, docker_stdout, docker_stderr, wait_thr|
|
14
|
+
docker_stdin.close
|
15
|
+
output_threads = [
|
16
|
+
read_lines(docker_stdout, save_stdout, stdout),
|
17
|
+
read_lines(docker_stderr, save_stderr),
|
18
|
+
]
|
19
|
+
exit_status = wait_thr.value
|
20
|
+
output_threads.each(&:join)
|
21
|
+
end
|
22
|
+
unless allow_failure || exit_status.success?
|
23
|
+
raise Error, "docker command '#{command.join(' ')}' failed with error:\n#{save_stderr}"
|
24
|
+
end
|
25
|
+
case output
|
26
|
+
when :json
|
27
|
+
begin
|
28
|
+
JSON.parse(save_stdout)
|
29
|
+
rescue JSON::ParserError
|
30
|
+
raise Error, "could not parse output of docker command '#{command.join(' ')}':\n#{save_stdout}"
|
31
|
+
end
|
32
|
+
when :id
|
33
|
+
save_stdout[/[0-9a-f]+/]
|
34
|
+
when :ignore
|
35
|
+
nil
|
36
|
+
else
|
37
|
+
raise ArgumentError, 'unknown option for :output'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_and_attach(image, *command, stdout: nil, stderr: nil, stdin: nil, env: {}, network: nil, volumes: [], working_dir: nil, allow_failure: false)
|
42
|
+
process_redirections = {}
|
43
|
+
args = []
|
44
|
+
if stdout
|
45
|
+
process_redirections[:out] = stdout
|
46
|
+
args += ['-a', 'STDOUT']
|
47
|
+
end
|
48
|
+
if stderr
|
49
|
+
process_redirections[:err] = stderr
|
50
|
+
args += ['-a', 'STDERR']
|
51
|
+
end
|
52
|
+
if stdin
|
53
|
+
process_redirections[:in] = stdin
|
54
|
+
args += ['-a', 'STDIN', '--interactive', '--tty']
|
55
|
+
end
|
56
|
+
if working_dir
|
57
|
+
args += ['-w', working_dir.to_s]
|
58
|
+
end
|
59
|
+
volumes.each do |volume|
|
60
|
+
args += ['-v', volume.to_param]
|
61
|
+
end
|
62
|
+
env.each do |key, value|
|
63
|
+
args += ['-e', "#{key}=#{value}"]
|
64
|
+
end
|
65
|
+
if network
|
66
|
+
args += ['--network', network.to_s]
|
67
|
+
end
|
68
|
+
exit_status = spawn(
|
69
|
+
env,
|
70
|
+
'docker',
|
71
|
+
'container',
|
72
|
+
'run',
|
73
|
+
'--rm',
|
74
|
+
*args,
|
75
|
+
image.to_s,
|
76
|
+
*stringify_command(command),
|
77
|
+
process_redirections,
|
78
|
+
)
|
79
|
+
unless allow_failure || exit_status.success?
|
80
|
+
raise Error, "docker container run failed"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def stringify_command(command)
|
87
|
+
command.collect(&:to_s)
|
88
|
+
end
|
89
|
+
|
90
|
+
def spawn(*command)
|
91
|
+
pid = Process.spawn(*command)
|
92
|
+
Process.wait(pid)
|
93
|
+
$?
|
94
|
+
end
|
95
|
+
|
96
|
+
def popen3(*command, &block)
|
97
|
+
Open3.popen3(*command, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_lines(io, save_to, output_to = nil)
|
101
|
+
Thread.new do
|
102
|
+
io.each_line do |l|
|
103
|
+
output_to&.write(l)
|
104
|
+
save_to << l
|
105
|
+
end
|
106
|
+
rescue IOError
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Rascal
|
2
|
+
module Docker
|
3
|
+
class Network
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
@prefixed_name = "#{NAME_PREFIX}#{name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def create
|
10
|
+
Docker.interface.run(
|
11
|
+
'network',
|
12
|
+
'create',
|
13
|
+
@prefixed_name,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?
|
18
|
+
!!id
|
19
|
+
end
|
20
|
+
|
21
|
+
def clean
|
22
|
+
if exists?
|
23
|
+
Docker.interface.run(
|
24
|
+
'network',
|
25
|
+
'rm',
|
26
|
+
id,
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def id
|
32
|
+
@id ||= Docker.interface.run(
|
33
|
+
'network',
|
34
|
+
'ls',
|
35
|
+
'--quiet',
|
36
|
+
'--filter', "name=^#{@prefixed_name}$",
|
37
|
+
output: :id,
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Rascal
|
2
|
+
module Docker
|
3
|
+
module Volume
|
4
|
+
class Base
|
5
|
+
end
|
6
|
+
|
7
|
+
class Named < Base
|
8
|
+
def initialize(name, container_path)
|
9
|
+
@prefixed_name = "#{NAME_PREFIX}#{name}"
|
10
|
+
@container_path = container_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_param
|
14
|
+
"#{@prefixed_name}:#{@container_path}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def clean
|
18
|
+
Docker.interface.run(
|
19
|
+
'volume',
|
20
|
+
'rm',
|
21
|
+
@prefixed_name,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Bind < Base
|
27
|
+
def initialize(local_path, container_path)
|
28
|
+
@local_path = local_path
|
29
|
+
@container_path = container_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_param
|
33
|
+
"#{@local_path}:#{@container_path}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def clean
|
37
|
+
# nothing to do
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rascal
|
2
|
+
module Docker
|
3
|
+
NAME_PREFIX = 'rascal-'
|
4
|
+
|
5
|
+
autoload :Container, 'rascal/docker/container'
|
6
|
+
autoload :Interface, 'rascal/docker/interface'
|
7
|
+
autoload :Network, 'rascal/docker/network'
|
8
|
+
autoload :Volume, 'rascal/docker/volume'
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_writer :interface
|
12
|
+
|
13
|
+
def interface
|
14
|
+
@interface ||= Interface.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rascal
|
2
|
+
class Environment
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name, image:, env_variables: {}, services: [], volumes: [], before_shell: [], working_dir: nil)
|
6
|
+
@name = name
|
7
|
+
@network = Docker::Network.new(name)
|
8
|
+
@container = Docker::Container.new(name, image)
|
9
|
+
@env_variables = env_variables
|
10
|
+
@services = services
|
11
|
+
@volumes = volumes
|
12
|
+
@working_dir = working_dir
|
13
|
+
@before_shell = before_shell
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_shell
|
17
|
+
download_missing
|
18
|
+
start_services
|
19
|
+
command = [*@before_shell, 'bash'].join(';')
|
20
|
+
@container.run_and_attach('bash', '-c', command,
|
21
|
+
env: @env_variables,
|
22
|
+
network: @network,
|
23
|
+
volumes: @volumes,
|
24
|
+
working_dir: @working_dir,
|
25
|
+
allow_failure: true
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def clean
|
30
|
+
@services.each(&:clean)
|
31
|
+
@network.clean
|
32
|
+
@volumes.each(&:clean)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def download_missing
|
38
|
+
@container.download_missing
|
39
|
+
@services.each(&:download_missing)
|
40
|
+
end
|
41
|
+
|
42
|
+
def start_services
|
43
|
+
@network.create unless @network.exists?
|
44
|
+
@services.each { |s| s.start_if_stopped(network: @network) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Rascal
|
4
|
+
module EnvironmentsDefinition
|
5
|
+
class Gitlab
|
6
|
+
class << self
|
7
|
+
def detect(path)
|
8
|
+
if path.directory?
|
9
|
+
path = path.join('.gitlab-ci.yml')
|
10
|
+
end
|
11
|
+
if path.file?
|
12
|
+
new(path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Config
|
18
|
+
def initialize(config, prefix)
|
19
|
+
@config = config
|
20
|
+
@prefix = prefix
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key, *default)
|
24
|
+
if @config.has_key?(key)
|
25
|
+
@config[key]
|
26
|
+
elsif default.size > 0
|
27
|
+
default.first
|
28
|
+
else
|
29
|
+
raise Error.new("missing config for '#{@prefix}.#{key}'")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def initialize(config_path)
|
36
|
+
@info = parse_definition(config_path.read)
|
37
|
+
@repo_dir = config_path.parent
|
38
|
+
@rascal_config = @info.fetch('.rascal', {})
|
39
|
+
end
|
40
|
+
|
41
|
+
def environment(name)
|
42
|
+
environments.detect do |e|
|
43
|
+
e.name == name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def available_environment_names
|
48
|
+
environments.collect(&:name).sort
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def parse_definition(yaml)
|
54
|
+
YAML.safe_load(yaml, [], [], true)
|
55
|
+
end
|
56
|
+
|
57
|
+
def environments
|
58
|
+
@environments ||= begin
|
59
|
+
@info.collect do |key, environment_config|
|
60
|
+
config = Config.new(deep_merge(environment_config, @rascal_config), key)
|
61
|
+
docker_repo_dir = config.get('repo_dir')
|
62
|
+
unless key.start_with?('.')
|
63
|
+
Environment.new(key,
|
64
|
+
image: config.get('image'),
|
65
|
+
env_variables: (config.get('variables', {})),
|
66
|
+
services: build_services(key, config.get('services', [])),
|
67
|
+
volumes: [build_repo_volume(docker_repo_dir), *build_volumes(key, config.get('volumes', {}))],
|
68
|
+
before_shell: config.get('before_shell', []),
|
69
|
+
working_dir: docker_repo_dir,
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end.compact
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def deep_merge(hash1, hash2)
|
77
|
+
if hash1.is_a?(Hash) && hash2.is_a?(Hash)
|
78
|
+
result = {}
|
79
|
+
hash1.each do |key1, value1|
|
80
|
+
if hash2.has_key?(key1)
|
81
|
+
result[key1] = deep_merge(value1, hash2[key1])
|
82
|
+
else
|
83
|
+
result[key1] = value1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
hash2.each do |key2, value2|
|
87
|
+
result[key2] ||= value2
|
88
|
+
end
|
89
|
+
result
|
90
|
+
else
|
91
|
+
hash2
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_services(name, services)
|
96
|
+
services.collect do |service_config|
|
97
|
+
service_alias = service_config['alias']
|
98
|
+
Service.new("#{name}_#{service_alias}",
|
99
|
+
alias_name: service_config['alias'],
|
100
|
+
image: service_config['name'],
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_repo_volume(docker_repo_dir)
|
106
|
+
Docker::Volume::Bind.new(@repo_dir, docker_repo_dir)
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_volumes(name, volume_config)
|
110
|
+
volume_config.collect do |volume_name, docker_path|
|
111
|
+
Docker::Volume::Named.new("#{name}-#{volume_name}", docker_path)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rascal
|
2
|
+
module EnvironmentsDefinition
|
3
|
+
autoload :Gitlab, 'rascal/environments_definition/gitlab'
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def detect(working_dir)
|
7
|
+
definition_formats.each do |format|
|
8
|
+
definition = format.detect(working_dir)
|
9
|
+
return definition if definition
|
10
|
+
end
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def definition_formats
|
17
|
+
[
|
18
|
+
Gitlab
|
19
|
+
]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rascal
|
2
|
+
module IOHelper
|
3
|
+
class << self
|
4
|
+
attr_accessor :stdout, :stdin, :stderr
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@stdout = $stdout
|
8
|
+
@stderr = $stderr
|
9
|
+
@stdin = $stdin
|
10
|
+
end
|
11
|
+
end
|
12
|
+
setup
|
13
|
+
|
14
|
+
def say(message)
|
15
|
+
stdout.puts(message)
|
16
|
+
end
|
17
|
+
|
18
|
+
def stdout
|
19
|
+
IOHelper.stdout
|
20
|
+
end
|
21
|
+
|
22
|
+
def stderr
|
23
|
+
IOHelper.stderr
|
24
|
+
end
|
25
|
+
|
26
|
+
def stdin
|
27
|
+
IOHelper.stdin
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rascal
|
2
|
+
class Service
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name, image:, alias_name:)
|
6
|
+
@name = name
|
7
|
+
@container = Docker::Container.new(name, image)
|
8
|
+
@alias = alias_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def download_missing
|
12
|
+
@container.download_missing
|
13
|
+
end
|
14
|
+
|
15
|
+
def start_if_stopped(network: nil)
|
16
|
+
unless @container.running?
|
17
|
+
@container.start(network: network, network_alias: @alias)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def clean
|
22
|
+
@container.clean
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rascal.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "rascal/version"
|
2
|
+
|
3
|
+
module Rascal
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
autoload :Docker, 'rascal/docker'
|
7
|
+
autoload :Environment, 'rascal/environment'
|
8
|
+
autoload :EnvironmentsDefinition, 'rascal/environments_definition'
|
9
|
+
autoload :IOHelper, 'rascal/io_helper'
|
10
|
+
autoload :Service, 'rascal/service'
|
11
|
+
end
|
data/rascal.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "rascal/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rascal"
|
8
|
+
spec.version = Rascal::VERSION
|
9
|
+
spec.authors = ["Tobias Kraze"]
|
10
|
+
spec.email = ["tobias.kraze@makandra.de"]
|
11
|
+
|
12
|
+
spec.summary = "Spin up CI environments locally."
|
13
|
+
spec.homepage = "https://github.com/makandra/rascal"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/makandra/rascal"
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/makandra/rascal/blob/master/CHANGELOG.md"
|
22
|
+
else
|
23
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
24
|
+
"public gem pushes."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
28
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
29
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
30
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
31
|
+
end
|
32
|
+
spec.bindir = "exe"
|
33
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ["lib"]
|
35
|
+
|
36
|
+
spec.add_dependency "thor", "~> 0.20.3"
|
37
|
+
end
|