*! NJC 1.2.3 5 February 2002
*! NJC 1.2.2 24 September 2001
* each tab replaced by spaces
program define ds3, rclass
    version 7

    * update for Stata/SE 1 February 2002
    local smax = cond("$S_StataSE" == "SE", 245, 81)

    syntax [varlist] [if] [in] [, NUMeric STRing1 byte int long float double /*
    */ STRing2(numlist int >0 <`smax') COmplement ANY(str asis) ALL(str asis) /*
    */ NONE(str asis) HAS(str) NOT(str) case Detail /*
    */ COLs(numlist int max=1 >0 <13) PLACEholder(str) LOcal(str) GLobal(str) ]

    * checking for number of has(), not(), any(), all(), none() options
    local ntopts = 0
    foreach opt in has not any all none {
        if `"``opt''"' != "" {
            local testopts "`testopts'`opt'"
            local Testopts "`Testopts'`opt' "
            local ntopts = `ntopts' + 1
        }
    }

    if `ntopts' > 1 {
        di as err "may not combine `Testopts'options"
        exit 198
    }

    if "`placeholder'" != "" {
        local np : word count `placeholder'
        if `np' > 1 {
            di as err "placeholder must be single symbol or word"
            exit 198
        }
        local X "`placeholder'"
    }
    else local X "X"

    if `"`any'`all'`none'"' != "" {
        if !index(`"`any'`all'`none'"',"`X'") {
            di as err "`testopts' does not contain `X'"
            exit 198
        }

        * identifying conditions which will fail with numbers | strings
        * test condition, first substituting the number 1
        local thistest : subinstr local `testopts' "`X'" "1", all
        capture count if `thistest'
        local numtest = cond(_rc, 0, 1)

        * next substituting string "a"
        local thistest : subinstr local `testopts' "`X'" `""a""', all
        capture count if `thistest'
        local strtest = cond(_rc, 0, 1)
        if !`numtest' & !`strtest' { /* failed both */
                di as err "invalid `testopts' option"
                exit 198
        }
    }

    if `"`not'"' != "" {
        local has "`not'"
        local not 1
    }
    else local not 0

    if `"`has'"' != "" {
        * what kind of thing? which particular thing(s)?
        tokenize `has'
        args what
        mac shift
        local which "`*'"

        * first element should start var | val | f | c
        local what = lower("`what'")

        local l = length("`what'")

        if "`what'" == substr("varlabel",1,max(4,`l')) {
            local what "varl"
        }
        else if "`what'" == substr("vallabel",1,max(4,`l')) {
            local what "vall"
        }
        else if "`what'" == substr("format",1,max(1,`l')) {
            if "`which'" == "" { BadHas `not' }
            local what "f"
        }
        else if "`what'" == substr("char",1,max(1,`l')) {
            local what "c"
        }
        else BadHas `not'

        * to lower case: fewer problems if `which' is longer than 80 chars
        if "`case'" != "" {
            local case 1
            foreach word in `which' {
                local lower = lower("`word'")
                local which2 "`which2' `lower'"
            }
            local which "`which2'"
        }
        else local case 0
    }

    * what restrictions on types of variables?
    local nopts : word count /*
    */ `numeric' `string1' `byte' `int' `long' `float' `double' `string2'

    if `nopts' == 0 { /* none */
        local list "`varlist'"
    }
    else { /* some */
        foreach opt in numeric string1 {
            local `opt' = "``opt''" != ""
        }

        local types "`byte' `int' `long' `float' `double'"
        if "`string2'" != "" {
            foreach n of local string2 {
                local types "`types'str`n' "
            }
        }
        local ntypes : word count `types'

        foreach x of local varlist {
            local OK 0
            if `string1' | `numeric' {
                capture confirm string variable `x'
                local isstr = _rc == 0
                if `isstr' & `string1' {
                    local OK 1
                }
                if !`isstr' & `numeric' {
                    local OK 1
                }
            }
            if `ntypes' {
                local type : type `x'
                foreach t of local types {
                    if "`t'" == "`type'" {
                        local OK 1
                    }
                }
            }
            if `OK' { local list "`list'`x' " }
        }
    }

    * complement of varlist?
    if "`complement'" != "" {
        foreach y of varlist _all {
            local found 0
            foreach x of local list {
                if "`x'" == "`y'" {
                    local found 1
                    continue, break
                }
            }
            if !`found' { local clist "`clist'`y' " }
        }
        local list "`clist'"
    }

    if `ntopts' == 0 { tokenize `list' }

    * implementation of has() or not()
    if `"`has'"' != "" {
        * variable or value labels
        if "`what'" == "varl" | "`what'" == "vall" {
            local kind = /*
            */ cond("`what'" == "varl", "variable", "value")
            if `"`which'"' == "" { /* any label */
                local op = cond(`not', "==", "!=")
                foreach x of local list {
                    local lbl : `kind' label `x'
                    if `"`lbl'"' `op' "" {
                        local list2 "`list2'`x' "
                    }
                }
            }
            else { /* some label pattern */
                if `not' { /* must match no pattern */
                    foreach x of local list {
                        local lbl : `kind' label `x'
                        if `case' {
                            local lbl = lower(`"`lbl'"')
                        }
                        local found 0
                        foreach w of local which {
                            if match(`"`lbl'"',`"`w'"') {
                                local found 1
                                continue, break
                            }
                        }
                        if !`found' {
                            local list2 "`list2' `x'"
                        }
                    }
                }
                else { /* can match any pattern */
                    foreach x of local list {
                        local lbl : `kind' label `x'
                        if `case' {
                            local lbl = lower(`"`lbl'"')
                        }
                        foreach w of local which {
                            if match(`"`lbl'"',`"`w'"') {
                                local list2 "`list2'`x' "
                                continue, break
                            }
                        }
                    }
                }
            }
        } /* end of code for variable or value labels */
        * formats
        else if "`what'" == "f" {
            if `not' { /* must match no pattern */
                foreach x of local list {
                    local fmt : format `x'
                    if `case' {
                        local fmt = lower(`"`fmt'"')
                    }
                    local found 0
                    foreach w of local which {
                        if match(`"`fmt'"',`"`w'"') | /*
                        */ match(`"`fmt'"',`"%`w'"') {
                            local found 1
                            continue, break
                        }
                    }
                    if !`found' {
                        local list2 "`list2' `x'"
                    }
                }
            }
            else { /* can match any pattern */
                foreach x of local list {
                    local fmt : format `x'
                    if `case' {
                        local fmt = lower(`"`fmt'"')
                    }
                    foreach w of local which {
                        if match(`"`fmt'"',`"`w'"') | /*
                        */ match(`"`fmt'"',`"%`w'"') {
                            local list2 "`list2'`x' "
                            continue, break
                        }
                    }
                }
            }
        } /* end of code for formats */
        * characteristics
        else {
            if `"`which'"' == "" { /* any char */
                local op = cond(`not', "==", "!=")
                foreach x of local list {
                    local chr : char `x'[]
                    if `"`chr'"' `op' "" {
                        local list2 "`list2'`x' "
                    }
                }
            }
            else { /* some char pattern */
                if `not' { /* must match no pattern */
                    foreach x of local list {
                        local chr : char `x'[]
                        local found 0
                        foreach c of local chr {
                            if `case' {
                                local c = lower(`"`c'"')
                            }
                            foreach w of local which {
                                if match(`"`c'"',`"`w'"') {
                                    local found 1
                                    continue, break
                                }
                            }
                        }
                        if !`found' {
                            local list2 "`list2' `x'"
                        }
                    }
                }
                else { /* can match any pattern */
                    foreach x of local list {
                        local chr : char `x'[]
                        local found 0
                        foreach c of local chr {
                            if `case' {
                                local c = lower(`"`c'"')
                            }
                            foreach w of local which {
                                if match(`"`c'"',`"`w'"') {
                                    local found 1
                                    local list2 "`list2'`x' "
                                    continue, break
                                }
                            }
                            if `found' { continue, break }
                        }
                    }
                }
            }
        } /* end of code for characteristics */

        tokenize `list2'
    }

    * implementation of any(), all(), none()
    if `"`any'`all'`none'"' != "" {
        marksample touse, strok novarlist
        qui count if `touse'
        local N = r(N)

        foreach v of local list {
            capture confirm string variable `v'
            local isstr = _rc == 0
            if (`isstr' & `strtest') | (!`isstr' & `numtest') {
                local thistest : subinstr local `testopts' "`X'" "`v'", all
                qui count if `thistest' & `touse'
                if "`testopts'" == "any" & r(N) > 0 {
                    local list2 "`list2'`v' "
                }
                else if "`testopts'" == "none" & r(N) == 0 {
                    local list2 "`list2'`v' "
                }
                else if "`testopts'" == "all" & r(N) == `N' {
                    local list2 "`list2'`v' "
                }
            }
        }

        tokenize `list2'
    }

    if "`*'" != "" {
        if "`detail'" != "" { describe `*' }
        else {
            if "`cols'" == "" { local cols = 8 }

            if `cols' == 1 { local length = 78 }
            else if `cols' == 2 { local length = 38 }
            else if `cols' == 3 { local length = 24 }
            else if `cols' == 4 { local length = 18 }
            else if `cols' == 5 { local length = 14 }
            else if `cols' == 6 { local length = 11 }
            else if `cols' == 7 { local length = 9 }
            else if `cols' == 8 { local length = 8 }
            else if `cols' == 9 { local length = 6 }
            else if `cols' == 10 { local length = 6 }
            else if `cols' == 11 { local length = 5 }
            else if `cols' == 12 { local length = 4 }

            local lp1 = `length' + 1
            local i 1
            while "``i''" != "" {
                if (mod(`i' - 1, `cols') == 0 & `i' != 1) { di }
                local abname = abbrev("``i''",`length')
                local l = `lp1' - length("`abname'") + (mod(`i', `cols') != 0)
                di in gr "`abname'" _skip(`l') _c
                local i = `i' + 1
            }
            di
        }
        return local varlist `*'
    }

    * undocumented, for now
    if "`local'" != "" { c_local `local' "`*'" }
    if "`global'" != "" { global `global' "`*'" }
end

program def BadHas
    args not
    if `not' {
        di as err "invalid not() option"
    }
    else di as err "invalid has() option"
    exit 198
end
exit