fig 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +27 -0
- data/README.rdoc +17 -0
- data/bin/fig +115 -0
- data/lib/fig/environment.rb +137 -0
- data/lib/fig/grammar.treetop +149 -0
- data/lib/fig/options.rb +49 -0
- data/lib/fig/os.rb +103 -0
- data/lib/fig/package.rb +195 -0
- data/lib/fig/parser.rb +25 -0
- data/lib/fig/repository.rb +149 -0
- data/lib/fig.rb +0 -0
- data/spec/fig_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +100 -0
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2009, Matthew Foemmel
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
12
|
+
documentation and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* The names of the contributors may not be used to endorse or promote
|
15
|
+
products derived from this software without specific prior written
|
16
|
+
permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
22
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= fig
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2009 Matthew Foemmel. See LICENSE for details.
|
data/bin/fig
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'net/ftp'
|
7
|
+
|
8
|
+
require 'fig/options'
|
9
|
+
require 'fig/environment'
|
10
|
+
require 'fig/repository'
|
11
|
+
require 'fig/os'
|
12
|
+
require 'fig/parser'
|
13
|
+
|
14
|
+
include Fig
|
15
|
+
|
16
|
+
def parse_descriptor(descriptor)
|
17
|
+
# todo should use treetop for these:
|
18
|
+
package_name = descriptor =~ /^([^:\/]+)/ ? $1 : nil
|
19
|
+
config_name = descriptor =~ /:([^:\/]+)/ ? $1 : nil
|
20
|
+
version_name = descriptor =~ /\/([^:\/]+)/ ? $1 : nil
|
21
|
+
return package_name, config_name, version_name
|
22
|
+
end
|
23
|
+
|
24
|
+
ARGV.each_with_index do |arg, i|
|
25
|
+
if arg == "-"
|
26
|
+
$stderr.puts "Use of single dash (-) is deprecated. Use double dash (--) instead"
|
27
|
+
exit 1
|
28
|
+
elsif arg == "--"
|
29
|
+
ARGV[i] = "-"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
options, argv = parse_options(ARGV)
|
34
|
+
|
35
|
+
vars = {}
|
36
|
+
ENV.each {|key,value| vars[key]=value }
|
37
|
+
|
38
|
+
remote_url = nil
|
39
|
+
if options[:update] || options[:publish]
|
40
|
+
remote_url = ENV['FIG_REMOTE_URL']
|
41
|
+
if remote_url.nil?
|
42
|
+
$stderr.puts "Please define the FIG_REMOTE_URL environment variable"
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
remote_user = nil
|
48
|
+
if options[:publish]
|
49
|
+
# remote_user = ENV['FIG_REMOTE_USER']
|
50
|
+
# if remote_user.nil?
|
51
|
+
# $stderr.puts "Please define the FIG_REMOTE_USER environment variable"
|
52
|
+
# exit 1
|
53
|
+
# end
|
54
|
+
end
|
55
|
+
|
56
|
+
os = OS.new
|
57
|
+
repos = Repository.new(os, File.expand_path("~/.fighome/repos"), remote_url, remote_user)
|
58
|
+
env = Environment.new(os, repos, vars)
|
59
|
+
|
60
|
+
options[:includes].each do |descriptor|
|
61
|
+
package_name, config_name, version_name = parse_descriptor(descriptor)
|
62
|
+
puts "Package: #{package_name}, config_name: #{config_name}, Version: #{version_name}"
|
63
|
+
env.include_config(nil, package_name, config_name, version_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
options[:sets].each do |name_val|
|
67
|
+
env.set_variable(nil, name_val[0], name_val[1])
|
68
|
+
end
|
69
|
+
|
70
|
+
options[:appends].each do |name_val|
|
71
|
+
env.append_variable(nil, name_val[0], name_val[1])
|
72
|
+
end
|
73
|
+
|
74
|
+
if os.exist?("./.fig")
|
75
|
+
package = Parser.new.parse_package(nil, nil, ".", os.read("./.fig"))
|
76
|
+
if options[:retrieve]
|
77
|
+
package.retrieves.each do |var, path|
|
78
|
+
env.add_retrieve(var, path)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
unless options[:publish] || options[:list]
|
82
|
+
env.register_package(package)
|
83
|
+
env.apply_config(package, options[:config])
|
84
|
+
end
|
85
|
+
else
|
86
|
+
package = Package.new(nil, nil, ".", [])
|
87
|
+
end
|
88
|
+
|
89
|
+
if options[:echo]
|
90
|
+
puts env[options[:echo]]
|
91
|
+
end
|
92
|
+
|
93
|
+
if options[:list]
|
94
|
+
repos.list_packages.sort.each do |item|
|
95
|
+
puts item
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if options[:publish]
|
100
|
+
raise "Unexpected arguments: #{argv.join(' ')}" if !argv.empty?
|
101
|
+
package_name, config_name, version_name = parse_descriptor(options[:publish])
|
102
|
+
if package_name.nil? || version_name.nil?
|
103
|
+
raise "Please specify a package name and a version name"
|
104
|
+
end
|
105
|
+
repos.publish_package(package.publish_statements, package_name, version_name)
|
106
|
+
elsif argv[0] == "-"
|
107
|
+
argv.shift
|
108
|
+
env.execute_shell(argv) { |cmd| exec cmd.join(' ') }
|
109
|
+
elsif argv[0]
|
110
|
+
package_name, config_name, version_name = parse_descriptor(argv.shift)
|
111
|
+
env.include_config(package, package_name, config_name, version_name)
|
112
|
+
env.execute_config(package, package_name, config_name, nil) { |cmd| exec((cmd + argv).join(' ')) }
|
113
|
+
else
|
114
|
+
env.execute_config(package, nil, options[:config], nil) { |cmd| exec((cmd + argv).join(' ')) }
|
115
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Fig
|
2
|
+
|
3
|
+
# This class manages the program's state, including the value of all environment
|
4
|
+
# variables, and which packages have already been applied
|
5
|
+
class Environment
|
6
|
+
DEFAULT_VERSION_NAME = "current"
|
7
|
+
|
8
|
+
def initialize(os, repository, variables)
|
9
|
+
@os = os
|
10
|
+
@repository = repository
|
11
|
+
@variables = variables
|
12
|
+
@retrieve_vars = {}
|
13
|
+
@packages = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the value of an envirionment variable
|
17
|
+
def [](name)
|
18
|
+
@variables[name]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Indicates that the values from a particular envrionment variable path should
|
22
|
+
# be copied to a local directory
|
23
|
+
def add_retrieve(name, path)
|
24
|
+
@retrieve_vars[name] = path
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_package(package)
|
28
|
+
name = package.package_name
|
29
|
+
raise "Package already exists with name: #{name}" if @packages[name]
|
30
|
+
@packages[name] = package
|
31
|
+
end
|
32
|
+
|
33
|
+
def apply_config(package, config_name)
|
34
|
+
config = package[config_name]
|
35
|
+
config.statements.each { |stmt| apply_config_statement(package, stmt) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def execute_shell(command)
|
39
|
+
with_environment do
|
40
|
+
yield command.map{|arg| expand_arg(arg)}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute_config(base_package, package_name, config_name, version_name)
|
45
|
+
package = lookup_package(package_name || base_package.package_name, version_name)
|
46
|
+
result = nil
|
47
|
+
commands = package[config_name || "default"].commands
|
48
|
+
with_environment do
|
49
|
+
# todo nil check
|
50
|
+
commands.each do |command|
|
51
|
+
result = yield expand_arg(command.command).gsub("@",package.directory).split(" ")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
def include_config(base_package, package_name, config_name, version_name)
|
58
|
+
package = lookup_package(package_name || base_package.package_name, version_name)
|
59
|
+
apply_config(package, config_name || "default")
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_variable(base_package, name, value)
|
63
|
+
@variables[name] = expand_value(base_package, name, value)
|
64
|
+
end
|
65
|
+
|
66
|
+
def append_variable(base_package, name, value)
|
67
|
+
value = expand_value(base_package, name, value)
|
68
|
+
prev = @variables[name]
|
69
|
+
if prev
|
70
|
+
@variables[name] = prev + File::PATH_SEPARATOR + value
|
71
|
+
else
|
72
|
+
@variables[name] = value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def with_environment
|
79
|
+
old_env = {}
|
80
|
+
begin
|
81
|
+
@variables.each { |key,value| old_env[key] = ENV[key]; ENV[key] = value }
|
82
|
+
yield
|
83
|
+
ensure
|
84
|
+
old_env.each { |key,value| ENV[key] = value }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def apply_config_statement(base_package, statement)
|
89
|
+
case statement
|
90
|
+
when Path
|
91
|
+
append_variable(base_package, statement.name, statement.value)
|
92
|
+
when Set
|
93
|
+
set_variable(base_package, statement.name, statement.value)
|
94
|
+
when Include
|
95
|
+
include_config(base_package, statement.package_name, statement.config_name, statement.version_name)
|
96
|
+
when Command
|
97
|
+
# ignore
|
98
|
+
else
|
99
|
+
fail "Unexpected statement: #{statement}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def lookup_package(package_name, version_name)
|
104
|
+
package = @packages[package_name]
|
105
|
+
if package.nil?
|
106
|
+
package = @repository.load_package(package_name, version_name || DEFAULT_VERSION_NAME)
|
107
|
+
@packages[package_name] = package
|
108
|
+
elsif version_name && version_name != package.version_name
|
109
|
+
raise "Version mismatch: #{package_name}"
|
110
|
+
end
|
111
|
+
package
|
112
|
+
end
|
113
|
+
|
114
|
+
# Replace @ symbol with the package's directory
|
115
|
+
def expand_value(base_package, name, value)
|
116
|
+
return value if base_package.nil?
|
117
|
+
file = value.gsub(/\@/, base_package.directory)
|
118
|
+
if @retrieve_vars.member?(name)
|
119
|
+
target = File.join(@retrieve_vars[name].gsub(/\[package\]/, base_package.package_name), File.basename(file))
|
120
|
+
unless @os.exist?(target) && @os.mtime(target) >= @os.mtime(file)
|
121
|
+
@os.log_info("retrieving #{target}")
|
122
|
+
@os.copy(file, target)
|
123
|
+
end
|
124
|
+
file = target
|
125
|
+
end
|
126
|
+
file
|
127
|
+
end
|
128
|
+
|
129
|
+
def expand_arg(arg)
|
130
|
+
arg.gsub(/\@([a-zA-Z0-9\-\.]+)/) do |match|
|
131
|
+
package = @packages[$1]
|
132
|
+
raise "Package not found: #{$1}" if package.nil?
|
133
|
+
package.directory
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'fig/package'
|
2
|
+
|
3
|
+
module Fig
|
4
|
+
|
5
|
+
grammar Fig
|
6
|
+
rule package
|
7
|
+
ws statements:(package_statement*) {
|
8
|
+
def to_package(package_name, version_name, directory)
|
9
|
+
Package.new(package_name, version_name, directory, statements.elements.map { |statement| statement.to_package_statement })
|
10
|
+
end
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
rule package_statement
|
15
|
+
archive / resource / retrieve / config / publish
|
16
|
+
end
|
17
|
+
|
18
|
+
rule archive
|
19
|
+
"archive" ws url {
|
20
|
+
def to_package_statement
|
21
|
+
Archive.new(url.value.text_value)
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
rule resource
|
27
|
+
"resource" ws url {
|
28
|
+
def to_package_statement
|
29
|
+
Resource.new(url.value.text_value)
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
rule retrieve
|
35
|
+
"retrieve" ws var:[a-zA-Z0-9]+ "->" path:[a-zA-Z0-9/\.-\[\]]+ ws {
|
36
|
+
def to_package_statement
|
37
|
+
Retrieve.new(var.text_value, path.text_value)
|
38
|
+
end
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
rule publish
|
43
|
+
"publish" ws statements:(package_statement / install)* "end" ws {
|
44
|
+
def to_package_statement
|
45
|
+
Publish.new(statements.elements.map { |statement| statement.to_package_statement })
|
46
|
+
end
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
rule install
|
51
|
+
"install" ws statements:config_statement* "end" ws {
|
52
|
+
def to_package_statement
|
53
|
+
Install.new(statements.elements.map { |statement| statement.to_config_statement })
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
rule config
|
59
|
+
"config" ws config_name ws statements:config_statement* "end" ws {
|
60
|
+
def to_package_statement
|
61
|
+
Configuration.new(config_name.text_value, statements.elements.map { |statement| statement.to_config_statement })
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
rule config_statement
|
67
|
+
include / command / path / set
|
68
|
+
end
|
69
|
+
|
70
|
+
rule include
|
71
|
+
"include" ws descriptor {
|
72
|
+
def to_config_statement
|
73
|
+
package = descriptor.respond_to?(:package) ? descriptor.package.text_value : nil
|
74
|
+
config = descriptor.get_config
|
75
|
+
version = descriptor.get_version
|
76
|
+
Include.new(package, config, version)
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
rule path
|
82
|
+
"append" ws name:[a-zA-Z0-9]+ "=" value:[@a-zA-Z0-9/\-\\._]+ ws {
|
83
|
+
def to_config_statement
|
84
|
+
Path.new(name.text_value, value.text_value)
|
85
|
+
end
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
rule set
|
90
|
+
"set" ws name:[a-zA-Z0-9]+ "=" value:[@a-zA-Z0-9/\-\\._]+ ws {
|
91
|
+
def to_config_statement
|
92
|
+
Set.new(name.text_value, value.text_value)
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
rule command
|
98
|
+
"command" ws string {
|
99
|
+
def to_config_statement
|
100
|
+
Command.new(string.value.text_value)
|
101
|
+
end
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
rule string
|
106
|
+
'"' value:(!'"' . )* '"' ws
|
107
|
+
end
|
108
|
+
|
109
|
+
rule descriptor
|
110
|
+
((package:[a-zA-Z0-9.-]+ ("/" version:[a-zA-Z0-9.\-]+)? (":" config:config_name)? ws) /
|
111
|
+
(":" config:config_name ws)) {
|
112
|
+
def get_version
|
113
|
+
elements.each do |element|
|
114
|
+
if element.respond_to?(:version)
|
115
|
+
return element.version.text_value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
def get_config
|
121
|
+
return self.config.text_value if self.respond_to?(:config)
|
122
|
+
elements.each do |element|
|
123
|
+
if element.respond_to?(:config)
|
124
|
+
return element.config.text_value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
rule config_name
|
133
|
+
[a-zA-Z0-9\\-]+
|
134
|
+
end
|
135
|
+
|
136
|
+
rule name
|
137
|
+
value:[a-zA-Z0-9]+ ws
|
138
|
+
end
|
139
|
+
|
140
|
+
rule url
|
141
|
+
'"' value:[a-zA-Z0-9:/\-\\._]+ '"' ws
|
142
|
+
end
|
143
|
+
|
144
|
+
rule ws
|
145
|
+
[ \n\t]+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
data/lib/fig/options.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Fig
|
4
|
+
def parse_options(argv)
|
5
|
+
options = {}
|
6
|
+
|
7
|
+
parser = OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: fig [--debug] [--update] [--config <config>] [-echo <var> | --list | <package> | - <command>]"
|
9
|
+
|
10
|
+
opts.on('-?', '-h','--help','display this help text') do
|
11
|
+
puts opts
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
|
15
|
+
options[:debug] = false
|
16
|
+
opts.on('-d', '--debug', 'print debug info') { options[:debug] = true }
|
17
|
+
|
18
|
+
options[:update] = false
|
19
|
+
opts.on('-u', '--update', 'check remote repository for updates') { options[:update] = true; options[:retrieve] = true }
|
20
|
+
|
21
|
+
options[:config] = "default"
|
22
|
+
opts.on('-c', '--config CFG', 'name of configuration to apply') { |config| options[:config] = config }
|
23
|
+
|
24
|
+
options[:echo] = nil
|
25
|
+
opts.on('-g', '--echo VAR', 'print value of environment variable') { |echo| options[:echo] = echo }
|
26
|
+
|
27
|
+
options[:publish] = nil
|
28
|
+
opts.on('--publish PKG', 'install package in local and remote repositories') { |publish| options[:publish] = publish }
|
29
|
+
|
30
|
+
options[:list] = false
|
31
|
+
opts.on('--list', 'list packages in local repository') { options[:list] = true }
|
32
|
+
|
33
|
+
options[:includes] = []
|
34
|
+
opts.on('-i', '--include PKG', 'include package in environment') { |descriptor| options[:includes] << descriptor }
|
35
|
+
|
36
|
+
options[:sets] = []
|
37
|
+
opts.on('-s', '--set VAR=VAL', 'set environment variable') { |var_val| options[:sets] << var_val.split('=') }
|
38
|
+
|
39
|
+
options[:appends] = []
|
40
|
+
opts.on('-p', '--append VAR=VAL', 'append environment variable') { |var_val| options[:appends] << var_val.split('=') }
|
41
|
+
|
42
|
+
options[:home] = ENV['FIG_HOME'] || File.expand_path("~/.fighome")
|
43
|
+
end
|
44
|
+
|
45
|
+
parser.parse!(argv)
|
46
|
+
|
47
|
+
return options, argv
|
48
|
+
end
|
49
|
+
end
|
data/lib/fig/os.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'ftools'
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module Fig
|
7
|
+
class OS
|
8
|
+
def list(dir)
|
9
|
+
Dir.entries(dir) - ['.','..']
|
10
|
+
end
|
11
|
+
|
12
|
+
def exist?(path)
|
13
|
+
File.exist?(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def mtime(path)
|
17
|
+
File.mtime(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(path)
|
21
|
+
File.read(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(path, content)
|
25
|
+
File.open(path, "w") { |f| f << content }
|
26
|
+
end
|
27
|
+
|
28
|
+
def download(url, path)
|
29
|
+
FileUtils.mkdir_p(File.dirname(path))
|
30
|
+
uri = URI.parse(url)
|
31
|
+
case uri.scheme
|
32
|
+
when "ftp"
|
33
|
+
ftp = Net::FTP.new(uri.host)
|
34
|
+
ftp.login
|
35
|
+
if File.exist?(path) && ftp.mtime(uri.path) <= File.mtime(path)
|
36
|
+
return false
|
37
|
+
else
|
38
|
+
puts "downloading #{url}"
|
39
|
+
ftp.getbinaryfile(uri.path, path, 256*1024)
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
when "http"
|
43
|
+
http = Net::HTTP.new(uri.host)
|
44
|
+
puts "downloading #{url}"
|
45
|
+
File.open(path, "w") do |file|
|
46
|
+
http.get(uri.path) do |block|
|
47
|
+
file.write(block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
raise "Unknown protocol: #{url}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def download_resource(url, dir)
|
56
|
+
FileUtils.mkdir_p(dir)
|
57
|
+
download(url, File.join(dir, URI.parse(url).path.split('/').last))
|
58
|
+
end
|
59
|
+
|
60
|
+
def download_archive(url, dir)
|
61
|
+
FileUtils.mkdir_p(dir)
|
62
|
+
basename = URI.parse(url).path.split('/').last
|
63
|
+
path = File.join(dir, basename)
|
64
|
+
download(url, path)
|
65
|
+
case basename
|
66
|
+
when /\.tar\.gz$/
|
67
|
+
fail unless system "tar -C #{dir} -zxf #{path}"
|
68
|
+
when /\.tgz$/
|
69
|
+
fail unless system "tar -C #{dir} -zxf #{path}"
|
70
|
+
when /\.tar\.bz2$/
|
71
|
+
fail unless system "tar -C #{dir} -jxf #{path}"
|
72
|
+
when /\.zip$/
|
73
|
+
fail unless system "unzip -q -d #{dir} #{path}"
|
74
|
+
else
|
75
|
+
raise "Unknown archive type: #{basename}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def upload(local_file, remote_file, user)
|
80
|
+
puts "uploading #{remote_file}"
|
81
|
+
fail unless system "curl -p -T #{local_file} --create-dirs --ftp-create-dirs #{remote_file}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def clear_directory(dir)
|
85
|
+
FileUtils.rm_rf(dir)
|
86
|
+
FileUtils.mkdir_p(dir)
|
87
|
+
end
|
88
|
+
|
89
|
+
def exec(dir,command)
|
90
|
+
Dir.chdir(dir) { raise "Command failed" unless system command }
|
91
|
+
end
|
92
|
+
|
93
|
+
def copy(source, target)
|
94
|
+
FileUtils.mkdir_p(File.dirname(target))
|
95
|
+
File.copy(source, target)
|
96
|
+
target
|
97
|
+
end
|
98
|
+
|
99
|
+
def log_info(msg)
|
100
|
+
puts msg
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/fig/package.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
module Fig
|
2
|
+
class Package
|
3
|
+
attr_reader :package_name, :version_name, :directory, :statements
|
4
|
+
|
5
|
+
def initialize(package_name, version_name, directory, statements)
|
6
|
+
@package_name = package_name
|
7
|
+
@version_name = version_name
|
8
|
+
@directory = directory
|
9
|
+
@statements = statements
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](config_name)
|
13
|
+
@statements.each do |stmt|
|
14
|
+
return stmt if stmt.is_a?(Configuration) && stmt.name == config_name
|
15
|
+
end
|
16
|
+
raise "Configuration not found: #{@package_name}/#{@version_name}:#{config_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def retrieves
|
20
|
+
retrieves = {}
|
21
|
+
statements.each { |statement| retrieves[statement.var] = statement.path if statement.is_a?(Retrieve) }
|
22
|
+
retrieves
|
23
|
+
end
|
24
|
+
def archive_urls
|
25
|
+
@statements.select{|s| s.is_a?(Archive)}.map{|s|s.url}
|
26
|
+
end
|
27
|
+
|
28
|
+
def resource_urls
|
29
|
+
@statements.select{|s| s.is_a?(Resource)}.map{|s|s.url}
|
30
|
+
end
|
31
|
+
|
32
|
+
def publish_statements
|
33
|
+
statements = []
|
34
|
+
@statements.each{ |s| statements += s.statements if s.is_a?(Publish) }
|
35
|
+
statements
|
36
|
+
end
|
37
|
+
|
38
|
+
def unparse
|
39
|
+
@statements.map { |statement| statement.unparse('') }.join("\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(other)
|
43
|
+
@package_name == other.package_name && @version_name == other.version_name && @statements.to_yaml == other.statements.to_yaml
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Archive
|
48
|
+
attr_reader :url
|
49
|
+
|
50
|
+
def initialize(url)
|
51
|
+
@url = url
|
52
|
+
end
|
53
|
+
|
54
|
+
def unparse(indent)
|
55
|
+
"#{indent}archive \"#{url}\""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Resource
|
60
|
+
attr_reader :url
|
61
|
+
|
62
|
+
def initialize(url)
|
63
|
+
@url = url
|
64
|
+
end
|
65
|
+
|
66
|
+
def unparse(indent)
|
67
|
+
"#{indent}resource \"#{url}\""
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Retrieve
|
72
|
+
attr_reader :var, :path
|
73
|
+
|
74
|
+
def initialize(var, path)
|
75
|
+
@var = var
|
76
|
+
@path = path
|
77
|
+
end
|
78
|
+
|
79
|
+
def unparse(indent)
|
80
|
+
"#{indent}retrieve #{var}->#{path}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Publish
|
85
|
+
attr_reader :statements
|
86
|
+
|
87
|
+
def initialize(statements)
|
88
|
+
@statements = statements
|
89
|
+
end
|
90
|
+
|
91
|
+
def unparse(indent)
|
92
|
+
prefix = "\n#{indent}publish"
|
93
|
+
body = @statements.map { |statement| statement.unparse(indent+' ') }.join("\n")
|
94
|
+
suffix = "#{indent}end"
|
95
|
+
return [prefix, body, suffix].join("\n")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Install
|
100
|
+
def initialize(statements)
|
101
|
+
@statements = statements
|
102
|
+
end
|
103
|
+
|
104
|
+
def unparse(indent)
|
105
|
+
prefix = "\n#{indent}install"
|
106
|
+
body = @statements.map { |statement| statement.unparse(indent+' ') }.join("\n")
|
107
|
+
suffix = "#{indent}end"
|
108
|
+
return [prefix, body, suffix].join("\n")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Configuration
|
113
|
+
attr_reader :name, :statements
|
114
|
+
|
115
|
+
def initialize(name, statements)
|
116
|
+
@name = name
|
117
|
+
@statements = statements
|
118
|
+
end
|
119
|
+
|
120
|
+
def commands
|
121
|
+
result = statements.select { |statement| statement.is_a?(Command) }
|
122
|
+
# if result.empty?
|
123
|
+
# raise "No commands found for config: #{@name}"
|
124
|
+
# end
|
125
|
+
result
|
126
|
+
end
|
127
|
+
|
128
|
+
def unparse(indent)
|
129
|
+
unparse_statements(indent, "config #{@name}", @statements, "end")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Path
|
134
|
+
attr_reader :name, :value
|
135
|
+
|
136
|
+
def initialize(name, value)
|
137
|
+
@name = name
|
138
|
+
@value = value
|
139
|
+
end
|
140
|
+
|
141
|
+
def unparse(indent)
|
142
|
+
"#{indent}append #{name}=#{value}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Set
|
147
|
+
attr_reader :name, :value
|
148
|
+
|
149
|
+
def initialize(name, value)
|
150
|
+
@name = name
|
151
|
+
@value = value
|
152
|
+
end
|
153
|
+
|
154
|
+
def unparse(indent)
|
155
|
+
"#{indent}set #{name}=#{value}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class Include
|
160
|
+
attr_reader :package_name, :config_name, :version_name
|
161
|
+
|
162
|
+
def initialize(package_name, config_name, version_name)
|
163
|
+
@package_name = package_name
|
164
|
+
@config_name = config_name
|
165
|
+
@version_name = version_name
|
166
|
+
end
|
167
|
+
|
168
|
+
def unparse(indent)
|
169
|
+
descriptor = ""
|
170
|
+
descriptor += @package_name if @package_name
|
171
|
+
descriptor += "/#{@version_name}" if @version_name
|
172
|
+
descriptor += ":#{@config_name}" if @config_name
|
173
|
+
"#{indent}include #{descriptor}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class Command
|
178
|
+
attr_reader :command
|
179
|
+
|
180
|
+
def initialize(command)
|
181
|
+
@command = command
|
182
|
+
end
|
183
|
+
|
184
|
+
def unparse(indent)
|
185
|
+
"#{indent}command \"#{@command}\""
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
def unparse_statements(indent, prefix, statements, suffix)
|
192
|
+
body = @statements.map { |statement| statement.unparse(indent+' ') }.join("\n")
|
193
|
+
return ["\n#{indent}#{prefix}", body, "#{indent}#{suffix}"].join("\n")
|
194
|
+
end
|
195
|
+
|
data/lib/fig/parser.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
|
4
|
+
require 'fig/grammar'
|
5
|
+
|
6
|
+
module Fig
|
7
|
+
class Parser
|
8
|
+
def initialize
|
9
|
+
@parser = FigParser.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_package(package_name, version_name, directory, input)
|
13
|
+
result = @parser.parse(" #{input} ")
|
14
|
+
if result.nil?
|
15
|
+
raise "#{directory}: #{@parser.failure_reason}"
|
16
|
+
end
|
17
|
+
result.to_package(package_name, version_name, directory)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_descriptor(descriptor)
|
21
|
+
puts @parser.methods.sort
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'fig/parser'
|
2
|
+
|
3
|
+
module Fig
|
4
|
+
class Repository
|
5
|
+
def initialize(os, local_repository_dir, remote_repository_url, remote_repository_user=nil)
|
6
|
+
@os = os
|
7
|
+
@local_repository_dir = local_repository_dir
|
8
|
+
@remote_repository_url = remote_repository_url
|
9
|
+
@remote_repository_user = remote_repository_user
|
10
|
+
@parser = Parser.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def list_packages
|
14
|
+
results = []
|
15
|
+
@os.list(@local_repository_dir).each do |package_name|
|
16
|
+
@os.list(File.join(@local_repository_dir, package_name)).each do |version_name|
|
17
|
+
results << "#{package_name}/#{version_name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
results
|
21
|
+
end
|
22
|
+
|
23
|
+
def publish_package(package_statements, package_name, version_name)
|
24
|
+
temp_dir = temp_dir_for_package(package_name, version_name)
|
25
|
+
@os.clear_directory(temp_dir)
|
26
|
+
fig_file = File.join(temp_dir, ".fig")
|
27
|
+
content = package_statements.map do |statement|
|
28
|
+
if statement.is_a?(Archive) || statement.is_a?(Resource)
|
29
|
+
archive_name = statement.url.split("/").last
|
30
|
+
archive_remote = "#{remote_dir_for_package(package_name, version_name)}/#{archive_name}"
|
31
|
+
if is_url?(statement.url)
|
32
|
+
archive_local = File.join(temp_dir, archive_name)
|
33
|
+
@os.download(statement.url, archive_local)
|
34
|
+
else
|
35
|
+
archive_local = statement.url
|
36
|
+
end
|
37
|
+
@os.upload(archive_local, archive_remote, @remote_repository_user)
|
38
|
+
statement.class.new(archive_name).unparse('')
|
39
|
+
else
|
40
|
+
statement.unparse('')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
@os.write(fig_file, content.join("\n"))
|
44
|
+
@os.upload(fig_file, remote_fig_file_for_package(package_name, version_name), @remote_repository_user)
|
45
|
+
update_package(package_name, version_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_package(package_name, version_name)
|
49
|
+
update_package(package_name, version_name) if @remote_repository_url
|
50
|
+
read_local_package(package_name, version_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_package(package_name, version_name)
|
54
|
+
remote_fig_file = remote_fig_file_for_package(package_name, version_name)
|
55
|
+
local_fig_file = local_fig_file_for_package(package_name, version_name)
|
56
|
+
if @os.download(remote_fig_file, local_fig_file)
|
57
|
+
install_package(package_name, version_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def read_local_package(package_name, version_name)
|
62
|
+
dir = local_dir_for_package(package_name, version_name)
|
63
|
+
read_package_from_directory(dir, package_name, version_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_remote_package(package_name, version_name)
|
67
|
+
url = remote_fig_file_for_package(package_name, version_name)
|
68
|
+
content = @os.read_url(url)
|
69
|
+
@parser.parse_package(package_name, version_name, nil, content)
|
70
|
+
end
|
71
|
+
|
72
|
+
def read_package_from_directory(dir, package_name, version_name)
|
73
|
+
read_package_from_file(File.join(dir, ".fig"), package_name, version_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_package_from_file(file_name, package_name, version_name)
|
77
|
+
raise "Package not found: #{file_name}" unless @os.exist?(file_name)
|
78
|
+
modified_time = @os.mtime(file_name)
|
79
|
+
content = @os.read(file_name)
|
80
|
+
@parser.parse_package(package_name, version_name, File.dirname(file_name), content)
|
81
|
+
end
|
82
|
+
|
83
|
+
def local_dir_for_package(package_name, version_name)
|
84
|
+
File.join(@local_repository_dir, package_name, version_name)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def install_package(package_name, version_name)
|
90
|
+
begin
|
91
|
+
package = read_local_package(package_name, version_name)
|
92
|
+
temp_dir = temp_dir_for_package(package_name, version_name)
|
93
|
+
@os.clear_directory(temp_dir)
|
94
|
+
package.archive_urls.each do |archive_url|
|
95
|
+
if not is_url?(archive_url)
|
96
|
+
archive_url = remote_dir_for_package(package_name, version_name) + "/" + archive_url
|
97
|
+
end
|
98
|
+
@os.download_archive(archive_url, File.join(temp_dir))
|
99
|
+
end
|
100
|
+
package.resource_urls.each do |resource_url|
|
101
|
+
if not is_url?(resource_url)
|
102
|
+
resource_url = remote_dir_for_package(package_name, version_name) + "/" + resource_url
|
103
|
+
end
|
104
|
+
@os.download_resource(resource_url, File.join(temp_dir))
|
105
|
+
end
|
106
|
+
local_dir = local_dir_for_package(package_name, version_name)
|
107
|
+
@os.clear_directory(local_dir)
|
108
|
+
# some packages contain no files, only a fig file.
|
109
|
+
if not (package.archive_urls.empty? && package.resource_urls.empty?)
|
110
|
+
@os.exec(temp_dir, "mv * #{local_dir}/")
|
111
|
+
end
|
112
|
+
write_local_package(package_name, version_name, package)
|
113
|
+
rescue
|
114
|
+
$stderr.puts "install failed, cleaning up"
|
115
|
+
delete_local_package(package_name, version_name)
|
116
|
+
raise
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def is_url?(url)
|
121
|
+
not (/ftp:\/\/|http:\/\/|file:\/\/|ssh:\/\// =~ url).nil?
|
122
|
+
end
|
123
|
+
|
124
|
+
def delete_local_package(package_name, version_name)
|
125
|
+
FileUtils.rm_rf(local_dir_for_package(package_name, version_name))
|
126
|
+
end
|
127
|
+
|
128
|
+
def write_local_package(package_name, version_name, package)
|
129
|
+
file = local_fig_file_for_package(package_name, version_name)
|
130
|
+
@os.write(file, package.unparse)
|
131
|
+
end
|
132
|
+
|
133
|
+
def remote_fig_file_for_package(package_name, version_name)
|
134
|
+
"#{@remote_repository_url}/#{package_name}/#{version_name}/.fig"
|
135
|
+
end
|
136
|
+
|
137
|
+
def local_fig_file_for_package(package_name, version_name)
|
138
|
+
File.join(local_dir_for_package(package_name, version_name), ".fig")
|
139
|
+
end
|
140
|
+
|
141
|
+
def remote_dir_for_package(package_name, version_name)
|
142
|
+
"#{@remote_repository_url}/#{package_name}/#{version_name}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def temp_dir_for_package(package_name, version_name)
|
146
|
+
File.join(@local_repository_dir, "tmp")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/fig.rb
ADDED
File without changes
|
data/spec/fig_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fig
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Foemmel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-29 00:00:00 -06:00
|
13
|
+
default_executable: fig
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: polyglot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.2.9
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: treetop
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.4.2
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.2.9
|
44
|
+
version:
|
45
|
+
description: |-
|
46
|
+
Fig is a utility for dynamically assembling an
|
47
|
+
environment from a set of packages. Shell commands can then be
|
48
|
+
executed in that environment, after which the environment
|
49
|
+
is thrown away. The caller's environment is never affected.
|
50
|
+
email: git@foemmel.com
|
51
|
+
executables:
|
52
|
+
- fig
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files:
|
56
|
+
- LICENSE
|
57
|
+
- README.rdoc
|
58
|
+
files:
|
59
|
+
- bin/fig
|
60
|
+
- lib/fig.rb
|
61
|
+
- lib/fig/environment.rb
|
62
|
+
- lib/fig/grammar.treetop
|
63
|
+
- lib/fig/options.rb
|
64
|
+
- lib/fig/os.rb
|
65
|
+
- lib/fig/package.rb
|
66
|
+
- lib/fig/parser.rb
|
67
|
+
- lib/fig/repository.rb
|
68
|
+
- LICENSE
|
69
|
+
- README.rdoc
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/mfoemmel/fig
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options:
|
76
|
+
- --charset=UTF-8
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: "0"
|
84
|
+
version:
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
version:
|
91
|
+
requirements: []
|
92
|
+
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.3.5
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Fig is a utility for dynamically assembling an environment from a set of packages.
|
98
|
+
test_files:
|
99
|
+
- spec/fig_spec.rb
|
100
|
+
- spec/spec_helper.rb
|