*! version 1.0.0  14aug2012  dcs

program define fastcc, rclass

version 10
syntax [ anything ] , ///
       [ ALLDepon(string) ///
         noCopy           ///
         SETtings(string) ///
         SImplemode       ///
         force            ///
         STarbang(passthru) ///
         Verbose          ///
         noProgdrop       ///
         Editadv]         //   undocumented (see copycode devdoc)

if `"`anything'"'!="" {
    capture confirm names `anything'
    if _rc {
        disp as error `"`anything' invalid name."'
        exit 198
    }
    if `: word count `anything''>1 {  // not caught by -confirm names-
        disp as error `"You may supply only one project name at a time."'
        exit 198
    }
    local project = strlower(`"`anything'"')
}

if `"`project'"'!="" & `"`alldepon'"'!="" {
    disp as error `"options 'project' and 'alldepon' are mutually exclusive."'
    exit 197
}

if "`editadv'"!="" {
    if `"`project'"'!="" | `"`alldepon'"'=="" {
        disp as error `"Option 'editadv' is only allowed in combination with option 'alldepon'."'
        exit 197
    }
}

if lower(`"`project'"')=="fastcc" {
    disp as error `"Argument of option 'project' not allowed; fastcc cannot make itself."'
    exit 197
}

if "`verbose'"!="" local verbose noisily

if `"`settings'"' != "" {
    if `"`project'`alldepon'`copy'`simplemode'`force'`starbang'`verbose'`progdrop'`editadv'"' != "" {
        disp as error `"Option 'settings' may not be combined with other arguments."'
        exit 197
    }
    if strpos(`"`settings'"',":") {
        local colonpos = strpos(`"`settings'"',":")
        local paraname = trim(lower(substr(`"`settings'"',1,`colonpos'-1)))
        local paraval  = trim(lower(substr(`"`settings'"',`colonpos'+1,.)))
        local paraname: list clean paraname  // get rid of double quotes if present
        local paraval : list clean paraval
        if !inlist(`"`paraname'"',"targetdir","inputfile") {
            disp as error `"Option settings: only arguments that can receive a value are 'targetdir' and 'inputfile'."'
            exit 197
        }
        if "`paraname'"=="targetdir" {    // may also be empty string; in that case an existing entry is deleted only
            mata: st_local("direxists",strofreal(direxists("`paraval'")))
            if `direxists'==0 {
                disp as error `"Option settings: target directory does not exist"'
                exit 170
            }
        }
        else if "`paraname'"=="inputfile" {
            mata: st_local("fileexists",strofreal(fileexists("`paraval'")))
            if `fileexists'==0 {
                disp as error `"Option settings: input file does not exist"'
                exit 601
            }
        }
        mata: st_local("pathisabs",strofreal(pathisabs("`paraval'")))
        if `pathisabs'==0 {
            disp as error `"Option settings: you must supply an absolute path."'
            exit 198
        }
        capture ini_delete fastcc `paraname'
        ini_write fastcc `paraname' `"`paraval'"'
    }
    else {
        if lower(`"`settings'"') != "list" {
            disp as error `"Option settings: only argument that may not receive a value is 'list'."'
            exit 198
        }
        ini_list fastcc
    }
    exit
}

if `"`project'`settings'`alldepon'"'=="" {
    disp as error `"You must either specify {it:projectname}, option 'alldepon', or option 'settings'."'
    exit 198
}

// READING SETTINGS
if `"`project'`alldepon'"'!="" {
    ini_read fastcc inputfile
    local inputfile `"`inivalue'"'
    capture confirm file `"`inputfile'"'
    if _rc {
        disp as error `"setting of ini entry 'inputfile' invalid."'
        disp as error `"file not found: `inputfile'"'
        exit 601
    }
    if "`copy'"!="nocopy" {
        ini_read fastcc targetdir
        local targetdir `"`inivalue'"'
        mata: st_local("direxists",strofreal(direxists("`targetdir'")))
        if `direxists'==0 {
            disp as error `"setting of ini entry 'targetdir' invalid."'
            disp as error `"directory not found: `targetdir'"'
            exit 170
        }
    }
}

