program define docvars
*!  DOCVARS.ADO     by Mead Over    Version 2.1  30Aug2022

/*
This program takes as input a STATA data file and produces as output
a new data file which contains information on the variables in the
source file, including the following characteristics:

variable name
variable type (Actual & Potential)
format
value label
variable label
number of observations
number of missing values
number of non-sequentially missing values
number of obs + number "justifiably" missing   //  Added 20Jun2017
mean
standard deviation
minimum
maximum
number of negative values
number of negative integers
number of zeros
number of positive values
number of positive integers
number of unique values (non-missing)
number of values without labels
number of values of a string variable that can be converted to numeric values
mode  //  Added 30Aug2022

*/

version 11.1
syntax [anything] [using/] , [ clear DTAname(string) SAving(string asis) compress real mychars(string) JUSTified(passthru)]

if `"`using'"' ~="" {
	use `"`using'"' , `clear'
}

local varlist `"`anything'"'

if "`dtaname'" == "" { /* If dtaname is not specified, set it to the name of the file in memory. */
	local dtaname  `c(filename)'
}

qui ds          /* Get the varlist in memory */
	if "`varlist'"~= "" {
		bothlist `varlist' \ `r(varlist)'
		local vars `r(list)'
	}
	else {
		local vars `r(varlist)'
	}
	local nvar : word count `vars'
	local chunk = int(c(maxvar)/60) * 10      /* Set size of chunk of variables */
	local niter = int(`nvar'/`chunk') + 1   /* Number of iterations required at 1 per chunk */

if `niter' > 1 {
	tempfile tmpdata tmpdocv
	qui save `tmpdata'
}

local i = 1
local vlist `vars'
local rstlist
while `i' <= `niter' {

		local lastqno = (`i'-1)*`chunk'

		if `i'==`niter' {
			local chunk : word count `vlist'
		}

		takelist `vlist', pos(1/`chunk')
			local vars `r(list)'
		dellist  `vlist', pos(1/`chunk')
			local vlist `r(list)'

		keep `vars'

		addchars `vars' , real `justified'

		tempfile tf`i'

		qui descsave `vars', charlist(nobsmax nobs nmiss nonseqms mean sd  min max  ///
			nneg nnegint nzero npos nposint nunique nundoc nreal nobsjms mode `mychars')  ///
			saving(`tf`i'', replace) idstr(`dtaname') `compress'

		qui use `tf`i'', clear
		drop order
		if "`dtaname'"~="" {
			label var idstr "File identifier"
		}
		//  Convert the additional characteristics to other variables
		qui gen long nobsmax=real(char1)
			label var nobsmax "No of obs. this data set"
		qui gen long nobs   =real(char2)
			label var nobs "No of obs. this variable"
			notes nobs  : The number of non-missing observations applies to both numeric and string variables.
		qui gen long nmiss  =real(char3)
			label var nmiss "No. of missing"
			notes nmiss : The definition of missing includes both numeric variables (i.e. .) and string variables (i.e. blank).
		qui gen long nonseqms  =real(char4)
			label var nonseqms "No. of non-sequentially missing"
			notes nonseqms : The number of times this variable is missing when the previous variable in the same file was not missing.
			notes nonseqms : The definition of missing includes both numeric variables (i.e. .) and string variables (i.e. blank).
		qui gen mean   =real(char5)
			label var mean "Mean"
		qui gen sd     =real(char6)
			label var sd "Std. deviation"
		qui gen min    =real(char7)
			label var min "Minimum"
		qui gen max    =real(char8)
			label var max "Maximum"
		qui gen long nneg   =real(char9)
			label var nneg "Number of negative values"
		qui gen long nnegint=real(char10)
			label var nnegint "Number of negative integers"
		qui gen long nzero  =real(char11)
			label var nzero "Number of zero values"
		qui gen long npos   =real(char12)
			label var npos "Number of positive values"
		qui gen long nposint=real(char13)
			label var nposint "Number of positive integers"
		qui gen long nunique=real(char14)
			label var nunique "Number of unique values"
		qui gen long nundoc =real(char15)
			label var nundoc "Number of values w/o labels"
		qui gen long nreal =real(char16)
			qui replace nreal = . if int(nreal) == -9
			label var nreal "Number of string values convertable to real numbers"
		qui gen long nobsjms =real(char17)
			label var nobsjms `"Number of observations plus number of justifiably missing (`justified')"'
		qui gen str mode =  char18
			label var mode "Mode"
		qui gen quesno = _n + `lastqno'
			label var quesno "Question sequence in data file"

		qui drop char*
		qui compress
		order quesno
		sort quesno

		if "`using'"~="" & "`dtaname'" ~= "" {
			label data "List variables in `using' with idstr = `dtaname'"
		}

		if `niter'==1 {
			if `"`saving'"'~="" {
				save `saving'
			}
		}
		else {
			if `i' > 1 {
				qui  save `tf`i'', replace
