jeni 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/Bugs.rdoc +6 -0
  2. data/Gemfile +14 -0
  3. data/History.txt +9 -0
  4. data/Intro.txt +3 -0
  5. data/LICENCE.rdoc +159 -0
  6. data/README.md +188 -0
  7. data/lib/jeni.rb +374 -0
  8. data/lib/jeni/actions.rb +400 -0
  9. data/lib/jeni/errors.rb +22 -0
  10. data/lib/jeni/io.rb +71 -0
  11. data/lib/jeni/options.rb +68 -0
  12. data/lib/jeni/optparse.rb +84 -0
  13. data/lib/jeni/utils.rb +181 -0
  14. data/lib/jeni/version.rb +13 -0
  15. data/spec/jeni_spec.rb +85 -0
  16. data/spec/jeni_utils_spec.rb +297 -0
  17. data/spec/spec_helper.rb +26 -0
  18. data/test/examples/source/coati.haml.conf +37 -0
  19. data/test/examples/source/executable +3 -0
  20. data/test/examples/source/jenny-diff.rb +64 -0
  21. data/test/examples/source/jenny.rb +63 -0
  22. data/test/examples/source/shebang.rb +3 -0
  23. data/test/examples/source/subfiles/subfile_1.rb +63 -0
  24. data/test/examples/source/template.haml.rb +10 -0
  25. data/test/examples/target/archive/coati.haml.conf +37 -0
  26. data/test/examples/target/archive/executable +3 -0
  27. data/test/examples/target/archive/jenny-diff.rb +64 -0
  28. data/test/examples/target/archive/jenny.rb +63 -0
  29. data/test/examples/target/archive/shebang.rb +3 -0
  30. data/test/examples/target/archive/subfiles/subfile_1.rb +63 -0
  31. data/test/examples/target/archive/template.haml.rb +10 -0
  32. data/test/examples/target/jenny.rb +63 -0
  33. data/test/examples/target/jenny_link.rb +63 -0
  34. data/test/examples/target2/coati.conf +36 -0
  35. data/test/examples/target2/coati.haml.conf +37 -0
  36. data/test/examples/target2/executable +3 -0
  37. data/test/examples/target2/jenny-diff.rb +64 -0
  38. data/test/examples/target2/jenny.rb +63 -0
  39. data/test/examples/target2/jenny_link.rb +63 -0
  40. data/test/examples/target2/jenny_template.rb +10 -0
  41. data/test/examples/target2/jenny_test.rb +63 -0
  42. data/test/examples/target2/shebang.rb +3 -0
  43. data/test/examples/target2/std_template.rb +12 -0
  44. data/test/examples/target2/template.haml.rb +10 -0
  45. data/test/examples/test1.rb +30 -0
  46. data/test/examples/test2.rb +27 -0
  47. data/test/examples/test_args +24 -0
  48. data/test/examples/test_users +16 -0
  49. metadata +162 -0
