stata-cli 0.4.2__tar.gz → 0.5.1__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.4.2 → stata_cli-0.5.1}/PKG-INFO +23 -3
- {stata_cli-0.4.2 → stata_cli-0.5.1}/README.md +22 -2
- {stata_cli-0.4.2 → stata_cli-0.5.1}/pyproject.toml +1 -1
- stata_cli-0.5.1/src/stata_cli/__init__.py +1 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/daemon.py +95 -29
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/main.py +59 -36
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli.egg-info/PKG-INFO +23 -3
- stata_cli-0.4.2/src/stata_cli/__init__.py +0 -1
- {stata_cli-0.4.2 → stata_cli-0.5.1}/setup.cfg +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/__main__.py +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/engine.py +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/graph_artifacts.py +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/output_filter.py +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skill_registry.py +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/overview.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/asdoc.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/binsreg.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/coefplot.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/data-manipulation.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/diagnostics.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/did.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/estout.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/event-study.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/graph-schemes.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/ivreg2.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/nprobust.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/outreg2.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/package-management.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/psmatch2.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/rdrobust.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/reghdfe.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/synth.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/tabout.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/winsor.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/packages/xtabond2.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/advanced-programming.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/basics-getting-started.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/bootstrap-simulation.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/data-import-export.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/data-management.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/date-time-functions.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/descriptive-statistics.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/difference-in-differences.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/external-tools-integration.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/gmm-estimation.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/graphics.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/limited-dependent-variables.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/linear-regression.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/machine-learning.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/mata-data-access.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/mata-introduction.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/mata-matrix-operations.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/mata-programming.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/matching-methods.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/mathematical-functions.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/maximum-likelihood.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/missing-data-handling.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/nonparametric-methods.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/panel-data.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/programming-basics.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/regression-discontinuity.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/sample-selection.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/sem-factor-analysis.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/spatial-analysis.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/string-functions.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/survey-data-analysis.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/survival-analysis.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/tables-reporting.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/time-series.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/treatment-effects.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/variables-operators.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/workflow-best-practices.md +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/smcl_parser.py +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/utils.py +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli.egg-info/SOURCES.txt +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli.egg-info/dependency_links.txt +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli.egg-info/entry_points.txt +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli.egg-info/requires.txt +0 -0
- {stata_cli-0.4.2 → stata_cli-0.5.1}/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.5.1
|
|
4
4
|
Summary: Command-line interface for running Stata commands via PyStata
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: stata,cli,statistics,data-science
|
|
@@ -51,7 +51,7 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
|
|
|
51
51
|
| **Frame Management** | List Stata frames and current working frame via `frame` |
|
|
52
52
|
| **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
|
|
53
53
|
| **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
|
|
54
|
-
| **Daemon Mode** | Persistent background process for sub-second execution via
|
|
54
|
+
| **Daemon Mode** | Persistent background process for sub-second execution; parallel sessions via `--session` |
|
|
55
55
|
| **Output Control** | Compact mode, JSON output, token limit management, log file output |
|
|
56
56
|
| **Interruption** | Send break signal to stop long-running commands |
|
|
57
57
|
| **Skill Library** | Built-in Stata reference with 57 topics: syntax, econometrics, causal inference, packages |
|
|
@@ -290,13 +290,32 @@ stata-cli daemon stop # Shut down
|
|
|
290
290
|
|---------|-------------|
|
|
291
291
|
| `daemon start` | Start the background daemon process |
|
|
292
292
|
| `daemon stop` | Graceful shutdown |
|
|
293
|
-
| `daemon
|
|
293
|
+
| `daemon stop --all` | Stop all running sessions |
|
|
294
|
+
| `daemon status` | Show all running sessions (PID, uptime, idle) |
|
|
294
295
|
| `daemon restart` | Stop + start (clean Stata state) |
|
|
295
296
|
|
|
296
297
|
Commands auto-route through the daemon when it is running. Use `--no-daemon` to force direct execution.
|
|
297
298
|
|
|
298
299
|
The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle-timeout`).
|
|
299
300
|
|
|
301
|
+
### Parallel Sessions
|
|
302
|
+
|
|
303
|
+
Run multiple independent Stata instances — like opening multiple Stata windows:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Start named sessions
|
|
307
|
+
stata-cli --session proj_a daemon start
|
|
308
|
+
stata-cli --session proj_b daemon start
|
|
309
|
+
|
|
310
|
+
# Each session has its own data, estimates, and macros
|
|
311
|
+
stata-cli --session proj_a run "use project_a.dta, clear"
|
|
312
|
+
stata-cli --session proj_b run "use project_b.dta, clear"
|
|
313
|
+
|
|
314
|
+
# Route any command to a specific session
|
|
315
|
+
stata-cli --session proj_a run "regress price mpg weight"
|
|
316
|
+
stata-cli --session proj_b return e
|
|
317
|
+
```
|
|
318
|
+
|
|
300
319
|
## Advanced Usage
|
|
301
320
|
|
|
302
321
|
### Global Options
|
|
@@ -305,6 +324,7 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
|
|
|
305
324
|
|--------|-------------|---------|
|
|
306
325
|
| `--stata-path PATH` | Stata installation directory | auto-detected |
|
|
307
326
|
| `--edition [mp\|se\|be]` | Stata edition | `mp` |
|
|
327
|
+
| `--session NAME` | Daemon session name (for parallel sessions) | `default` |
|
|
308
328
|
| `--compact` | Strip verbose output noise | off |
|
|
309
329
|
| `--json` | Structured JSON output | off |
|
|
310
330
|
| `--timeout SECONDS` | Execution timeout | 600 |
|
|
@@ -38,7 +38,7 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
|
|
|
38
38
|
| **Frame Management** | List Stata frames and current working frame via `frame` |
|
|
39
39
|
| **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
|
|
40
40
|
| **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
|
|
41
|
-
| **Daemon Mode** | Persistent background process for sub-second execution via
|
|
41
|
+
| **Daemon Mode** | Persistent background process for sub-second execution; parallel sessions via `--session` |
|
|
42
42
|
| **Output Control** | Compact mode, JSON output, token limit management, log file output |
|
|
43
43
|
| **Interruption** | Send break signal to stop long-running commands |
|
|
44
44
|
| **Skill Library** | Built-in Stata reference with 57 topics: syntax, econometrics, causal inference, packages |
|
|
@@ -277,13 +277,32 @@ stata-cli daemon stop # Shut down
|
|
|
277
277
|
|---------|-------------|
|
|
278
278
|
| `daemon start` | Start the background daemon process |
|
|
279
279
|
| `daemon stop` | Graceful shutdown |
|
|
280
|
-
| `daemon
|
|
280
|
+
| `daemon stop --all` | Stop all running sessions |
|
|
281
|
+
| `daemon status` | Show all running sessions (PID, uptime, idle) |
|
|
281
282
|
| `daemon restart` | Stop + start (clean Stata state) |
|
|
282
283
|
|
|
283
284
|
Commands auto-route through the daemon when it is running. Use `--no-daemon` to force direct execution.
|
|
284
285
|
|
|
285
286
|
The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle-timeout`).
|
|
286
287
|
|
|
288
|
+
### Parallel Sessions
|
|
289
|
+
|
|
290
|
+
Run multiple independent Stata instances — like opening multiple Stata windows:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# Start named sessions
|
|
294
|
+
stata-cli --session proj_a daemon start
|
|
295
|
+
stata-cli --session proj_b daemon start
|
|
296
|
+
|
|
297
|
+
# Each session has its own data, estimates, and macros
|
|
298
|
+
stata-cli --session proj_a run "use project_a.dta, clear"
|
|
299
|
+
stata-cli --session proj_b run "use project_b.dta, clear"
|
|
300
|
+
|
|
301
|
+
# Route any command to a specific session
|
|
302
|
+
stata-cli --session proj_a run "regress price mpg weight"
|
|
303
|
+
stata-cli --session proj_b return e
|
|
304
|
+
```
|
|
305
|
+
|
|
287
306
|
## Advanced Usage
|
|
288
307
|
|
|
289
308
|
### Global Options
|
|
@@ -292,6 +311,7 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
|
|
|
292
311
|
|--------|-------------|---------|
|
|
293
312
|
| `--stata-path PATH` | Stata installation directory | auto-detected |
|
|
294
313
|
| `--edition [mp\|se\|be]` | Stata edition | `mp` |
|
|
314
|
+
| `--session NAME` | Daemon session name (for parallel sessions) | `default` |
|
|
295
315
|
| `--compact` | Strip verbose output noise | off |
|
|
296
316
|
| `--json` | Structured JSON output | off |
|
|
297
317
|
| `--timeout SECONDS` | Execution timeout | 600 |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.1"
|
|
@@ -18,13 +18,19 @@ from pathlib import Path
|
|
|
18
18
|
from typing import Any, Dict, Optional
|
|
19
19
|
|
|
20
20
|
_STATE_DIR = os.path.join(os.path.expanduser("~"), ".stata-cli")
|
|
21
|
-
_PID_FILE = os.path.join(_STATE_DIR, "daemon.pid")
|
|
22
|
-
_SOCK_FILE = os.path.join(_STATE_DIR, "daemon.sock")
|
|
23
21
|
_IS_WINDOWS = platform.system() == "Windows"
|
|
24
22
|
_DEFAULT_PORT = 4718
|
|
25
23
|
_IDLE_TIMEOUT = 3600 # 1 hour
|
|
26
24
|
|
|
27
25
|
|
|
26
|
+
def _pid_file(session: str = "default") -> str:
|
|
27
|
+
return os.path.join(_STATE_DIR, f"daemon-{session}.pid")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _sock_file(session: str = "default") -> str:
|
|
31
|
+
return os.path.join(_STATE_DIR, f"daemon-{session}.sock")
|
|
32
|
+
|
|
33
|
+
|
|
28
34
|
# ── wire protocol: 4-byte big-endian length prefix + JSON ────────────────
|
|
29
35
|
|
|
30
36
|
def _send_msg(sock: socket.socket, obj: Any) -> None:
|
|
@@ -57,11 +63,13 @@ class DaemonServer:
|
|
|
57
63
|
"""Single-threaded daemon that wraps a StataEngine."""
|
|
58
64
|
|
|
59
65
|
def __init__(self, stata_path: str, edition: str = "mp",
|
|
60
|
-
graphs_dir: Optional[str] = None, idle_timeout: float = _IDLE_TIMEOUT
|
|
66
|
+
graphs_dir: Optional[str] = None, idle_timeout: float = _IDLE_TIMEOUT,
|
|
67
|
+
session: str = "default"):
|
|
61
68
|
self.stata_path = stata_path
|
|
62
69
|
self.edition = edition
|
|
63
70
|
self.graphs_dir = graphs_dir
|
|
64
71
|
self.idle_timeout = idle_timeout
|
|
72
|
+
self.session = session
|
|
65
73
|
self._engine = None
|
|
66
74
|
self._running = False
|
|
67
75
|
self._start_time = time.time()
|
|
@@ -77,6 +85,7 @@ class DaemonServer:
|
|
|
77
85
|
os.makedirs(_STATE_DIR, exist_ok=True)
|
|
78
86
|
|
|
79
87
|
sock = self._create_listener()
|
|
88
|
+
self._port = sock.getsockname()[1] if _IS_WINDOWS else 0
|
|
80
89
|
self._write_pid()
|
|
81
90
|
self._running = True
|
|
82
91
|
|
|
@@ -113,16 +122,31 @@ class DaemonServer:
|
|
|
113
122
|
if _IS_WINDOWS:
|
|
114
123
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
115
124
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
116
|
-
|
|
125
|
+
port = self._find_free_port()
|
|
126
|
+
sock.bind(("127.0.0.1", port))
|
|
117
127
|
else:
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
sock_path = _sock_file(self.session)
|
|
129
|
+
if os.path.exists(sock_path):
|
|
130
|
+
os.unlink(sock_path)
|
|
120
131
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
121
|
-
sock.bind(
|
|
132
|
+
sock.bind(sock_path)
|
|
122
133
|
sock.listen(4)
|
|
123
134
|
sock.setblocking(False)
|
|
124
135
|
return sock
|
|
125
136
|
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _find_free_port() -> int:
|
|
139
|
+
for port in range(_DEFAULT_PORT, _DEFAULT_PORT + 200):
|
|
140
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
141
|
+
try:
|
|
142
|
+
s.bind(("127.0.0.1", port))
|
|
143
|
+
s.close()
|
|
144
|
+
return port
|
|
145
|
+
except OSError:
|
|
146
|
+
s.close()
|
|
147
|
+
continue
|
|
148
|
+
raise RuntimeError("No free port found")
|
|
149
|
+
|
|
126
150
|
def _handle_connection(self, conn: socket.socket) -> None:
|
|
127
151
|
conn.settimeout(600.0)
|
|
128
152
|
msg = _recv_msg(conn)
|
|
@@ -216,18 +240,19 @@ class DaemonServer:
|
|
|
216
240
|
return asdict(result)
|
|
217
241
|
|
|
218
242
|
def _write_pid(self) -> None:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
243
|
+
sock_path = _sock_file(self.session)
|
|
244
|
+
addr = f"127.0.0.1:{self._port}" if _IS_WINDOWS else sock_path
|
|
245
|
+
with open(_pid_file(self.session), "w") as fh:
|
|
246
|
+
json.dump({"pid": os.getpid(), "address": addr, "started": time.time(), "session": self.session}, fh)
|
|
222
247
|
|
|
223
248
|
def _cleanup(self) -> None:
|
|
224
249
|
try:
|
|
225
|
-
os.unlink(
|
|
250
|
+
os.unlink(_pid_file(self.session))
|
|
226
251
|
except OSError:
|
|
227
252
|
pass
|
|
228
253
|
if not _IS_WINDOWS:
|
|
229
254
|
try:
|
|
230
|
-
os.unlink(
|
|
255
|
+
os.unlink(_sock_file(self.session))
|
|
231
256
|
except OSError:
|
|
232
257
|
pass
|
|
233
258
|
|
|
@@ -236,9 +261,10 @@ class DaemonServer:
|
|
|
236
261
|
|
|
237
262
|
|
|
238
263
|
def run_daemon_server(stata_path: str, edition: str = "mp",
|
|
239
|
-
graphs_dir: Optional[str] = None, idle_timeout: float = _IDLE_TIMEOUT
|
|
264
|
+
graphs_dir: Optional[str] = None, idle_timeout: float = _IDLE_TIMEOUT,
|
|
265
|
+
session: str = "default") -> None:
|
|
240
266
|
"""Entry point called in the daemon subprocess."""
|
|
241
|
-
server = DaemonServer(stata_path, edition, graphs_dir=graphs_dir, idle_timeout=idle_timeout)
|
|
267
|
+
server = DaemonServer(stata_path, edition, graphs_dir=graphs_dir, idle_timeout=idle_timeout, session=session)
|
|
242
268
|
server.serve()
|
|
243
269
|
|
|
244
270
|
|
|
@@ -249,14 +275,16 @@ def run_daemon_server(stata_path: str, edition: str = "mp",
|
|
|
249
275
|
class DaemonClient:
|
|
250
276
|
"""Connects to a running daemon over the Unix socket / TCP port."""
|
|
251
277
|
|
|
252
|
-
def __init__(self) -> None:
|
|
278
|
+
def __init__(self, session: str = "default") -> None:
|
|
279
|
+
self.session = session
|
|
253
280
|
self._sock: Optional[socket.socket] = None
|
|
254
281
|
|
|
255
282
|
def is_running(self) -> bool:
|
|
256
|
-
|
|
283
|
+
pid_path = _pid_file(self.session)
|
|
284
|
+
if not os.path.isfile(pid_path):
|
|
257
285
|
return False
|
|
258
286
|
try:
|
|
259
|
-
with open(
|
|
287
|
+
with open(pid_path) as fh:
|
|
260
288
|
info = json.load(fh)
|
|
261
289
|
pid = info["pid"]
|
|
262
290
|
os.kill(pid, 0)
|
|
@@ -298,30 +326,33 @@ class DaemonClient:
|
|
|
298
326
|
self._sock = None
|
|
299
327
|
|
|
300
328
|
def _read_pid(self) -> Optional[Dict]:
|
|
301
|
-
|
|
329
|
+
pid_path = _pid_file(self.session)
|
|
330
|
+
if not os.path.isfile(pid_path):
|
|
302
331
|
return None
|
|
303
332
|
try:
|
|
304
|
-
with open(
|
|
333
|
+
with open(pid_path) as fh:
|
|
305
334
|
return json.load(fh)
|
|
306
335
|
except (json.JSONDecodeError, OSError):
|
|
307
336
|
return None
|
|
308
337
|
|
|
309
338
|
|
|
310
339
|
def start_daemon(stata_path: str, edition: str = "mp",
|
|
311
|
-
graphs_dir: Optional[str] = None, idle_timeout: float = _IDLE_TIMEOUT
|
|
340
|
+
graphs_dir: Optional[str] = None, idle_timeout: float = _IDLE_TIMEOUT,
|
|
341
|
+
session: str = "default") -> bool:
|
|
312
342
|
"""Launch daemon as a detached background process. Returns True on success."""
|
|
313
|
-
client = DaemonClient()
|
|
343
|
+
client = DaemonClient(session)
|
|
314
344
|
if client.is_running():
|
|
315
345
|
return True
|
|
316
346
|
|
|
317
347
|
os.makedirs(_STATE_DIR, exist_ok=True)
|
|
318
|
-
log_file = os.path.join(_STATE_DIR, "daemon.log")
|
|
348
|
+
log_file = os.path.join(_STATE_DIR, f"daemon-{session}.log")
|
|
319
349
|
|
|
320
350
|
args = [
|
|
321
351
|
sys.executable, "-m", "stata_cli.daemon",
|
|
322
352
|
"--stata-path", stata_path,
|
|
323
353
|
"--edition", edition,
|
|
324
354
|
"--idle-timeout", str(int(idle_timeout)),
|
|
355
|
+
"--session", session,
|
|
325
356
|
]
|
|
326
357
|
if graphs_dir:
|
|
327
358
|
args += ["--graphs-dir", graphs_dir]
|
|
@@ -345,9 +376,9 @@ def start_daemon(stata_path: str, edition: str = "mp",
|
|
|
345
376
|
return False
|
|
346
377
|
|
|
347
378
|
|
|
348
|
-
def stop_daemon() -> bool:
|
|
379
|
+
def stop_daemon(session: str = "default") -> bool:
|
|
349
380
|
"""Ask the daemon to shut down gracefully."""
|
|
350
|
-
client = DaemonClient()
|
|
381
|
+
client = DaemonClient(session)
|
|
351
382
|
if not client.is_running():
|
|
352
383
|
return True
|
|
353
384
|
if client.connect():
|
|
@@ -362,7 +393,7 @@ def stop_daemon() -> bool:
|
|
|
362
393
|
return True
|
|
363
394
|
# Force kill
|
|
364
395
|
try:
|
|
365
|
-
with open(
|
|
396
|
+
with open(_pid_file(session)) as fh:
|
|
366
397
|
pid = json.load(fh)["pid"]
|
|
367
398
|
os.kill(pid, signal.SIGKILL if not _IS_WINDOWS else signal.SIGTERM)
|
|
368
399
|
except Exception:
|
|
@@ -370,9 +401,43 @@ def stop_daemon() -> bool:
|
|
|
370
401
|
return True
|
|
371
402
|
|
|
372
403
|
|
|
373
|
-
def
|
|
374
|
-
"""
|
|
375
|
-
|
|
404
|
+
def stop_all_daemons() -> int:
|
|
405
|
+
"""Stop all running daemon sessions. Returns count stopped."""
|
|
406
|
+
import glob
|
|
407
|
+
count = 0
|
|
408
|
+
for pid_path in glob.glob(os.path.join(_STATE_DIR, "daemon-*.pid")):
|
|
409
|
+
name = os.path.basename(pid_path)
|
|
410
|
+
session = name[len("daemon-"):-len(".pid")]
|
|
411
|
+
stop_daemon(session)
|
|
412
|
+
count += 1
|
|
413
|
+
return count
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def list_sessions() -> list:
|
|
417
|
+
"""Return list of running session info dicts."""
|
|
418
|
+
import glob
|
|
419
|
+
sessions = []
|
|
420
|
+
for pid_path in glob.glob(os.path.join(_STATE_DIR, "daemon-*.pid")):
|
|
421
|
+
name = os.path.basename(pid_path)
|
|
422
|
+
session = name[len("daemon-"):-len(".pid")]
|
|
423
|
+
client = DaemonClient(session)
|
|
424
|
+
if not client.is_running():
|
|
425
|
+
continue
|
|
426
|
+
info = {"session": session}
|
|
427
|
+
if client.connect():
|
|
428
|
+
try:
|
|
429
|
+
status = client.send("status")
|
|
430
|
+
info.update(status)
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
433
|
+
client.close()
|
|
434
|
+
sessions.append(info)
|
|
435
|
+
return sessions
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def daemon_status(session: str = "default") -> Optional[Dict[str, Any]]:
|
|
439
|
+
"""Query a running daemon's status. Returns None if not running."""
|
|
440
|
+
client = DaemonClient(session)
|
|
376
441
|
if not client.is_running():
|
|
377
442
|
return None
|
|
378
443
|
if not client.connect():
|
|
@@ -394,5 +459,6 @@ if __name__ == "__main__":
|
|
|
394
459
|
parser.add_argument("--edition", default="mp")
|
|
395
460
|
parser.add_argument("--graphs-dir", default=None)
|
|
396
461
|
parser.add_argument("--idle-timeout", type=float, default=_IDLE_TIMEOUT)
|
|
462
|
+
parser.add_argument("--session", default="default")
|
|
397
463
|
args = parser.parse_args()
|
|
398
|
-
run_daemon_server(args.stata_path, args.edition, args.graphs_dir, args.idle_timeout)
|
|
464
|
+
run_daemon_server(args.stata_path, args.edition, args.graphs_dir, args.idle_timeout, session=args.session)
|
|
@@ -36,11 +36,12 @@ def _exit(code: int) -> None:
|
|
|
36
36
|
@click.option("--timeout", type=float, default=600.0, help="Execution timeout in seconds.")
|
|
37
37
|
@click.option("--max-tokens", type=int, default=0, help="Max output tokens (0=unlimited). Saves full output to file when exceeded.")
|
|
38
38
|
@click.option("--no-daemon", is_flag=True, default=False, help="Force direct execution, skip daemon.")
|
|
39
|
+
@click.option("--session", default="default", help="Daemon session name (for parallel sessions).")
|
|
39
40
|
@click.option("--graphs-dir", envvar="STATA_CLI_GRAPHS_DIR", default=None, help="Graph export directory.")
|
|
40
41
|
@click.option("--graph-format", type=click.Choice(["png", "svg", "pdf"], case_sensitive=False), default="png", help="Graph export format.")
|
|
41
42
|
@click.option("--log", "log_file", default=None, help="Save Stata output to a log file.")
|
|
42
43
|
@click.pass_context
|
|
43
|
-
def cli(ctx, stata_path, edition, compact, use_json, timeout, max_tokens, no_daemon, graphs_dir, graph_format, log_file):
|
|
44
|
+
def cli(ctx, stata_path, edition, compact, use_json, timeout, max_tokens, no_daemon, session, graphs_dir, graph_format, log_file):
|
|
44
45
|
"""Command-line interface for Stata."""
|
|
45
46
|
ctx.ensure_object(dict)
|
|
46
47
|
ctx.obj["stata_path"] = stata_path
|
|
@@ -50,6 +51,7 @@ def cli(ctx, stata_path, edition, compact, use_json, timeout, max_tokens, no_dae
|
|
|
50
51
|
ctx.obj["timeout"] = timeout
|
|
51
52
|
ctx.obj["max_tokens"] = max_tokens
|
|
52
53
|
ctx.obj["no_daemon"] = no_daemon
|
|
54
|
+
ctx.obj["session"] = session
|
|
53
55
|
ctx.obj["graphs_dir"] = graphs_dir
|
|
54
56
|
ctx.obj["graph_format"] = graph_format
|
|
55
57
|
ctx.obj["log_file"] = log_file
|
|
@@ -75,7 +77,7 @@ def _try_daemon(ctx, cmd_type: str, payload: dict) -> Result | None:
|
|
|
75
77
|
return None
|
|
76
78
|
try:
|
|
77
79
|
from .daemon import DaemonClient
|
|
78
|
-
client = DaemonClient()
|
|
80
|
+
client = DaemonClient(ctx.obj.get("session", "default"))
|
|
79
81
|
if not client.is_running():
|
|
80
82
|
return None
|
|
81
83
|
if not client.connect():
|
|
@@ -100,7 +102,7 @@ def _try_daemon_dict(ctx, cmd_type: str, payload: dict) -> dict | None:
|
|
|
100
102
|
return None
|
|
101
103
|
try:
|
|
102
104
|
from .daemon import DaemonClient
|
|
103
|
-
client = DaemonClient()
|
|
105
|
+
client = DaemonClient(ctx.obj.get("session", "default"))
|
|
104
106
|
if not client.is_running():
|
|
105
107
|
return None
|
|
106
108
|
if not client.connect():
|
|
@@ -222,7 +224,7 @@ def data_cmd(ctx, if_condition, rows):
|
|
|
222
224
|
# Try daemon first
|
|
223
225
|
try:
|
|
224
226
|
from .daemon import DaemonClient
|
|
225
|
-
client = DaemonClient()
|
|
227
|
+
client = DaemonClient(ctx.obj.get("session", "default"))
|
|
226
228
|
if not ctx.obj.get("no_daemon") and client.is_running() and client.connect():
|
|
227
229
|
resp = client.send("get_data", {"if_condition": if_condition, "max_rows": rows})
|
|
228
230
|
client.close()
|
|
@@ -266,7 +268,7 @@ def stop_cmd(ctx):
|
|
|
266
268
|
"""Interrupt a running Stata command (daemon mode)."""
|
|
267
269
|
try:
|
|
268
270
|
from .daemon import DaemonClient
|
|
269
|
-
client = DaemonClient()
|
|
271
|
+
client = DaemonClient(ctx.obj.get("session", "default"))
|
|
270
272
|
if client.is_running() and client.connect():
|
|
271
273
|
resp = client.send("stop")
|
|
272
274
|
client.close()
|
|
@@ -462,48 +464,68 @@ def daemon_start(ctx, idle_timeout):
|
|
|
462
464
|
click.echo("Error: Stata installation not found.", err=True)
|
|
463
465
|
_exit(EXIT_INIT_FAILURE)
|
|
464
466
|
|
|
467
|
+
session = ctx.obj["session"]
|
|
465
468
|
from .daemon import start_daemon, DaemonClient
|
|
466
|
-
client = DaemonClient()
|
|
469
|
+
client = DaemonClient(session)
|
|
467
470
|
if client.is_running():
|
|
468
|
-
click.echo("Daemon already running.")
|
|
471
|
+
click.echo(f"Daemon '{session}' already running.")
|
|
469
472
|
return
|
|
470
473
|
|
|
471
|
-
click.echo("Starting daemon...")
|
|
472
|
-
ok = start_daemon(stata_path, ctx.obj["edition"], graphs_dir=ctx.obj.get("graphs_dir"), idle_timeout=idle_timeout)
|
|
474
|
+
click.echo(f"Starting daemon '{session}'...")
|
|
475
|
+
ok = start_daemon(stata_path, ctx.obj["edition"], graphs_dir=ctx.obj.get("graphs_dir"), idle_timeout=idle_timeout, session=session)
|
|
473
476
|
if ok:
|
|
474
|
-
click.echo("Daemon started.")
|
|
477
|
+
click.echo(f"Daemon '{session}' started.")
|
|
475
478
|
else:
|
|
476
479
|
click.echo("Failed to start daemon.", err=True)
|
|
477
480
|
_exit(EXIT_INIT_FAILURE)
|
|
478
481
|
|
|
479
482
|
|
|
480
483
|
@daemon.command("stop")
|
|
481
|
-
|
|
484
|
+
@click.option("--all", "stop_all", is_flag=True, default=False, help="Stop all running sessions.")
|
|
485
|
+
@click.pass_context
|
|
486
|
+
def daemon_stop(ctx, stop_all):
|
|
482
487
|
"""Stop the Stata daemon."""
|
|
483
|
-
from .daemon import stop_daemon, DaemonClient
|
|
484
|
-
|
|
488
|
+
from .daemon import stop_daemon as _stop, stop_all_daemons, DaemonClient
|
|
489
|
+
if stop_all:
|
|
490
|
+
count = stop_all_daemons()
|
|
491
|
+
click.echo(f"Stopped {count} session(s).")
|
|
492
|
+
return
|
|
493
|
+
session = ctx.obj["session"]
|
|
494
|
+
client = DaemonClient(session)
|
|
485
495
|
if not client.is_running():
|
|
486
|
-
click.echo("Daemon not running.")
|
|
496
|
+
click.echo(f"Daemon '{session}' not running.")
|
|
487
497
|
return
|
|
488
|
-
click.echo("Stopping daemon...")
|
|
489
|
-
|
|
490
|
-
click.echo("Daemon stopped.")
|
|
498
|
+
click.echo(f"Stopping daemon '{session}'...")
|
|
499
|
+
_stop(session)
|
|
500
|
+
click.echo(f"Daemon '{session}' stopped.")
|
|
491
501
|
|
|
492
502
|
|
|
493
503
|
@daemon.command("status")
|
|
494
|
-
|
|
504
|
+
@click.pass_context
|
|
505
|
+
def daemon_status_cmd(ctx):
|
|
495
506
|
"""Show daemon status."""
|
|
496
|
-
from .daemon import daemon_status
|
|
497
|
-
|
|
498
|
-
if
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
+
from .daemon import daemon_status, list_sessions
|
|
508
|
+
session = ctx.obj["session"]
|
|
509
|
+
if session == "default":
|
|
510
|
+
sessions = list_sessions()
|
|
511
|
+
if not sessions:
|
|
512
|
+
click.echo("No daemons running.")
|
|
513
|
+
return
|
|
514
|
+
for s in sessions:
|
|
515
|
+
uptime = s.get("uptime", 0)
|
|
516
|
+
idle = s.get("idle", 0)
|
|
517
|
+
click.echo(f"[{s['session']}] PID {s.get('pid', '?')} | uptime {int(uptime)}s | idle {int(idle)}s")
|
|
518
|
+
else:
|
|
519
|
+
info = daemon_status(session)
|
|
520
|
+
if not info:
|
|
521
|
+
click.echo(f"Daemon '{session}' not running.")
|
|
522
|
+
return
|
|
523
|
+
uptime = info.get("uptime", 0)
|
|
524
|
+
idle = info.get("idle", 0)
|
|
525
|
+
click.echo(f"Daemon '{session}' running (PID {info.get('pid', '?')})")
|
|
526
|
+
click.echo(f" Stata: {info.get('stata_path', '?')} ({info.get('edition', '?')})")
|
|
527
|
+
click.echo(f" Uptime: {int(uptime)}s")
|
|
528
|
+
click.echo(f" Idle: {int(idle)}s")
|
|
507
529
|
|
|
508
530
|
|
|
509
531
|
@daemon.command("restart")
|
|
@@ -511,21 +533,22 @@ def daemon_status_cmd():
|
|
|
511
533
|
@click.pass_context
|
|
512
534
|
def daemon_restart(ctx, idle_timeout):
|
|
513
535
|
"""Restart the Stata daemon."""
|
|
514
|
-
|
|
515
|
-
|
|
536
|
+
session = ctx.obj["session"]
|
|
537
|
+
from .daemon import stop_daemon as _stop, start_daemon, DaemonClient
|
|
538
|
+
client = DaemonClient(session)
|
|
516
539
|
if client.is_running():
|
|
517
|
-
click.echo("Stopping daemon...")
|
|
518
|
-
|
|
540
|
+
click.echo(f"Stopping daemon '{session}'...")
|
|
541
|
+
_stop(session)
|
|
519
542
|
|
|
520
543
|
stata_path = ctx.obj["stata_path"] or detect_stata_path()
|
|
521
544
|
if not stata_path:
|
|
522
545
|
click.echo("Error: Stata installation not found.", err=True)
|
|
523
546
|
_exit(EXIT_INIT_FAILURE)
|
|
524
547
|
|
|
525
|
-
click.echo("Starting daemon...")
|
|
526
|
-
ok = start_daemon(stata_path, ctx.obj["edition"], graphs_dir=ctx.obj.get("graphs_dir"), idle_timeout=idle_timeout)
|
|
548
|
+
click.echo(f"Starting daemon '{session}'...")
|
|
549
|
+
ok = start_daemon(stata_path, ctx.obj["edition"], graphs_dir=ctx.obj.get("graphs_dir"), idle_timeout=idle_timeout, session=session)
|
|
527
550
|
if ok:
|
|
528
|
-
click.echo("Daemon restarted.")
|
|
551
|
+
click.echo(f"Daemon '{session}' restarted.")
|
|
529
552
|
else:
|
|
530
553
|
click.echo("Failed to restart daemon.", err=True)
|
|
531
554
|
_exit(EXIT_INIT_FAILURE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stata-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Command-line interface for running Stata commands via PyStata
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: stata,cli,statistics,data-science
|
|
@@ -51,7 +51,7 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
|
|
|
51
51
|
| **Frame Management** | List Stata frames and current working frame via `frame` |
|
|
52
52
|
| **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
|
|
53
53
|
| **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
|
|
54
|
-
| **Daemon Mode** | Persistent background process for sub-second execution via
|
|
54
|
+
| **Daemon Mode** | Persistent background process for sub-second execution; parallel sessions via `--session` |
|
|
55
55
|
| **Output Control** | Compact mode, JSON output, token limit management, log file output |
|
|
56
56
|
| **Interruption** | Send break signal to stop long-running commands |
|
|
57
57
|
| **Skill Library** | Built-in Stata reference with 57 topics: syntax, econometrics, causal inference, packages |
|
|
@@ -290,13 +290,32 @@ stata-cli daemon stop # Shut down
|
|
|
290
290
|
|---------|-------------|
|
|
291
291
|
| `daemon start` | Start the background daemon process |
|
|
292
292
|
| `daemon stop` | Graceful shutdown |
|
|
293
|
-
| `daemon
|
|
293
|
+
| `daemon stop --all` | Stop all running sessions |
|
|
294
|
+
| `daemon status` | Show all running sessions (PID, uptime, idle) |
|
|
294
295
|
| `daemon restart` | Stop + start (clean Stata state) |
|
|
295
296
|
|
|
296
297
|
Commands auto-route through the daemon when it is running. Use `--no-daemon` to force direct execution.
|
|
297
298
|
|
|
298
299
|
The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle-timeout`).
|
|
299
300
|
|
|
301
|
+
### Parallel Sessions
|
|
302
|
+
|
|
303
|
+
Run multiple independent Stata instances — like opening multiple Stata windows:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Start named sessions
|
|
307
|
+
stata-cli --session proj_a daemon start
|
|
308
|
+
stata-cli --session proj_b daemon start
|
|
309
|
+
|
|
310
|
+
# Each session has its own data, estimates, and macros
|
|
311
|
+
stata-cli --session proj_a run "use project_a.dta, clear"
|
|
312
|
+
stata-cli --session proj_b run "use project_b.dta, clear"
|
|
313
|
+
|
|
314
|
+
# Route any command to a specific session
|
|
315
|
+
stata-cli --session proj_a run "regress price mpg weight"
|
|
316
|
+
stata-cli --session proj_b return e
|
|
317
|
+
```
|
|
318
|
+
|
|
300
319
|
## Advanced Usage
|
|
301
320
|
|
|
302
321
|
### Global Options
|
|
@@ -305,6 +324,7 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
|
|
|
305
324
|
|--------|-------------|---------|
|
|
306
325
|
| `--stata-path PATH` | Stata installation directory | auto-detected |
|
|
307
326
|
| `--edition [mp\|se\|be]` | Stata edition | `mp` |
|
|
327
|
+
| `--session NAME` | Daemon session name (for parallel sessions) | `default` |
|
|
308
328
|
| `--compact` | Strip verbose output noise | off |
|
|
309
329
|
| `--json` | Structured JSON output | off |
|
|
310
330
|
| `--timeout SECONDS` | Execution timeout | 600 |
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.2"
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/basics-getting-started.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/descriptive-statistics.md
RENAMED
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/difference-in-differences.md
RENAMED
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/external-tools-integration.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/limited-dependent-variables.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/mata-matrix-operations.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/mathematical-functions.md
RENAMED
|
File without changes
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/missing-data-handling.md
RENAMED
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/nonparametric-methods.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/regression-discontinuity.md
RENAMED
|
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
|
|
File without changes
|
{stata_cli-0.4.2 → stata_cli-0.5.1}/src/stata_cli/skills/references/workflow-best-practices.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|