stata-cli 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stata_cli-0.2.1 → stata_cli-0.3.0}/PKG-INFO +85 -3
- {stata_cli-0.2.1 → stata_cli-0.3.0}/README.md +84 -2
- {stata_cli-0.2.1 → stata_cli-0.3.0}/pyproject.toml +1 -1
- stata_cli-0.3.0/src/stata_cli/__init__.py +1 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/daemon.py +31 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/engine.py +181 -8
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/main.py +162 -6
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/utils.py +0 -1
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli.egg-info/PKG-INFO +85 -3
- stata_cli-0.2.1/src/stata_cli/__init__.py +0 -1
- {stata_cli-0.2.1 → stata_cli-0.3.0}/setup.cfg +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/__main__.py +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/graph_artifacts.py +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/output_filter.py +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli/smcl_parser.py +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli.egg-info/SOURCES.txt +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli.egg-info/dependency_links.txt +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli.egg-info/entry_points.txt +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli.egg-info/requires.txt +0 -0
- {stata_cli-0.2.1 → stata_cli-0.3.0}/src/stata_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stata-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Command-line interface for running Stata commands via PyStata
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: stata,cli,statistics,data-science
|
|
@@ -13,6 +13,8 @@ Requires-Dist: pandas; extra == "data"
|
|
|
13
13
|
|
|
14
14
|
# stata-cli
|
|
15
15
|
|
|
16
|
+

