• Solutions
    • FERC XBRL Reporting
    • FDTA Financial Reporting
    • SEC Compliance
    • Windows Clipboard Management
    • Legato Scripting
  • Products
    • GoFiler Suite
    • XBRLworks
    • SEC Exhibit Explorer
    • SEC Extractor
    • Clipboard Scout
    • Legato
  • Education
    • Training
    • SEC and EDGAR Compliance
    • Legato Developers
  • Blog
  • Support
  • Skip to blog entries
  • Skip to archive page
  • Skip to right sidebar

Friday, September 28. 2018

LDC #104: Copy All the Things - An Example of Recursion

This week I want to go over a short script that I’ve written recently in order to help automate some of the tasks I have picked up around the office. Some of these tasks involve moving around files and folders on the network. I got tired of manually copying and pasting folders, so I wrote a script to help make moving large amounts of files and folders easier. There’s plenty of ways that this script could be made more in-depth, but this is a practical example of a way to make your life a little bit easier.


The biggest thing I wanted to do with this script was to copy entire folders around. This script does not introduce any new concepts if you’re a regular reader of the LDC, but it is a good example of putting together several concepts in a very simple manner.


RecursionOriginally I was going to put together several loops, but soon I realized that this was an impractical solution and decided that recursion was the easiest way to make sure that the script was both simple to read and simple in execution. The script as a whole is about 60 lines of code. Let’s take a look:


/* Parse.ls
 * 
 * Author: Joshua Kwiatkowski
 * 
 * Rev 01       JCK             09/28/18
 *
 * Notes: Copy files and folders from one location to another
 */

//Declare function
int parse(string old_folder, string new_folder, int recursive);

//Main
void main() {

    string      old, new;
    int         recurse;
    
    //Get original folder
    old = BrowseFolder("Pick Originating Folder");
    //If canceled bail
    if (GetLastError() == ERROR_CANCEL) { 
      return;
      }
    //Get destination folder
    new = BrowseFolder("Pick Destination Folder", old);
    //If canceled bail
    if (GetLastError() == ERROR_CANCEL) { 
      return;
      }
    //Ask if you want to do subfolders
    recurse = YesNoBox('Q', "Move subfolders in addition to files?");
    //If Canceled
    if (recurse == IDCANCEL) {
      return;
      }

    //Start the process
    parse(old, new, recurse);
    
    }
      
      
int parse(string old_folder, string new_folder, int recursive) {

    handle      hDS;
    int         rc;
    string      filename;

    //Start the parse
    hDS = GetFirstFile(old_folder + "*.*");
    //Assume there are more files than this
    boolean moreFiles;
    moreFiles = true;
    
    //Are there more files?
    while (moreFiles) {
      //Get the filename
      filename = GetName(hDS);
      //Is the filename not the home or previous folder?
      if (filename != "." && filename != "..") {
        //Is the file a folder?
        if (IsFolder(old_folder + filename)) {
          //Are we supposed to move subfolders?
          if (recursive == IDYES) {
            //Create the folder
            CreateFolder(new_folder + filename);
            //Call yourself
            parse(old_folder + filename + "\\", new_folder + filename + "\\", recursive);
            }
          }
        //If not a folder
        else {
          //Make sure it's a file
          if (IsFile(old_folder + filename)) {
            //Copy the file to the new location
            rc = CopyFile(old_folder + filename, new_folder + filename);
            }
          }
      }
      //Get the next file
      rc = GetNextFile(hDS);
      //If out of files
      if (rc == ERROR_EOD) {
        //Break out of the loop
        moreFiles = false;
        }
      }
    //Close the parse handle
    CloseHandle(hDS);
    //Return
    return ERROR_NONE;
    }

As you can see, there’s nothing overly complicated. Let’s go ahead and break down what is being done in this script. The main function has to get some user input before we can start the real workhorse function of the script.


