/* unziparch.sas */

/*** HELP START ***//*
 
## >>> `%unzipArch()` macro: <<< <a name="unziparch-macro"></a> #######################  

The unzipArch() macro allows to unzip content of a ZIP archive. 
Macro is OS-independent, the `XCMD` option is not required.

The `dlCreateDir` option is used under the hood.

Content of unzipped archive can be listed in the log.

Source files can be deleted after decompression.
Errors of decompression and are reported. If any occur
the deletion is suspended.

See examples below for the details.

### SYNTAX: ###################################################################

The basic syntax is the following, the `<...>` means optional parameters:
~~~~~~~~~~~~~~~~~~~~~~~sas
%unzipArch(
    archName
  <,path=>
  <,pathRef=>
  <,target=>
  <,targetRef=>
  <,list=> 
  <,clean=>
)
~~~~~~~~~~~~~~~~~~~~~~~

**Arguments description**:

1. `archName`      - *Required*, name of the ZIP archive to be extracted.
                     Name should be full, i.e., with the extension!

* `path=`          - *Optional*, a path pointing to zipped file location.
                     The path should be provided unquoted.
                     Default value is `WORK` location.

* `pathRef=`       - *Optional*, a fileref to path pointing to zipped file location.
                     The `path`, if not null, has precedense over the `pathRef`.

* `target=`        - *Optional*, a path pointing to target location where
                     files will be extracted.
                     The path should be provided unquoted.
                     Default value is `WORK` location.

* `target=`        - *Optional*, a fileref to path pointing to target location where
                     files will be extracted.
                     The `target`, if not null, has precedense over the `targetRef`.

* `list = 0`       - *Optional*, default value is `0`,
                     indicates if zip content should be listed in the log. 
                     `1` means *yes*, `0` means *no*. 

* `clean = 0`      - *Optional*, default value is `0`,
                     indicates if zip file should be deleted after unzipping. 
                     `1` means *yes*, `0` means *no*. 

---

*//*** HELP END ***/


%macro unzipArch(
  archName /* name of a ZIP file (no extension) */
, path =   /* location of a ZIP */
, pathRef = /* fileref to location of a ZIP */
, target = /* a path in which the content will be generated,
              if empty default location is SAS work */
, targetRef = /* fileref to a path in which the content will be generated */
, list = 0 /*indicates should extracted data be listed */
, clean = 0 /* should ZIP file be deleted after unzipping */
)/des = 'Macro to unzip an archive.';
%local zip;
%let zip = zip;


%local notes_tmp source_tmp stimer_tmp fullstimer_tmp;
%let notes_tmp      = %sysfunc(getoption(notes));
%let source_tmp     = %sysfunc(getoption(source));
%let stimer_tmp     = %sysfunc(getoption(stimer));
%let fullstimer_tmp = %sysfunc(getoption(fullstimer));
%let msglevel_tmp   = %sysfunc(getoption(msglevel));
options NOnotes NOsource NOfullstimer NOstimer msglevel=N;

%local _PackageFileref_ _TargetFileref_;
data _null_; 
  call symputX("_PackageFileref_", "A" !! put(MD5(symget("archName")), hex7. -L), "L"); 
  call symputX("_TargetFileref_",  "T" !! put(MD5(symget("archName")), hex7. -L), "L"); 
run;

%if %superq(path)= %then
  %do;
    %if %superq(pathRef)= %then
      %do;
        %let path = %sysfunc(pathname(WORK));
        %put NOTE:[&sysmacroname.] Parameters PATH and PATHREF are empty.;
        %put NOTE-[&sysmacroname.] The WORK library location will be used.;
      %end;
    %else
      %let path = %sysfunc(pathname(%superq(pathRef)));
  %end;

%if %superq(target)= %then
  %do;
    %if %superq(targetRef)= %then
      %do;
        %let target = %sysfunc(pathname(WORK));
        %put NOTE:[&sysmacroname.] Parameters TARGET and TARGETREF are empty.;
        %put NOTE-[&sysmacroname.] The WORK library location will be used.;
      %end;
    %else
      %let target = %sysfunc(pathname(%superq(targetRef)));
  %end;



filename &_PackageFileref_. &ZIP. 
  "%sysfunc(dequote(&path.))/&archName."
;

filename &_TargetFileref_. 
  "%sysfunc(dequote(&target.))"
;

/* restore optionos */
options &notes_tmp. &source_tmp. 
        &stimer_tmp. &fullstimer_tmp.
        msglevel=&msglevel_tmp.;


%if %superq(list)=1 %then
  %do;
    filename &_PackageFileref_. list;
    filename &_TargetFileref_. list;
    %let list = options msglevel=i notes;
  %end;
%else %let list=;

