fig 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Fig" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'fig'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
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