disp as text _n `"  fastcc settings:"'
disp as text    `"    inputfile  : `inputfile'"'
if "`copy'"!="nocopy" disp as text    `"    target dir : `targetdir'"'   // targetdir not relevant if -nocopy- is used
disp ""

if `"`project'"'!="" {
    if "`copy'"!="nocopy" {
        capture `verbose' copycode `project', inputfile(`inputfile') targetfile(`targetdir'/`project'.ado) replace `simplemode' `force' `starbang' `progdrop'
    }
    else {
        capture `verbose' copycode `project', inputfile(`inputfile') nocopy                                replace `simplemode' `force' `starbang'
    }
    local rc = _rc
    if "`verbose'"=="" {
        if `rc' {
            disp as error `"  {bf:failed to process :      `project'}"'
            exit _rc
        }
        else {
            disp as text  `"  {bf:successfully processed : `project'}"'
            if "`copy'"=="nocopy" {
                disp as text _n `"  option 'nocopy' used: output file not created."'
            }
        }
    }
    if `rc' {
        exit _rc
    }

    return add  // return r-results from -copycode-

}

if `"`alldepon'"'!="" {
    local alldepon = trim(lower(`"`alldepon'"'))

    mata: ds_pathparts(`"`alldepon'"')
    local ext `r(ext)'
    if "`ext'"=="" {
        disp as error `"Argument to option -alldepon- must be supplied with file extension."'
        exit 198
    }

    tempname fhandle
    file open `fhandle' using `inputfile', read text
    file read `fhandle' curline

    local space ""
    local projlist

    if "`simplemode'"!="" {
        while !r(eof) {
            if substr(strtrim(`"`curline'"'),1,2)!="//" {
                tokenize `"`curline'"'
                mata: ds_pathparts(strlower(`"`2'"'))
                if `"`r(filename)'"'==`"`alldepon'"' {
                    local projlist `projlist'`space'`1'
                    local space " "
                }
            }
            file read `fhandle' curline
        }
    }
    else {  // higher-order dependencies must now be detected; this is also true if an .stp file or the like is passed!
        local failed_check ""
        local space ""
        local prevproj ""
        while !r(eof) {
            local curline = strtrim(subinstr(`"`curline'"',char(9)," ",.))   // translate out tabs
            if substr(`"`curline'"',1,2)!="//" {
                tokenize `"`curline'"'
                local curproj `1'
                if "`curproj'"!="`prevproj'" {
                    capture copycode `curproj', inp(`inputfile') nocopy `simplemode' noprogdrop
                    if _rc!=0 {
                        local failed_check `failed_check'`space'`curproj'
                        local space " "
                    }
                    local curdep_all `"`r(dep_all)'"'
                    local isdep: list alldepon in curdep_all
                    if `isdep' local projlist `projlist'`space'`curproj'
                    local space " "
                }
                local prevproj `curproj'
            }
            file read `fhandle' curline
        }
    }

    file close `fhandle'

    if "`failed_check'"!="" {
        disp as text _n `"  {bf:projects that could not be checked for dependencies because errors occurred:}"'
        foreach curproj of local failed_check {
            disp as text    `"    `curproj'"'
        }
    }

    if `"`projlist'"'=="" {
        disp as text _n `"  no project is dependent on `alldepon'"'
        return local failed_check `failed_check'
        exit
    }

    if "`editadv'"!="" {
        foreach curproj of local projlist {
            qui findfile `curproj'.adv
            local filelist `"`filelist' "`r(fn)'""'
        }
        winexec "C:\Program Files (x86)\TextPad 5\TextPad.exe" `filelist'
    }
    else {
        local space ""
        local failed_make ""
        if "`copy'"!="nocopy" {
            disp as text _n `"  {bf:`alldepon'-dependent projects processed by fastcc:}"'
        }
        else {
            disp as text _n `"  {bf:projects dependent on `alldepon':}"'
        }
        foreach curproj of local projlist {
            capture program drop `curproj'
            if "`copy'"!="nocopy" {
                capture `verbose' copycode `curproj' , inp(`inputfile') targetfile(`targetdir'/`curproj'.ado) replace `simplemode' `force' `starbang' `progdrop'
            }
            else {
                capture `verbose' copycode `curproj' , inp(`inputfile') nocopy                                        `simplemode'
            }
            local rc = _rc
            if "`verbose'"=="" {
                if `rc'==0 {
                    disp as text  `"    `curproj'"'
                }
                else {
                    disp as error `"    `curproj' (failed)"'
                }
            }
            if `rc'!=0 local failed_make `failed_make'`space'`curproj'
        }

    }
    return local failed_check `failed_check'
    return local failed_make `failed_make'
    return local projectlist `projlist'
}