|
|
17
|
+
|
|
16
18
|
[](https://opensource.org/licenses/MIT)
|
|
17
19
|
[](https://www.python.org/)
|
|
18
20
|
[](https://www.npmjs.com/package/stata-cli)
|
|
@@ -39,10 +41,16 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
|
|
|
39
41
|
| **Run Code** | Execute inline Stata code, multi-line blocks, or pipe from stdin |
|
|
40
42
|
| **Do Files** | Run `.do` files with `///` line continuation support and graph auto-naming |
|
|
41
43
|
| **Data Viewer** | View current dataset as JSON with `if`-condition filtering and row limits |
|
|
44
|
+
| **Variable Metadata** | Inspect variable names, types, formats, and labels via `vars` |
|
|
45
|
+
| **Stored Results** | Retrieve r(), e(), s() results as structured JSON via `return` |
|
|
46
|
+
| **Matrix Access** | Read Stata matrices (e.g. `e(b)`, `e(V)`) as JSON via `matrix` |
|
|
47
|
+
| **Value Labels** | List and inspect value labels via `labels` |
|
|
48
|
+
| **Macro Access** | Get/set Stata macros including `c()`, `e()`, `r()` system macros |
|
|
49
|
+
| **Frame Management** | List Stata frames and current working frame via `frame` |
|
|
42
50
|
| **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
|
|
43
|
-
| **Graph Export** | Auto-detect and export graphs as PNG to `~/.stata-cli/graphs/` |
|
|
51
|
+
| **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
|
|
44
52
|
| **Daemon Mode** | Persistent background process for sub-second execution via Unix socket |
|
|
45
|
-
| **Output Control** | Compact mode, JSON output, token limit management
|
|
53
|
+
| **Output Control** | Compact mode, JSON output, token limit management, log file output |
|
|
46
54
|
| **Interruption** | Send break signal to stop long-running commands |
|
|
47
55
|
|
|
48
56
|
## Installation & Quick Start
|
|
@@ -197,6 +205,60 @@ stata-cli detect
|
|
|
197
205
|
|
|
198
206
|
Prints the auto-detected Stata installation path.
|
|
199
207
|
|
|
208
|
+
### `return` — Retrieve Stored Results
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
stata-cli return r # r() results (after summarize, etc.)
|
|
212
|
+
stata-cli return e # e() results (after regress, etc.)
|
|
213
|
+
stata-cli return s # s() results
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Returns r(), e(), or s() stored results as structured JSON — scalars, macros, and matrix references.
|
|
217
|
+
|
|
218
|
+
### `vars` — Variable Metadata
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
stata-cli vars # all variables
|
|
222
|
+
stata-cli vars price mpg # specific variables
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Returns variable names, types, formats, and labels as JSON. More structured than `describe`.
|
|
226
|
+
|
|
227
|
+
### `matrix` — Read Stata Matrices
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
stata-cli matrix e(b) # coefficient vector
|
|
231
|
+
stata-cli matrix e(V) # variance-covariance matrix
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Returns matrix data, dimensions, and row/column names as JSON.
|
|
235
|
+
|
|
236
|
+
### `labels` — Value Labels
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
stata-cli labels # list all value label names
|
|
240
|
+
stata-cli labels origin # show value-label mapping
|
|
241
|
+
stata-cli labels --var foreign # show label attached to a variable
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `macro` — Get/Set Macros
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
stata-cli macro get "c(current_date)"
|
|
248
|
+
stata-cli macro get "e(cmd)"
|
|
249
|
+
stata-cli macro set myvar "hello"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Access Stata macros including system macros (`c()`, `e()`, `r()`).
|
|
253
|
+
|
|
254
|
+
### `frame` — List Frames
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
stata-cli frame
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Shows all Stata frames and the current working frame.
|
|
261
|
+
|
|
200
262
|
## Daemon Mode
|
|
201
263
|
|
|
202
264
|
The daemon keeps PyStata alive in the background — reduces execution time from **~2-3s to ~85ms** (35x speedup).
|
|
@@ -234,6 +296,8 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
|
|
|
234
296
|
| `--max-tokens N` | Max output tokens (0=unlimited) | 0 |
|
|
235
297
|
| `--no-daemon` | Force direct execution | off |
|
|
236
298
|
| `--graphs-dir PATH` | Graph export directory | `~/.stata-cli/graphs/` |
|
|
299
|
+
| `--graph-format [png\|svg\|pdf]` | Graph export format | `png` |
|
|
300
|
+
| `--log PATH` | Save output to a log file | off |
|
|
237
301
|
|
|
238
302
|
### JSON Output
|
|
239
303
|
|
|
@@ -313,6 +377,21 @@ regress price mpg weight
|
|
|
313
377
|
predict yhat
|
|
314
378
|
list make price yhat in 1/5"
|
|
315
379
|
|
|
380
|
+
# Retrieve regression results as structured JSON
|
|
381
|
+
stata-cli return e
|
|
382
|
+
|
|
383
|
+
# Get coefficient matrix
|
|
384
|
+
stata-cli matrix e(b)
|
|
385
|
+
|
|
386
|
+
# Inspect variable metadata
|
|
387
|
+
stata-cli vars price mpg weight
|
|
388
|
+
|
|
389
|
+
# Check value labels
|
|
390
|
+
stata-cli labels --var foreign
|
|
391
|
+
|
|
392
|
+
# Read system macros
|
|
393
|
+
stata-cli macro get "c(N)"
|
|
394
|
+
|
|
316
395
|
# Check data after loading
|
|
317
396
|
stata-cli data --if "price>10000"
|
|
318
397
|
|
|
@@ -325,6 +404,9 @@ describe"
|
|
|
325
404
|
|
|
326
405
|
# JSON mode for structured parsing
|
|
327
406
|
stata-cli --json run "display 1+1"
|
|
407
|
+
|
|
408
|
+
# Export graph as SVG
|
|
409
|
+
stata-cli --graph-format svg run "scatter price mpg"
|
|
328
410
|
```
|
|
329
411
|
|
|
330
412
|
## Contributing
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# stata-cli
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
[](https://opensource.org/licenses/MIT)
|
|
4
6
|
[](https://www.python.org/)
|
|
5
7
|
[](https://www.npmjs.com/package/stata-cli)
|
|
@@ -26,10 +28,16 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
|
|
|
26
28
|
| **Run Code** | Execute inline Stata code, multi-line blocks, or pipe from stdin |
|
|
27
29
|
| **Do Files** | Run `.do` files with `///` line continuation support and graph auto-naming |
|
|
28
30
|
| **Data Viewer** | View current dataset as JSON with `if`-condition filtering and row limits |
|
|
31
|
+
| **Variable Metadata** | Inspect variable names, types, formats, and labels via `vars` |
|
|
32
|
+
| **Stored Results** | Retrieve r(), e(), s() results as structured JSON via `return` |
|
|
33
|
+
| **Matrix Access** | Read Stata matrices (e.g. `e(b)`, `e(V)`) as JSON via `matrix` |
|
|
34
|
+
| **Value Labels** | List and inspect value labels via `labels` |
|
|
35
|
+
| **Macro Access** | Get/set Stata macros including `c()`, `e()`, `r()` system macros |
|
|
36
|
+
| **Frame Management** | List Stata frames and current working frame via `frame` |
|
|
29
37
|
| **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
|
|
30
|
-
| **Graph Export** | Auto-detect and export graphs as PNG to `~/.stata-cli/graphs/` |
|
|
38
|
+
| **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
|
|
31
39
|
| **Daemon Mode** | Persistent background process for sub-second execution via Unix socket |
|
|
32
|
-
| **Output Control** | Compact mode, JSON output, token limit management
|
|
40
|
+
| **Output Control** | Compact mode, JSON output, token limit management, log file output |
|
|
33
41
|
| **Interruption** | Send break signal to stop long-running commands |
|
|
34
42
|
|
|
35
43
|
## Installation & Quick Start
|
|
@@ -184,6 +192,60 @@ stata-cli detect
|
|
|
184
192
|
|
|
185
193
|
Prints the auto-detected Stata installation path.
|
|
186
194
|
|
|
195
|
+
### `return` — Retrieve Stored Results
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
stata-cli return r # r() results (after summarize, etc.)
|
|
199
|
+
stata-cli return e # e() results (after regress, etc.)
|
|
200
|
+
stata-cli return s # s() results
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Returns r(), e(), or s() stored results as structured JSON — scalars, macros, and matrix references.
|
|
204
|
+
|
|
205
|
+
### `vars` — Variable Metadata
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
stata-cli vars # all variables
|
|
209
|
+
stata-cli vars price mpg # specific variables
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Returns variable names, types, formats, and labels as JSON. More structured than `describe`.
|
|
213
|
+
|
|
214
|
+
### `matrix` — Read Stata Matrices
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
stata-cli matrix e(b) # coefficient vector
|
|
218
|
+
stata-cli matrix e(V) # variance-covariance matrix
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Returns matrix data, dimensions, and row/column names as JSON.
|
|
222
|
+
|
|
223
|
+
### `labels` — Value Labels
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
stata-cli labels # list all value label names
|
|
227
|
+
stata-cli labels origin # show value-label mapping
|
|
228
|
+
stata-cli labels --var foreign # show label attached to a variable
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### `macro` — Get/Set Macros
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
stata-cli macro get "c(current_date)"
|
|
235
|
+
stata-cli macro get "e(cmd)"
|
|
236
|
+
stata-cli macro set myvar "hello"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Access Stata macros including system macros (`c()`, `e()`, `r()`).
|
|
240
|
+
|
|
241
|
+
### `frame` — List Frames
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
stata-cli frame
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Shows all Stata frames and the current working frame.
|
|
248
|
+
|
|
187
249
|
## Daemon Mode
|
|
188
250
|
|
|
189
251
|
The daemon keeps PyStata alive in the background — reduces execution time from **~2-3s to ~85ms** (35x speedup).
|
|
@@ -221,6 +283,8 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
|
|
|
221
283
|
| `--max-tokens N` | Max output tokens (0=unlimited) | 0 |
|
|
222
284
|
| `--no-daemon` | Force direct execution | off |
|
|
223
285
|
| `--graphs-dir PATH` | Graph export directory | `~/.stata-cli/graphs/` |
|
|
286
|
+
| `--graph-format [png\|svg\|pdf]` | Graph export format | `png` |
|
|
287
|
+
| `--log PATH` | Save output to a log file | off |
|
|
224
288
|
|
|
225
289
|
### JSON Output
|
|
226
290
|
|
|
@@ -300,6 +364,21 @@ regress price mpg weight
|
|
|
300
364
|
predict yhat
|
|
301
365
|
list make price yhat in 1/5"
|
|
302
366
|
|
|
367
|
+
# Retrieve regression results as structured JSON
|
|
368
|
+
stata-cli return e
|
|
369
|
+
|
|
370
|
+
# Get coefficient matrix
|
|
371
|
+
stata-cli matrix e(b)
|
|
372
|
+
|
|
373
|
+
# Inspect variable metadata
|
|
374
|
+
stata-cli vars price mpg weight
|
|
375
|
+
|
|
376
|
+
# Check value labels
|
|
377
|
+
stata-cli labels --var foreign
|
|
378
|
+
|
|
379
|
+
# Read system macros
|
|
380
|
+
stata-cli macro get "c(N)"
|
|
381
|
+
|
|
303
382
|
# Check data after loading
|
|
304
383
|
stata-cli data --if "price>10000"
|
|
305
384
|
|
|
@@ -312,6 +391,9 @@ describe"
|
|
|
312
391
|
|
|
313
392
|
# JSON mode for structured parsing
|
|
314
393
|
stata-cli --json run "display 1+1"
|
|
394
|
+
|
|
395
|
+
# Export graph as SVG
|
|
396
|
+
stata-cli --graph-format svg run "scatter price mpg"
|
|
315
397
|
```
|
|
316
398
|
|
|
317
399
|
## Contributing
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -162,6 +162,37 @@ class DaemonServer:
|
|
|
162
162
|
ok = self._engine.stop()
|
|
163
163
|
_send_msg(conn, {"status": "ok" if ok else "no_op"})
|
|
164
164
|
|
|
165
|
+
elif cmd_type == "get_return":
|
|
166
|
+
data = self._engine.get_return(rtype=payload.get("rtype", "r"))
|
|
167
|
+
_send_msg(conn, data)
|
|
168
|
+
|
|
169
|
+
elif cmd_type == "get_vars":
|
|
170
|
+
data = self._engine.get_vars()
|
|
171
|
+
_send_msg(conn, data)
|
|
172
|
+
|
|
173
|
+
elif cmd_type == "get_matrix":
|
|
174
|
+
data = self._engine.get_matrix(payload.get("name", ""))
|
|
175
|
+
_send_msg(conn, data)
|
|
176
|
+
|
|
177
|
+
elif cmd_type == "get_labels":
|
|
178
|
+
data = self._engine.get_labels(
|
|
179
|
+
name=payload.get("name"),
|
|
180
|
+
var=payload.get("var"),
|
|
181
|
+
)
|
|
182
|
+
_send_msg(conn, data)
|
|
183
|
+
|
|
184
|
+
elif cmd_type == "get_macro":
|
|
185
|
+
data = self._engine.get_macro(payload.get("name", ""))
|
|
186
|
+
_send_msg(conn, data)
|
|
187
|
+
|
|
188
|
+
elif cmd_type == "set_macro":
|
|
189
|
+
data = self._engine.set_macro(payload.get("name", ""), payload.get("value", ""))
|
|
190
|
+
_send_msg(conn, data)
|
|
191
|
+
|
|
192
|
+
elif cmd_type == "get_frames":
|
|
193
|
+
data = self._engine.get_frames()
|
|
194
|
+
_send_msg(conn, data)
|
|
195
|
+
|
|
165
196
|
elif cmd_type == "status":
|
|
166
197
|
_send_msg(conn, {
|
|
167
198
|
"status": "ok",
|
|
@@ -50,14 +50,18 @@ _EXISTING_GRAPHN_RE = re.compile(r"\bname\s*\(\s*graph(\d+)", re.IGNORECASE)
|
|
|
50
50
|
class StataEngine:
|
|
51
51
|
"""Thin wrapper around PyStata for single-process command execution."""
|
|
52
52
|
|
|
53
|
-
def __init__(self, stata_path: str, edition: str = "mp", graphs_dir: Optional[str] = None):
|
|
53
|
+
def __init__(self, stata_path: str, edition: str = "mp", graphs_dir: Optional[str] = None, graph_format: str = "png"):
|
|
54
54
|
self.stata_path = stata_path
|
|
55
55
|
self.edition = edition.lower()
|
|
56
56
|
self.graphs_dir = graphs_dir or get_graphs_root()
|
|
57
|
+
self.graph_format = graph_format.lower()
|
|
57
58
|
self._stata = None
|
|
58
59
|
self._stlib = None
|
|
59
60
|
self._initialized = False
|
|
60
61
|
self._stop_sent = False
|
|
62
|
+
self._last_r: dict = {}
|
|
63
|
+
self._last_e: dict = {}
|
|
64
|
+
self._last_s: dict = {}
|
|
61
65
|
|
|
62
66
|
def _ensure_initialized(self) -> None:
|
|
63
67
|
if self._initialized:
|
|
@@ -89,6 +93,13 @@ class StataEngine:
|
|
|
89
93
|
|
|
90
94
|
self._stata = stata_module
|
|
91
95
|
|
|
96
|
+
if self.graph_format != "png":
|
|
97
|
+
try:
|
|
98
|
+
from pystata import config as pystata_config # type: ignore[import-untyped]
|
|
99
|
+
pystata_config.set_graph_format(self.graph_format)
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
|
|
92
103
|
try:
|
|
93
104
|
from pystata.config import stlib as stlib_module # type: ignore[import-untyped]
|
|
94
105
|
self._stlib = stlib_module
|
|
@@ -113,19 +124,27 @@ class StataEngine:
|
|
|
113
124
|
)
|
|
114
125
|
log_file_stata = log_file.replace("\\", "/")
|
|
115
126
|
|
|
116
|
-
|
|
127
|
+
setup = (
|
|
117
128
|
f'capture log close _all\n'
|
|
118
129
|
f'log using "{log_file_stata}", replace text\n'
|
|
119
|
-
f'{code}\n'
|
|
120
|
-
f'capture log close _all\n'
|
|
121
130
|
)
|
|
131
|
+
teardown = 'capture log close _all\n'
|
|
122
132
|
|
|
123
133
|
start = time.time()
|
|
124
134
|
try:
|
|
125
135
|
old_stdout = sys.stdout
|
|
126
136
|
sys.stdout = io.StringIO()
|
|
127
137
|
try:
|
|
128
|
-
self._stata.run(
|
|
138
|
+
self._stata.run(setup + code, echo=True, inline=False)
|
|
139
|
+
finally:
|
|
140
|
+
sys.stdout = old_stdout
|
|
141
|
+
|
|
142
|
+
self._capture_stored_results()
|
|
143
|
+
|
|
144
|
+
old_stdout = sys.stdout
|
|
145
|
+
sys.stdout = io.StringIO()
|
|
146
|
+
try:
|
|
147
|
+
self._stata.run(teardown, echo=False, inline=False)
|
|
129
148
|
finally:
|
|
130
149
|
captured_stdout = sys.stdout.getvalue()
|
|
131
150
|
sys.stdout = old_stdout
|
|
@@ -300,6 +319,119 @@ class StataEngine:
|
|
|
300
319
|
|
|
301
320
|
return result
|
|
302
321
|
|
|
322
|
+
def get_return(self, rtype: str = "r") -> Dict[str, Any]:
|
|
323
|
+
"""Retrieve stored results: r(), e(), or s()."""
|
|
324
|
+
self._ensure_initialized()
|
|
325
|
+
try:
|
|
326
|
+
if rtype == "r":
|
|
327
|
+
return {"status": "success", "type": "r", "results": dict(self._last_r)}
|
|
328
|
+
elif rtype == "e":
|
|
329
|
+
return {"status": "success", "type": "e", "results": dict(self._last_e)}
|
|
330
|
+
elif rtype == "s":
|
|
331
|
+
return {"status": "success", "type": "s", "results": dict(self._last_s)}
|
|
332
|
+
else:
|
|
333
|
+
return {"status": "error", "error": f"Unknown return type: {rtype}"}
|
|
334
|
+
except Exception as exc:
|
|
335
|
+
return {"status": "error", "error": str(exc)}
|
|
336
|
+
|
|
337
|
+
def get_vars(self) -> Dict[str, Any]:
|
|
338
|
+
"""Return variable metadata for the current dataset."""
|
|
339
|
+
self._ensure_initialized()
|
|
340
|
+
try:
|
|
341
|
+
import sfi # type: ignore[import-untyped]
|
|
342
|
+
nvar = sfi.Data.getVarCount()
|
|
343
|
+
nobs = sfi.Data.getObsTotal()
|
|
344
|
+
variables = []
|
|
345
|
+
for i in range(nvar):
|
|
346
|
+
name = sfi.Data.getVarName(i)
|
|
347
|
+
variables.append({
|
|
348
|
+
"name": name,
|
|
349
|
+
"type": sfi.Data.getVarType(i),
|
|
350
|
+
"format": sfi.Data.getVarFormat(i),
|
|
351
|
+
"label": sfi.Data.getVarLabel(i),
|
|
352
|
+
"is_string": sfi.Data.isVarTypeStr(i),
|
|
353
|
+
})
|
|
354
|
+
return {
|
|
355
|
+
"status": "success",
|
|
356
|
+
"n_vars": nvar,
|
|
357
|
+
"n_obs": nobs,
|
|
358
|
+
"variables": variables,
|
|
359
|
+
}
|
|
360
|
+
except Exception as exc:
|
|
361
|
+
return {"status": "error", "error": str(exc)}
|
|
362
|
+
|
|
363
|
+
def get_matrix(self, name: str) -> Dict[str, Any]:
|
|
364
|
+
"""Return a Stata matrix as a dict."""
|
|
365
|
+
self._ensure_initialized()
|
|
366
|
+
try:
|
|
367
|
+
import sfi # type: ignore[import-untyped]
|
|
368
|
+
nrows = sfi.Matrix.getRowTotal(name)
|
|
369
|
+
ncols = sfi.Matrix.getColTotal(name)
|
|
370
|
+
row_names = sfi.Matrix.getRowNames(name)
|
|
371
|
+
col_names = sfi.Matrix.getColNames(name)
|
|
372
|
+
data = sfi.Matrix.get(name)
|
|
373
|
+
return {
|
|
374
|
+
"status": "success",
|
|
375
|
+
"name": name,
|
|
376
|
+
"rows": nrows,
|
|
377
|
+
"cols": ncols,
|
|
378
|
+
"row_names": row_names,
|
|
379
|
+
"col_names": col_names,
|
|
380
|
+
"data": data,
|
|
381
|
+
}
|
|
382
|
+
except Exception as exc:
|
|
383
|
+
return {"status": "error", "error": str(exc)}
|
|
384
|
+
|
|
385
|
+
def get_labels(self, name: Optional[str] = None, var: Optional[str] = None) -> Dict[str, Any]:
|
|
386
|
+
"""Return value labels."""
|
|
387
|
+
self._ensure_initialized()
|
|
388
|
+
try:
|
|
389
|
+
import sfi # type: ignore[import-untyped]
|
|
390
|
+
if var:
|
|
391
|
+
label_name = sfi.ValueLabel.getVarValueLabel(var)
|
|
392
|
+
if not label_name:
|
|
393
|
+
return {"status": "success", "variable": var, "label_name": "", "labels": {}}
|
|
394
|
+
mapping = sfi.ValueLabel.getValueLabels(label_name)
|
|
395
|
+
return {"status": "success", "variable": var, "label_name": label_name, "labels": mapping}
|
|
396
|
+
if name:
|
|
397
|
+
mapping = sfi.ValueLabel.getValueLabels(name)
|
|
398
|
+
return {"status": "success", "name": name, "labels": mapping}
|
|
399
|
+
names = sfi.ValueLabel.getNames()
|
|
400
|
+
return {"status": "success", "names": names}
|
|
401
|
+
except Exception as exc:
|
|
402
|
+
return {"status": "error", "error": str(exc)}
|
|
403
|
+
|
|
404
|
+
def get_macro(self, name: str) -> Dict[str, Any]:
|
|
405
|
+
"""Get the value of a Stata macro."""
|
|
406
|
+
self._ensure_initialized()
|
|
407
|
+
try:
|
|
408
|
+
import sfi # type: ignore[import-untyped]
|
|
409
|
+
value = sfi.Macro.getGlobal(name)
|
|
410
|
+
return {"status": "success", "name": name, "value": value}
|
|
411
|
+
except Exception as exc:
|
|
412
|
+
return {"status": "error", "error": str(exc)}
|
|
413
|
+
|
|
414
|
+
def set_macro(self, name: str, value: str) -> Dict[str, Any]:
|
|
415
|
+
"""Set a Stata global macro."""
|
|
416
|
+
self._ensure_initialized()
|
|
417
|
+
try:
|
|
418
|
+
import sfi # type: ignore[import-untyped]
|
|
419
|
+
sfi.Macro.setGlobal(name, value)
|
|
420
|
+
return {"status": "success", "name": name, "value": value}
|
|
421
|
+
except Exception as exc:
|
|
422
|
+
return {"status": "error", "error": str(exc)}
|
|
423
|
+
|
|
424
|
+
def get_frames(self) -> Dict[str, Any]:
|
|
425
|
+
"""Return list of Stata frames and the current working frame."""
|
|
426
|
+
self._ensure_initialized()
|
|
427
|
+
try:
|
|
428
|
+
import sfi # type: ignore[import-untyped]
|
|
429
|
+
frames = sfi.Frame.getFrames()
|
|
430
|
+
cwf = sfi.Frame.getCWF()
|
|
431
|
+
return {"status": "success", "frames": frames, "current": cwf}
|
|
432
|
+
except Exception as exc:
|
|
433
|
+
return {"status": "error", "error": str(exc)}
|
|
434
|
+
|
|
303
435
|
def stop(self) -> bool:
|
|
304
436
|
"""Interrupt a running Stata command. Returns True if signal sent."""
|
|
305
437
|
if self._stop_sent or self._stlib is None:
|
|
@@ -316,6 +448,44 @@ class StataEngine:
|
|
|
316
448
|
|
|
317
449
|
# ── graph detection ──────────────────────────────────────────────────
|
|
318
450
|
|
|
451
|
+
def _capture_stored_results(self) -> None:
|
|
452
|
+
"""Snapshot r(), e(), s() results via sfi before log close clears them."""
|
|
453
|
+
try:
|
|
454
|
+
import sfi # type: ignore[import-untyped]
|
|
455
|
+
except ImportError:
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
for rtype, store in [("r", "_last_r"), ("e", "_last_e"), ("s", "_last_s")]:
|
|
459
|
+
result: Dict[str, Any] = {}
|
|
460
|
+
cat = f"{rtype}()"
|
|
461
|
+
try:
|
|
462
|
+
scalar_names = sfi.SFIToolkit.listReturn(cat, "scalar")
|
|
463
|
+
if scalar_names and scalar_names.strip():
|
|
464
|
+
for name in scalar_names.strip().split():
|
|
465
|
+
try:
|
|
466
|
+
result[name] = sfi.Scalar.getValue(f"{rtype}({name})")
|
|
467
|
+
except Exception:
|
|
468
|
+
pass
|
|
469
|
+
|
|
470
|
+
macro_names = sfi.SFIToolkit.listReturn(cat, "macro")
|
|
471
|
+
if macro_names and macro_names.strip():
|
|
472
|
+
for name in macro_names.strip().split():
|
|
473
|
+
try:
|
|
474
|
+
result[name] = sfi.Macro.getGlobal(f"{rtype}({name})")
|
|
475
|
+
except Exception:
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
if rtype != "s":
|
|
479
|
+
matrix_names = sfi.SFIToolkit.listReturn(cat, "matrix")
|
|
480
|
+
if matrix_names and matrix_names.strip():
|
|
481
|
+
for name in matrix_names.strip().split():
|
|
482
|
+
result[f"matrix:{name}"] = f"[matrix, use 'stata-cli matrix {rtype}({name})']"
|
|
483
|
+
except Exception:
|
|
484
|
+
pass
|
|
485
|
+
setattr(self, store, result)
|
|
486
|
+
|
|
487
|
+
# ── graph detection (continued) ─────────────────────────────────────
|
|
488
|
+
|
|
319
489
|
def _reset_graph_tracking(self) -> None:
|
|
320
490
|
if self._stlib is None:
|
|
321
491
|
return
|
|
@@ -348,12 +518,15 @@ class StataEngine:
|
|
|
348
518
|
self._stlib.StataSO_Execute(
|
|
349
519
|
get_encode_str(f"quietly graph display {gname}"), False
|
|
350
520
|
)
|
|
351
|
-
|
|
521
|
+
ext = self.graph_format
|
|
522
|
+
graph_file = os.path.join(batch["batch_dir"], f"{gname}.{ext}")
|
|
352
523
|
graph_file_stata = graph_file.replace("\\", "/")
|
|
524
|
+
fmt_opt = f"as({ext}) " if ext != "png" else ""
|
|
525
|
+
size_opt = "width(800) height(600)" if ext == "png" else ""
|
|
353
526
|
export_cmd = (
|
|
354
527
|
f'quietly graph export "{graph_file_stata}", '
|
|
355
|
-
f"name({gname}) replace
|
|
356
|
-
)
|
|
528
|
+
f"{fmt_opt}name({gname}) replace {size_opt}"
|
|
529
|
+
).rstrip()
|
|
357
530
|
rc = self._stlib.StataSO_Execute(get_encode_str(export_cmd), False)
|
|
358
531
|
if rc != 0:
|
|
359
532
|
continue
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"""Stata CLI - run Stata commands from the terminal."""
|
|
2
|
-
|
|
3
2
|
from __future__ import annotations
|
|
4
3
|
|
|
5
4
|
import json
|
|
@@ -38,8 +37,10 @@ def _exit(code: int) -> None:
|
|
|
38
37
|
@click.option("--max-tokens", type=int, default=0, help="Max output tokens (0=unlimited). Saves full output to file when exceeded.")
|
|
39
38
|
@click.option("--no-daemon", is_flag=True, default=False, help="Force direct execution, skip daemon.")
|
|
40
39
|
@click.option("--graphs-dir", envvar="STATA_CLI_GRAPHS_DIR", default=None, help="Graph export directory.")
|
|
40
|
+
@click.option("--graph-format", type=click.Choice(["png", "svg", "pdf"], case_sensitive=False), default="png", help="Graph export format.")
|
|
41
|
+
@click.option("--log", "log_file", default=None, help="Save Stata output to a log file.")
|
|
41
42
|
@click.pass_context
|
|
42
|
-
def cli(ctx, stata_path, edition, compact, use_json, timeout, max_tokens, no_daemon, graphs_dir):
|
|
43
|
+
def cli(ctx, stata_path, edition, compact, use_json, timeout, max_tokens, no_daemon, graphs_dir, graph_format, log_file):
|
|
43
44
|
"""Command-line interface for Stata."""
|
|
44
45
|
ctx.ensure_object(dict)
|
|
45
46
|
ctx.obj["stata_path"] = stata_path
|
|
@@ -50,6 +51,8 @@ def cli(ctx, stata_path, edition, compact, use_json, timeout, max_tokens, no_dae
|
|
|
50
51
|
ctx.obj["max_tokens"] = max_tokens
|
|
51
52
|
ctx.obj["no_daemon"] = no_daemon
|
|
52
53
|
ctx.obj["graphs_dir"] = graphs_dir
|
|
54
|
+
ctx.obj["graph_format"] = graph_format
|
|
55
|
+
ctx.obj["log_file"] = log_file
|
|
53
56
|
|
|
54
57
|
|
|
55
58
|
def _get_engine(ctx) -> StataEngine:
|
|
@@ -59,7 +62,7 @@ def _get_engine(ctx) -> StataEngine:
|
|
|
59
62
|
click.echo("Set --stata-path or the STATA_PATH environment variable.", err=True)
|
|
60
63
|
_exit(EXIT_INIT_FAILURE)
|
|
61
64
|
try:
|
|
62
|
-
engine = StataEngine(stata_path, ctx.obj["edition"], graphs_dir=ctx.obj.get("graphs_dir"))
|
|
65
|
+
engine = StataEngine(stata_path, ctx.obj["edition"], graphs_dir=ctx.obj.get("graphs_dir"), graph_format=ctx.obj.get("graph_format", "png"))
|
|
63
66
|
return engine
|
|
64
67
|
except Exception as exc:
|
|
65
68
|
click.echo(f"Error initializing Stata: {exc}", err=True)
|
|
@@ -91,7 +94,25 @@ def _try_daemon(ctx, cmd_type: str, payload: dict) -> Result | None:
|
|
|
91
94
|
return None
|
|
92
95
|
|
|
93
96
|
|
|
94
|
-
def
|
|
97
|
+
def _try_daemon_dict(ctx, cmd_type: str, payload: dict) -> dict | None:
|
|
98
|
+
"""Try to route a dict-returning command through daemon. Returns None if unavailable."""
|
|
99
|
+
if ctx.obj.get("no_daemon"):
|
|
100
|
+
return None
|
|
101
|
+
try:
|
|
102
|
+
from .daemon import DaemonClient
|
|
103
|
+
client = DaemonClient()
|
|
104
|
+
if not client.is_running():
|
|
105
|
+
return None
|
|
106
|
+
if not client.connect():
|
|
107
|
+
return None
|
|
108
|
+
resp = client.send(cmd_type, payload)
|
|
109
|
+
client.close()
|
|
110
|
+
return resp
|
|
111
|
+
except Exception:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _print_result(result, compact: bool, use_json: bool = False, max_tokens: int = 0, filter_echo: bool = False, log_file: str = None) -> None:
|
|
95
116
|
output = result.output
|
|
96
117
|
if output:
|
|
97
118
|
output = clean_log_wrapper(output)
|
|
@@ -114,6 +135,13 @@ def _print_result(result, compact: bool, use_json: bool = False, max_tokens: int
|
|
|
114
135
|
if graphs:
|
|
115
136
|
for g in graphs:
|
|
116
137
|
click.echo(f"[graph] {g.get('name', 'graph')}: {g.get('path', '')}")
|
|
138
|
+
if log_file and output:
|
|
139
|
+
try:
|
|
140
|
+
with open(log_file, "a", encoding="utf-8") as fh:
|
|
141
|
+
fh.write(output + "\n")
|
|
142
|
+
click.echo(f"[log] Output appended to: {log_file}")
|
|
143
|
+
except OSError as exc:
|
|
144
|
+
click.echo(f"[log] Failed to write log: {exc}", err=True)
|
|
117
145
|
if not result.success:
|
|
118
146
|
if result.error:
|
|
119
147
|
click.echo(result.error, err=True)
|
|
@@ -146,7 +174,7 @@ def run(ctx, code):
|
|
|
146
174
|
if result is None:
|
|
147
175
|
engine = _get_engine(ctx)
|
|
148
176
|
result = engine.run(code, timeout=ctx.obj["timeout"])
|
|
149
|
-
_print_result(result, ctx.obj["compact"], use_json=ctx.obj["json"], max_tokens=ctx.obj["max_tokens"])
|
|
177
|
+
_print_result(result, ctx.obj["compact"], use_json=ctx.obj["json"], max_tokens=ctx.obj["max_tokens"], log_file=ctx.obj.get("log_file"))
|
|
150
178
|
|
|
151
179
|
|
|
152
180
|
@cli.command("do")
|
|
@@ -164,7 +192,7 @@ def do_file(ctx, path):
|
|
|
164
192
|
if result is None:
|
|
165
193
|
engine = _get_engine(ctx)
|
|
166
194
|
result = engine.run_file(path, timeout=ctx.obj["timeout"])
|
|
167
|
-
_print_result(result, ctx.obj["compact"], use_json=ctx.obj["json"], max_tokens=ctx.obj["max_tokens"], filter_echo=True)
|
|
195
|
+
_print_result(result, ctx.obj["compact"], use_json=ctx.obj["json"], max_tokens=ctx.obj["max_tokens"], filter_echo=True, log_file=ctx.obj.get("log_file"))
|
|
168
196
|
|
|
169
197
|
|
|
170
198
|
@cli.command()
|
|
@@ -250,6 +278,134 @@ def stop_cmd(ctx):
|
|
|
250
278
|
_exit(EXIT_USAGE_ERROR)
|
|
251
279
|
|
|
252
280
|
|
|
281
|
+
@cli.command("return")
|
|
282
|
+
@click.argument("rtype", type=click.Choice(["r", "e", "s"], case_sensitive=False), default="r")
|
|
283
|
+
@click.pass_context
|
|
284
|
+
def return_cmd(ctx, rtype):
|
|
285
|
+
"""Show stored Stata results (r/e/s).
|
|
286
|
+
|
|
287
|
+
\b
|
|
288
|
+
Examples:
|
|
289
|
+
stata-cli return r # r() results after a command
|
|
290
|
+
stata-cli return e # e() results after estimation
|
|
291
|
+
stata-cli return s # s() results
|
|
292
|
+
"""
|
|
293
|
+
resp = _try_daemon_dict(ctx, "get_return", {"rtype": rtype.lower()})
|
|
294
|
+
if resp is None:
|
|
295
|
+
engine = _get_engine(ctx)
|
|
296
|
+
resp = engine.get_return(rtype.lower())
|
|
297
|
+
click.echo(json.dumps(resp, ensure_ascii=False, indent=2))
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@cli.command("vars")
|
|
301
|
+
@click.argument("names", nargs=-1)
|
|
302
|
+
@click.pass_context
|
|
303
|
+
def vars_cmd(ctx, names):
|
|
304
|
+
"""Show variable metadata for the current dataset.
|
|
305
|
+
|
|
306
|
+
\b
|
|
307
|
+
Examples:
|
|
308
|
+
stata-cli vars # all variables
|
|
309
|
+
stata-cli vars price mpg # specific variables
|
|
310
|
+
"""
|
|
311
|
+
resp = _try_daemon_dict(ctx, "get_vars", {})
|
|
312
|
+
if resp is None:
|
|
313
|
+
engine = _get_engine(ctx)
|
|
314
|
+
resp = engine.get_vars()
|
|
315
|
+
|
|
316
|
+
if names and resp.get("status") == "success":
|
|
317
|
+
name_set = set(names)
|
|
318
|
+
resp["variables"] = [v for v in resp.get("variables", []) if v["name"] in name_set]
|
|
319
|
+
resp["n_vars"] = len(resp["variables"])
|
|
320
|
+
|
|
321
|
+
click.echo(json.dumps(resp, ensure_ascii=False, indent=2))
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@cli.command("matrix")
|
|
325
|
+
@click.argument("name")
|
|
326
|
+
@click.pass_context
|
|
327
|
+
def matrix_cmd(ctx, name):
|
|
328
|
+
"""Show a Stata matrix.
|
|
329
|
+
|
|
330
|
+
\b
|
|
331
|
+
Examples:
|
|
332
|
+
stata-cli matrix e(b) # coefficient vector
|
|
333
|
+
stata-cli matrix e(V) # variance-covariance matrix
|
|
334
|
+
stata-cli matrix r(table) # results table
|
|
335
|
+
"""
|
|
336
|
+
resp = _try_daemon_dict(ctx, "get_matrix", {"name": name})
|
|
337
|
+
if resp is None:
|
|
338
|
+
engine = _get_engine(ctx)
|
|
339
|
+
resp = engine.get_matrix(name)
|
|
340
|
+
click.echo(json.dumps(resp, ensure_ascii=False, indent=2))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@cli.command("labels")
|
|
344
|
+
@click.argument("name", required=False, default=None)
|
|
345
|
+
@click.option("--var", default=None, help="Show value label attached to a variable.")
|
|
346
|
+
@click.pass_context
|
|
347
|
+
def labels_cmd(ctx, name, var):
|
|
348
|
+
"""Show value labels.
|
|
349
|
+
|
|
350
|
+
\b
|
|
351
|
+
Examples:
|
|
352
|
+
stata-cli labels # list all value label names
|
|
353
|
+
stata-cli labels origin # show label-value mapping
|
|
354
|
+
stata-cli labels --var foreign # show label for a variable
|
|
355
|
+
"""
|
|
356
|
+
resp = _try_daemon_dict(ctx, "get_labels", {"name": name, "var": var})
|
|
357
|
+
if resp is None:
|
|
358
|
+
engine = _get_engine(ctx)
|
|
359
|
+
resp = engine.get_labels(name=name, var=var)
|
|
360
|
+
click.echo(json.dumps(resp, ensure_ascii=False, indent=2))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@cli.command("macro")
|
|
364
|
+
@click.argument("action", type=click.Choice(["get", "set"], case_sensitive=False))
|
|
365
|
+
@click.argument("name")
|
|
366
|
+
@click.argument("value", required=False, default=None)
|
|
367
|
+
@click.pass_context
|
|
368
|
+
def macro_cmd(ctx, action, name, value):
|
|
369
|
+
"""Get or set a Stata macro.
|
|
370
|
+
|
|
371
|
+
\b
|
|
372
|
+
Examples:
|
|
373
|
+
stata-cli macro get "c(current_date)"
|
|
374
|
+
stata-cli macro get "e(cmd)"
|
|
375
|
+
stata-cli macro set myvar "hello world"
|
|
376
|
+
"""
|
|
377
|
+
if action.lower() == "set":
|
|
378
|
+
if value is None:
|
|
379
|
+
click.echo("Error: value is required for 'set'.", err=True)
|
|
380
|
+
_exit(EXIT_USAGE_ERROR)
|
|
381
|
+
resp = _try_daemon_dict(ctx, "set_macro", {"name": name, "value": value})
|
|
382
|
+
if resp is None:
|
|
383
|
+
engine = _get_engine(ctx)
|
|
384
|
+
resp = engine.set_macro(name, value)
|
|
385
|
+
else:
|
|
386
|
+
resp = _try_daemon_dict(ctx, "get_macro", {"name": name})
|
|
387
|
+
if resp is None:
|
|
388
|
+
engine = _get_engine(ctx)
|
|
389
|
+
resp = engine.get_macro(name)
|
|
390
|
+
click.echo(json.dumps(resp, ensure_ascii=False, indent=2))
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@cli.command("frame")
|
|
394
|
+
@click.pass_context
|
|
395
|
+
def frame_cmd(ctx):
|
|
396
|
+
"""List Stata frames and the current working frame.
|
|
397
|
+
|
|
398
|
+
\b
|
|
399
|
+
Examples:
|
|
400
|
+
stata-cli frame
|
|
401
|
+
"""
|
|
402
|
+
resp = _try_daemon_dict(ctx, "get_frames", {})
|
|
403
|
+
if resp is None:
|
|
404
|
+
engine = _get_engine(ctx)
|
|
405
|
+
resp = engine.get_frames()
|
|
406
|
+
click.echo(json.dumps(resp, ensure_ascii=False, indent=2))
|
|
407
|
+
|
|
408
|
+
|
|
253
409
|
# ── Daemon subcommands ───────────────────────────────────────────────────
|
|
254
410
|
|
|
255
411
|
@cli.group()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stata-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Command-line interface for running Stata commands via PyStata
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: stata,cli,statistics,data-science
|
|
@@ -13,6 +13,8 @@ Requires-Dist: pandas; extra == "data"
|
|
|
13
13
|
|
|
14
14
|
# stata-cli
|
|
15
15
|
|
|
16
|
+

|
|
17
|
+
|
|
16
18
|
[](https://opensource.org/licenses/MIT)
|
|
17
19
|
[](https://www.python.org/)
|
|
18
20
|
[](https://www.npmjs.com/package/stata-cli)
|
|
@@ -39,10 +41,16 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
|
|
|
39
41
|
| **Run Code** | Execute inline Stata code, multi-line blocks, or pipe from stdin |
|
|
40
42
|
| **Do Files** | Run `.do` files with `///` line continuation support and graph auto-naming |
|
|
41
43
|
| **Data Viewer** | View current dataset as JSON with `if`-condition filtering and row limits |
|
|
44
|
+
| **Variable Metadata** | Inspect variable names, types, formats, and labels via `vars` |
|
|
45
|
+
| **Stored Results** | Retrieve r(), e(), s() results as structured JSON via `return` |
|
|
46
|
+
| **Matrix Access** | Read Stata matrices (e.g. `e(b)`, `e(V)`) as JSON via `matrix` |
|
|
47
|
+
| **Value Labels** | List and inspect value labels via `labels` |
|
|
48
|
+
| **Macro Access** | Get/set Stata macros including `c()`, `e()`, `r()` system macros |
|
|
49
|
+
| **Frame Management** | List Stata frames and current working frame via `frame` |
|
|
42
50
|
| **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
|
|
43
|
-
| **Graph Export** | Auto-detect and export graphs as PNG to `~/.stata-cli/graphs/` |
|
|
51
|
+
| **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
|
|
44
52
|
| **Daemon Mode** | Persistent background process for sub-second execution via Unix socket |
|
|
45
|
-
| **Output Control** | Compact mode, JSON output, token limit management
|
|
53
|
+
| **Output Control** | Compact mode, JSON output, token limit management, log file output |
|
|
46
54
|
| **Interruption** | Send break signal to stop long-running commands |
|
|
47
55
|
|
|
48
56
|
## Installation & Quick Start
|
|
@@ -197,6 +205,60 @@ stata-cli detect
|
|
|
197
205
|
|
|
198
206
|
Prints the auto-detected Stata installation path.
|
|
199
207
|
|
|
208
|
+
### `return` — Retrieve Stored Results
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
stata-cli return r # r() results (after summarize, etc.)
|
|
212
|
+
stata-cli return e # e() results (after regress, etc.)
|
|
213
|
+
stata-cli return s # s() results
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Returns r(), e(), or s() stored results as structured JSON — scalars, macros, and matrix references.
|
|
217
|
+
|
|
218
|
+
### `vars` — Variable Metadata
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
stata-cli vars # all variables
|
|
222
|
+
stata-cli vars price mpg # specific variables
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Returns variable names, types, formats, and labels as JSON. More structured than `describe`.
|
|
226
|
+
|
|
227
|
+
### `matrix` — Read Stata Matrices
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
stata-cli matrix e(b) # coefficient vector
|
|
231
|
+
stata-cli matrix e(V) # variance-covariance matrix
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Returns matrix data, dimensions, and row/column names as JSON.
|
|
235
|
+
|
|
236
|
+
### `labels` — Value Labels
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
stata-cli labels # list all value label names
|
|
240
|
+
stata-cli labels origin # show value-label mapping
|
|
241
|
+
stata-cli labels --var foreign # show label attached to a variable
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `macro` — Get/Set Macros
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
stata-cli macro get "c(current_date)"
|
|
248
|
+
stata-cli macro get "e(cmd)"
|
|
249
|
+
stata-cli macro set myvar "hello"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Access Stata macros including system macros (`c()`, `e()`, `r()`).
|
|
253
|
+
|
|
254
|
+
### `frame` — List Frames
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
stata-cli frame
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Shows all Stata frames and the current working frame.
|
|
261
|
+
|
|
200
262
|
## Daemon Mode
|
|
201
263
|
|
|
202
264
|
The daemon keeps PyStata alive in the background — reduces execution time from **~2-3s to ~85ms** (35x speedup).
|
|
@@ -234,6 +296,8 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
|
|
|
234
296
|
| `--max-tokens N` | Max output tokens (0=unlimited) | 0 |
|
|
235
297
|
| `--no-daemon` | Force direct execution | off |
|
|
236
298
|
| `--graphs-dir PATH` | Graph export directory | `~/.stata-cli/graphs/` |
|
|
299
|
+
| `--graph-format [png\|svg\|pdf]` | Graph export format | `png` |
|
|
300
|
+
| `--log PATH` | Save output to a log file | off |
|
|
237
301
|
|
|
238
302
|
### JSON Output
|
|
239
303
|
|
|
@@ -313,6 +377,21 @@ regress price mpg weight
|
|
|
313
377
|
predict yhat
|
|
314
378
|
list make price yhat in 1/5"
|
|
315
379
|
|
|
380
|
+
# Retrieve regression results as structured JSON
|
|
381
|
+
stata-cli return e
|
|
382
|
+
|
|
383
|
+
# Get coefficient matrix
|
|
384
|
+
stata-cli matrix e(b)
|
|
385
|
+
|
|
386
|
+
# Inspect variable metadata
|
|
387
|
+
stata-cli vars price mpg weight
|
|
388
|
+
|
|
389
|
+
# Check value labels
|
|
390
|
+
stata-cli labels --var foreign
|
|
391
|
+
|
|
392
|
+
# Read system macros
|
|
393
|
+
stata-cli macro get "c(N)"
|
|
394
|
+
|
|
316
395
|
# Check data after loading
|
|
317
396
|
stata-cli data --if "price>10000"
|
|
318
397
|
|
|
@@ -325,6 +404,9 @@ describe"
|
|
|
325
404
|
|
|
326
405
|
# JSON mode for structured parsing
|
|
327
406
|
stata-cli --json run "display 1+1"
|
|
407
|
+
|
|
408
|
+
# Export graph as SVG
|
|
409
|
+
stata-cli --graph-format svg run "scatter price mpg"
|
|
328
410
|
```
|
|
329
411
|
|
|
330
412
|
## Contributing
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|