sundae 0.9.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ doc
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
@@ -1,3 +1,11 @@
1
+ === 1.0.0 / 2012-02-21
2
+
3
+ * Backwards incompatible: use Ruby for config file (not YAML)
4
+ * minor enhancements:
5
+ * support globs or regexp for ignore_paths
6
+ * better documentation
7
+ * switched to bones for gem config
8
+
1
9
  === 0.9.2 / 2009-04-28
2
10
 
3
11
  * 1 minor enhancement
data/README.rdoc ADDED
@@ -0,0 +1,155 @@
1
+ = Sundae
2
+
3
+ == Synopsis
4
+
5
+ (Re)generates directories by mixing the file hierarchies contained in
6
+ various 'mounted' directories. The generated directories contain
7
+ symbolic links to the mounted files. Combined with other tools (to
8
+ sync files), this scheme allows you to create separate collections of
9
+ files (work, personal, reference, linux, osx, etc.), choose which of
10
+ these you want to mount on each of your computers, and then build a
11
+ hierarchy that allows you to work on them side by side.
12
+
13
+ For example, let's take your bash config files. You want to separate
14
+ startup commands that you use on all unix computers from those that
15
+ you only need on Linux or OS X. Plus maybe you have some aliases that
16
+ you use only at work and some that you need only at home.
17
+
18
+ Rewrite your .bashrc to load everything in ~/etc/bash. Store that
19
+ file and your other bash config files in a folder with just general
20
+ *nix bash stuff. Then use Sundae to create links in ~/etc/bash to all
21
+ of the things that you need for your particular computer (general unix
22
+ stuff + either linux or OS X stuff + work aliases + ...). The files
23
+ are together in one folder so your script knows to read them, but they
24
+ can be version controlled and/or synced across all of your computers
25
+ in separate bundles (one for *nix, one for ubuntu, one for OS X,
26
+ etc.).
27
+
28
+ You have to figure out how to use this model. It's not for everyone.
29
+ It requires rewriting config files, making sure certain files kept in
30
+ different folders don't have the same name, etc. But it's worth it
31
+ when you sit down at a new computer and say "I only want to use my
32
+ *nix and linux config files, my work files, and music" and it all just
33
+ works. Like this:
34
+
35
+ ~> ls
36
+ Desktop local mnt src WualaDrive
37
+ ~> sundae
38
+ ~> ls
39
+ bin Desktop doc etc lib local mnt share src tmp var WualaDrive
40
+
41
+ == Install
42
+
43
+ sudo gem install sundae
44
+
45
+ == Usage
46
+
47
+ The first time you run Sundae, it will create a template config file
48
+ in your home directory. This file, <tt>.sundae</tt>, needs to be
49
+ customized. It is just a Ruby file that defines the following:
50
+
51
+ [+configatron.paths+]
52
+ array; where the collections are stored
53
+ [+configatron.ignore_rules+]
54
+ array; each element is a string or Regexp and becomes a rule that prevents
55
+ links to files or directories that match the Regexp. Globs in strings are expanded.
56
+
57
+ The hierarchy in <em>path</em> should look something like
58
+ this:
59
+
60
+ path/
61
+ |-- collection1/
62
+ | |-- mnt1/
63
+ | | |-- real_files_and_dirs
64
+ | | ` ...
65
+ | |-- mnt2/
66
+ `-- collection2/
67
+ ` ...
68
+
69
+ Why is this double layer "collection" stuff going on? Because while
70
+ most of the time you can share a whole folder between computers,
71
+ sometimes you want to mix your config files into a folder that also
72
+ contains nonsymlinked files.
73
+
74
+ For example, your ~/.ssh folder probably has a public and private key
75
+ that you want to stay unique to that machine, but you might want to
76
+ mix in a "config" file that has host aliases that you share between
77
+ machines. This is how you do that. I do it with my .unison, .mocp,
78
+ .ssh, and .lftp folders.
79
+
80
+ For example, the hierarchy in my <em>path</em>s looks sort of like this:
81
+
82
+ ~/mnt/git/ <-- "path"
83
+ |-- nix/ <-- "collection"
84
+ | |-- home/ <-- "mnt"
85
+ | | |-- .emacs.d/ (~/.emacs.d will point here)
86
+ | | |-- etc/ (~/etc will point here)
87
+ | | ` ...
88
+ | |-- dot-unison
89
+ | | |-- .sundae_path (says "~/.unison")
90
+ | | |-- default.prf (~/.unison/default.prf will point here)
91
+ | | `
92
+ | |
93
+ |-- osx/
94
+ | |-- home_library/ (says "~/Library")
95
+ | | |-- .sundae_path
96
+ | | `-- Library-Keyboard_Layouts/
97
+ | | `-- Keyboard Layouts/
98
+ | | ` Colemak.keylayout
99
+ | |
100
+ |-- personal
101
+ | `-- home/
102
+ | |-- doc/
103
+ | | ` ...
104
+ | ` ...
105
+ ` ...
106
+ ~/mnt/sync/ <-- "path"
107
+ |-- reference <-- "collection"
108
+ | |-- home/ <-- "mnt"
109
+ | ` ...
110
+ |-- music
111
+ | |-- home/
112
+ | ` ...
113
+ ` ...
114
+
115
+ Sundae will act on all of the <em>mnt</em>s--subdirectories of the
116
+ <em>collection</em>s, that is, the sub-subdirectories of the
117
+ <em>path</em>. The "collections" are only there to facilitate
118
+ grouping common files and syncronizing them between computers.
119
+
120
+ By default, all of the contents in each of the <em>mnt</em>s are
121
+ placed in the user's home directory. This can be altered by
122
+ creating a file called <tt>.sundae_path</tt> in the top of the
123
+ <em>mnt</em>; the file should contain one line, which is the
124
+ absolute path to where that directory should be "mounted."
125
+
126
+ And that's it. When called, Sundae creates links so that you can
127
+ work on your files from seperate parts of life as if they were side
128
+ by side.
129
+
130
+ == Author
131
+ <don@ohspite.net>
132
+
133
+ == Copyright
134
+ Copyright (c) 2011, 2008 <don@ohspite.net>.
135
+ Licensed under the MIT License.
136
+
137
+ Permission is hereby granted, free of charge, to any person obtaining
138
+ a copy of this software and associated documentation files (the
139
+ 'Software'), to deal in the Software without restriction, including
140
+ without limitation the rights to use, copy, modify, merge, publish,
141
+ distribute, sublicense, and/or sell copies of the Software, and to
142
+ permit persons to whom the Software is furnished to do so, subject to
143
+ the following conditions:
144
+
145
+ The above copyright notice and this permission notice shall be
146
+ included in all copies or substantial portions of the Software.
147
+
148
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
149
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
150
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
151
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
152
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
153
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
154
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
155
+
data/Rakefile CHANGED
@@ -1,18 +1,30 @@
1
- # -*- ruby -*-
1
+ begin
2
+ require 'bones'
3
+ rescue LoadError
4
+ abort '### please install the "bones" gem ###'
5
+ end
2
6
 
3
- require 'rubygems'
4
- require 'hoe'
5
- require './lib/sundae.rb'
7
+ task :default => 'test:run'
8
+ task 'gem:release' => 'test:run'
6
9
 
7
- Hoe.new('sundae', Sundae::VERSION) do |p|
8
- p.developer('Don', 'don@ohspite.net')
9
- p.email = 'don@ohspite.net'
10
- p.description = "Mix collections of files while maintaining complete separation. Synchronize any combination of your documents and configuration settings between all of your computers."
11
- p.summary = "Mix collections of files while maintaining complete separation."
12
- p.url = "http://rubyforge.org/projects.sundae"
13
- # p.changes = p.paragraphs_of('CHANGELOG', 0..1).join("\n\n")
14
- p.remote_rdoc_dir = ''
15
- p.extra_deps = ['configatron']
16
- end
10
+ Bones do
11
+ name 'sundae'
12
+ authors 'Don'
13
+ email 'don@ohspite.net'
14
+ url 'https://github.com/ohspite/sundae'
15
+ summary 'Mix collections of files while maintaining complete separation.'
16
+ description 'Mix collections of files while maintaining complete separation.'
17
+ history_file 'CHANGELOG'
18
+ manifest_file 'Manifest'
19
+ readme_file 'README.rdoc'
20
+ rdoc.main 'README.rdoc'
17
21
 
18
- # vim: syntax=Ruby
22
+ ignore_file '.gitignore'
23
+ exclude %w(tmp$ bak$ ~$ CVS \.svn/ \.git/ \.bzr/ \.bzrignore ^pkg/)
24
+ rdoc.include %w(README ^lib/ ^bin/ ^ext/ \.txt$ \.rdoc$)
25
+ depend_on 'highline'
26
+ depend_on 'configatron'
27
+ depend_on 'rdoc'
28
+
29
+ # spec.opts << '--color'
30
+ end
data/bin/sundae CHANGED
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # == Usage
9
9
  #
10
- # sundae [--config-path PATH]
10
+ # sundae [options] [commands]
11
11
  #
12
12
  # For command line details see
13
13
  # sundae --help
@@ -16,25 +16,84 @@
16
16
  # <don@ohspite.net>
17
17
  #
18
18
  # == Copyright
19
- # Copyright (c) 2008 <don@ohspite.net>.
19
+ # Copyright (c) 2012, 2008 <don@ohspite.net>.
20
20
  # Licensed under the MIT License.
21
21
 
22
- require 'rdoc/usage'
23
22
  require 'optparse'
23
+ require 'highline/import'
24
24
 
25
25
  $:.unshift File.join(File.dirname(__FILE__), "../lib")
26
26
 
27
27
  require 'sundae'
28
28
 
29
29
  class App # :nodoc:
30
+
31
+ COMMAND_LIST = %w{run
32
+ source
33
+ move
34
+ }
35
+
30
36
  def initialize
31
37
  parse_commandline(ARGV)
32
38
 
33
39
  Sundae.load_config_file(@options[:config_path])
34
-
35
- Sundae.remove_dead_links
36
- Sundae.remove_generated_directories
37
- Sundae.create_filesystem
40
+
41
+ case @commands[0]
42
+ when :run
43
+ Sundae.update_filesystem
44
+ when :remove
45
+ Sundae.remove_filesystem
46
+ when :sources
47
+ ARGV << "." if ARGV.empty?
48
+ Process.abort "#{ARGV[0]} is not a directory." unless File.directory?(ARGV[0])
49
+ path = File.expand_path(ARGV[0])
50
+ mnts = Sundae.find_source_directories(path)
51
+ mnts.each do |mnt|
52
+ install_location = Sundae.install_location(mnt)
53
+ relative = path.sub(install_location, '')
54
+ puts mnt
55
+ Dir.entries(File.join(mnt, relative)).sort.each do |e|
56
+ next if e =~ /^..?$/
57
+ line_end = File.directory?(e) ? "/" : ""
58
+ puts " " + e + line_end
59
+ end
60
+ puts
61
+ end
62
+ when :move
63
+ Process.abort "The 'move' command requires a file or directory as an argument." if ARGV.empty?
64
+ Process.abort "#{ARGV[0]} is not a file or directory." unless File.exist?(ARGV[0])
65
+
66
+ if ARGV.size == 1
67
+ path = File.expand_path(ARGV[0])
68
+
69
+ current_mnt = nil
70
+ Sundae.all_mnts.map do |mnt|
71
+ current_mnt = mnt if path =~ Regexp.new(mnt)
72
+ end
73
+ choices = if current_mnt
74
+ Sundae.all_mnts
75
+ else
76
+ Sundae.find_source_directories(File.dirname(path))
77
+ end
78
+ choices.push "--Cancel?--"
79
+ choose do |menu|
80
+ menu.prompt = "move to: "
81
+ menu.choices(*choices) do |new_path|
82
+ if new_path == "--Cancel?--"
83
+ Process.abort("No move performed.")
84
+ else
85
+ Sundae.move_to_mnt(path, new_path)
86
+ end
87
+ end
88
+ end
89
+ else
90
+ relative_path = ARGV.pop
91
+ ARGV.each do |path|
92
+ path = File.expand_path(path)
93
+ Sundae.move_to_relative_path(path, relative_path)
94
+ end
95
+ end
96
+ end
38
97
  end
39
98
 
40
99
  private
@@ -42,12 +101,18 @@ class App # :nodoc:
42
101
  def parse_commandline(option_line)
43
102
  options = {:verbose => false}
44
103
  option_parser = OptionParser.new do |opts|
45
- opts.banner = "Usage: #{File.basename(__FILE__)} [options] "
104
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options] [command]"
105
+ opts.separator ""
106
+ opts.separator "Available commands (can be abbreviated):"
107
+ opts.separator ' run create the filesystem from the mounts; the default command'
108
+ opts.separator ' rm remove generated directories and symlinks'
109
+ opts.separator ' move PATH moves the resource at \'path\' (or pointed to by \'path\' if it is a link) to another mount collection'
110
+ opts.separator ' sources DIR=./ print the mounts that have resources in \'dir\' and what comes from each'
46
111
  opts.separator ""
47
112
  opts.separator "Specific options:"
48
113
  opts.on('-c',
49
114
  '--config-path PATH',
50
- 'specify the path to the \'.sundae\' directory (default is \'~/.sundae\')') do |path|
115
+ 'specify the path to the \'.sundae\' directory (default is \'~/.sundae\'); used with the regular \'run\' command') do |path|
51
116
  options[:config_path] = File.expand_path(path)
52
117
  end
53
118
  # opts.on('-v',
@@ -71,23 +136,31 @@ class App # :nodoc:
71
136
  end
72
137
 
73
138
  argv = Array.new
74
- begin
75
- option_parser.order!(option_line) do |command|
76
- case command
77
- when "some_value"
78
-
79
- else
80
- argv << command
81
- end
139
+ commands = Array.new
140
+ if option_line.empty? then option_line = ['run'] end
141
+ option_parser.order!(option_line) do |input|
142
+ action = case input
143
+ when nil then :run
144
+ when /^ru?n?$/ then :run
145
+ when /^re?m?o?v?e?$/ then :remove
146
+ when /^so?u?r?c?e?s?$/ then :sources
147
+ when /^mo?v?e?$/ then :move
148
+ else nil
149
+ end
150
+ if action.nil?
151
+ argv << input
152
+ else
153
+ commands << action
82
154
  end
83
- rescue
84
- RDoc::usage('usage')
85
155
  end
86
156
  argv.each { |a| option_line << a }
87
-
157
+
158
+ if commands.empty? then Process.abort "No command unambiguously specified." end
159
+ if commands.size > 1 then Process.abort "More than one command specified." end
160
+
88
161
  @options = options
162
+ @commands = commands
89
163
  end
90
164
  end
91
165
 
92
-
93
166
  App.new
data/lib/sundae.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'rubygems'
1
+ # require 'rubygems'
2
2
  require 'configatron'
3
3
  require 'fileutils'
4
4
  require 'find'
@@ -7,7 +7,11 @@ require 'find'
7
7
  # together using symbolic links.
8
8
  #
9
9
  module Sundae
10
- VERSION = "0.9.2"
10
+ # :stopdoc:
11
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
12
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
13
+ # :startdoc:
14
+ VERSION = ::File.read(PATH + 'version.txt').strip
11
15
 
12
16
  DEFAULT_CONFIG_FILE = File.expand_path(File.join(ENV['HOME'], '.sundae'))
13
17
 
@@ -17,25 +21,18 @@ module Sundae
17
21
  #
18
22
  def self.load_config_file(config_file = DEFAULT_CONFIG_FILE)
19
23
  config_file ||= DEFAULT_CONFIG_FILE
20
- config_file = File.join(config_file, '.sundae') unless File.basename(config_file) == '.sundae'
24
+ config_file = File.join(config_file, '.sundae') if File.directory?(config_file)
21
25
 
22
26
  create_template_config_file(config_file) unless File.file?(config_file)
23
27
 
24
- configatron.set_default(:collection_links, false)
25
- configatron.set_default(:collection_link_prefix, '_')
26
-
27
- configatron.configure_from_yaml(config_file)
28
+ load(config_file)
28
29
  configatron.paths.map! { |p| File.expand_path(p) }
29
- configatron.ignore_rules.map! { |a| Regexp.new(a) }
30
30
 
31
31
  # An array which lists the directories where mnts are stored.
32
32
  @paths = configatron.paths
33
33
  # These are the rules that are checked to see if a file in a mnt
34
34
  # should be ignored.
35
35
  @ignore_rules = configatron.ignore_rules
36
-
37
- @collection_links = configatron.collection_links
38
- @collection_link_prefix = configatron.collection_link_prefix
39
36
  end
40
37
 
41
38
  # Create a template configuration file at <em>config_file</em> after
@@ -47,16 +44,23 @@ module Sundae
47
44
  ans = gets.downcase.strip
48
45
  if ans == "y" || ans == "yes"
49
46
  File.open(config_file, "w") do |f|
50
- f.puts ":paths:"
51
- f.puts "- ~/mnt"
52
- f.puts ":collection_links:"
53
- f.puts " false"
54
- f.puts ":collection_link_prefix:"
55
- f.puts " '_'"
56
- f.puts ":ignore_rules: # Ruby Regexps"
57
- f.puts "- \\.svn"
58
- f.puts "- \\.bzr"
59
- f.puts "- \\.DS_Store"
47
+ f.puts <<-EOM.gsub(/^ {14}/, '')
48
+ # -*-Ruby-*-
49
+
50
+ # An array which lists the directories where mnts are stored.
51
+ configatron.paths = ["~/mnt"]
52
+
53
+ # These are the rules that are checked to see if a file in a mnt
54
+ # should be ignored.
55
+ #
56
+ # For `ignore_rules', use either strings (can be globs)
57
+ # or Ruby regexps. You can mix both in the same array.
58
+ # Globs are matched using the method File.fnmatch.
59
+ configatron.ignore_rules = %w(.git,
60
+ .bzr,
61
+ .svn,
62
+ .DS_Store)
63
+ EOM
60
64
  end