@@ -0,0 +1,400 @@
1
+ #
2
+ #
3
+ # = Title
4
+ #
5
+ # == SubTitle
6
+ #
7
+ # Author:: Robert Sharp
8
+ # Copyright:: Copyright (c) 2012 Robert Sharp
9
+ # License:: Open Software Licence v3.0
10
+ #
11
+ # This software is licensed for use under the Open Software Licence v. 3.0
12
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
13
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
14
+ # must themselves be licensed under the Open Software Licence v. 3.0
15
+ #
16
+ #
17
+ #
18
+
19
+ module Jeni
20
+
21
+ # Implementation mixin containing methods for each action that Jeni carries out
22
+ module Actions
23
+
24
+ # Create a copy of the source file as the target file
25
+ #
26
+ # the checks are really redundant, but have been left in as belt and braces
27
+ #
28
+ def copy(source, target)
29
+ # check the source exists
30
+ puts "Copying #{source} to #{target}" if @verbose
31
+ unless FileTest.exists?(source)
32
+ say(:missing, source, :error)
33
+ return nil
34
+ end
35
+ #check the target directory is writable
36
+ target_dir = File.dirname(target)
37
+ unless FileTest.writable?(target_dir)
38
+ say(:unwritable, target_dir, :error)
39
+ return nil
40
+ end
41
+ # check if the target exists
42
+ if FileTest.exists?(target) then
43
+
44
+ unless FileUtils.compare_file(source, target) then
45
+ say(:exists, target, :warning)
46
+ loop do
47
+ case ask('Do you want to overwrite', :no)
48
+ when :yes
49
+ say(:overwrite, target, :ok)
50
+ break
51
+ when :no, :skip
52
+ say(:skipping, target, :warning)
53
+ return nil
54
+ when :diff
55
+ puts Diffy::Diff.new(target, source, :source=>'files').to_s(:color)
56
+ when :list
57
+ File.open(target) do |tfile|
58
+ tfile.readline do |line|
59
+ puts line
60
+ end
61
+ end
62
+ end # case
63
+ end # loop
64
+ else # unless
65
+ # files are the same
66
+ say(:identical, target, :no_change)
67
+ return target
68
+ end # unless
69
+ else # if
70
+ say(:create, target, :ok)
71
+ end
72
+
73
+ FileUtils.cp(source, target) unless @pretend
74
+ return target
75
+
76
+ end
77
+
78
+ # make the given directory, and any intermediates
79
+ def mkdir(target)
80
+ if FileTest.directory?(target)
81
+ say(:identical, target, :no_change)
82
+ elsif @pretend then
83
+ say(:mkdir, target, :ok)
84
+ else
85
+ FileUtils.mkdir_p(target)
86
+ if FileTest.directory?(target) then
87
+ say(:mkdir, target, :ok)
88
+ else
89
+ say(:mkdir, "Failed to make #{target}", :error)
90
+ continue?
91
+ end
92
+ end
93
+ end
94
+
95
+ # change the owner of a file
96
+ def chown(file, owner)
97
+ message = "#{file} to #{owner}"
98
+ if @pretend then
99
+ say(:chown, message, :ok)
100
+ return
101
+ end
102
+ if FileTest.exists?(file) then
103
+ FileUtils.chown(owner, nil, file)
104
+ say(:chown, message, :ok)
105
+ else
106
+ say(:missing, file, :error)
107
+ end
108
+ end
109
+
110
+ # change the group of a file
111
+ def chgrp(file, owner)
112
+ message = "#{file} to #{owner}"
113
+ if @pretend then
114
+ say(:chgrp, message, :ok)
115
+ return
116
+ end
117
+ if FileTest.exists?(file) then
118
+ FileUtils.chown(nil, owner, file)
119
+ say(:chgrp, message, :ok)
120
+ else
121
+ say(:missing, file, :error)
122
+ end
123
+ end
124
+
125
+ # make a file executable
126
+ def chmod(file, mode)
127
+ message = "#{file} to #{mode.to_s(8)}"
128
+ if @pretend then
129
+ say(:chmod, message, :ok)
130
+ elsif FileTest.exists?(file) then
131
+ FileUtils.chmod(mode,file)
132
+ say(:chmod, message, :ok)
133
+ else
134
+ say(:missing, file, :error)
135
+ end
136
+ end
137
+
138
+ # generate a file from a template, which is done even if the user
139
+ # request pretend. This ensures that a proper comparison is made
140
+ # with any existing file
141
+ def generate(template_file, target_file, locals={})
142
+ template = [":plain\n"]
143
+ # read in the file, prepending 2 spaces to each line
144
+ File.open(template_file) do |tfile|
145
+ tfile.each_line do |tline|
146
+ template << " " + tline
147
+ end
148
+ end
149
+ # now create the template as an engine and render it
150
+ engine = Haml::Engine.new(template.join)
151
+ target = engine.render(binding, locals)
152
+
153
+ puts target if @verbose
154
+
155
+ # and write it out to the target, but make it a temp
156
+ temp_file = target_file + "._temp"
157
+ File.open(temp_file, 'w') do |tfile|
158
+ target.each_line do |tline|
159
+ tfile.puts tline
160
+ end
161
+ end
162
+
163
+ # and now copy the temp file to the target
164
+ copy(temp_file, target_file)
165
+
166
+ # and finally clean up
167
+ FileUtils.rm_f(temp_file)
168
+ end
169
+
170
+ # return the path to the standard template, which can be found in
171
+ # the places search below.
172
+ def get_template(source)
173
+ gsource = File.expand_path(File.join('.jermine', 'templates', source), '~')
174
+ unless File.exists?(gsource)
175
+ gsource = File.join('usr', 'local', 'share', 'templates', source)
176
+ unless File.exists(gsource)
177
+ @errors[:missing] = 'Standard template file: #{source}'
178
+ return nil
179
+ end
180
+ end
181
+ return gsource
182
+ end
183
+
184
+ # create a wrapper for a file, which is specific to a gem
185
+ def wrap(source, target, fullsource)
186
+ #create the wrapper somewhere
187
+ basename = File.basename(source)
188
+ tempname = File.join('/tmp', File.basename(source) + ".wrapper")
189
+ File.open(tempname, 'w') {|f| f.write wrap_file(source, fullsource)}
190
+ copy(tempname, target)
191
+ end
192
+
193
+ # create a link to a file
194
+ def link_it(source, target)
195
+ if FileTest.exists?(source) then
196
+ if FileTest.symlink?(target) then
197
+ # already got a link, is it identical
198
+ existing_link = File.readlink(target)
199
+ if existing_link == source then
200
+ say(:identical, target, :no_change)
201
+ else
202
+ say(:exists, "#{target} -> #{existing_link}", :warning)
203
+ loop do
204
+ case ask('Do you want to overwrite', :no, 'ynsl')
205
+ when :yes
206
+ say(:overwrite, target, :ok)
207
+ break
208
+ when :no, :skip
209
+ say(:skipping, target, :warning)
210
+ return nil
211
+ else
212
+ puts "Link name: #{target}"
213
+ puts "New target: #{target}"
214
+ puts "Existing target: #{existing_link}"
215
+ end # case
216
+ end # loop
217
+ FileUtils.ln_sf(source, target) unless @pretend
218
+ say(:link, target, :ok)
219
+ end # if
220
+ elsif FileTest.exists?(target)
221
+ say(:exists, "as file: #{target}", :warning)
222
+ loop do
223
+ case ask('Do you want to replace it', :no, 'yns')
224
+ when :yes
225
+ say(:replace, target, :ok)
226
+ break
227
+ when :no, :skip
228
+ say(:skipping, target, :warning)
229
+ return nil
230
+ else
231
+ puts "Please make a valid selection"
232
+ end # case
233
+ end # loop
234
+ FileUtils.rm_f(target) unless @pretend
235
+ FileUtils.ln_s(source, target) unless @pretend
236
+ say(:link, target, :ok)
237
+ else
238
+ FileUtils.ln_s(source, target) unless @pretend
239
+ say(:link, target, :ok)
240
+ end
241
+ else
242
+ say(:missing, source, :error)
243
+ end
244
+ end
245
+
246
+
247
+ # create a wrapper script as text
248
+ def wrap_file(source, fullsource)
249
+
250
+ return <<-TEXT
251
+ #{shebang fullsource}
252
+ #
253
+ # This file was generated by RubyGems.
254
+ #
255
+ # The application '#{@app_name}' is installed as part of a gem, and
256
+ # this file is here to facilitate running it.
257
+ #
258
+ # Updated to wrap non-bin executables
259
+ #
260
+
261
+ require 'rubygems'
262
+
263
+ version = "#{Gem::Requirement.default}"
264
+
265
+ # check if there is a version spec here, e.g.: gem_name _0.1.2_ args
266
+ if ARGV.first
267
+ str = ARGV.first
268
+ str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
269
+ if str =~ /\\A_(.*)_\\z/
270
+ # there is, so get it
271
+ version = $1
272
+ ARGV.shift
273
+ end
274
+ end
275
+
276
+ gem_spec = Gem::Specification.find_by_name('#{@app_name}', version)
277
+ gem_path = File.join(gem_spec.gem_dir, '#{source}')
278
+
279
+ gem '#{@app_name}', version
280
+ load gem_path
281
+ TEXT
282
+
283
+ end
284
+
285
+ ##
286
+ # Generates a #! line for +bin_file_name+'s wrapper copying arguments if
287
+ # necessary.
288
+ #
289
+ # If the :custom_shebang config is set, then it is used as a template
290
+ # for how to create the shebang used for to run a gem's executables.
291
+ #
292
+ # The template supports 4 expansions:
293
+ #
294
+ # $env the path to the unix env utility
295
+ # $ruby the path to the currently running ruby interpreter
296
+ # $exec the path to the gem's executable
297
+ # $name the name of the gem the executable is for
298
+ #
299
+ def shebang(source)
300
+
301
+ env_paths = %w[/usr/bin/env /bin/env]
302
+
303
+ ruby_name = Gem::ConfigMap[:ruby_install_name] #if @env_shebang
304
+
305
+ first_line = File.open(source, "rb") {|file| file.gets}
306
+ env_path = nil
307
+
308
+ if /\A#!/ =~ first_line then
309
+ # Preserve extra words on shebang line, like "-w". Thanks RPA.
310
+ shebang = first_line.sub(/\A\#!.*?ruby\S*((\s+\S+)+)/, "#!#{Gem.ruby}")
311
+ opts = $1
312
+ shebang.strip! # Avoid nasty ^M issues.
313
+ end
314
+
315
+ if which = Gem.configuration[:custom_shebang]
316
+ which = which.gsub(/\$(\w+)/) do
317
+ case $1
318
+ when "env"
319
+ env_path ||= env_paths.find do |e_path|
320
+ File.executable? e_path
321
+ end
322
+ when "ruby"
323
+ "#{Gem.ruby}#{opts}"
324
+ when "exec"
325
+ bin_file_name
326
+ when "name"
327
+ spec.name
328
+ end
329
+ end
330
+
331
+ return "#!#{which}"
332
+ end
333
+
334
+ if not ruby_name then
335
+ "#!#{Gem.ruby}#{opts}"
336
+ elsif opts then
337
+ "#!/bin/sh\n'exec' #{ruby_name.dump} '-x' \"$0\" \"$@\"\n#{shebang}"
338
+ else
339
+ # Create a plain shebang line.
340
+ env_path ||= env_paths.find {|e_path| File.executable? e_path }
341
+ "#!#{env_path} #{ruby_name}"
342
+ end
343
+ end
344
+
345
+ # create a new user
346
+ def add_user(username, opts={})
347
+ if opts[:skip] then
348
+ say(:user, "#{username} already exists", :warning)
349
+ return false
350
+ end
351
+
352
+ home = opts[:home] || "/home/#{username}"
353
+ shell = opts[:shell] || "/bin/bash"
354
+ uid = opts[:uid]
355
+ gid = opts[:gid]
356
+ user_group = opts[:user_group]
357
+ cmd = "/usr/sbin/useradd -d #{home} -s #{shell}"
358
+
359
+ cmd << " -u #{uid}" if uid
360
+ cmd << " -g #{gid}" if gid
361
+ cmd << " -U" if user_group
362
+ cmd << " #{username}"
363
+
364
+ unless @pretend
365
+ system(cmd)
366
+ raise JeniError if $? != 0
367
+ end
368
+ say(:user, "Added user #{username}", :ok)
369
+
370
+ rescue JeniError
371
+ say(:user, "Failed to add user #{username} with error #{$?}", :error)
372
+
373
+ end
374
+
375
+ # create a new group
376
+ def add_group(group, opts={})
377
+ if opts[:skip] then
378
+ say(:group, "#{group} already exists", :warning)
379
+ return false
380
+ end
381
+
382
+ gid = opts[:gid]
383
+ cmd = "/usr/sbin/groupadd "
384
+
385
+ cmd << "-g #{gid}" if gid
386
+ cmd << " #{group}"
387
+
388
+ unless @pretend
389
+ system(cmd)
390
+ raise JeniError if $? != 0
391
+ end
392
+ say(:group, "Added group #{group}", :ok)
393
+
394
+ rescue JeniError
395
+ say(:group, "Failed to add user #{username} with error #{$?}", :error)
396
+
397
+ end
398
+
399
+ end
400
+ end
@@ -0,0 +1,22 @@
1
+ #
2
+ # Author:: R.J.Sharp
3
+ # Email:: robert(a)osburn-sharp.ath.cx
4
+ # Copyright:: Copyright (c) 2012
5
+ # License:: Open Software Licence v3.0
6
+ #
7
+ # This software is licensed for use under the Open Software Licence v. 3.0
8
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
9
+ # and in the file LICENCE. Under the terms of this licence, all derivative works
10
+ # must themselves be licensed under the Open Software Licence v. 3.0
11
+ #
12
+ #
13
+ # This file groups together all the errors for jeni.
14
+ # Preceed each class with a description of the error
15
+
16
+ module Jeni
17
+
18
+ # A general class for all errors created by this project. All specific exceptions
19
+ # should be children of this class
20
+ class JeniError < RuntimeError; end
21
+
22
+ end
@@ -0,0 +1,71 @@
1
+ #
2
+ #
3
+ # = Jeni
4
+ #
5
+ # == say method
6
+ #
7
+ # Author:: Robert Sharp
8
+ # Copyright:: Copyright (c) 2012 Robert Sharp
9
+ # License:: Open Software Licence v3.0
10
+ #
11
+ # This software is licensed for use under the Open Software Licence v. 3.0
12
+ # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
13
+ # and in the file copyright.txt. Under the terms of this licence, all derivative works
14
+ # must themselves be licensed under the Open Software Licence v. 3.0
15
+ #
16
+ #
17
+ #
18
+
19
+
20
+ module Jeni
21
+
22
+ # Implementation mixin for IO related methods
23
+ module IO
24
+
25
+ # lookup that maps status symbol onto colours
26
+ Colours = Hash.new(:white).merge({:ok=>:green,
27
+ :no_change=>:blue,
28
+ :warning=>:yellow,
29
+ :error=>:red
30
+ })
31
+
32
+ # lookup to map between symbols and responses
33
+ Answers = Hash.new(false).merge({:yes=>'y', :no=>'n', :skip=>'s', :diff=>'d', :list=>'l'})
34
+ AnswerKeys = Answers.values.join('')
35
+
36
+ # set the length of the field in which verbs are set, right-justified
37
+ VerbLen = 15
38
+
39
+ # print out a message with nice indenting and colours
40
+ def say(verb, message, status=:ok, quiet=false)
41
+ mstr = verb.to_s.rjust(VerbLen) + ": "
42
+ mstr << message
43
+ mstr = mstr.send(Colours[status])
44
+ puts mstr unless @quiet || quiet
45
+ return mstr
46
+ end
47
+
48
+ # ask a question and give back the answer as a symbol using
49
+ # the Answers constant hash to get the symbol
50
+ def ask(question, default=:no, options=AnswerKeys)
51
+ default = :no unless Answers.has_key?(default)
52
+ return default if @pretend && ! @answer
53
+ def_key = Answers[default]
54
+ answers = (options.split(//) & AnswerKeys.split(//)).collect {|k| k == def_key ? k.upcase : k}.join('')
55
+ print "#{question}(#{answers})? "
56
+ response = gets.chomp.downcase
57
+ if Answers.has_value?(response) then
58
+ return Answers.index(response)
59
+ else
60
+ return default
61
+ end
62
+ end
63
+
64
+ # check if the user wants to continue
65
+ def continue?
66
+ ans = ask("Do you want to continue?")
67
+ raise JeniError unless ans == :yes
68
+ end
69
+
70
+ end #IO
71
+ end