sundae 0.9.2 → 1.0.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/.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