61
65
  puts
62
66
  puts "Okay then."
@@ -74,7 +78,13 @@ module Sundae
74
78
  def self.ignore_file?(file) # :doc:
75
79
  return true if File.basename(file) =~ /^\.\.?$/
76
80
  return true if File.basename(file) == ".sundae_path"
77
- @ignore_rules.each { |r| return true if File.basename(file) =~ r }
81
+ @ignore_rules.each do |r|
82
+ if r.kind_of? Regexp
83
+ return true if File.basename(file) =~ r
84
+ else
85
+ return true if File.fnmatch(r, file)
86
+ end
87
+ end
78
88
  return false
79
89
  end
80
90
 
@@ -94,12 +104,7 @@ module Sundae
94
104
  # be created.
95
105
  #
96
106
  def self.install_locations
97
- locations = []
98
-
99
- all_mnts.each do |mnt|
100
- locations << install_location(mnt)
101
- end
102
- return locations.sort.uniq
107
+ all_mnts.map { |m| install_location(m) }.sort.uniq
103
108
  end
104
109
 
105
110
  # Given _path_, return all mnts (i.e., directories two levels down)
@@ -111,7 +116,7 @@ module Sundae
111
116
  collections.each do |c|
112
117
  collection_mnts = Dir.entries(File.join(path, c)).delete_if {|a| a=~/^\./}
