changeset 19232:931cc13a6f3b

Rework ls.m and dir.m * dir.m: Improve docstring. Place input validation first. Use numel in place of length for clarity. Add BIST tests. * ls.m: Improve docstring. Use interpreter to concatenate strings rather than sprintf. Eliminate unnecessary test for empty retval, strvcat works with empty inputs. Add expected error outpu to %!error test.
author Rik <rik@octave.org>
date Wed, 01 Oct 2014 10:58:03 -0700
parents 09bc8304f182
children 3a6fd52e1458
files scripts/miscellaneous/dir.m scripts/miscellaneous/ls.m
diffstat 2 files changed, 111 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/miscellaneous/dir.m	Wed Oct 01 07:51:43 2014 -0700
+++ b/scripts/miscellaneous/dir.m	Wed Oct 01 10:58:03 2014 -0700
@@ -51,21 +51,21 @@
 ## than a single directory or file.
 ##
 ## @var{directory} is subject to shell expansion if it contains any wildcard
-## characters @samp{*}, @samp{?}, @samp{[]}.  If you want to find a
-## literal example of a wildcard character you must escape it using the
-## backslash operator @samp{\}.
+## characters @samp{*}, @samp{?}, @samp{[]}.  To find a literal example of a
+## wildcard character the wildcard must be escaped using the backslash operator
+## @samp{\}.
 ##
 ## Note that for symbolic links, @code{dir} returns information about
 ## the file that the symbolic link points to rather than the link itself.
 ## However, if the link points to a nonexistent file, @code{dir} returns
 ## information about the link.
-## @seealso{ls, readdir, glob, what, stat}
+## @seealso{ls, readdir, glob, what, stat, lstat}
 ## @end deftypefn
 
 ## Author: jwe
 
-## FIXME: This is quite slow for large directories, so perhaps
-##        it should be converted to C++.
+## FIXME: This is quite slow for large directories.
+##        Perhaps it should be converted to C++?
 
 function retval = dir (directory)
 
@@ -75,79 +75,78 @@
     print_usage ();
   endif
 
+  if (! ischar (directory))
+    error ("dir: DIRECTORY argument must be a string");
+  endif
+
   ## Prep the retval.
   info = struct (zeros (0, 1),
                  {"name", "date", "bytes", "isdir", "datenum", "statinfo"});
 
-  if (ischar (directory))
-    if (strcmp (directory, "*"))
-      directory = ".";
-    endif
-    if (strcmp (directory, "."))
-      flst = {"."};
-      nf = 1;
-    else
-      flst = glob (directory);
-      nf = length (flst);
-    endif
 
-    ## Determine the file list for the case where a single directory is
-    ## specified.
-    if (nf == 1)
-      fn = flst{1};
-      [st, err, msg] = stat (fn);
-      if (err < 0)
-        warning ("dir: 'stat (%s)' failed: %s", fn, msg);
-        nf = 0;
-      elseif (S_ISDIR (st.mode))
-        flst = readdir (flst{1});
-        nf = length (flst);
-        for i = 1:nf
-          flst{i} = fullfile (fn, flst{i});
-        endfor
-      endif
-    endif
+  if (strcmp (directory, "*"))
+    directory = ".";
+  endif
+  if (strcmp (directory, "."))
+    flst = {"."};
+    nf = 1;
+  else
+    flst = glob (directory);
+    nf = numel (flst);
+  endif
 
-    if (length (flst) > 0)
-      ## Collect results.
-      for i = nf:-1:1
-        fn = flst{i};
-        [st, err, msg] = lstat (fn);
-        if (err < 0)
-          warning ("dir: 'lstat (%s)' failed: %s", fn, msg);
-        else
-          ## If we are looking at a link that points to something,
-          ## return info about the target of the link, otherwise, return
-          ## info about the link itself.
-          if (S_ISLNK (st.mode))
-            [xst, err, msg] = stat (fn);
-            if (! err)
-              st = xst;
-            endif
-          endif
-          [dummy, fn, ext] = fileparts (fn);
-          fn = [fn ext];
-          info(i,1).name = fn;
-          lt = localtime (st.mtime);
-          info(i,1).date = strftime ("%d-%b-%Y %T", lt);
-          info(i,1).bytes = st.size;
-          info(i,1).isdir = S_ISDIR (st.mode);
-          info(i,1).datenum = datenum (lt.year + 1900, lt.mon + 1, lt.mday,
-                                       lt.hour, lt.min, lt.sec);
-          info(i,1).statinfo = st;
-        endif
+  ## Determine the file list for the case where a single directory is specified.
+  if (nf == 1)
+    fn = flst{1};
+    [st, err, msg] = stat (fn);
+    if (err < 0)
+      warning ("dir: 'stat (%s)' failed: %s", fn, msg);
+      nf = 0;
+    elseif (S_ISDIR (st.mode))
+      flst = readdir (flst{1});
+      nf = numel (flst);
+      for i = 1:nf
+        flst{i} = fullfile (fn, flst{i});
       endfor
     endif
+  endif
 