end





program define ini_read

args cmdname paraname argcheck
if `"`argcheck'"' != "" | `"`paraname'"'=="" {
    disp as error `"`cmdname'.ado, ini_read: must receive exactly two arguments.  Try enclosing args in double quotes."'
    exit 9
}

local fullfilepath `"`c(sysdir_plus)'`=substr(`"`cmdname'"',1,1)'`c(dirsep)'`cmdname'.ini"'
capture confirm file `"`fullfilepath'"'
if _rc {
    disp as error `"`cmdname'.ado, ini_read: ini file not found."'
    exit 9
}
tempname fhandle
quietly file open `fhandle' using `"`fullfilepath'"', read text
file read `fhandle' curline
while !r(eof) {
    tokenize `"`curline'"'  // tokens are enclosed in compound double quotes in file; however, tokenize strips these quotes
    if `"`1'"' == `"`paraname'"' {
        local foundvalue true
        c_local inivalue `2'
        continue, break
    }
    file read `fhandle' curline
}
file close `fhandle'
if "`foundvalue'" == "" {
    disp as error `"`cmdname'.ado, ini_read: no entry in ini file for parameter: `paraname'."'
    exit 9
}

end




program define ini_delete

args cmdname paraname argcheck
if `"`argcheck'"' != "" | `"`paraname'"'=="" {
    disp as error `"`cmdname'.ado, ini_delete: must receive exactly two arguments.  Try enclosing args in double quotes."'
    exit 197
}

local fullfilepath `"`c(sysdir_plus)'`=substr(`"`cmdname'"',1,1)'`c(dirsep)'`cmdname'.ini"'
capture confirm file `"`fullfilepath'"'
if _rc {
    disp as error `"`cmdname'.ado, ini_delete: ini file not found."'
    exit 601
}
tempfile inicopy
tempname copyhandle
file open `copyhandle' using `"`inicopy'"', write text
tempname orighandle
quietly file open `orighandle' using `"`fullfilepath'"', read text
file read `orighandle' curline
while !r(eof) {
    tokenize `"`curline'"'  // tokens are enclosed in compound double quotes in file; however, tokenize strips these quotes
    if `"`1'"' == `"`paraname'"' {
        local foundvalue true
    }
    else {
        file write `copyhandle' `"`curline'"' _n
    }
    file read `orighandle' curline // works if file does not exist???
}
if "`foundvalue'" == "" {
    disp as error `"`cmdname'.ado, ini_delete: no entry in ini file for parameter: `paraname'."'
    exit 197
}
file close `orighandle'
file close `copyhandle'
copy `"`inicopy'"' `"`fullfilepath'"', replace

end




program define ini_write

args cmdname paraname value argcheck
if `"`argcheck'"' != "" | `"`value'"'=="" {
    disp as error `"`cmdname'.ado, ini_write: must receive exactly three arguments.  Try enclosing args in double quotes."'
    exit 9
}

local fullfilepath `"`c(sysdir_plus)'`=substr(`"`cmdname'"',1,1)'`c(dirsep)'`cmdname'.ini"'
tempname fhandle
// check that value does not exist
quietly file open `fhandle' using `"`fullfilepath'"', read write text
local foundvalue true
file read `fhandle' curline
while !r(eof) {
    tokenize `"`curline'"'  // tokens are enclosed in compound double quotes in file; however, tokenize strips these quotes
    if `"`1'"'==`"`paraname'"' {
        if "`foundvalue'" == "true" {
            disp as error `"`cmdname'.ado, ini_write: ini file entry `paraname' already exists."'
            exit 9
        }
        local foundvalue true
    }
    file read `fhandle' curline
}

file write `fhandle' `"`"`paraname'"'   `"`value'"'"' _n

file close `fhandle'