*				di _n in gr "Use tmpdocv, clear"
				qui  use `tmpdocv', clear
*				di _n in gr "append using tf`i' "
				qui  append using `tf`i''
			}
			if `i' < `niter' {
*				di _n in gr "Save tmpdocv"
				qui  save `tmpdocv', replace
			}
			else {
				if `"`saving'"'~="" {
				save `saving'
				}
			}
		}

		if `niter' > 1 {
			use `tmpdata', clear
		}

		local i = `i' + 1
}


end  /* End of main program */

*********************************************************************

* Create variable characteristics containing descriptive statistics
*	These characteristics are then added to the other variable data
*	by -descsave.ado-
program def addchars
version 11.1
syntax [varlist] , real [ justified(string) ]

local nvar:word count `varlist'

qui describe
	local nobsmax = `r(N)'

local i1=0
while(`i1'<`nvar') {
  local i1=`i1'+1
  local i0=`i1'-1
  local varcur: word `i1' of `varlist'
  if `i1' == 1 {
      local nonseqms = .
  }
  else {
    local varlst: word `i0' of `varlist'
    tempvar thisms`i1' nmiss`i1' `nonseqms`i1'
    egen byte `thisms`i1'' = rmiss( `varcur' )
    egen byte `nmiss`i1'' = rmiss( `varcur' `varlst' )
    gen byte `nonseqms`i1'' = `nmiss`i1''==1 & `thisms`i1''==1
    qui summarize `nonseqms`i1''
      local nonseqms = r(sum)
  }

    tempvar ms
    egen `ms' = rmiss(`varcur')
    qui summarize `ms'
    local nmiss = `r(sum)'
	local tp : type `varcur'

  qui summarize `varcur'
	local n_obs = `r(N)'
	char /* 1*/ define `varcur'[nobsmax]   `nobsmax'
	char /* 2*/ define `varcur'[nobs]      `r(N)'
	char /* 3*/ define `varcur'[nmiss]     `nmiss'
	char /* 4*/ define `varcur'[nonseqms]  `nonseqms'
	char /* 5*/ define `varcur'[mean]      `r(mean)'
	char /* 6*/ define `varcur'[sd]        `r(sd)'
	char /* 7*/ define `varcur'[min]       `r(min)'
	char /* 8*/ define `varcur'[max]       `r(max)'

  if substr("`tp'",1,3)=="str"  {
	  qui count if `varcur'~=""
		char /* 2*/ define `varcur'[nobs]      `r(N)'
	  qui count if `varcur'==""
		char /* 3*/ define `varcur'[nmiss]     `r(N)'
  }

  local vlbl : value label `varcur'
  qui inspect `varcur'
	char /* 9*/ define `varcur'[nneg]      `r(N_neg)'
	char /*10*/ define `varcur'[nnegint]   `r(N_negint)'
	char /*11*/ define `varcur'[nzero]     `r(N_0)'
	char /*12*/ define `varcur'[npos]      `r(N_pos)'
	char /*13*/ define `varcur'[nposint]   `r(N_posint)'
*	char /*14*/ define `varcur'[nunique]   `r(N_unique)'
	char /*15*/ define `varcur'[nundoc]    `r(N_undoc)'
	if `r(N_undoc)'==. & "`vlbl'"~="" {
        char define `varcur'[nundoc]    `n_obs'
	}

