* version 1.0 07feb2018 David Fisher *! version 1.1 08nov2018 David Fisher *! based on geo2xy_run.ado by Robert Picard, picard@netbox.com program define admetan_hlp_run version 11 ** Notes on "preserve/restore" behaviour: // 1. The default (i.e. specifying nothing) is to preserve the data originally in memory (assumed to be the user's own data) // before reading in the contents of the help file and running it. // Hence, the resulting example code should be entirely "self-sufficient", loading its own example data if necessary // and the data orignally in memory will be restored upon completion. // 2. Specifying -restnot- cancels -preserve- once the contents of the help file have been run. // 3. Specifying -restpres- preserves the data originally in memory as above, // but restores and *re-preserves* that data before running the code. // Hence, the code is intended to apply to the data in memory at the time admetan_hlp_run was called. // 4. Specifying -restpresnot- is equivalent to specifying -restnot- and -restpres- together; // which leaves behind the data originally in memory, but "damaged" by the contents of the help file. syntax anything(name=example_name id="example name") using/ /// , [RESTNOT RESTPRES RESTPRESNOT LIST] quietly { findfile `"`using'"' preserve // read in contents of help file infix str txt 1-244 using `"`r(fn)'"', clear replace txt = trim(txt) // identify example_start and example_end limits gen long obs = _n summ obs if strpos(txt, "{* example_start - `example_name'}{...}") if r(min) == . { disp as err "example `example_name' not found" exit 111 } local pos1 = r(min) + 1 summ obs if strpos(txt, "{* example_end}{...}") & obs > `pos1' if r(min) == . { disp as err "example `example_name' incorrectly coded" exit 111 } local pos2 = r(min) - 1 keep in `pos1'/`pos2' replace obs = _n // In general, do-file command lines should *not* start with "{" or end with "}" // instead, the characters {} should be identifiers of SMCL. // Examples of specific patterns of SMCL to identify: // {* comment} or {* comment}{directive} // {directive:output} or {directive:output}{directive} // {directive:output}{* comment} or {directive:output}{* comment}{directive} // {c output} or {char output} // Strategy: // - Identify sets of brackets, i.e. {content} // - For each set of brackets, isolate content and test for {directive:output} or {* comment}; // if so, keep content; else, drop // exception is {c output} or {char output}; these need conversion back to their original characters (see below) // Problem 1: what if SMCL tags {} are used for other purposes, e.g. loops within Stata commands? // Solution: onus is on help-file authors to replace such characters with {c -(} and {c -)} respectively. // These can then be converted back to {} before the final do-file is run. // Problem 2: how to deal with "{* ...}" ?? "..." could be either SMCL or Stata; no way to know in advance. // Solution: onus is on help-file authors not to use the syntax "{* [SMCL directive]}" within "example limits". gen str newtxt = "" gen str content = "" gen int strpos = . count if regexm(txt, "{") while r(N) { // identify anything before the next { replace strpos = cond(strpos(txt, "{"), strpos(txt, "{"), .) replace newtxt = newtxt + substr(txt, 1, strpos-1) replace txt = substr(txt, cond(strpos, strpos, 1), .) // keep the "{" for matching with "}" // identify content within the next {} replace strpos = strpos(txt, "}") cap assert (strpos>0) == (substr(txt, 1, 1)=="{") if _rc { nois disp as err "found unpaired SMCL tags {}" nois list txt if (strpos>0) != (substr(txt, 1, 1)=="{") exit 132 } replace content = trim(substr(txt, 1, strpos)) if substr(txt, 1, 1)=="{" replace txt = substr(txt, cond(strpos, strpos+1, 1), .) // remaining part of original text // analyse content; look for {directive:output} replace newtxt = newtxt + regexs(1) if regexm(trim(content), "^{[a-z0-9_ ]+:(.*)}$") // analyse content; look for {* comment} replace newtxt = newtxt + regexs(1) if regexm(trim(content), "^{\*(.*)}$") // analyse content; look for {c ...} or {char ...}; add these wholesale to newtxt replace newtxt = newtxt + content if regexm(trim(content), "^{c (.*)}$") | regexm(trim(content), "^{char (.*)}$") // reset replace strpos = . replace content = "" // check for further tags in original text string; // if yes, loop again // otherwise, add anything from the end, then check for *nested* tags in newtxt // this must be done eventually; and from then on the useful code is stored in txt, not newtxt // (hence: drop if txt=="" ) count if regexm(txt, "{") if !r(N) { replace newtxt = newtxt + txt drop txt rename newtxt txt gen str newtxt = "" count if regexm(txt, "{") } } drop if txt=="" // tidy up: // - convert SMCL characters to standard characters // - remove initial periods, if present replace txt = subinstr(txt, "{c -(}", "{", .) replace txt = subinstr(txt, "{c -)}", "}", .) replace txt = subinstr(txt, "{c S|}", "$", .) replace txt = subinstr(txt, "{c 'g}", "`", .) replace txt = trim(subinstr(txt, ".", "", 1)) if substr(trim(txt), 1, 1)=="." } // end quietly // Optionally list contents of do-file to be run if `"`list'"'!=`""' { disp _n _c format txt %-1s list txt, clean noheader } tempfile f outfile txt using "`f'", noquote if `"`restpres'`restpresnot'"'!=`""' { restore, preserve } disp _newline(2) as txt "{hline 27} {it:example do-file content} {hline 27}" do "`f'" disp as txt _n "{hline 23} {it:end of example do-file content} {hline 24}" _newline(2) if `"`restnot'`restpresnot'"'!=`""' { restore, not } end