chrysalis 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,19 @@
1
+ Copyright (c) 2007 Jared Hanson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,5 @@
1
+ = Chrysalis
2
+
3
+ Chrysalis is a simple dependency manager built on Ruby and Rake. Chrysalis can
4
+ simplify your development environment by making it possible to build entire
5
+ applications, including dependencies, in a single command.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/testtask'
7
+
8
+ PKG_NAME = 'chrysalis'
9
+ PKG_VERSION = '0.1.0'
10
+
11
+ PKG_FILES = FileList[
12
+ '[A-Z]*',
13
+ 'lib/**/*'
14
+ ]
15
+
16
+
17
+ spec = Gem::Specification.new do |s|
18
+ s.name = PKG_NAME
19
+ s.version = PKG_VERSION
20
+ s.files = PKG_FILES
21
+ s.add_dependency('rake', '>= 0.7.3')
22
+ s.add_dependency('gratr', '>= 0.4.3')
23
+
24
+ s.author = "Jared Hanson"
25
+ s.email = "jaredhanson@gmail.com"
26
+ s.homepage = "http://chrysalis.rubyforge.org/"
27
+ s.rubyforge_project = "chrysalis"
28
+
29
+ s.summary = "Simple dependency management with Ruby and Rake."
30
+ end
31
+
32
+ Rake::GemPackageTask.new(spec) do |pkg|
33
+ pkg.gem_spec = spec
34
+ end
35
+
36
+ Rake::RDocTask.new do |rdoc|
37
+ rdoc.rdoc_dir = 'doc'
38
+ rdoc.rdoc_files.include('README')
39
+ rdoc.rdoc_files.include('lib/chrysalis.rb')
40
+ rdoc.rdoc_files.include('lib/chrysalis/**/*.rb')
41
+ end
42
+
43
+ desc "Preview the rdoc HTML files in web browser"
44
+ task :preview_rdoc do
45
+ sh "heel -r doc" + (RUBY_PLATFORM.match(/mswin/) ? " 2>&1" : "")
46
+ end
47
+ task :preview_rdoc => :rdoc
48
+
49
+ desc "Publish the rdoc HTML files to RubyForge"
50
+ task :publish_rdoc do
51
+ sh "scp -r doc/* jaredhanson@rubyforge.org:/var/www/gforge-projects/chrysalis/api"
52
+ end
53
+ task :publish_rdoc => :rdoc
54
+
55
+ Rake::TestTask.new do |test|
56
+ test.libs << "test"
57
+ test.test_files = FileList['test/test_suite.rb']
58
+ end
@@ -0,0 +1,56 @@
1
+ require 'chrysalis/archive'
2
+ require 'chrysalis/core_ext/string'
3
+
4
+
5
+ module Chrysalis
6
+ module Ar
7
+
8
+ # Implements support for extracting tar archives.
9
+ #
10
+ # Archives of this type are identified by the file extension <em>tar</em>.
11
+ class TarArchive < Archive
12
+
13
+ EXTENSION = ".tar".freeze
14
+
15
+ def self.extracts?(path)
16
+ path.end? EXTENSION
17
+ end
18
+
19
+ def initialize(path, params = {})
20
+ super
21
+ @extract_to = params[:extract_to]
22
+ @extracts_into = params[:extracts_into]
23
+ @noop = params[:noop]
24
+ end
25
+
26
+ def extract(to = '.')
27
+ dir = Pathname.new(to)
28
+ dir = dir + @extract_to if @extract_to
29
+
30
+ unless @noop
31
+ FileUtils.mkpath dir
32
+
33
+ bin = (RUBY_PLATFORM.match(/mswin/) ? "bsdtar" : "tar")
34
+
35
+ puts "Extracting #{@path} ..."
36
+ sh "#{bin} #{self.flags} #{@path} -C #{dir.cleanpath}"
37
+ end
38
+
39
+ ex_path = Pathname.new(dir).join(self.extracts_into)
40
+ ex_path.cleanpath.to_s
41
+ end
42
+
43
+ protected
44
+ def flags
45
+ "xvf"
46
+ end
47
+
48
+ def extracts_into
49
+ return @extracts_into if @extracts_into
50
+ Pathname.new(@path).basename(EXTENSION)
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ require 'chrysalis/ar/tar'
2
+
3
+
4
+ module Chrysalis
5
+ module Ar
6
+
7
+ # Implements support for extracting tar archives compressed with bzip2
8
+ # compression.
9
+ #
10
+ # Archives of this type are identified by the file extension <em>.tar.bz2</em>.
11
+ class TarBz2Archive < TarArchive
12
+
13
+ EXTENSION = ".tar.bz2".freeze
14
+
15
+ def self.extracts?(path)
16
+ path.end? EXTENSION
17
+ end
18
+
19
+ protected
20
+ def flags
21
+ "xjvf"
22
+ end
23
+
24
+ def extracts_into
25
+ return @extracts_into if @extracts_into
26
+ Pathname.new(@path).basename(EXTENSION)
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ require 'chrysalis/ar/tar'
2
+
3
+
4
+ module Chrysalis
5
+ module Ar
6
+
7
+ # Implements support for extracting tar archives compressed with gzip
8
+ # compression.
9
+ #
10
+ # Archives of this type are identified by the file extension <em>.tar.gz</em>.
11
+ class TarGzArchive < TarArchive
12
+
13
+ EXTENSION = ".tar.gz".freeze
14
+
15
+ def self.extracts?(path)
16
+ path.end? EXTENSION
17
+ end
18
+
19
+ protected
20
+ def flags
21
+ "xzvf"
22
+ end
23
+
24
+ def extracts_into
25
+ return @extracts_into if @extracts_into
26
+ Pathname.new(@path).basename(EXTENSION)
27
+ end
28
+
29
+ end
30
+
31
+
32
+ # Implements support for extracting tar archives compressed with gzip
33
+ # compression.
34
+ #
35
+ # Archives of this type are identified by the file extension <em>.tgz</em>.
36
+ class TgzArchive < TarGzArchive
37
+ EXTENSION = ".tgz".freeze
38
+
39
+ def self.extracts?(path)
40
+ path.end? EXTENSION
41
+ end
42
+
43
+ protected
44
+ def extracts_into
45
+ return @extracts_into if @extracts_into
46
+ Pathname.new(@path).basename(EXTENSION)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ require 'chrysalis/archive'
2
+ require 'chrysalis/core_ext/string'
3
+
4
+
5
+ module Chrysalis
6
+ module Ar
7
+
8
+ # Implements support for extracting ZIP archives.
9
+ #
10
+ # Archives of this type are identified by the file extension <em>.zip</em>.
11
+ class ZipArchive < Archive
12
+
13
+ EXTENSION = ".zip".freeze
14
+
15
+ def self.extracts?(path)
16
+ path.end? EXTENSION
17
+ end
18
+
19
+ def initialize(path, params = {})
20
+ super
21
+ @extract_to = params[:extract_to]
22
+ @extracts_into = params[:extracts_into]
23
+ @noop = params[:noop]
24
+ end
25
+
26
+ def extract(to = '.')
27
+ dir = Pathname.new(to)
28
+ dir = dir + @extract_to if @extract_to
29
+
30
+ unless @noop
31
+ puts "Extracting #{@path} ..."
32
+ sh "unzip #{@path} -d #{dir.cleanpath}"
33
+ end
34
+
35
+ ex_path = Pathname.new(dir).join(extracts_into)
36
+ ex_path.cleanpath.to_s
37
+ end
38
+
39
+ private
40
+ def extracts_into
41
+ return @extracts_into if @extracts_into
42
+ Pathname.new(@path).basename(EXTENSION)
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,4 @@
1
+ require 'chrysalis/ar/tar'
2
+ require 'chrysalis/ar/tar_gz'
3
+ require 'chrysalis/ar/tar_bz2'
4
+ require 'chrysalis/ar/zip'
@@ -0,0 +1,55 @@
1
+ require 'pathname'
2
+ require 'chrysalis/errors'
3
+
4
+
5
+ module Chrysalis
6
+
7
+ class Archive
8
+ @@archives = []
9
+
10
+ def self.inherited(archive)
11
+ @@archives << archive
12
+ end
13
+
14
+ # Extracts the archive, located at +path+, to the directory +to+ (which
15
+ # defaults to the current directory). An optional set of parameters is
16
+ # accepted as +params+.
17
+ #
18
+ # Returns the directory the archive was extracted into.
19
+ #
20
+ # The return value assumes standard conventions are followed by the archive,
21
+ # namely that the directory is the name of the archive file, without the
22
+ # extension.
23
+ #
24
+ # For example, given an archive file libfoo-1.4.2.tar.gz, the directory
25
+ # returned would be libfoo-1.4.2.
26
+ #
27
+ # For archives that don't follow this practice, parameters are available
28
+ # to override default behavior.
29
+ def self.extract(path, to = '.', params = {})
30
+ pn = Pathname.new(path)
31
+ return pn.cleanpath.to_s if pn.directory?
32
+ return pn.cleanpath.to_s if params[:noop]
33
+
34
+ @@archives.reverse.each { |archive|
35
+ return archive.new(path, params).extract(to) if archive.extracts?(path)
36
+ }
37
+ raise ArchiveError, "Unknown archive format. (ext: #{pn.extname})"
38
+ end
39
+
40
+
41
+ def self.extracts?(path)
42
+ false
43
+ end
44
+
45
+ def initialize(path, params = {})
46
+ @path = path
47
+ end
48
+
49
+ def extract(to = '.')
50
+ raise UnimplementedError, "Archive#extract not implemented"
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,19 @@
1
+ class String
2
+
3
+ def begin?(other_str)
4
+ self.index(other_str) == 0
5
+ end
6
+
7
+ def end?(other_str)
8
+ i = self.rindex(other_str)
9
+ return false if i.nil?
10
+ return (self.length == i + other_str.length)
11
+ end
12
+
13
+ def concat_sep(other_str, sep)
14
+ return if ! other_str
15
+ self.concat(sep) if ! empty?
16
+ self.concat(other_str)
17
+ end
18
+
19
+ end
@@ -0,0 +1,18 @@
1
+ module Chrysalis
2
+
3
+ class ManifestError < RuntimeError
4
+ end
5
+
6
+ class RepositoryError < RuntimeError
7
+ end
8
+
9
+ class ArchiveError < RuntimeError
10
+ end
11
+
12
+ class ParameterError < RuntimeError
13
+ end
14
+
15
+ class UnimplementedError < NameError
16
+ end
17
+
18
+ end
@@ -0,0 +1,151 @@
1
+ module Chrysalis
2
+
3
+ # Loader is responsible for loading rakefiles.
4
+ #
5
+ # The loader loads the rakefiles defined by dependencies. The tasks defined
6
+ # within a dependency rakefile are intercepted and re-synthesized as
7
+ # <em>all:*</em> tasks.
8
+ #
9
+ # Additionally, a hook is declared in order to receive notification that the
10
+ # main rakefile has been loaded. The tasks in the main rakefile serve to seed
11
+ # the <em>all:</em> namespace with tasks, prior to retrieving any
12
+ # dependencies.
13
+
14
+ class Loader
15
+ include Singleton
16
+
17
+ def initialize
18
+ @loading = :main
19
+ @icept_scope = []
20
+ @icept_tasks = []
21
+ @last_comment = nil
22
+
23
+ Chrysalis.hook :rakefile_loaded, self.method(:on_rakefile_loaded).to_proc
24
+ end
25
+
26
+ # Returns the URL of the project currently being loaded.
27
+ def loading
28
+ @loading
29
+ end
30
+
31
+ # Loads a dependency's rakefile.
32
+ #
33
+ # The +url+ is the location of the repository from which the dependency was
34
+ # retrieved. The +working_copy+ is the WorkingCopy retrieved.
35
+ #
36
+ # This function will search for and load the rakefile found in the working
37
+ # copy's path, if one exists.
38
+ def load(url, working_copy)
39
+ @loading = url
40
+
41
+ Rake::Application::DEFAULT_RAKEFILES.each do |file|
42
+ path = Pathname.new(working_copy.path).join(file)
43
+
44
+ if File.exist?(path)
45
+ Kernel.load(path, true)
46
+ return
47
+ end
48
+ end
49
+
50
+ ensure
51
+ # If the manifest does not contain an entry for the URL being loaded, one
52
+ # of two things occured:
53
+ # 1. a project was not instantiated in the rakefile -- or --
54
+ # 2. no rakefile existed in the working copy's path
55
+ #
56
+ # In either case, an empty project will be created as a placeholder.
57
+
58
+ if Chrysalis::Manifest.instance[@loading].nil?
59
+ proj = Project.new
60
+ end
61
+
62
+ Chrysalis::Manifest.instance[@loading].working_copy = working_copy
63
+ Chrysalis.post :rakefile_loaded
64
+ end
65
+
66
+ def on_rakefile_loaded # :nodoc:
67
+ return if Chrysalis::Manifest.instance[@loading].nil?
68
+
69
+ @icept_tasks.each do |task|
70
+ Chrysalis::Manifest.instance[@loading].task(task[0], task[1])
71
+ Chrysalis::TaskManager.synthesize_task(task[0], task[1])
72
+ end
73
+
74
+ ensure
75
+ @loading = :main
76
+ @icept_tasks = []
77
+ end
78
+
79
+ def task_intercept(name)
80
+ scoped_name = (@icept_scope + [name]).join(':')
81
+ @icept_tasks << [scoped_name, @last_comment]
82
+ @last_comment = nil
83
+ yield if @loading == :main
84
+ end
85
+
86
+ def file_task_intercept(name)
87
+ @icept_tasks << [name, @last_comment]
88
+ @last_comment = nil
89
+ yield if @loading == :main
90
+ end
91
+
92
+ def desc_intercept(comment)
93
+ @last_comment = comment
94
+ yield if @loading == :main
95
+ end
96
+
97
+ #--
98
+ # TODO: If name is nil, Rake generates an anonymous namespace. Chrysalis
99
+ # currently does not support this feature. In practice, anonymous
100
+ # namespaces are rarely used. However, support should be implemented
101
+ # for maximum compatibility.
102
+ def namespace_intercept(name, orig_block)
103
+ @icept_scope.push(name)
104
+
105
+ # Call the original block given to the namespace. This allows is to
106
+ # progress deeper, intercepting further tasks within the namespace.
107
+ #
108
+ # TODO: Rake instantiates a NameSpace, which is yeiled as a parameter.
109
+ # In practice, most code doesn't expect this parameter. However,
110
+ # support for it is currently unimplemented, but should be
111
+ # implemented for maximum compatibility.
112
+ #
113
+ # Example:
114
+ # orig_block.call(a_namespace)
115
+ if @loading != :main
116
+ orig_block.call
117
+ else
118
+ yield
119
+ end
120
+
121
+ ensure
122
+ @icept_scope.pop
123
+ end
124
+
125
+ def rule_intercept
126
+ yield if @loading == :main
127
+ end
128
+
129
+ end
130
+
131
+
132
+ module Hookable
133
+ @@handlers = []
134
+
135
+ def hook(evt, proc)
136
+ @@handlers << [evt, proc]
137
+ end
138
+
139
+ def post(evt, *args)
140
+ callbacks = @@handlers.select { |e, ignore| evt == e }
141
+ callbacks.each do |ignore, proc|
142
+ proc.call(*args)
143
+ end
144
+ end
145
+ end
146
+
147
+ class << self
148
+ include Hookable
149
+ end
150
+
151
+ end