*	To count distinct values, use -distinct- if it is installed
	capture which distinct
	if _rc == 0 {
		qui distinct `varcur' // Excludes missing values unless option -missing- specified
		char /*14*/ define `varcur'[nunique]   `r(ndistinct)'
	}
	else {
		di as err "This program's computation of the number of non-missing unique observations"  ///
			_n "works best using the user-written program -distinct- published by Cox and Longton"  ///
			_n "in Stata Journal 15(3), p.899. Install that program to improve the calculation of -nunique-"  ///
			_n "In the absence of -distinct-, -docvars- attempts to use Stata's command -levelsof-."  ///
			_n "Execution continuing."

		qui levelsof `varcur'
		char /*14*/ define `varcur'[nunique]   `r(r)'
	}

*	Count how many observations in each string variable can be converted to a real value.
	if "`real'"~="" {
		if substr("`tp'",1,3)=="str"  {
			tempvar tmpreal
			qui gen `tmpreal' = real(`varcur')
				qui count if `tmpreal' ~=. & `tmpreal' ~= 0
			char /*16*/ define `varcur'[nreal]    `r(N)'
		}
		else {
			char /*16*/ define `varcur'[nreal]   -9
		}
	}

*	Count how many missing observations would be non-missing if the `justified' condition is satisfied
	//	Code added 20Jun2017
	if "`justified'"~=""  {
		if substr("`tp'",1,3)=="str"  {
			count if `varcur'~=""  // So "55" is not blank and thus adds to the number of obsd
			char /*17*/ define `varcur'[nobsjms]    `r(N)'
		}
		else {
			count if `varcur' `justified'  //  e.g. if `varcur' <=. , . are OK but .a, .b, .c are unjustifiably missing
			char /*17*/ define `varcur'[nobsjms]   `r(N)'
		}
	}

*	Find and store the mode, which may be numeric or string,
*	but will be stored and displayed as a string.
	//	Code added 30Aug2022
	tempvar modeofvarcur
	//  If there are multiple equal modes, display the minimum
	qui egen `modeofvarcur' = mode(`varcur') , minmode
	qui levelsof `modeofvarcur'
	drop `modeofvarcur'

	char /*18*/ define `varcur'[mode]   `"`r(levels)'"'

}
end		/* End program addchars  */

*********************************************************************

program def bothlist, rclass
* NJC 1.3.0 6 June 2000
* NJC 1.2.0 31 Jan 2000
* NJC 1.1.0 22 Dec 1999
* NJC 1.0.0 21 Dec 1999
	version 6.0
	gettoken lists 0 : 0, parse(",")
	if "`lists'" == "" | "`lists'" == "," { /* no \ */
		di in r "incorrect syntax: no separator"
		exit 198
	}

	tokenize "`lists'", parse("\")
	if "`4'" != "" {
		di in r "incorrect syntax: too much stuff"
		exit 198
	}
	if "`1'" == "\" { /* list1 empty */
		if "`2'" == "\" {
			di in r "incorrect syntax: one \ only"
			exit 198
		}
		local list2 "`2'" /* might be empty */
	}
	else if "`2'" == "\" {
		local list1 "`1'"
		local list2 "`3'" /* might be empty */
	}
	else {
		di in r "incorrect syntax: what to compare?"
		exit 198
	}

	syntax [ , Global(str) Noisily ]

	if length("`global'") > 8 {
		di in r "global name must be <=8 characters"
		exit 198
	}

	* remove duplicates from `list1'

	tokenize `list1'
	local list1 "`1'"
	mac shift

	while "`1'" != "" {
		local n1 : word count `list1'
		local i = 1
		local putin = 1
		while `i' <= `n1' {
			local word : word `i' of `list1'
			if "`word'" == "`1'" {
				local putin = 0
				local i = `n1'
			}
			local i = `i' + 1
		}
		if `putin' { local list1 "`list1' `1'" }
		mac shift
	}

	* what is in both lists?

	local n1 : word count `list1'
	tokenize `list1'
	local n2 : word count `list2'

	local i = 1
	while `i' <= `n1' {
		local j = 1
		local putin = 0
		while `j' <= `n2' {
			local word : word `j' of `list2'
			if "`word'" == "``i''" {
				local putin = 1
				local j = `n2'
			}
			local j = `j' + 1
		}
		if `putin' { local newlist "`newlist' ``i''" }
		local i = `i' + 1
	}

	if "`noisily'" != "" { di "`newlist'" }
	if "`global'" != "" { global `global' "`newlist'" }
	return local list `newlist'
end

*********************************************************************

program def dellist, rclass
* NJC 1.5.0 14 December 2000
* NJC 1.4.0 6 Sept 2000
* NJC 1.3.0 6 June 2000
* NJC 1.2.0 31 Jan 2000
* NJC 1.1.0 22 Dec 1999
* NJC 1.0.0 22 Sept 1999
	version 6.0
	gettoken list 0 : 0, parse(",")
	if "`list'" == "" | "`list'" == "," {
		di in r "nothing in list"
		exit 198
	}

	local nlist : word count `list'

	syntax , [ Delete(str) Pos(numlist sort int >=-`nlist' <=`nlist') /*
	*/ Exact All Global(str) Noisily ]

	local nopts = ("`delete'" != "") + ("`pos'" != "")
	if `nopts' != 1 {
		di in r "must specify one of delete( ) and pos( )"
		exit 198
	}

	if length("`global'") > 8 {
		di in r "global name must be <=8 characters"
		exit 198
	}

	if "`exact'" != "" & "`all'" != ""  {
		di in r "all option not allowed with exact option"
		exit 198
	}

	tokenize `list'

	if "`delete'" != "" {
		local i = 1
		while `i' <= `nlist' {
			local len = length("``i''")
			if `len' > 80 {
				di in r "cannot handle word length > 80"
				exit 498
			}
			local i = `i' + 1
		}

		tknz `delete', s(d)
		local nd : word count `delete'

		if "`exact'" != "" {
			while "`1'" != "" {
				local i = 1
				local OK = 0
				while `i' <= `nd' & !`OK' {
					local OK = "`1'" == "`d`i''"
					local i = `i' + 1
				}
				if !`OK' { local newlist "`newlist'`1' " }
				mac shift
			}
		}
		else {
			while "`1'" != "" {
				local i = 1
				local OK = 0
				while `i' <= `nd' {
					local thisOK = /*
					*/ index("`1'", "`d`i''") > 0
					local OK = `OK' + `thisOK'
					local i = `i' + 1
				}
				if "`all'" != "" { local OK = `OK' == `nd' }
	 			if !`OK' { local newlist "`newlist'`1' " }
				mac shift
			}
		}
	}
	else {
		local np1 = `nlist' + 1
		tknz `pos' `np1', s(p)
		local np : word count `pos'

		* negative indexes to positive
		local i = 1
		while `p`i'' < 0 {
			local p`i' = `nlist' + 1 + `p`i''
			local i = `i' + 1
		}

		local i = 1
		local j = 1
		while `i' <= `nlist' {
			if `i' == `p`j'' { local j = `j' + 1 }
			else local newlist "`newlist'``i'' "
			local i = `i' + 1
		}
	}

	if "`noisily'" != "" { di "`newlist'" }
	if "`global'" != "" { global `global' "`newlist'" }
	return local list `newlist'
end

program def tknz, rclass
* NJC 1.1.0 2 June 2000
	version 6.0
	gettoken list 0 : 0, parse(",")
	syntax , Stub(str) [ * ]
	tokenize `"`list'"' , `options'

	local i = 1
	while "``i''" != "" {
		c_local `stub'`i' `"``i''"'
		local i = `i' + 1
	}
end


*********************************************************************

program def takelist, rclass
* NJC 1.3.0 14 December 2000
* NJC 1.2.0 7 June 2000
* NJC 1.1.0 22 Dec 1999
* NJC 1.0.0 19 Dec 1999 from a suggestion by Kit Baum
	version 6.0
	gettoken list 0 : 0, parse(",")
	if "`list'" == "" | "`list'" == "," {
		di in r "nothing in list"
		exit 198
	}

	local nlist : word count `list'

	syntax , [ Pos(numlist int >=-`nlist' <=`nlist') Global(str) Noisily ]

	* blanking this out traps a bug if 0 is included in -pos( )-
	local 0

	if length("`global'") > 8 {
		di in r "global name must be <=8 characters"
		exit 198
	}

	tknz `pos', s(p)
	local np : word count `pos'

	* negative indexes to positive
	local i = 1
	while `i' <= `np' {
		local p`i' = cond(`p`i'' < 0, `nlist' + 1 + `p`i'', `p`i'')
		local i = `i' + 1
	}

	tokenize `list'

	local i = 1
	while `i' <= `np' {
		local newlist "`newlist'``p`i''' "
		local i = `i' + 1
	}

	if "`noisily'" != "" { di "`newlist'" }
	if "`global'" != "" { global `global' "`newlist'" }
	return local list `newlist'
end

*********************************************************************

#delim ;
prog def descsave;
version 7.0;
syntax [varlist] [, SAving(passthru) DOfile(string asis)
 CHarlist(string asis) IDNum(string) IDStr(string)
 REName(passthru) GSort(passthru) KEep(passthru)
 compress real *];
/*
 Extension of describe
 creating two output files.
 The saving option specifies a Stata data set,
 with 1 obs per variable in varlist,
 and data on names, types, formats, value labels, variable labels,
 and characteristics specified by the charlist option.
 The dofile option specifies a do-file
 which can reconstruct types, formats, value labels, variable labels,
 and characteristics specified by the charlist option,
 assuming that variables of the specified names exist
 (as they will if the data set has been saved using outsheet
 and re-input using insheet).
 The idnum and idstr options
 specify numeric and string identifiers, respectively,
 which will be stored in variables of the same name
 in the saving data set,
 and can be used as identifiers if the saving data set
 is concatenated with other saving data sets.
 The rename option specifies a list
 of alternating old and new variable names,
 so the user can rename variables in the saving data set.
 The gsort option specifies the sort order of the output data set,
 which defaults to the single variable order.
 The keep option specifies the variables to keep
 in the output data set.
* Author: Roger Newson
* Date: 23 April 2004
*/

local nvar:word count `varlist';
local nchar:word count `charlist';

* Call describe *;
describe `varlist',`options';

* Create macro variables containing descriptive features *;
local i1=0;
while(`i1'<`nvar'){;local i1=`i1'+1;
  local varcur:word `i1' of `varlist';
  local name`i1' "`varcur'";
  local actype`i1':type `varcur';
	if "`compress'"~="" {;
		compress `varcur' ;
		local potype`i1':type `varcur';
	} ;
  local form`i1':format `varcur';
  local vall`i1':value label `varcur';
  local varl`i1':variable label `varcur';
  local i2=0;
  while(`i2'<`nchar'){;local i2=`i2'+1;
    local charcur:word `i2' of `charlist';
    local char`i2'_`i1' `"``varcur'[`charcur']'"';
  };
};

preserve;
drop _all;

*
 Create file containing label definitions
 if do-file is required
*;
if(`"`dofile'"'!=""){;
  tempfile labdef;
  local vllist "";
  local i1=0;
  while(`i1'<`nvar'){;local i1=`i1'+1;
    * Check that variable label exists *;
    cap lab list `vall`i1'';
    if _rc==0 {;
      if("`vllist'"==""){;local vllist "`vall`i1''";};
      else if("`vall`i1''"!=""){;
        *
         Check that the value label is not in the list
         and add it to the list if it is not already there
        *;
        local newvl=1;
        foreach vallcur in `vllist' {;
          if "`vallcur'" == "`vall`i1''" {; local newvl=0; };
        };
        if `newvl' {;local vllist "`vllist' `vall`i1''";};
      };
    };
  };
  if("`vllist'"!=""){;
   qui label save `vllist' using `"`labdef'"',replace;
   label drop _all;
  };
  else{;
    label drop _all;
    qui label save using `"`labdef'"',replace;
  };
};

* Create new data set with 1 obs per variable in varlist *;
qui set obs `nvar';
qui gene long order=_n;
qui compress order;

if "`compress'"~="" {;
	local potype potype ;
} ;

foreach X of new name actype `potype' format vallab varlab{;qui gene str1 `X'="";};
local i2=0;
while(`i2'<`nchar'){;local i2=`i2'+1;
  qui gene str1 char`i2'="";
};
local i1=0;
while(`i1'<`nvar'){;local i1=`i1'+1;
  qui{;
    replace name=`"`name`i1''"' in `i1';
    replace actype=`"`actype`i1''"' in `i1';
    if "`compress'"~="" {; replace potype=`"`potype`i1''"' in `i1'; } ;
    replace format=`"`form`i1''"' in `i1';
    replace vallab=`"`vall`i1''"' in `i1';
    replace varlab=`"`varl`i1''"' in `i1';
    local i2=0;
    while(`i2'<`nchar'){;local i2=`i2'+1;
      replace char`i2'=`"`char`i2'_`i1''"' in `i1';
    };
  };
};
lab var order "Variable order";
lab var name "Variable name";
lab var actype "Actual storage type";
if "`compress'"~="" {; lab var potype "Potential storage type"; };
lab var format "Display format";
lab var vallab "Value label";
lab var varlab "Variable label";
local i2=0;
while(`i2'<`nchar'){;local i2=`i2'+1;
  local charcur:word `i2' of `charlist';
  lab var char`i2' `"char[`charcur']"';
};

*
 Left-justify formats for all character variables
 in the base output variable set
*;
unab outvars: *;
foreach X of var `outvars' {;
    local typecur: type `X';
    if index("`typecur'","str")==1 {;
        local formcur: format `X';
        local formcur=subinstr("`formcur'","%","%-",1);
        format `X' `formcur';
    };
};

*
 Create numeric and/or string ID variables if requested
 and move them to the beginning of the variable order
*;
if("`idstr'"!=""){;
    qui gene str1 idstr=" ";
    qui replace idstr="`idstr'";
    qui compress idstr;
    qui order idstr;
    lab var idstr "String id";
};
if("`idnum'"!=""){;
    qui gene double idnum=real("`idnum'");
    qui compress idnum;
    qui order idnum;
    lab var idnum "Numeric id";
};


* Save Stata output if required *;
rensave, `rename' `saving' `gsort' `keep';

*
 Create output do-file if required
*;
if(`"`dofile'"'!=""){;
  *
   Create variable dquote containing a single double quote
   (this is a workaround for an obscure Stata bug
   encountered on 18 April 2001. - RBN)
  *;
  gene str1 dquote=`""""';
  * Create file containing actual storage types *;
  tempfile actype_f;
  qui{;
    gene str1 line1="";gene str1 line2="";gene str1 line3="";
    replace line1="cap recast "+actype+" "+name if(actype!="");
    linesave `"`actype_f'"';
    drop line1 line2 line3;
  };
  if "`compress'"~="" {;
  * Create file containing potential storage types *;
    tempfile potype_f;
    qui{;
      gene str1 line1="";gene str1 line2="";gene str1 line3="";
      replace line1="cap recast "+potype+" "+name if(potype!="");
      linesave `"`potype_f'"';
      drop line1 line2 line3;
    };
  };
  * Create file containing formats *;
  tempfile format_f;
  qui{;
    gene str1 line1="";gene str1 line2="";gene str1 line3="";
    replace line1="cap form "+name+" "+format if(format!="");
    linesave `"`format_f'"';
    drop line1 line2 line3;
  };
  * Create file containing value labels *;
  tempfile vallab_f;
  qui{;
    gene str1 line1="";gene str1 line2="";gene str1 line3="";
    replace line1="cap la val "+name+" "+vallab if(vallab!="");
    linesave `"`vallab_f'"';
    drop line1 line2 line3;
  };
  * Create file containing variable labels *;
  tempfile varlab_f;
  qui{;
    gene str1 line1="";gene str1 line2="";gene str1 line3="";
    disp "Line variables initialized to missing...";
    replace line1="cap la var "+name+" `"+dquote if(varlab!="");
    replace line2=varlab if(varlab!="");
    replace line3=dquote+"'" if(varlab!="");
    linesave `"`varlab_f'"';
    drop line1 line2 line3;
  };
  * Create files containing characteristics *;
  local i2=0;
  while(`i2'<`nchar'){;local i2=`i2'+1;
    local charcur:word `i2' of `charlist';
    tempfile char`i2'_f;
    qui{;
    gene str1 line1="";gene str1 line2="";gene str1 line3="";
      replace line1="cap char "+name+"[`charcur'] "+" `"+dquote if(char`i2'!="");
      replace line2=char`i2' if(char`i2'!="");
      replace line3=dquote+"'" if(char`i2'!="");
      linesave `"`char`i2'_f'"';
    drop line1 line2 line3;
    };
  };
  *
   Concatenate all files into memory
   and write to output file
  *;
  drop _all;
  qui{;
    infix str line1 1-80 str line2 81-160 str line3 161-240 using `"`labdef'"';
    replace line1=subinstr(line1,"label define ","cap la de ",1);
    append using `"`actype_f'"';
    if "`compress'"~="" {; append using `"`potype_f'"'; };
    append using `"`format_f'"';
    append using `"`vallab_f'"';
    append using `"`varlab_f'"';
    local i2=0;
    while(`i2'<`nchar'){;local i2=`i2'+1;
      append using `"`char`i2'_f'"';
    };
  };
  dosave using `dofile';
};

restore;

end;

prog def linesave;
args file;
* Save variables line1, line2 and line3 to data set file *;

preserve;
keep line1 line2 line3;
keep if((!missing(line1))|(!missing(line2))|(!missing(line3)));
save `"`file'"',replace;
restore;

end;

prog def dosave;
syntax using/ [,REPLACE];
* Save the do-file *;

outfile line1 line2 line3 using `"`using'"',runtogether `replace';

end;

prog def rensave;
syntax [,  REName(string) SAving(string asis) GSort(string) KEep(string) ];
* Save file, renaming variables as specified in -rename- *;

preserve;

*
 Rename variables if requested
*;
if "`rename'"!="" {;
    local nrename:word count `rename';
    if mod(`nrename',2) {;
        disp in green
          "Warning: odd number of variable names in rename list - last one ignored";
        local nrename=`nrename'-1;
    };
    local nrenp=`nrename'/2;
    local i1=0;
    while `i1'<`nrenp' {;
        local i1=`i1'+1;
        local i3=`i1'+`i1';
        local i2=`i3'-1;
        local oldname:word `i2' of `rename';
        local newname:word `i3' of `rename';
        cap{;
            confirm var `oldname';
            confirm new var `newname';
        };
        if _rc!=0 {;
            disp in green
             "Warning: it is not possible to rename `oldname' to `newname'";
        };
        else {;
            rename `oldname' `newname';
        };
    };
};