113
118
  collection_mnts.map! { |mnt| File.join(c, mnt) }
114
- mnts |= collection_mnts
119
+ mnts |= collection_mnts # |= is the union
115
120
  end
116
121
 
117
122
  return mnts.sort.uniq
@@ -124,7 +129,7 @@ module Sundae
124
129
 
125
130
  @paths.each do |path|
126
131
  next unless File.exist?(path)
127
- mnts |= mnts_in_path(path).map { |mnt| File.join(path, mnt) }
132
+ mnts |= mnts_in_path(path).map { |mnt| File.join(path, mnt) } # |= is the union operator
128
133
  end
129
134
 
130
135
  return mnts
@@ -143,7 +148,7 @@ module Sundae
143
148
  end
144
149
  end
145
150
 
146
- return dirs.sort.uniq
151
+ return dirs.sort.uniq.select { |d| File.directory?(d) }
147
152
  end
148
153
 
149
154
  # Check for symlinks in the base directories that are missing their
@@ -172,7 +177,7 @@ module Sundae
172
177
  generated_directories.each do |dir|
173
178
  next if File.basename(dir) == ('.sundae')
174
179
 
175
- # Do a quick search to make sure no non-symlink file is being
180
+ # Do a search to make sure no non-symlink file is being
176
181
  # deleted. That would suck.
