21 Replies Latest reply on Mar 6, 2011 2:49 PM by Harbs.

    How do I test for a directory?

    John Hawkinson Level 5

      How do I test if a path leads to a directory rather than a file?

      new File(path).exists() seems to return true in both cases.

       

      I ended up new File(path).length===-1, because directories seem to have a length of -1 as reported by the InDesign scripting DOM. But I don't really have much confidence that that is either portable or reliable into the future. Is there a better way?

       

      I suppose I could try to create a file within the directory, but I don't like modifying the filesystem, and the script might not have permission to do so anyhow. This is JavaScript, so it's hard to shell out to an external program (like ls -l), and that would be really kludgey anyhow.

       

      Trailing slashes don't affect .exists().

      the File constructor allegedly returns a Folder object in this case, but .constructor.name is still File.

       

      What is recommended? Thanks!

        • 1. Re: How do I test for a directory?
          [Jongware]-9BC6tI Level 4

          .. That was particularly tricky. It seems JS doesn't really check what type it is with "exists" , i.e., it doesn't mind whether you call something a File or a Folder, if it exists all is O-Kay -- as far as JS is concerned.

           

          Other objects behave something like this, so you can for example do, "alert (app.activeDocument.images[7265].constructor.name)" and get "Image" as a result. Only when you try to actively read or set a property, or try to perform any function on that image, JS checks somewhat deeper and then realizes the object doesn't exist at all. Some time ago, one of the real JS guys (probably Harbs) wrote up something about this.

           

          As for your problem, it seems about the only thing you can do with a File and not with a Folder is to open it. So this might do (until one of the real JS guys steps in and tells you what the correct way is, that is ):

           

          var path = new File("~/Documents");
          if (path.exists)
          if (path.open("r"))
               alert (path+" is a file");
          else
               alert (path+" is a folder");
          

          • 2. Re: How do I test for a directory?
            [Jongware]-9BC6tI Level 4

            Bah -- on second thought, your own checking of length is less ugly

             

            What if you test a specific characteristic of modern file systems? Every folder has two shortcuts built in, "." (which always refers to itself) and ".." (which always refer to the full path of itself minus itself -- its parent). What if you check if your path has one of these?

             

             

            var path = new File("~/Documents");
            if (path.exists)
                 if ((Folder(path.fullName+"/.")).exists)
                      alert (path+" is a folder");
                 else
                      alert (path+" is a file");
            

            • 3. Re: How do I test for a directory?
              Muppet Mark-QAl63s Level 4

              I just use instanceof works fine on my mac… Or have I miss this point somewhere? (probably/usually)

               

              //var f = File(Folder.desktop);
              //var f = File(Folder.desktop+'/');
              var f = File('~/desktop/');
              
              if (f instanceof File) alert('Its a File…');
              
              if (f instanceof Folder) alert('Its a Folder…');
              

              • 4. Re: How do I test for a directory?
                Harbs. Level 6

                Both instanceof and .constructor.name seem to work for me...

                 

                Harbs

                • 5. Re: How do I test for a directory?
                  [Jongware]-9BC6tI Level 4

                  Perhaps we should mention ID and OS details at this point ...

                   

                   

                  var path1 = new File("~/Documents");
                  var path2 = new Folder("~/Documents");
                  alert (path1+" is a "+path1.constructor.name+"\n"
                       +path2+" is a "+path2.constructor.name);
                  
                  does NOT work -- all it does is numbly repeating what constructor you used yourself -- on OS X SL, CS4.
                  It's the same when written using instanceof:
                  var path1 = new File("~/Documents");
                  var path2 = new Folder("~/Documents");
                  alert (path1+" is a File: "+(path1 instanceof File)+"/Folder: "+(path1 instanceof Folder)+"\n"
                       +path2+" is a File: "+(path2 instanceof File)+"/Folder: "+(path2 instanceof Folder));
                  
                  
                  Screen shot 2011-03-06 at 12.18.50 PM.png
                  • 6. Re: How do I test for a directory?
                    Harbs. Level 6

                    I copied your first sample code and I get the same results in both CS4 and CS5:

                    2011-03-06_1327.png

                    2011-03-06_1328.png

                    • 7. Re: How do I test for a directory?
                      Dirk Becker  Level 4

                      As I understand the question, at the beginning all we have is a path string, and the decision to be made is whether to create a File or Folder object from that. Of course then checking instanceof or constructor.name is moot.

                       

                      An observation similar to the length==-1 would be the File.type property, which is undefined for folders.

                       

                      Having a look into the JavaScript Tools Guide gives the best approach, though.

                       

                      "If the path refers to an existing folder:

                      The File function returns a Folder object instead of a File object.

                      The new operator returns a File object for a nonexisting file with the same name."

                       

                      So - just omit the "new" and test the types as discussed.

                       

                      Dirk

                      • 8. Re: How do I test for a directory?
                        [Jongware]-9BC6tI Level 4

                        Yes, and that's exactly what John is worrying about

                         

                        After all, "~/Documents" is a Folder, not a File. He's trying to find out what any given path is -- a file or a folder.

                        • 9. Re: How do I test for a directory?
                          Harbs. Level 6

                          However if you remove the "new" is works as it should:

                           

                          var path1 = File("~/Documents");
                          var path2 = Folder("~/Documents");
                          alert (path1+" is a "+path1.constructor.name+"\n"
                               +path2+" is a "+path2.constructor.name);

                           

                           

                          2011-03-06_1331.png

                          2011-03-06_1331_1.png

                           

                          Lesson to be learned: "new" is bad in javascript...

                           

                          Harbs

                          • 10. Re: How do I test for a directory?
                            Harbs. Level 6

                            Ah. Dirk beat me to the punch while I was typing...


                            Harbs

                            • 11. Re: How do I test for a directory?
                              [Jongware]-9BC6tI Level 4

                              (cough) Simply removing the "new"

                               

                              var path1 = File("~/Documents");
                              var path2 = Folder("~/Documents");
                              alert ("The laydown:\n"+path1+" is a File: "+(path1 instanceof File)+"/Folder: "+(path1 instanceof Folder)+"\n"
                                   +path2+" is a File: "+(path2 instanceof File)+"/Folder: "+(path2 instanceof Folder));
                              

                               

                              results in this:

                               

                              Screen shot 2011-03-06 at 12.34.32 PM.png

                               

                              Thanks, Dirk ...

                              • 12. Re: How do I test for a directory?
                                Harbs. Level 6

                                Just for the record:

                                 

                                You should never use "new" in javascript unless you have a really good reason to do so. It's almost never necessary, and can be the cause for unexpected results like you just saw...

                                 

                                Harbs

                                • 13. Re: How do I test for a directory?
                                  Muppet Mark-QAl63s Level 4

                                  Harbs, is right I noticed your 'new File' too…

                                   

                                  Picture 2.png

                                   

                                  Picture 3.png

                                  • 14. Re: How do I test for a directory?
                                    John Hawkinson Level 5

                                    Harbs wrote:

                                    You should never use "new" in javascript unless you have a really good reason to do so. It's almost never necessary, and can be the cause for unexpected results like you just saw...

                                    Whoa! Stop! That's not true and it's very very bad advice!

                                     

                                    If you are calling a Constructor, you must call it with new. Otherwise very bad things happen. Or they can.

                                     

                                    If you are calling a normal function, you should not call it with new. Otherwise...well, it probably doesn't matter.

                                     

                                    [ Oh. Good morning! Thanks for all the replies! Awesome! I'm impressed! ]

                                     

                                    How do you know if a function is a Constructor? By convention, you know it because it starts with an initial capital letter. Of course, its Javascript, so sometimes it is wrong and people, developers, and implementors don't follow conventions.

                                     

                                    Why must you use 'new' on a Constructor? See the "Constructors and new" from the JSLint  documentation (and the referenced blog post, JavaScript We Hardly new Ya). JSLint is a Javascript code quality tool (a lot of its advice happens to deal with browser-specific stuff and portability among different JS implemtations, which doesn't apply to Adobe ExtendScript. But a lot of its advice is about the language in general, and this falls in the latter case). It comes from Douglas Crockford, the author of Javascript: The Good Parts, a book you should be familiar with if you do any serious JavaScript programming .

                                     

                                    In short, because a Constructor assumes that a new object has been created and that "this" will be bound to that newly created object. If you do not call it with "new," then "this" is bound to the global object, and executing the constructor will modify the global object when it thinks it is initializing its own new object. This can be very very bad.

                                     

                                    OK, so what happens if you call a normal function with new? It wraps the function in a newly created object with an extra link in the prototype chain and a token amount of memory. Not a serious error, but not correct either.

                                     

                                     

                                    So, that brings us to File. Is it a constructor? Well, it's supposed to be. Does it work without the "new"? It sure looks like it often does, but who knows what is going on? And what global state is being mistakenly altered. How do we know? Well, I guess we can inspect the global object before and after ($.global) and maybe that will tell us. But maybe not. And if there are multiple File/Folder objects running around, I'd expect the lack of "new" to potentially be catastrophic.

                                     

                                    But also, File() is an internal method probably not implemented in Javascript. That may make it less important.

                                     

                                    But you should be extremely wary of invoking File() without the new. It looks like it might work here, but I don't have any way to know that it's safe.

                                    • 15. Re: How do I test for a directory?
                                      John Hawkinson Level 5

                                      OK, so I think Dirk is also right and my above concerns are a bit too strong. I was worried about people learning the wrong lesson about 'new'. What is true for File is not true in general.

                                       

                                      The section above what Dirk quoted is a bit more clear, I think:

                                      File object constructors
                                      To create a File object, use the File function or the new operator. The constructor accepts full or partial
                                      path names, and returns the new object. The CRLF sequence for the file is preset to the system default, and
                                      the encoding is preset to the default system encoding.
                                      File ([path]); //can return a Folder object
                                      new File ([path]); //always returns a File object

                                       

                                      I also feel a bit dumb since the very first page of the File System Access chapter says:

                                       

                                      There are several ways to distinguish between a File and a Folder object. For example:
                                      if (f instanceof File) ...
                                      if (typeof f.open == "undefined") ...// Folders do not open

                                      So perhaps the portable solution is to try to open the folder. (That occured to me before, but I dismissed it since under Unix you can certainly "cat" a directory).

                                       

                                      I'm loathe to use File without the new, both because it's a habit that will break the world in other Javascript implementations, and also because JSLint will complain about it and I try to make sure that all my code passes JSLint cleanly.

                                       

                                      Maybe instead I should run the .getFiles constructor -- that might fail to distinguish an empty directory from a non-existant one, though in my application that would not be a problem. More generally it would though.

                                       

                                      What does the standard say? Turns out ECMA-262 (3rd edition) doesn't define the File object. I guess because most web implementation didn't have one since it was insecure? Maybe? Mozilla's docs (File_object) don't  discuss a File function (distinct from the constructor) and they seem to be obsolete. Oh, and there's also https://developer.mozilla.org/en/DOM/File.

                                       

                                      Oof. OK, I still don't know what to do. I guess opening the object might be best after all. I wonder if there's much overhead.

                                      • 16. Re: How do I test for a directory?
                                        Harbs. Level 6

                                        John, I don't think it's bad advice at all. I've seen other instances where new caused trouble (although I don't remember off-hand what they were).

                                         

                                        The only native javascript object where new (calling the constructor) is necessary is Date (to intialize it).

                                         

                                         

                                        Unless you write heavy object oriented code (i.e. using this, etc.), new is really not necessary. The only time I ever use "new" is in code which uses custo Script UI widgets where I rely on this a lot.

                                         

                                        I didn't say to never use new, but if you are using a heavy object-oriented style, you will probably know where it's needed...

                                         

                                        Crawford's advice is to avoid new if you can:

                                        new

                                        JavaScript's new operator
                                              creates a new object that inherits from the operand's prototype member,
                                              and then calls the operand, binding the new object to this. This gives the operand (which had better
                                              be a constructor function) a chance to customize the new object before
                                              it is returned to the requestor.

                                        If you forget to use the new
                                              operator, you instead get an ordinary function call, and this is bound to the global object instead of
                                              to a new object. That means that your function will be clobbering global
                                              variables when it attempts to initialize the new members. That is a very
                                              bad thing. There is no compile-time warning. There is no runtime
                                              warning.

                                        By convention, functions that are intended to be used with
                                              new should be given names with
                                              initial capital letters, and names with initial capital letters should
                                              be used only with constructor functions that take the new prefix. This convention gives us a visual
                                              cue that can help spot expensive mistakes that the language itself is
                                              keen to overlook.

                                        An even better coping strategy is to not use new at all.

                                         

                                        There's lots of arguments for and against new. I'm in the "against" camp (where it's not necessary).

                                         

                                        A very good piece of advice I've seen is that if a specific object requires a constructor, have the constructor call itself:

                                         

                                        function foo()
                                        {
                                           // if user accidentally omits the new keyword, this will 
                                           // silently correct the problem...
                                           if ( !(this instanceof foo) )
                                              return new foo();
                                        
                                           // constructor logic follows...
                                        }
                                        

                                         

                                        Harbs

                                        • 17. Re: How do I test for a directory?
                                          Harbs. Level 6

                                          And just to be a bit clearer:

                                           

                                          NONE of the native Javascript or ExtendScript objects require "new" except Date, and Crawford himself recommends not using "new" on native objects.

                                           

                                          The only time you'll need "new" is when using custom cunstructors assuming you didn't build a call to "new" in the constructor itself.

                                           

                                          Better?

                                           

                                          Harbs

                                          • 18. Re: How do I test for a directory?
                                            John Hawkinson Level 5

                                            You're right, Harbs, I overreacted. Sorry!

                                             

                                            I think I also conflated your two posts, and took "Lesson to be learned: 'new' is bad in javascript" to mean "never use the 'new' operator in JavaScript," which is not what you said and not what you meant.

                                             

                                            But omitting "new" from functions that start with a capital letter gives me the jeebies.

                                             

                                            I don't think it's good practice to build a call to "new" into a Constructor. (maybe into a almost-constructor...). It certainly violates the expectations of what's going to happen and how you should call it, and also will confuse JSLint (well, if you omit the 'new').

                                             

                                            I don't think there are many people in the 'for' camp on new. I'm certainly not. But if I call a function that needs it, omitting it is at your peril. And it can be hard to know.

                                             

                                            It's ironic that you mention Date [aside: what is up with Jive? Font selection and sizing no longer work, nor does underline--they get stripped out. But boldface and italics do?]. Because browsing the spec on this fine Sunday morning while doing laundry, we note:

                                             

                                            15.9.2 The Date Constructor Called as a Function
                                            When Date is called as a function rather than as a constructor, it returns a string representing the
                                            current time (UTC).
                                            NOTE
                                            The function call Date(…) is not equivalent to the object creation expression new Date(…) with the
                                            same arguments.

                                            This is not very useful, mind you, but it is technically legit

                                            • 19. Re: How do I test for a directory?
                                              Dirk Becker  Level 4

                                              There is an exception to every rule ...

                                               

                                              While the ECMAScript standard does not mention files, there are 9 built-in types including Number, String and finally Object.

                                              See Section 8 (3rd edition).

                                               

                                              Later on:

                                               

                                              15.5 String Objects

                                              15.5.1 The String Constructor Called as a Function

                                              When String is called as a function rather than as a constructor, it performs a type conversion.

                                              15.5.1.1 String ( [ value ] )

                                              Returns a string value (not a String object) computed by ToString(value). If value is not supplied, the empty string "" is returned.

                                              15.5.2 The String Constructor

                                              When String is called as part of a new expression, it is a constructor: it initialises the newly created object.

                                               

                                              I'd consider File and Folder native types in the same league, except that they are not mentioned in the standard. On the other hand they and that specifc feature are clearly described in the quoted Adobe document.

                                               

                                              Regarding JSLint - if it is anything close to its C or C++ sisters, there must be tons of configuration switches to temporarily disable otherwise meaningful errors and warnings. Even if not, you could still encapsulate the in your gut feeling "questionable" code into a single function, add a few comments and focus on new code.

                                               

                                              Speaking of strings, JSLint, good coding style and so forth: a while ago I found a String object (not primitive) that the original developer had extended with a bunch of additional properties, because he was too lazy to introduce a new object and handle that change in existing code. Shudder ...

                                               

                                              Dirk

                                              • 20. Re: How do I test for a directory?
                                                John Hawkinson Level 5

                                                Well, it depends on what you mean by rule...

                                                I believe that Crockford would say, and I agree with him here, that you should have a 1:1 mapping between "new" calls  and Capitalized constructors, because that reduces the liklihood of errors.

                                                 

                                                Yes, there are other functions you can call that start with capital letters, but there is no good reason to use them. You can convert to a String with .toString, you can convert to a Number with the + prefix operator (+"46"), and you can create an Object with {} which is smaller and more succinct anyhow.

                                                 

                                                JSLint is not quite like lint(1). It is a bit more caught up in style issues than lint is, though that is in part because style issues can bite you in JavaScript that they cannot bite you in more well-designed languages. (Did you know that JavaScript was designed and implemented in something like two weeks? It's a wonder there's not more wrong with it.) You can turn on and off some kinds of errors, but not all (there is no /*LINTED*/ equivalent).

                                                 

                                                So I do strive to make my code pass JSLint cleanly with the options I can specify, and I wouldn't want to use the /*jslint newcap: false*/ directive, which will make it not "Require Initial Caps for constructors." I suppose I could do so conditionally (actually, JSLint added support for /*jslint */ directives that support function scope...yesterday. Previously each directive messed with global state, so you'd have to turn features on and off surrounding your lines, which was strongly discouraged ("I wish I had never added that feature," Crockford said, or words to the equivalent).

                                                 

                                                The reason its dangerous to consider File and Folder as native types like that is that there are other implementations of JavaScript that have them where they are not safe without "new."

                                                • 21. Re: How do I test for a directory?
                                                  Harbs. Level 6

                                                  Good point. Yes. There is a difference between yes-new and no-new in a number of the native objects. Literals are not "constructed".

                                                   

                                                  "my string" instanceof String will return false, but "my string" constructor.name is in fact a string.

                                                   

                                                  The same goes for Boolean and RegExp. I don't see that a reason to use new though...

                                                   

                                                  Harbs