* Sort if requested *;
if "`gsort'"=="" {;local gsort "order";};
tempvar tiebreak;
qui gen long `tiebreak'=_n;
qui compress `tiebreak';
gsort `gsort' + `tiebreak';
drop `tiebreak';

* Keep only selected variables if requested *;
if "`keep'"!="" {;
    confirm variable `keep';
    keep `keep';
};

* Save file if requested *;
if(`"`saving'"'!=""){;
  save `saving';
};

restore;

end;

*	Version 1.5 Revised August 22, 2011
/*
The revised version includes the following changes:
1.	Newson's program -descsave.ado- (version 4/04) is internal to this ADO file.
	The original program can be found at: http://www.kcl-phs.org.uk/rogernewson/stata.htm

2.	No limit on the number of variables in the input data file.
	To achieve this result, three other Newson programs are also used.
	These are -bothlist-, -takelist.ado- and -dellist.ado-

3.	If compress option is specified, both the actual and the potential type of
	of each variable is stored.

4.	The additional variable nreal is added to count the number of observations of a string
	variable which are convertable to numeric values.
*/
*	Ver. 1.6  1Jun2016   Define "nunique" for string variables"
*	Ver. 1.7  21Jun2017  Add provision to relax the missing criterion
*			with a variable "justified" which fulfills the condition specified in the option.
*	Ver. 1.8  26Feb2018  Enable the use of -distinct-, if installed,
*			to count the number of unique observations,
*	Ver. 1.9  20Jun2021  Allow path and filenames with blanks by adding double quotes in line 37
*	Ver. 2.0  16Aug2022
*	Ver. 2.1  30Aug2022  Add the mode whether the variable is numeric or string.