177
182
  if sf = find_static_file(dir)
178
183
  puts "found static file: #{sf}"
@@ -229,18 +234,6 @@ module Sundae
229
234
 
230
235
  Find.prune if File.directory?(path)
231
236
  end
232
- create_collection_links(target, link_path)
233
- end
234
-
235
- # Create links in a generated mirror directory to the analogous
236
- # location in the mounted directories.
237
- #
238
- def self.create_collection_links(target, link_name)
239
- return unless @collection_links
240
-
241
- collection_name = File.basename(root_path(target))
242
- collection_link = File.join(link_name, @collection_link_prefix + collection_name)
243
- create_link(target, collection_link) unless File.exist? collection_link
244
237
  end
245
238
 
246
239
  # Starting at _dir_, walk up the directory hierarchy and return the
@@ -250,11 +243,7 @@ module Sundae
250
243
  raise ArgumentError if dir == '/'
251
244
 
252
245
  parent = File.expand_path(File.join(dir, '..'))
253
- if @paths.include? parent
254
- return dir
255
- else
256
- root_path parent
257
- end
246
+ return (@paths.include?(parent)) ? dir : root_path(parent)
258
247
  end
259
248
 
260
249
  # Dispatch calls to create_directory_link and create_file_link.
@@ -326,5 +315,78 @@ module Sundae
326
315
  minimally_create_links(target_path2, link_name)
