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