1 # This file is part of GNU Stow.
3 # GNU Stow is free software: you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # GNU Stow is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see https://www.gnu.org/licenses/.
20 Stow::Util - general utilities
24 use Stow::Util qw(debug set_debug_level error ...);
28 Supporting utility routines for L<Stow>.
38 use base
qw(Exporter);
40 error debug set_debug_level set_test_mode
41 join_paths parent canon_path restore_cwd adjust_dotfile
44 our $ProgramName = 'stow';
45 our $VERSION = '@VERSION@';
47 #############################################################################
49 # General Utilities: nothing stow specific here.
51 #############################################################################
53 =head1 IMPORTABLE SUBROUTINES
55 =head2 error($format, @args)
57 Outputs an error message in a consistent form and then dies.
62 my ($format, @args) = @_;
63 die "$ProgramName: ERROR: " . sprintf($format, @args) . "\n";
66 =head2 set_debug_level($level)
68 Sets verbosity level for C<debug()>.
76 $debug_level = $level;
79 =head2 set_test_mode($on_or_off)
81 Sets testmode on or off.
97 =head2 debug($level[, $indent_level], $msg)
99 Logs to STDERR based on C<$debug_level> setting. C<$level> is the
100 minimum verbosity level required to output C<$msg>. All output is to
101 STDERR to preserve backward compatibility, except for in test mode,
102 when STDOUT is used instead. In test mode, the verbosity can be
103 overridden via the C<TEST_VERBOSE> environment variable.
111 =item >= 1: print operations: LINK/UNLINK/MKDIR/RMDIR/MV
113 =item >= 2: print operation exceptions
115 e.g. "_this_ already points to _that_", skipping, deferring,
116 overriding, fixing invalid links
118 =item >= 3: print trace detail: trace: stow/unstow/package/contents/node
120 =item >= 4: debug helper routines
122 =item >= 5: debug ignore lists
131 # Maintain backwards-compatibility in case anyone's relying on this.
132 $indent_level = $_[0] =~ /^\d+$/ ?
shift : 0;
134 if ($debug_level >= $level) {
135 my $indent = ' ' x
$indent_level;
137 print "# $indent$msg\n";
140 warn "$indent$msg\n";
145 #===== METHOD ===============================================================
146 # Name : join_paths()
147 # Purpose : concatenates given paths
148 # Parameters: path1, path2, ... => paths
149 # Returns : concatenation of given paths
151 # Comments : Factors out some redundant path elements:
152 # : '//' => '/', and 'a/b/../c' => 'a/c'. We need this function
153 # : with this behaviour, even though b could be a symlink to
154 # : elsewhere, as noted in the perldoc for File::Spec->canonpath().
155 # : This behaviour is deliberately different to
156 # : Stow::Util::canon_path(), because the way join_paths() is used
157 # : relies on this. Firstly, there is no guarantee that the paths
158 # : exist, so a filesystem check is inappropriate.
160 # : For example, it's used to determine the path from the target
161 # : directory to a symlink destination. So if a symlink
162 # : path/to/target/a/b/c points to ../../../stow/pkg/a/b/c,
163 # : then joining path/to/target/a/b with ../../../stow/pkg/a/b/c
164 # : yields path/to/stow/pkg/a/b/c, and it's crucial that the
165 # : path/to/stow prefix matches a recognisable stow directory.
166 #============================================================================
170 debug
(5, 5, "| Joining: @paths");
172 for my $part (@paths) {
173 next if ! length $part; # probably shouldn't happen?
174 $part = File
::Spec
->canonpath($part);
176 if (substr($part, 0, 1) eq '/') {
177 $result = $part; # absolute path, so ignore all previous parts
180 $result .= '/' if length $result && $result ne '/';
183 debug
(7, 6, "| Join now: $result");
185 debug
(6, 5, "| Joined: $result");
187 # Need this to remove any initial ./
188 $result = File
::Spec
->canonpath($result);
191 1 while $result =~ s
,(^|/)(?!\.\.)[^/]+/\.\.(/|$),$1,;
192 debug
(6, 5, "| After .. removal: $result");
194 $result = File
::Spec
->canonpath($result);
195 debug
(5, 5, "| Final join: $result");
200 #===== METHOD ===============================================================
202 # Purpose : find the parent of the given path
203 # Parameters: @path => components of the path
204 # Returns : returns a path string
206 # Comments : allows you to send multiple chunks of the path
207 # : (this feature is currently not used)
208 #============================================================================
211 my $path = join '/', @_;
212 my @elts = split m{/+}, $path;
214 return join '/', @elts;
217 #===== METHOD ===============================================================
219 # Purpose : find absolute canonical path of given path
221 # Returns : absolute canonical path
223 # Comments : is this significantly different from File::Spec->rel2abs?
224 #============================================================================
229 chdir($path) or error
("canon_path: cannot chdir to $path from $cwd");
230 my $canon_path = getcwd
();
238 chdir($prev) or error
("Your current directory $prev seems to have vanished");
243 (my $adjusted = $pkg_node) =~ s/^dot-([^.])/.$1/;