327
316
  end
328
317
 
318
+ def self.update_filesystem
319
+ remove_dead_links
320
+ remove_generated_directories
321
+ create_filesystem
322
+ end
323
+
324
+ def self.remove_filesystem
325
+ remove_dead_links
326
+ remove_generated_directories
327
+ end
328
+
329
+ # Return an array of mnts that are installing to +path+.
330
+ #
331
+ def self.find_source_directories(path)
332
+ sources = Array.new
333
+ all_mnts.each do |mnt|
334
+ install_location = File.expand_path(install_location(mnt))
335
+ if path.include?(install_location)
336
+ relative_path = path.sub(Regexp.new(install_location), "")
337
+ sources << mnt if File.exist?(File.join(mnt, relative_path))
338
+ end
339
+ end
340
+ return sources
341
+ end
342
+
343
+ # Move the file at +path+ (or its target in the case of a link) to
344
+ # +mnt+ preserving relative path.
345
+ #
346
+ def self.move_to_mnt(path, mnt)
347
+ if File.symlink?(path)
348
+ to_move = File.readlink(path)
349
+ current = Sundae.find_source_directories(path)[0]
350
+ relative_path = to_move.sub(Regexp.new(current), "")
351
+ FileUtils.mv(to_move, mnt + relative_path) unless current == mnt
352
+ FileUtils.ln_sf(mnt + relative_path, path)
353
+ else
354
+ location = Sundae.install_location(mnt)
355
+ relative_path = path.sub(Regexp.new(location), "")
356
+ FileUtils.mv(path, mnt + relative_path) unless path == mnt + relative_path
357
+ FileUtils.ln_s(mnt + relative_path, path)
358
+ end
359
+ end
360
+
361
+ # Move the target at +link+ according to +relative_path+.
362
+ #
363
+ def self.move_to_relative_path(link, relative_path)
364
+ raise ArgumentError, "#{link} is not a link." unless File.symlink?(link)
365
+
366
+ target = File.readlink(link)
367
+
368
+ pwd = FileUtils.pwd
369
+ mnt = Sundae.find_source_directories(link)[0]
370
+ mnt_pwd = File.join(mnt, pwd.sub(Regexp.new(install_location(mnt)), ""))
371
+
372
+ if File.directory?(relative_path)
373
+ new_target_path = File.join(mnt_pwd, relative_path, File.basename(link))
374
+ new_link_path = File.join(pwd, relative_path, File.basename(link))
375
+ else
376
+ new_target_path = File.join(mnt_pwd, relative_path)
377
+ new_link_path = File.join(pwd, relative_path)
378
+ end
379
+
380
+ target = File.expand_path(target)
381
+ new_target_path = File.expand_path(new_target_path)
382
+ new_link_path = File.expand_path(new_link_path)
383
+
384
+ raise ArgumentError, "#{link} and #{new_target_path} are the same file" if target == new_target_path
385
+ FileUtils.mkdir_p(File.dirname(new_target_path))
386
+ FileUtils.mv(target, new_target_path)
387
+ FileUtils.rm(link)
388
+ FileUtils.ln_s(new_target_path, new_link_path)
389
+ end
390
+
329
391
  end
330
392