end




program define ini_list

args cmdname argcheck
if `"`argcheck'"' != "" | `"`cmdname'"'=="" {
    disp as error `"`cmdname'.ado, ini_list: must receive exactly one argument.  Try enclosing args in double quotes."'
    exit 9
}

local fullfilepath `"`c(sysdir_plus)'`=substr(`"`cmdname'"',1,1)'`c(dirsep)'`cmdname'.ini"'
capture confirm file `"`fullfilepath'"'
if _rc {
    disp as error `"`cmdname'.ado, ini_list: ini file not found."'
    exit 9
}
tempname fhandle

// determine longest parameter name
quietly file open `fhandle' using `"`fullfilepath'"', read text
file read `fhandle' curline
local maxlen 0
while !r(eof) {
    tokenize `"`curline'"'  // tokens are enclosed in compound double quotes in file; however, tokenize strips these quotes
    local curparalength: length local 1
    if `curparalength' > `maxlen' {
        local maxlen `curparalength'
    }
    file read `fhandle' curline
}

local maxlen = `maxlen' + 4
file seek `fhandle' tof
file read `fhandle' curline
while !r(eof) {
    tokenize `"`curline'"'
    if `"`1'"'!="//" {
        disp as text `"`1':"' as result `"{col `maxlen'}`2'"'
        local ini_paralist `"`ini_paralist' `"`1'"'"'
        local ini_valuelist `"`ini_valuelist' `"`2'"'"'
    }
    file read `fhandle' curline
}
file close `fhandle'
c_local ini_paralist `"`ini_paralist'"'
c_local ini_valuelist `"`ini_valuelist'"'

end



version 10
mata:
mata set matastrict on

void ds_pathparts(string scalar origpath) {
// version 1.0.0  01jul2012  dcs
// break full path into parts: path, filename, root (of filename), extension
// store results in r() macros r(root), r(ext), r(filename), r(path)
//
// rules
// - to get a r(filename), r(ext), r(root), there must be a dot present in the last element of the string supplied
//   multiple dots in filename are allowed; the last one defines the extension
//   if no dot is present in the last element of the string supplied, everything is packed into r(path)
// - to get r(path), there must either be
//     no dot in the last elem of the path or
//     if a dot is present, there must be a dir separator
// - if a colon is present, it must be preceeded by some string, otherwise the function errors out
// - the first ending directory separator is removed from r(path); so normally r(path) does not end in a dir separator
//   r(path) only contains a separator for the root dir (e.g. "c:\")
//   it also contains separators if multiple separators are passed
//     i.e. passing "mydir//a.lst" will be plit into "mydir/" and "a.lst"
// - r(ext) contains a dot as the first character
// - r-values of missing path parts are not returned (e.g. if only the filename is supplied, r(path) is missing)
// - path may contain blanks
// - dots in paths are allowed

    string scalar path,
                  filename,
                  ext,
                  jnk
    real scalar numdots

    pragma unset path
    pragma unset filename

    pathsplit(origpath, path, filename)
    ext = pathsuffix(origpath)
    if (ext == "") {     // no extension exists => last elem of path is assumed to be part of directory path and not a file name
        path = pathjoin(path, filename)
        filename = ""
    }

    st_rclear()
    st_global("r(path)", path)
    st_global("r(filename)", filename)
    st_global("r(ext)", ext)

    // getting root of filename: account for possibility of several dots in filename
    if (filename != "") {
        jnk = subinstr(filename, ".", "")
        numdots = strlen(filename) - strlen(jnk)
        if (numdots == 0) {
            st_global("r(root)", filename)
        }
        if (numdots == 1 & strpos(filename, ".") > 1) {
            st_global("r(root)", substr(filename, 1, strpos(filename, ".") - 1))
        }
        if (numdots > 1) {
            st_global("r(root)", substr(filename, 1, strlen(filename) - strpos(strreverse(filename), ".")))
        }
    }
}
end