void main() {

    string      old, new;
    int         recurse;
    
    //Get original folder
    old = BrowseFolder("Pick Originating Folder");
    //If canceled exit
    if (GetLastError() == ERROR_CANCEL) { 
      return;
      }
    //Get destination folder
    new = BrowseFolder("Pick Destination Folder", old);
    //If canceled bail
    if (GetLastError() == ERROR_CANCEL) { 
      return;
      }
    //Ask if you want to do subfolders
    recurse = YesNoBox('Q', "Move subfolders in addition to files?");
    //If Canceled
    if (recurse == IDCANCEL) {
      return;
      }

    //Start the process
    parse(old, new, recurse);
    
    }

We get two folders from the user: a source folder and a destination folder. I’m not doing very much error checking other than to make sure that the user doesn’t press the cancel button. In reality, you’d want to do more, like making sure the user has write access to the locations and so forth. Also, you’d want to query the user about copying over or replacing files with duplicate names in the new location. For simplicity’s sake and to focus on our recursion, we’ll skip these important checks.


We also ask the user to decide if we should look through subfolders and copy everything there or not. This is where the recursive operation comes into play. Copying everything will run the copy operation recursively on every file, subfolder, and so on until a stop condition is reached (i.e., all of the files have been copied). This means that the script could run for a while depending on the speed of your hard disk and the amount of files and subfolders the folder to be copied has. If the user selects no to whether or not the subfolders should be copied, the script skips folders and only runs on the files in the folder. Finally, if the user closes the dialog rather than pressing yes or no will abort the script at that point. If the user closes the dialog or hits cancel, the script stops and doesn’t execute. Note that the destination folder dialog will start opened to the folder that the user picks for the source folder. This can help the user if he or she is selecting folders that are near one another.


Once we have the user’s preferences, we take that information and we start the parse function. That’s the last thing that the main function has to do, because the parse function takes over here and goes until the script ends.


int parse(string old_folder, string new_folder, int recursive) {

    handle      hDS;
    int         rc;
    string      filename;

    //Start the parse
    hDS = GetFirstFile(old_folder + "*.*");
    //Assume there are more files than this
    boolean moreFiles;
    moreFiles = true;
    
    //Are there more files?
    while (moreFiles) {

We get a file copy started by creating a Legato FolderEnumeration object and getting the handle to it. We pass the object the source folder and add “*.*” to the end of the path. Again, you might want to put more error checking in here sinply to make sure the old_folder variable contains a valid, qualified path and so forth. Appending the “*.*” string means that the file parse will go through every single file in the folder. Next we set a boolean signifying if there are more files to be parsed, and by default we set it to true. This means that when we get to the beginning of the while loop we’re sure that we go through the loop at least once.


      //Get the filename
      filename = GetName(hDS);
      //Is the filename not the home or previous folder?
      if (filename != "." && filename != "..") {
        //Is the file a folder?
        if (IsFolder(old_folder + filename)) {
          //Are we supposed to move subfolders?
          if (recursive == IDYES) {
            //Create the folder
            CreateFolder(new_folder + filename);
            //Call yourself
            parse(old_folder + filename + "\\", new_folder + filename + "\\", recursive);
            }
          }
        //If not a folder
        else {
          //Make sure it's a file
          if (IsFile(old_folder + filename)) {
            //Copy the file to the new location
            rc = CopyFile(old_folder + filename, new_folder + filename);
            }
          }
      }

Next is the meat of the loop. We get the file name of the current file using the GetName SDK function, and then we check to make sure that it’s not the shorthand for the current folder or the parent folder. Next, we check to see if the file that we have is a folder using the IsFolder function. If it is, we check to see if we are supposed to copy the subfolders or not. If yes, we create the folder in the new location via the CreateFolder function and then we call our parse routine again with the old location in the folder and the new location set to the new folder we just created, and then we pass along that we are in recursive mode


If the file is not a folder, we make sure that it is a file with the IsFile function and then we copy the file to the new location. Once more, you could and probably should enhance the error-checking for use in a more public environment, but for our purposes here we’ll keep it simple.


      //Get the next file
      rc = GetNextFile(hDS);
      //If out of files
      if (rc == ERROR_EOD) {
        //Break out of the loop
        moreFiles = false;
        }
      }
    //Close the parse handle
    CloseHandle(hDS);
    //Return
    return ERROR_NONE;
    }

