stickler 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/COPYING +339 -0
- data/HISTORY +4 -0
- data/LICENSE +54 -0
- data/README +86 -0
- data/bin/stickler +58 -0
- data/data/stickler.yml +14 -0
- data/gemspec.rb +62 -0
- data/lib/stickler.rb +19 -0
- data/lib/stickler/cli.rb +302 -0
- data/lib/stickler/configuration.rb +74 -0
- data/lib/stickler/console.rb +72 -0
- data/lib/stickler/paths.rb +62 -0
- data/lib/stickler/repository.rb +502 -0
- data/lib/stickler/source.rb +76 -0
- data/lib/stickler/source_group.rb +365 -0
- data/lib/stickler/spec_lite.rb +48 -0
- data/lib/stickler/version.rb +36 -0
- data/spec/configuration_spec.rb +68 -0
- data/spec/paths_spec.rb +25 -0
- data/spec/repository_spec.rb +55 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/version_spec.rb +17 -0
- data/tasks/announce.rake +39 -0
- data/tasks/config.rb +107 -0
- data/tasks/distribution.rake +38 -0
- data/tasks/documentation.rake +31 -0
- data/tasks/rspec.rake +29 -0
- data/tasks/rubyforge.rake +51 -0
- data/tasks/utils.rb +80 -0
- metadata +156 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Stickler
|
2
|
+
#
|
3
|
+
#
|
4
|
+
#
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
attr_reader :config_file_name
|
8
|
+
|
9
|
+
def initialize( config_file_name )
|
10
|
+
@config_file_name = config_file_name
|
11
|
+
@hash = YAML.load_file( config_file_name )
|
12
|
+
@hash['sources'] = @hash['sources'].collect do |uri|
|
13
|
+
p = uri.split("/")
|
14
|
+
p.join("/") + "/" # ensure a trailing slash
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# the array of sources in this configuration
|
19
|
+
def sources
|
20
|
+
hash['sources']
|
21
|
+
end
|
22
|
+
|
23
|
+
# the downstream source this repository represents
|
24
|
+
def downstream_source
|
25
|
+
hash['downstream_source']
|
26
|
+
end
|
27
|
+
|
28
|
+
# The gem and version requirements for this repository
|
29
|
+
def gem_dependencies
|
30
|
+
unless @gem_dependencies
|
31
|
+
@gem_dependencies = []
|
32
|
+
if hash['gems'] then
|
33
|
+
hash['gems'].each do |name, reqs|
|
34
|
+
[ reqs ].flatten.each do |req|
|
35
|
+
@gem_dependencies << ::Gem::Dependency.new( name, req )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
return @gem_dependencies
|
41
|
+
end
|
42
|
+
|
43
|
+
def write
|
44
|
+
File.open( config_file_name, 'w' ) do |f|
|
45
|
+
g = Hash.new { |h,k| h[k] = [] }
|
46
|
+
gem_dependencies.each do |d|
|
47
|
+
g[d.name] << d.requirement_list
|
48
|
+
g[d.name].flatten!
|
49
|
+
end
|
50
|
+
hash['gems'] = g
|
51
|
+
f.write hash.to_yaml
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def keys
|
56
|
+
@hash.keys
|
57
|
+
end
|
58
|
+
|
59
|
+
def []( key )
|
60
|
+
@hash[ key.to_s ]
|
61
|
+
end
|
62
|
+
|
63
|
+
def []=( key, value )
|
64
|
+
@hash[ key.to_s ] = value
|
65
|
+
end
|
66
|
+
|
67
|
+
def ==( other )
|
68
|
+
self.class === other and hash == other.hash
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
attr_reader :hash
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
module Stickler
|
3
|
+
#
|
4
|
+
# containment for the output that a user should see. This uses a Logger so
|
5
|
+
# that it can be throttled based on level at some point
|
6
|
+
#
|
7
|
+
class Console
|
8
|
+
class << self
|
9
|
+
#
|
10
|
+
# Items that get logged to stderr for the user to see should use this logger
|
11
|
+
#
|
12
|
+
def logger
|
13
|
+
unless @logger
|
14
|
+
@logger = ::Logging::Logger['User']
|
15
|
+
@logger.level = :info
|
16
|
+
@logger.add_appenders(::Logging::Appender.stderr)
|
17
|
+
::Logging::Appender.stderr.layout = Logging::Layouts::Pattern.new( :pattern => "%m\n" )
|
18
|
+
::Logging::Appender.stderr.level = :info
|
19
|
+
end
|
20
|
+
return @logger
|
21
|
+
end
|
22
|
+
|
23
|
+
# force initialization
|
24
|
+
Console.logger
|
25
|
+
|
26
|
+
#
|
27
|
+
# default logging leve
|
28
|
+
#
|
29
|
+
def default_level
|
30
|
+
:info
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Quick wrappers around the log levels
|
35
|
+
#
|
36
|
+
::Logging::LEVELS.keys.each do |l|
|
37
|
+
module_eval <<-code
|
38
|
+
def #{ l }( msg )
|
39
|
+
@logger.#{l} msg
|
40
|
+
end
|
41
|
+
code
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Turn off the logging
|
46
|
+
#
|
47
|
+
def silent!
|
48
|
+
logger.level = :off
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Resume logging
|
53
|
+
#
|
54
|
+
def resume!
|
55
|
+
logger.level = self.default_level
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
#
|
60
|
+
# Turn off logging for the execution of a block
|
61
|
+
#
|
62
|
+
def silent( &block )
|
63
|
+
begin
|
64
|
+
silent!
|
65
|
+
block.call
|
66
|
+
ensure
|
67
|
+
resume!
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. Licensed under the same terms as Ruby. No warranty is
|
4
|
+
# provided. See LICENSE and COPYING for details.
|
5
|
+
#++
|
6
|
+
|
7
|
+
module Stickler
|
8
|
+
|
9
|
+
# Paths module used by all the other modules and classes for
|
10
|
+
# determining paths and default values.
|
11
|
+
#
|
12
|
+
module Paths
|
13
|
+
|
14
|
+
#
|
15
|
+
# The root directory of the project is considered to be the parent directory
|
16
|
+
# of the 'lib' directory. returns the full expanded path of the parent
|
17
|
+
# directory of 'lib' going up the path from the current file. A trailing
|
18
|
+
# File::SEPARATOR is guaranteed.
|
19
|
+
#
|
20
|
+
def self.root_dir
|
21
|
+
unless @root_dir
|
22
|
+
path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
|
23
|
+
lib_index = path_parts.rindex("lib")
|
24
|
+
@root_dir = path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
|
25
|
+
end
|
26
|
+
return @root_dir
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# return the full expanded path of the +config+ directory below +root_dir+.
|
31
|
+
# All parameters passed in are joined on to the result. a Trailing
|
32
|
+
# File::SEPARATOR is guaranteed if _args_ are *not* present
|
33
|
+
#
|
34
|
+
def self.config_path(*args)
|
35
|
+
self.sub_path("config", *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# return the full expanded path of the +data+ directory below +root_dir+.
|
40
|
+
# All parameters passed in are joined on to the result. a Trailing
|
41
|
+
# File::SEPARATOR is guaranteed if _args_ are *not* present
|
42
|
+
#
|
43
|
+
def self.data_path(*args)
|
44
|
+
self.sub_path("data", *args)
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# return the full expanded path of the +lib+ directory below +root_dir+.
|
49
|
+
# All parameters passed in are joined on to the result. a Trailing
|
50
|
+
# File::SEPARATOR is guaranteed if _args_ are *not* present
|
51
|
+
#
|
52
|
+
def self.lib_path(*args)
|
53
|
+
self.sub_path("lib", *args)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def self.sub_path(sub,*args)
|
58
|
+
sp = ::File.join(root_dir, sub) + File::SEPARATOR
|
59
|
+
sp = ::File.join(sp, *args) if args
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,502 @@
|
|
1
|
+
|
2
|
+
# Copyright (c) 2008 Jeremy Hinegardner
|
3
|
+
# All rights reserved. Licensed under the same terms as Ruby. No warranty is
|
4
|
+
# provided. See LICENSE and COPYING for details.
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'stickler'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'ostruct'
|
10
|
+
require 'highline'
|
11
|
+
require 'stickler/configuration'
|
12
|
+
require 'stickler/source_group'
|
13
|
+
|
14
|
+
module Stickler
|
15
|
+
#
|
16
|
+
# A Repository a directory with a particular layout used by stickler. It is
|
17
|
+
# is very similar to a GEM_HOME directory. The layout is as follows, this is
|
18
|
+
# all assumed to be under STICKLER_HOME.
|
19
|
+
#
|
20
|
+
# stickler.yml - the Stickler configuration file for this repository.
|
21
|
+
# The existence of this file indicates this is the root
|
22
|
+
# of a stickler repository
|
23
|
+
# gems/ - storage of the actual gem files
|
24
|
+
# cache/ - cache of gem files downloaded from upstream sources
|
25
|
+
# managed by the rubygems fetcher classes
|
26
|
+
# specifications/ - ruby gemspec files
|
27
|
+
# log/ - directory holding rotating logs files for stickler
|
28
|
+
# dist/ - directory holding the distributable gem index location,
|
29
|
+
# rsync this to your webserver, or serve from it directly.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
class Repository
|
33
|
+
class Error < ::StandardError ; end
|
34
|
+
|
35
|
+
# The repository directory. the directory containing the stickler.yml file
|
36
|
+
attr_reader :directory
|
37
|
+
|
38
|
+
# The configuration
|
39
|
+
attr_reader :configuration
|
40
|
+
|
41
|
+
# Are requrements satisfied in a minimal or maximal approach
|
42
|
+
attr_reader :requirement_satisfaction_behavior
|
43
|
+
|
44
|
+
class << self
|
45
|
+
def other_dir_names
|
46
|
+
%w[ gems_dir log_dir specification_dir dist_dir cache_dir ]
|
47
|
+
end
|
48
|
+
|
49
|
+
def config_file_basename
|
50
|
+
"stickler.yml"
|
51
|
+
end
|
52
|
+
|
53
|
+
def basedir
|
54
|
+
"stickler"
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# What should the default stickler directory be, this is one of two
|
59
|
+
# things. If there is a stickler.yml file in the current directory, then
|
60
|
+
# the default directory is the current directory, otherwise, it is the
|
61
|
+
# _stickler_ directory below the current_directory
|
62
|
+
#
|
63
|
+
def default_directory
|
64
|
+
defaults = [ File.join( Dir.pwd, config_file_basename ), File.join( Dir.pwd, basedir ) ]
|
65
|
+
defaults.each do |def_dir|
|
66
|
+
if File.exist?( def_dir ) then
|
67
|
+
return File.dirname( def_dir ) if File.file?( def_dir )
|
68
|
+
return def_dir
|
69
|
+
end
|
70
|
+
end
|
71
|
+
return defaults.last
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# gem requirement information
|
76
|
+
#
|
77
|
+
def requirement_meta
|
78
|
+
[ [ "=" , "Equals version" ],
|
79
|
+
[ "!=" , "Not equal to version" ],
|
80
|
+
[ ">" , "Greater than version" ],
|
81
|
+
[ "<" , "Less than version" ],
|
82
|
+
[ ">=" , "Greater than or equal to" ],
|
83
|
+
[ "<=" , "Less than or equal to" ],
|
84
|
+
[ "~>" , "Approximately greater than" ]]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Initialize a stickler repository
|
90
|
+
#
|
91
|
+
def initialize( opts )
|
92
|
+
|
93
|
+
@directory = File.expand_path( opts['directory'] )
|
94
|
+
@requirement_satisfaction_behavior = ( opts['requirements'] || "maximum" ).to_sym
|
95
|
+
enhance_logging( opts ) if File.directory?( log_dir )
|
96
|
+
@overwrite = opts['force']
|
97
|
+
|
98
|
+
@configuration = nil
|
99
|
+
load_configuration if File.exist?( config_file )
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
def configuration_loaded?
|
104
|
+
@configuration.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# should existing items be overwritten
|
109
|
+
#
|
110
|
+
def overwrite?
|
111
|
+
@overwrite
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# update logging by turning on a log file in the repository directory, and
|
116
|
+
# possibly turning off the stdout logger that is the default.
|
117
|
+
#
|
118
|
+
def enhance_logging( opts )
|
119
|
+
|
120
|
+
layout = ::Logging::Layouts::Pattern.new(
|
121
|
+
:pattern => "[%d] %c %6p %5l : %m\n",
|
122
|
+
:date_pattern => "%Y-%m-%d %H:%M:%S"
|
123
|
+
)
|
124
|
+
Logging::Logger.root.add_appenders ::Logging::Appenders::RollingFile.new( 'stickler_rolling_logfile',
|
125
|
+
{ :filename => log_file,
|
126
|
+
:layout => layout,
|
127
|
+
# at 5MB roll the log
|
128
|
+
:size => 5 * (1024**2),
|
129
|
+
:keep => 5,
|
130
|
+
:safe => true,
|
131
|
+
:level => :debug
|
132
|
+
})
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# return a handle to the repository configuration found in stickler.yml.
|
137
|
+
#
|
138
|
+
def load_configuration
|
139
|
+
begin
|
140
|
+
@configuration = Configuration.new( config_file )
|
141
|
+
source_group # force load
|
142
|
+
rescue => e
|
143
|
+
logger.error "Failure to load configuration #{e}"
|
144
|
+
exit 1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# The configuration file for the repository
|
150
|
+
#
|
151
|
+
def config_file
|
152
|
+
@config_file ||= File.join( directory, Repository.config_file_basename )
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# The log directory
|
157
|
+
#
|
158
|
+
def log_dir
|
159
|
+
@log_dir ||= File.join( directory, 'log' )
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# The log file
|
164
|
+
#
|
165
|
+
def log_file
|
166
|
+
@log_file ||= File.join( log_dir, 'stickler.log' )
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# The gem storage directory.
|
171
|
+
#
|
172
|
+
# This holds the raw gem files downloaded from the sources. This is
|
173
|
+
# equivalent to a gem installations 'gems' directory.
|
174
|
+
#
|
175
|
+
def gems_dir
|
176
|
+
@gems_dir ||= File.join( directory, 'gems' )
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# The Gem specification directory
|
181
|
+
#
|
182
|
+
def specification_dir
|
183
|
+
@specification_dir ||= File.join( directory, 'specifications' )
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# The cache dir for the downloads
|
188
|
+
#
|
189
|
+
def cache_dir
|
190
|
+
@cache_dir ||= File.join( directory, 'cache' )
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# The Distribution directory
|
195
|
+
#
|
196
|
+
# this is the document root for the webserver that will serve your rubygems.
|
197
|
+
# Or they can be served directly from this location
|
198
|
+
#
|
199
|
+
def dist_dir
|
200
|
+
@dist_dir ||= File.join( directory, 'dist' )
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# logging handler
|
205
|
+
#
|
206
|
+
def logger
|
207
|
+
@logger ||= ::Logging::Logger[self]
|
208
|
+
end
|
209
|
+
|
210
|
+
#
|
211
|
+
# The SourceGroup containing all of the sources for this repository
|
212
|
+
#
|
213
|
+
def source_group
|
214
|
+
unless @source_group
|
215
|
+
sg = SourceGroup.new( self )
|
216
|
+
Console.info "Setting up sources"
|
217
|
+
configuration.sources.each do |source_uri|
|
218
|
+
sg.add_source( source_uri )
|
219
|
+
end
|
220
|
+
@source_group = sg
|
221
|
+
end
|
222
|
+
return @source_group
|
223
|
+
end
|
224
|
+
|
225
|
+
#
|
226
|
+
# Is the repository valid?
|
227
|
+
#
|
228
|
+
def valid?
|
229
|
+
if @valid.nil? then
|
230
|
+
begin
|
231
|
+
valid!
|
232
|
+
@valid = true
|
233
|
+
rescue StandardError => e
|
234
|
+
logger.error "Repository is not valid : #{e}"
|
235
|
+
@valid = false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
return @valid
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# raise an error if the repository is not valid
|
243
|
+
#
|
244
|
+
def valid!
|
245
|
+
raise Error, "#{directory} does not exist" unless File.exist?( directory )
|
246
|
+
raise Error, "#{directory} is not writable" unless File.writable?( directory )
|
247
|
+
|
248
|
+
raise Error, "#{config_file} does not exist" unless File.exist?( config_file )
|
249
|
+
raise Error, "#{config_file} is not loaded" unless configuration
|
250
|
+
|
251
|
+
Repository.other_dir_names.each do |method|
|
252
|
+
other_dir = self.send( method )
|
253
|
+
raise Error, "#{other_dir} does not exist" unless File.exist?( other_dir )
|
254
|
+
raise Error, "#{other_dir} is not writeable" unless File.writable?( other_dir )
|
255
|
+
end
|
256
|
+
|
257
|
+
if File.exist?( log_file ) then
|
258
|
+
raise Error, "#{log_file} is not writable" unless File.writable?( log_file )
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Setup the repository.
|
264
|
+
#
|
265
|
+
# This is executed with the 'setup' mode on the command line. Only those
|
266
|
+
# files and directories that do not already exist are created. Nothing is
|
267
|
+
# destroyed.
|
268
|
+
#
|
269
|
+
def setup
|
270
|
+
if File.exist?( directory ) then
|
271
|
+
Console.info "repository root already exists #{directory}"
|
272
|
+
else
|
273
|
+
FileUtils.mkdir_p( directory )
|
274
|
+
Console.info "created repository root #{directory}"
|
275
|
+
end
|
276
|
+
|
277
|
+
Repository.other_dir_names.each do |method|
|
278
|
+
d = self.send( method )
|
279
|
+
if File.exist?( d ) then
|
280
|
+
Console.info "directory #{d} already exists"
|
281
|
+
else
|
282
|
+
FileUtils.mkdir_p( d )
|
283
|
+
Console.info "created directory #{d}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
if overwrite? or not File.exist?( config_file ) then
|
288
|
+
FileUtils.cp Stickler::Paths.data_path( Repository.config_file_basename ), config_file
|
289
|
+
Console.info "copied in default configuration to #{config_file}"
|
290
|
+
else
|
291
|
+
Console.info "configuration file #{config_file} already exists"
|
292
|
+
end
|
293
|
+
|
294
|
+
# load the configuration for the repo
|
295
|
+
load_configuration
|
296
|
+
|
297
|
+
rescue => e
|
298
|
+
$stderr.puts "Unable to setup the respository"
|
299
|
+
$stderr.puts e
|
300
|
+
$stderr.puts e.backtrace.join("\n")
|
301
|
+
exit 1
|
302
|
+
end
|
303
|
+
|
304
|
+
#
|
305
|
+
# Report information about the repository. This is what is called from the
|
306
|
+
# 'info' mode on the commandline
|
307
|
+
#
|
308
|
+
def info
|
309
|
+
return unless valid?
|
310
|
+
|
311
|
+
Console.info ""
|
312
|
+
Console.info "Upstream Sources"
|
313
|
+
Console.info "----------------"
|
314
|
+
Console.info ""
|
315
|
+
|
316
|
+
max_width = configuration.sources.collect { |s| s.length }.max
|
317
|
+
source_group.sources.each do |source|
|
318
|
+
Console.info " #{source.uri.to_s.rjust( max_width )} : #{source.source_specs.size} gems available"
|
319
|
+
Console.info " #{" ".rjust( max_width )} : #{source_group.existing_specs_for_source_uri( source.uri ).size} gems existing"
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
Console.info ""
|
324
|
+
Console.info "Configured gems (in stickler.yml)"
|
325
|
+
Console.info "---------------------------------"
|
326
|
+
Console.info ""
|
327
|
+
configuration.gem_dependencies.sort.each do |dep|
|
328
|
+
Console.info "#{dep.name} : #{dep.version_requirements}"
|
329
|
+
end
|
330
|
+
|
331
|
+
Console.info ""
|
332
|
+
Console.info "Existing gems"
|
333
|
+
Console.info "-------------"
|
334
|
+
Console.info ""
|
335
|
+
|
336
|
+
source_group.gems.keys.sort.each do |g|
|
337
|
+
puts g
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
#
|
344
|
+
# Add a source to the repository
|
345
|
+
#
|
346
|
+
def add_source( source_uri )
|
347
|
+
load_configuration unless configuration_loaded?
|
348
|
+
if configuration.sources.include?( source_uri ) then
|
349
|
+
Console.info "#{source_uri} already in sources"
|
350
|
+
else
|
351
|
+
source_group.add_source( source_uri )
|
352
|
+
configuration.sources << source_uri
|
353
|
+
configuration.write
|
354
|
+
Console.info "#{source_uri} added to sources"
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
#
|
359
|
+
# Remove a source from the repository
|
360
|
+
#
|
361
|
+
def remove_source( source_uri )
|
362
|
+
load_configuration unless configuration_loaded?
|
363
|
+
uri = ::URI.parse source_uri
|
364
|
+
if configuration.sources.delete( source_uri ) then
|
365
|
+
source_group.remove_source( source_uri )
|
366
|
+
configuration.write
|
367
|
+
Console.info "#{source_uri} removed from sources"
|
368
|
+
else
|
369
|
+
Console.info "#{source_uri} is not one of your sources"
|
370
|
+
Console.info "Your sources are:"
|
371
|
+
configuration.sources.each do |src|
|
372
|
+
Console.info " #{src}"
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
#
|
378
|
+
# Add a gem to the repository
|
379
|
+
#
|
380
|
+
def add_gem( gem_name, version )
|
381
|
+
|
382
|
+
Console.info ""
|
383
|
+
|
384
|
+
::HighLine.track_eof = false
|
385
|
+
hl = ::HighLine.new( STDIN, STDOUT, :auto)
|
386
|
+
hl.say("You need to pick the #{gem_name} Requirement to configure Stickler.")
|
387
|
+
hl.say("This involves picking one of the following Requirement operators")
|
388
|
+
hl.say('See http://docs.rubygems.org/read/chapter/16#page74 for operator info.')
|
389
|
+
hl.say("\nYou need to (1) pick an operator and (2) pick a requirement.")
|
390
|
+
hl.say("The most common operators are >=, > and ~>")
|
391
|
+
|
392
|
+
op = hl.choose(*Repository.requirement_meta.collect { |k,v| "#{k.ljust(3)}#{v}" } ) do |m|
|
393
|
+
m.prompt = "(1) Pick an operator ? "
|
394
|
+
end
|
395
|
+
|
396
|
+
op = op.split.first # get only the operator, not the trailing text
|
397
|
+
|
398
|
+
version = ::Gem::Requirement.default if version == :latest
|
399
|
+
search_pattern = ::Gem::Dependency.new( gem_name, version )
|
400
|
+
choices = []
|
401
|
+
source_group.search( search_pattern ).each do |spec|
|
402
|
+
choices << "#{op} #{spec.version.to_s}"
|
403
|
+
end
|
404
|
+
choices = choices.sort.reverse
|
405
|
+
|
406
|
+
hl.say("\nNow to pick a requirement. Based upon your chosen operator '#{op}',")
|
407
|
+
hl.say("These are the available version of the #{gem_name} gem.")
|
408
|
+
requirement = hl.choose do |m|
|
409
|
+
m.flow = :columns_down
|
410
|
+
m.prompt = "(2) Pick a requirement ? "
|
411
|
+
m.choices( *choices )
|
412
|
+
end
|
413
|
+
|
414
|
+
Console.info ""
|
415
|
+
|
416
|
+
dep = ::Gem::Dependency.new( gem_name, requirement )
|
417
|
+
if configuration.gem_dependencies.include?( dep ) then
|
418
|
+
Console.info "#{dep} is already in your list of gems"
|
419
|
+
else
|
420
|
+
source_group.add_from_dependency( dep )
|
421
|
+
configuration.gem_dependencies << dep
|
422
|
+
configuration.write
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
#
|
427
|
+
# Remove a gem from the repository
|
428
|
+
#
|
429
|
+
def remove_gem( gem_name, version )
|
430
|
+
Console.info ""
|
431
|
+
version = ::Gem::Requirement.default if version == :all
|
432
|
+
search_pattern = ::Gem::Dependency.new( gem_name, version )
|
433
|
+
ulist = source_group.search_existing( search_pattern )
|
434
|
+
source_group.search_existing( search_pattern ).each do |spec|
|
435
|
+
source_group.remove( spec )
|
436
|
+
configuration.gem_dependencies.reject! { |d| d.name == spec.name }
|
437
|
+
end
|
438
|
+
configuration.write
|
439
|
+
end
|
440
|
+
|
441
|
+
#
|
442
|
+
# Sync the repository
|
443
|
+
#
|
444
|
+
def sync( rebuild = false )
|
445
|
+
Console.info ""
|
446
|
+
Console.info "Making sure that all gems listed in configuration are available"
|
447
|
+
Console.info ""
|
448
|
+
|
449
|
+
if rebuild then
|
450
|
+
Console.info "Removing existing gems and specifications ... "
|
451
|
+
Dir[ File.join( gems_dir, "*.gem" ) ].each { |g| FileUtils.rm_f g }
|
452
|
+
Dir[ File.join( specification_dir , "*.gemspec" ) ].each { |s| FileUtils.rm_f s }
|
453
|
+
end
|
454
|
+
configuration.gem_dependencies.each do |dep|
|
455
|
+
source_group.add_from_dependency( dep )
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
#
|
460
|
+
# generate the system configuration to be used by rubygem clients of the
|
461
|
+
# repository that stickler managers
|
462
|
+
#
|
463
|
+
def generate_sysconfig( to = $stdout )
|
464
|
+
Console.info "Generating configuration to stdout"
|
465
|
+
txt = <<-cfg
|
466
|
+
#
|
467
|
+
# This is the system wide configuration to be used by
|
468
|
+
# rubygem clients that install gems from the repository
|
469
|
+
# located at :
|
470
|
+
#
|
471
|
+
# #{configuration.downstream_source}
|
472
|
+
#
|
473
|
+
# On Unix like machines install in
|
474
|
+
#
|
475
|
+
# /etc/gemrc
|
476
|
+
#
|
477
|
+
# On Windows machines install in
|
478
|
+
#
|
479
|
+
# C:\\Documents and Settings\\All Users\\Application Data\\gemrc
|
480
|
+
#
|
481
|
+
---
|
482
|
+
:sources:
|
483
|
+
- #{configuration.downstream_source}
|
484
|
+
cfg
|
485
|
+
to.puts txt
|
486
|
+
end
|
487
|
+
|
488
|
+
#
|
489
|
+
# Generate the gem index that can be rsynced to another location
|
490
|
+
#
|
491
|
+
#
|
492
|
+
def generate_index
|
493
|
+
require 'rubygems/indexer'
|
494
|
+
Console.info "Generating rubygems index in #{dist_dir}"
|
495
|
+
FileUtils.rm_rf dist_dir
|
496
|
+
FileUtils.mkdir_p dist_dir
|
497
|
+
FileUtils.cp_r gems_dir, dist_dir
|
498
|
+
indexer = ::Gem::Indexer.new dist_dir
|
499
|
+
indexer.generate_index
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|