jeni 0.2.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.
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