Finally we get the next file in the folder using the GetNextFile function and see if the return code is that there are no more files (ERROR_EOD). If this was the last file in the parse object we set the moreFiles boolean to false, which will break us out of the loop and becomes the end condition for this recursive operation. After the loop ends, we close the handle to our FolderEnumeration object and then we return. You’ll notice I left the return type of this function as an int. This is to allow easy expansion to returning additional information to the previous recursive folder parsing operations. For example, if a file fails, you could report that error, allow it to bubble up the recursive levels, and then have the initial script report that an error was encountered somewhere within it. I’m not currently doing that, but it is a change that could be made pretty easily.


And there we have it, a simple script that loops and recursively runs if necessary until everything in that original folder is copied to its new location. There is a lot of room for expansion on this script. Obviously the error checking we mentioned would be good to add, including checking for an overwrite or replacement condition. Excluding or including only certain file types would be one idea for making this script more functional. Everything depends upon your requirements as a developer. My requirements were simple, but yours may be more complex. This is a good example of taking some simple requirements and turning it into a simple script, while Steve’s example a couple weeks ago on creating a page break template manager was a better example at taking more complex requirements and turning them into a more polished result. This just goes to show that Legato can be used for many different purposes, from simple to complex.


 


Joshua Kwiatkowski is a developer at Novaworks, primarily working on Novaworks’ cloud-based solution, GoFiler Online. He is a graduate of the Rochester Institute of Technology with a Bachelor of Science degree in Game Design and Development. He has been with the company since 2013.

Additional Resources

Novaworks’ Legato Resources

Legato Script Developers LinkedIn Group

Primer: An Introduction to Legato 


Posted by
Joshua Kwiatkowski
in Development at 17:52
Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)
No comments
Add Comment
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
   
 

Quicksearch

Categories

  • XML Accounting
  • XML AICPA News
  • XML FASB News
  • XML GASB News
  • XML IASB News
  • XML Development
  • XML Events
  • XML FERC
  • XML eForms News
  • XML FERC Filing Help
  • XML Filing Technology
  • XML Information Technology
  • XML Investor Education
  • XML MSRB
  • XML EMMA News
  • XML FDTA
  • XML MSRB Filing Help
  • XML Novaworks News
  • XML GoFiler Online Updates
  • XML GoFiler Updates
  • XML XBRLworks Updates
  • XML SEC
  • XML Corporation Finance
  • XML DERA
  • XML EDGAR News
  • XML Investment Management
  • XML SEC Filing Help
  • XML XBRL
  • XML Data Quality Committee
  • XML GRIP Taxonomy
  • XML IFRS Taxonomy
  • XML US GAAP Taxonomy

Calendar

Back May '25 Forward
Mo Tu We Th Fr Sa Su
Sunday, May 18. 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Feeds

  • XML
Sign Up Now
Get SEC news articles and blog posts delivered monthly to your inbox!
Based on the s9y Bulletproof template framework

Compliance

  • FERC
  • EDGAR
  • EMMA

Software

  • GoFiler Suite
  • SEC Exhibit Explorer
  • SEC Extractor
  • XBRLworks
  • Legato Scripting

Company

  • About Novaworks
  • News
  • Site Map
  • Support

Follow Us:

  • LinkedIn
  • YouTube
  • RSS
  • Newsletter
  • © 2024 Novaworks, LLC
  • Privacy
  • Terms of Use
  • Trademarks and Patents
  • Contact Us