-  else
-    error ("dir: expecting directory or filename to be a char array");
+  if (numel (flst) > 0)
+    ## Collect results.
+    for i = nf:-1:1
+      fn = flst{i};
+      [st, err, msg] = lstat (fn);
+      if (err < 0)
+        warning ("dir: 'lstat (%s)' failed: %s", fn, msg);
+      else
+        ## If we are looking at a link that points to something,
+        ## return info about the target of the link, otherwise, return
+        ## info about the link itself.
+        if (S_ISLNK (st.mode))
+          [xst, err, msg] = stat (fn);
+          if (! err)
+            st = xst;
+          endif
+        endif
+        [dummy, fn, ext] = fileparts (fn);
+        fn = [fn ext];
+        info(i,1).name = fn;
+        lt = localtime (st.mtime);
+        info(i,1).date = strftime ("%d-%b-%Y %T", lt);
+        info(i,1).bytes = st.size;
+        info(i,1).isdir = S_ISDIR (st.mode);
+        info(i,1).datenum = datenum (lt.year + 1900, lt.mon + 1, lt.mday,
+                                     lt.hour, lt.min, lt.sec);
+        info(i,1).statinfo = st;
+      endif
+    endfor
   endif
 
   ## Return the output arguments.
   if (nargout > 0)
     ## Return the requested structure.
     retval = info;
-  elseif (length (info) > 0)
+  elseif (numel (info) > 0)
     ## Print the structure to the screen.
     printf ("%s", list_in_columns ({info.name}));
   else
@@ -156,3 +155,26 @@
 
 endfunction
 
+
+%!test
+%! list = dir ();
+%! assert (isstruct (list) && ! isempty (list));
+%! assert (fieldnames (list),
+%!         {"name"; "date"; "bytes"; "isdir"; "datenum"; "statinfo"});
+%!
+%! if (isunix ())
+%!   assert ({list(1:2).name}, {".", ".."});
+%!   assert ([list(1:2).isdir], [true true]);
+%! endif
+%!
+%! ## test that specifying a filename works the same as using a directory.
+%! found = find (! [list.isdir], 1);
+%! if (! isempty (found))
+%!   list2 = dir (list(found).name);
+%!   assert (list(found), list2);
+%! endif
+
+## Test input validation
+%!error <DIRECTORY argument must be a string> dir (1)
+%!warning <nonexistent directory> dir ("_%UNLIKELY_DIR_NAME%_");
+
--- a/scripts/miscellaneous/ls.m	Wed Oct 01 07:51:43 2014 -0700
+++ b/scripts/miscellaneous/ls.m	Wed Oct 01 10:58:03 2014 -0700
@@ -18,10 +18,26 @@
 
 ## -*- texinfo -*-
 ## @deftypefn  {Command} {} ls
-## @deftypefnx {Command} {} ls filenames
-## @deftypefnx {Command} {} ls options
-## @deftypefnx {Command} {} ls options filenames
-## List directory contents.  For example:
+## @deftypefnx {Command} {} ls @var{filenames}
+## @deftypefnx {Command} {} ls @var{options}
+## @deftypefnx {Command} {} ls @var{options} @var{filenames}
+## @deftypefnx {Function File} {@var{list} =} ls (@dots{})
+##
+## List directory contents.
+##
+## The @code{ls} command is implemented by calling the native operating
+## system's directory listing command---available @var{options} will vary from
+## system to system.
+##
+## Filenames are subject to shell expansion if they contain any wildcard
+## characters @samp{*}, @samp{?}, @samp{[]}.  To find a literal example of a
+## wildcard character the wildcard must be escaped using the backslash operator
+## @samp{\}.
+##
+## If the optional output @var{list} is requested then @code{ls} returns a
+## character array with one row for each file/directory name.
+##
+## Example usage on a UNIX-like system:
 ##
 ## @example
 ## @group
@@ -32,14 +48,6 @@
 ## @end group
 ## @end example
 ##
-## The @code{dir} and @code{ls} commands are implemented by calling your
-## system's directory listing command, so the available options will vary
-## from system to system.
-##
-## Filenames are subject to shell expansion if they contain any wildcard
-## characters @samp{*}, @samp{?}, @samp{[]}.  If you want to find a
-## literal example of a wildcard character you must escape it using the
-## backslash operator @samp{\}.
 ## @seealso{dir, readdir, glob, what, stat, filesep, ls_command}
 ## @end deftypefn
 
@@ -50,8 +58,7 @@
   global __ls_command__;
 
   if (isempty (__ls_command__) || ! ischar (__ls_command__))
-    ## Initialize value for __ls_command__.
-    ls_command ();
+    ls_command ();  # Initialize global value for __ls_command__.
   endif
 
   if (! iscellstr (varargin))
@@ -75,7 +82,7 @@
     args = "";
   endif
 
-  cmd = sprintf ("%s %s", __ls_command__, args);
+  cmd = [__ls_command__ args];
 
   if (page_screen_output () || nargout > 0)
     [status, output] = system (cmd);
@@ -84,8 +91,6 @@
       error ("ls: command exited abnormally with status %d\n", status);
     elseif (nargout == 0)
       puts (output);
-    elseif (isempty (output))
-      retval = "";
     else
       retval = strvcat (regexp (output, '\S+', 'match'){:});
     endif
@@ -104,5 +109,7 @@
 %! assert (ischar (list));
 %! assert (! isempty (list));
 
-%!error ls (1)
+%!error <all arguments must be character strings> ls (1)
+## Test below is valid, but produces confusing output on screen
+%!#error <command exited abnormally> ls ("-!")