%if %sysfunc(fexist(&_PackageFileref_.)) %then
  %do;
    %if %sysfunc(fexist(&_TargetFileref_.)) %then
      %do;
        %local rc;
        %let rc = %sysfunc(doSubL(%str(
          options dlCreateDir;
          options nostimer nofullstimer nosource nonotes msglevel=N ps=min;
          data WORK.__&_TargetFileref_._zip___;
            did = dopen("&_PackageFileref_.");
            if not did then 
              do;
                put "ERROR: Can not access content data.";
                stop;
              end;
            if did then
             do i=1 to dnum(did);
              length file $ 8192;
              file = dread(did, i);
              output;
              keep file;
             end;
            did = dclose(did);
          run;
          &list.;
          data _null_; 
            set WORK.__&_TargetFileref_._zip___ end = EOF;
            wc = countw(file,"/\");
          
            length libText pathname_f $ 8192;
            libText = pathname(symget("_TargetFileref_"), "F");
         
            if scan(file, wc , "/\") = "" then
              do j = 1 to wc-1;
                libText = catx("/", libText, scan(file, j , "/\"));
                rc = libname("test", libText);
                rc = libname("test");
              end;
            else
              do;
                do j = 1 to wc-1;
                  libText = catx("/", libText, scan(file, j , "/\"));
                  rc = libname("test", libText);
                  rc = libname("test");
                end;

                pathname_f = pathname(symget("_PackageFileref_"));
                rc1 = filename("in", strip(pathname_f), "zip", "member='" !! strip(file) !! "' lrecl=1 recfm=n");
                length rc1msg $ 8192;
                rc1msg = sysmsg();
                rc2 = filename("out", catx("/", libText, scan(file, j , "/\")), "disk", "lrecl=1 recfm=n");
                length rc2msg $ 8192;
                rc2msg = sysmsg();
              
                rc3 = fcopy("in", "out");
                length rc3msg $ 8192;
                rc3msg = sysmsg();
          
                loadingProblem + (rc3 & 1);

                if rc3 then
                  do;
                    put "ERROR:[&sysmacroname] Cannot extract: " file;
                    put (rc1 rc2 rc3) (=); 
                    put (rc1msg rc2msg rc3msg) (/);
                    put "ERROR-"; 
                  end;
                crc1=filename("in");
                crc2=filename("out");
              end;

              if EOF and loadingProblem then
                do;
                  put "ERROR:[&sysmacroname.] Not all files from ZIP Content were extracted successfully!";
                  if input(symget('clean'),??best.) ne 0 then
                    do;
                      call symputX('clean',0);
                      put "ERROR-[&sysmacroname.] The ZIP file deletion is suspended.";  
                    end;
                end;
          run;
          options nonotes msglevel=N;
          proc delete data = WORK.__&_TargetFileref_._zip___;
          run;
        )));        
      %end;
    %else %put ERROR:[&sysmacroname.] Directory "&target." does not exist!;

    /* delete zip */
    %if %superq(clean) = 1 %then
      %do;
        data _null_;
          put "[&sysmacroname.] File &archName. will be deleted.;";
          rcClean = fdelete(symget('_PackageFileref_'));
          put "[&sysmacroname.] Deletion status is " rcClean= "(0=success).";
        run;
      %end;

  %end;
%else %put ERROR:[&sysmacroname.] File "&path./&archName." does not exist!;

filename &_TargetFileref_. clear;
filename &_PackageFileref_. clear;
  
%mend unzipArch;


/*** HELP START ***//*

### EXAMPLES AND USECASES: ####################################################

**EXAMPLE 1.** Unzip compressed archive. Example requires the `basePlus` package.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas

filename arch ZIP "%workPath()/testArch.zip";

data _null_;
  file arch(abc/test1.txt);
  put "text for test file 1";
data _null_;
  file arch(abc/subdir/test2.txt);
  put "text for test file 2";
data _null_;
  file arch(abc/subdir/test3.txt);
  put "text for test file 3";
run;

%unzipArch(
  testArch.zip 
, path = %workPath()
, target = %workPath()
, list=1
);



filename pR "%workPath()";

%unzipArch(
  testArch.zip 
, pathRef = pR
, targetRef = pR
, clean=1
);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

---

*//*** HELP END ***/

/**###################################################################**/
/*                                                                     */
/*  Copyright Bartosz Jablonski, since 2023.                           */
/*                                                                     */
/*  Code is under the MIT license. If you want - you can use it.       */
/*  But it comes with absolutely no warranty whatsoever.               */
/*  If you cause any damage or something - it will be your own fault.  */
/*  You've been warned! You are using it on your own risk.             */
/*  However, if you decide to use it don't forget to mention author.   */
/*  Bartosz Jablonski (yabwon@gmail.com)                               */
/*                                                                     */
/**###################################################################**/
