*! 2.0.3 RP 30may2017
* 2.0.2 RP 23jun2016
* 2.0.1 RP 15sep2015
* 2.0.0 RP 10sep2015
* 1.0.6 RP 24aug2015
* 1.0.5 RP 08aug2015
* 1.0.4 RP 22jul2015
* 1.0.3 RP 08jul2015
* 1.0.2 NJC 02jul2015
* version 1.0.1, 02jul2015, Robert Picard, picard@netbox.com
program dataex

	version 9.2

	syntax [varlist] [if] [in] [, Varlabels Elsewhere Count(string)]

	if "`count'" == "" local maxobs 100
	else {
		if "`count'" != "." confirm number `count'
		local maxobs `count'
	}

	if "`varlist'" == "" {
		dis as err "no variables in memory; nothing to generate"
		exit 111
	}

	local varlist : list uniq varlist

	local raw_obs = _N

	preserve


	if `"`if'`in'"' != "" qui keep `if' `in'
	if _N == 0 {
		dis as err "no observations; nothing to generate"
		exit 2000
	}
	keep `varlist'
	local Ntouse = _N
	if `Ntouse' > `maxobs' qui keep in 1/`maxobs'


	// set linesize to the maximum allowed under all Stata versions up to 14.
	// this should be 255 but strings of that length cannot be copied
	local lssave = c(linesize)
	local maxwidth 254
	set linesize `maxwidth'


	// group variables by type; respect current variable order
	local i 0
	foreach v of varlist `varlist'  {

		local vtype : type `v'
		if "`vtype'" != "`vtypeold'" {
			local ++i
			local vtype`i' `vtype'
			local vlist`i' `v'
			local vtypeold `vtype'
		}
		else {
			local vlist`i' `vlist`i'' `v'
		}

	}


	// build the input statement
	local input input
	forvalues j = 1/`i' {

		local n : word count `vlist`j''
		if `n' == 1 {
			local input `input' `vtype`j'' `vlist`j''
		}
		else {
			local input `input' `vtype`j''(`vlist`j'')
		}

	}

	if `: length local input' > `maxwidth' {

		dis as err "input statement exceeds linesize limit. Try specifying fewer variables"
		set linesize `lssave'
		exit 1000

	}


	dis as txt _n "{hline 23} copy starting from the next line {hline 23}"

	if "`elsewhere'" == "" dis as res "[CODE]"

	dis as res "* Example generated by -dataex-. To install: ssc install dataex"
	dis as res "clear"

	dis as res "`input'"


	// cycle through variables and build output lines using a string variable
	local spaces : dis _dup(`maxwidth') " "
	tempvar line_out sv sv_len x xtend xdif
	qui gen `line_out' = ""
	local line_out_len 0

	foreach v of varlist `varlist'  {

		local vtype : type `v'

		if strpos("`vtype'","str") == 1 {
			qui gen `sv' = cond(strpos(`v',char(34)), ///
				"`" + `"""' + `v' + `"""' + "'", `"""' + `v' + `"""')
		}
		else if inlist("`vtype'", "byte", "int", "long") {
			qui gen `sv' = string(`v',"%11.0f")
		}
		else {

			// initially, write floats and doubles using default display format
			local w = cond("`vtype'" == "double", 10, 9)
			qui gen `sv' = string(`v', "%`w'.0g")

			// widen format until the string representation can be converted
			// back to within an epsilon of the original number (in proportion)
			local wmax = cond("`vtype'" == "double", 25, 16)
			local epsilon = cond("`vtype'" == "double", 1e-16, 1e-7)
			local more 1
			qui while `more' {

				gen `vtype' `x' = real(`sv')
				gen double `xdif' = abs((`v' - `x') / `v')

				gen byte `xtend' = `xdif' > `epsilon' & `v' != 0 & !mi(`v')
				replace `xtend' = 1 if mi(`x') & !mi(`v') // if near min/max

				// there's a bug in the %g format that can generate a number
				// that can be off by a factor of 10; switch to %e format when needed
				sum `xdif' if `xtend', meanonly
				local eg = cond(r(max) > .1, "e", "g")

				count if `xtend'
				if r(N) replace `sv' = string(`v',"%`++w'.0`eg'") if `xtend'
				if r(N) == 0 | `w' == `wmax' local more 0
				drop `xtend' `x' `xdif'

			}

		}

		// pad all values to align columns; left-align strings, right-align everything else
		gen `sv_len' = length(`sv')
		sum `sv_len', meanonly

		if strpos("`vtype'","str") == 1 {
			qui replace `sv' = `sv' + substr("`spaces'", 1, r(max) - `sv_len')
		}
		else {
			qui replace `sv' = substr("`spaces'", 1, r(max) - `sv_len') + `sv'
		}


		local line_out_len = `line_out_len' + length("`space1'") + r(max)

		if c(stata_version) < 13 {
			if `line_out_len' > c(maxstrvarlen) {
				dis as err "data width (`line_out_len' chars) exceeds max string length. Try specifying fewer variables"
				set linesize `lssave'
				exit 1000
			}
		}
		else if `line_out_len' > `maxwidth' {
			dis as err "data width (`line_out_len' chars) exceeds max linesize. Try specifying fewer variables"
			set linesize `lssave'
			exit 1000
		}


		qui replace `line_out' = `line_out' + "`space1'" + `sv'

		local space1 " "

		drop `sv' `sv_len'

	}


	// allow user to break out of the list output
	cap noi forvalues i = 1/`=_N' {
		local nout = `i'
		dis as res `line_out'[`i']
	}


	// continue if the break key (return code 1) was used to stop the output
	local break_obs 0
	if _rc == 1 {
		local break_obs 1
	}
	else if _rc > 1 exit _rc

	dis as res "end"


	// so that labels can be based on what was printed in case of a break
	if `nout' < _N qui keep in 1/`nout'


	cap noi {
		// format numeric variable if it's a SIF (datetime)
		foreach v of varlist `varlist'  {

			local f : format `v'
			if regexm(`"`f'"', "^%-?(t|d)") dis as res `"format `f' `v'"'

		}


		// make a list of values label names assigned to variables
		local vlabels
		foreach v of varlist `varlist'  {
			local l : value label `v'
			if "`l'" != "" local vlabels : list vlabels | l
		}

		// define value labels once for all variables that use them
		foreach vl in `vlabels' {
			local alllevels
			qui ds , has(vallabel `vl')
			local vlist `r(varlist)'
			foreach v of varlist `vlist' {
				qui levelsof `v', local(levels) missing
				local alllevels : list alllevels | levels
				dis as res "label values `v' `vl'"
			}

			foreach n in `alllevels' {

				local ltext : label `vl' `n', strict
				if `"`ltext'"' != "" {
					if strpos(`"`ltext'"',char(34)) dis as res `"label def `vl' `n' `"`ltext'"', modify"'
					else dis as res `"label def `vl' `n' "`ltext'", modify"'
				}
			}

		}


		// variable labels
		if "`varlabels'" != "" {
			foreach v of varlist `varlist'  {

				local ltext : var label `v'
				if `"`ltext'"' != "" {
					if strpos(`"`ltext'"',char(34)) dis as res `"label var `v' `"`ltext'"' "'
					else dis as res `"label var `v' "`ltext'" "'
				}

			}

		}
	}

	local break_post 0
	if _rc == 1 {
		local break_post 1
	}
	else if _rc > 1 exit _rc

	if "`elsewhere'" == "" dis as res "[/CODE]"

	dis as txt "{hline 18} copy up to and including the previous line {hline 18}"
	dis as txt

	dis as txt "Listed " as res `nout' as txt " out of " as res `raw_obs' as txt " observations"

	if `break_obs' dis as err "Listing of observations interrupted due to user -break-"
	else if "`count'" == "" & `nout' < `Ntouse' dis as txt "Use the count() option to list more"

	if `break_post' {
		dis as err "Some important information about formats and value labels is missing due to user -break-"
	}

	set linesize `lssave'


end