fig 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.
- 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
|