├── .github ├── FUNDING.yml └── workflows │ ├── deploy.yaml │ └── test.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── index_files └── figure-commonmark │ ├── 226326ec-1-image-2.png │ └── cell-7-1-image-2.png ├── manual_test_nbs ├── delimit tests.ipynb ├── nbstata demo.ipynb ├── nbstata manual tests.ipynb └── stata_kernel example.ipynb ├── nbs ├── .gitignore ├── 00_misc_utils.ipynb ├── 01_config.ipynb ├── 02_stata.ipynb ├── 03_stata_more.ipynb ├── 04_code_utils.ipynb ├── 05_noecho.ipynb ├── 06_pandas.ipynb ├── 07_browse.ipynb ├── 08_stata_session.ipynb ├── 09_magics.ipynb ├── 10_completion_env.ipynb ├── 11_completions.ipynb ├── 12_inspect.ipynb ├── 13_cell.ipynb ├── 14_kernel.ipynb ├── 15_install.ipynb ├── _quarto.yml ├── custom.yml ├── dev_docs_index.qmd ├── images │ └── status example.png ├── index.ipynb ├── nbdev.yml ├── sidebar.yml ├── styles.css └── user_guide.ipynb ├── nbstata ├── __init__.py ├── __main__.py ├── _modidx.py ├── browse.py ├── cell.py ├── code_utils.py ├── completion_env.py ├── completions.py ├── config.py ├── css │ └── _StataKernelHelpDefault.css ├── inspect.py ├── install.py ├── kernel.py ├── logo-64x64.png ├── magics.py ├── misc_utils.py ├── noecho.py ├── pandas.py ├── stata.py ├── stata_more.py └── stata_session.py ├── settings.ini └── setup.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: hugetim # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | on: 3 | push: 4 | branches: [ "main", "master" ] 5 | workflow_dispatch: 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: [uses: fastai/workflows/quarto-ghp@master] 10 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [workflow_dispatch, pull_request, push] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | steps: [uses: fastai/workflows/nbdev-ci@master] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _docs/ 2 | _proc/ 3 | 4 | *.bak 5 | .gitattributes 6 | .last_checked 7 | .gitconfig 8 | *.bak 9 | *.log 10 | *~ 11 | ~* 12 | _tmp* 13 | tmp* 14 | tags 15 | *.pkg 16 | 17 | # Byte-compiled / optimized / DLL files 18 | __pycache__/ 19 | *.py[cod] 20 | *$py.class 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | env/ 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | *.egg-info/ 41 | .installed.cfg 42 | *.egg 43 | conda/ 44 | 45 | # PyInstaller 46 | # Usually these files are written by a python script from a template 47 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 48 | *.manifest 49 | *.spec 50 | 51 | # Installer logs 52 | pip-log.txt 53 | pip-delete-this-directory.txt 54 | 55 | # Unit test / coverage reports 56 | htmlcov/ 57 | .tox/ 58 | .coverage 59 | .coverage.* 60 | .cache 61 | nosetests.xml 62 | coverage.xml 63 | *.cover 64 | .hypothesis/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Django stuff: 71 | *.log 72 | local_settings.py 73 | 74 | # Flask stuff: 75 | instance/ 76 | .webassets-cache 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | 84 | # PyBuilder 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # pyenv 91 | .python-version 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # dotenv 100 | .env 101 | 102 | # virtualenv 103 | .venv 104 | venv/ 105 | ENV/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | 120 | .vscode 121 | *.swp 122 | 123 | # osx generated files 124 | .DS_Store 125 | .DS_Store? 126 | .Trashes 127 | ehthumbs.db 128 | Thumbs.db 129 | .idea 130 | 131 | # pytest 132 | .pytest_cache 133 | 134 | # tools/trust-doc-nbs 135 | docs_src/.last_checked 136 | 137 | # symlinks to fastai 138 | docs_src/fastai 139 | tools/fastai 140 | 141 | # link checker 142 | checklink/cookies.txt 143 | 144 | # .gitconfig is now autogenerated 145 | .gitconfig 146 | 147 | # Quarto installer 148 | .deb 149 | .pkg 150 | 151 | # Quarto 152 | .quarto 153 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | This project is developed using [nbdev](https://nbdev.fast.ai/blog/posts/2022-07-28-nbdev2/#whats-nbdev), a way to create delightful software with Jupyter notebooks. The Python library and docs are automatically created from the notebooks in the `/nbs` directory. 4 | 5 | ## How to get started 6 | Note: nbdev works on macOS, Linux, and most Unix-style operating systems. It works on Windows under [WSL](https://learn.microsoft.com/en-us/windows/wsl/setup/environment), but some features will not work under cmd or Powershell. 7 | 8 | Follow the [install nbdev](https://nbdev.fast.ai/tutorials/tutorial.html#installation) instructions, specifically: 9 | 10 | 1. [Install jupyter notebook](https://nbdev.fast.ai/tutorials/tutorial.html#install-jupyter-notebook) 11 | 2. [Install nbdev](https://nbdev.fast.ai/tutorials/tutorial.html#install-nbdev) 12 | 3. [Install quarto](https://nbdev.fast.ai/tutorials/tutorial.html#install-quarto) 13 | 4. After cloning the repository, run this command inside it: [nbdev_install_hooks](https://nbdev.fast.ai/tutorials/modular_nbdev.html#jupyter-git-integration) 14 | 5. Run [nbdev_export](https://nbdev.fast.ai/tutorials/tutorial.html#install-quarto) inside the project directory 15 | 6. Run [pip install -e '.[dev]'](https://nbdev.fast.ai/tutorials/tutorial.html#install-your-package) inside the project directory 16 | 17 | Visit [https://hugetim.github.io/nbstata/dev_docs_index.html](https://hugetim.github.io/nbstata/dev_docs_index.html) to get oriented. 18 | 19 | ## How to submit notebook PRs 20 | After making changes to the `/nbs` notebooks, you should run [nbdev_prepare](https://nbdev.fast.ai/tutorials/tutorial.html#prepare-your-changes) and make any necessary changes in order to pass all the tests. 21 | 22 | (You may also make limited changes directly to the `.py` files in the `/nbstata` folder, in which case you should sync those changes back to the notebooks with [nbdev_update](https://nbdev.fast.ai/api/sync.html).) 23 | 24 | ReviewNB gives us visual diffs for notebooks and enables PR comments specific to a cell: https://app.reviewnb.com/hugetim/nbstata/ (free account needed to login) 25 | 26 | ## Do you want to contribute to the documentation? 27 | * Docs are automatically created from the notebooks in the `/nbs` folder. 28 | * You can preview the docs locally by running [nbdev_preview](https://nbdev.fast.ai/tutorials/tutorial.html#preview-your-docs). While in preview mode, you can make updates to notebooks and they will be reflected (after a small delay) in your browser. 29 | 30 | ## Specifics to be aware of 31 | * The [@patch_to](https://fastcore.fast.ai/basics.html#patch_to) decorator is occasionally used to break up class definitions into separate cells. 32 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include settings.ini 2 | include LICENSE 3 | include CONTRIBUTING.md 4 | include README.md 5 | recursive-include nbstata * 6 | recursive-exclude * __pycache__ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nbstata: a new Stata kernel 2 | 3 | 4 | 5 | 6 |   7 | 8 | *nbstata* is a [Jupyter 9 | kernel](https://docs.jupyter.org/en/latest/projects/kernels.html) for 10 | [Stata](https://www.stata.com/why-use-stata/) built on top of 11 | [pystata](https://www.stata.com/python/pystata18/index.html). 12 | 13 | **[*For the User Guide, click 14 | here.*](https://hugetim.github.io/nbstata/user_guide.html)** 15 | 16 |   17 | 18 | ## What is Jupyter? 19 | 20 | A Jupyter notebook allows you to combine interactive code and results 21 | with [Markdown](https://daringfireball.net/projects/markdown/basics) in 22 | a single document. Though it is named after the three core programming 23 | languages it supports (Julia, Python, and R), it can be used with with a 24 | wide variety of languages. 25 | 26 | *nbstata* allows you to create Stata notebooks (as opposed to [using 27 | Stata within a *Python* 28 | notebook](https://www.stata.com/python/pystata18/notebook/Example2.html), 29 | which is needlessly clunky if you are working primarily with Stata). 30 | 31 | ### Key *nbstata* features 32 | 33 | - [x] [Easy 34 | setup](https://hugetim.github.io/nbstata/user_guide.html#install) 35 | - [x] Works with Stata 17+ (only). 36 | - [x] DataGrid widget with `browse`-like capabilities (e.g., interactive 37 | filtering) 38 | - [x] Variable and data properties available in a ‘contextual help’ side 39 | panel 40 | - [x] Quarto [inline 41 | code](https://quarto.org/docs/computations/inline-code.html) support 42 | 43 | Users of Stata 17 or 18.0 also get these features only built-in natively 44 | to Stata 18.5+: 45 | 46 | - Displays Stata output without the redundant ‘echo’ of (multi-line) 47 | commands 48 | - Autocompletion for variables, macros, matrices, and file paths 49 | - Interactive/richtext help files accessible within notebook 50 | - `#delimit ;` interactive support (along with all types of comments) 51 | 52 | The video below demonstrates using Stata in a Jupyter notebook. In 53 | addition to the 54 | [NBClassic](https://nbclassic.readthedocs.io/en/stable/notebook.html) 55 | application shown there, *nbstata* can also be used with 56 | [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/getting_started/overview.html), 57 | [VS 58 | Code](https://code.visualstudio.com/docs/datascience/jupyter-notebooks), 59 | or [Quarto](https://quarto.org/). 60 | 61 | Animated GIF demoing major Stata kernel features 62 | 63 | ### What can you do with Stata notebooks… 64 | 65 | …that you can’t do with the [official Stata 66 | interface](https://www.stata.com/features/overview/graphical-user-interface/)? 67 | 68 | - Exploratory analysis that is both: 69 | - interactive 70 | - preserved for future reference/editing 71 | - Present results in a way that interweaves:[^1] 72 | - code 73 | - results (including graphs) 74 | - rich text: 75 | 1. lists 76 | 2. **Headings** 77 | 3. WordArt of the word 'images' 78 | 4. [links](https://hugetim.github.io/nbstata/) 79 | 5. math: $y_{it}=\beta_0+\varepsilon_{it}$ 80 | 81 | ## Contributing 82 | 83 | *nbstata* is being developed using 84 | [nbdev](https://nbdev.fast.ai/blog/posts/2022-07-28-nbdev2/#whats-nbdev). 85 | The `/nbs` directory is where edits to the source code should be made. 86 | (The python code is then exported to the `/nbdev` library folder.) 87 | 88 | For more, see 89 | [CONTRIBUTING.md](https://github.com/hugetim/nbstata/blob/master/CONTRIBUTING.md). 90 | 91 | ## Acknowledgements 92 | 93 | Kyle Barron authored the original *stata_kernel*, which works for older 94 | versions of Stata. Vinci Chow created a Stata kernel that instead uses 95 | [pystata](https://www.stata.com/python/pystata18/), which first became 96 | available with Stata 17. *nbstata* was originally derived from his 97 | [*pystata-kernel*](https://github.com/ticoneva/pystata-kernel), but much 98 | of the docs and newer features are derived from *stata_kernel*. 99 | 100 | [^1]: Stata [dynamic 101 | documents](https://www.stata.com/manuals/rptdynamicdocumentsintro.pdf) 102 | can do this part, though with a less interactive workflow. (See 103 | also: [markstat](https://grodri.github.io/markstat/), 104 | [stmd](https://www.ssc.wisc.edu/~hemken/Stataworkshops/stmd/Usage/stmdusage.html), 105 | and 106 | [Statamarkdown](https://ssc.wisc.edu/~hemken/Stataworkshops/Statamarkdown/stata-and-r-markdown.html)) 107 | Using *nbstata* with 108 | [Quarto](https://www.statalist.org/forums/forum/general-stata-discussion/general/1703835-ado-files-and-literate-programming) 109 | instead gives you a similar workflow, with greater flexibility of 110 | output. 111 | -------------------------------------------------------------------------------- /index_files/figure-commonmark/226326ec-1-image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugetim/nbstata/a22d0a111cdf76d3452b779941d56cb5d094fd27/index_files/figure-commonmark/226326ec-1-image-2.png -------------------------------------------------------------------------------- /index_files/figure-commonmark/cell-7-1-image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugetim/nbstata/a22d0a111cdf76d3452b779941d56cb5d094fd27/index_files/figure-commonmark/cell-7-1-image-2.png -------------------------------------------------------------------------------- /manual_test_nbs/delimit tests.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "7c51e910-746b-415e-9ccc-ae353d67511e", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "name": "stdout", 11 | "output_type": "stream", 12 | "text": [ 13 | "echo is now None\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "%set echo=None" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "id": "76875a65-f8ea-48d9-89af-5b28382dd70b", 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stdout", 29 | "output_type": "stream", 30 | "text": [ 31 | "(U.S. life expectancy, 1900-1940)\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "sysuse uslifeexp2" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 3, 42 | "id": "b51998bf-8a70-4d23-8ece-a568d6bb8bcf", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "#delimit ;" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 4, 52 | "id": "ec6024c7-c399-4fee-8429-1c2fddcc27e3", 53 | "metadata": {}, 54 | "outputs": [ 55 | { 56 | "name": "stdout", 57 | "output_type": "stream", 58 | "text": [ 59 | "1\n", 60 | "2\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "disp 1; disp 2;" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 5, 71 | "id": "05a642a0-0fc9-4818-8570-6a9ebeb4c51a", 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "name": "stdout", 76 | "output_type": "stream", 77 | "text": [ 78 | "\u001b[31mWarning: Code cell (with #delimit; in effect) does not end in ';'. Exported .do script may behave differently from notebook. In v1.0, nbstata may trigger an error instead of just a warning.\u001b[0m\n" 79 | ] 80 | } 81 | ], 82 | "source": [ 83 | "list in 1/1, \n", 84 | "clean noobs" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 6, 90 | "id": "23b5c548-8cb5-4965-a3af-a45e44185050", 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "\n", 98 | " year le \n", 99 | " 1900 47.3 \n" 100 | ] 101 | } 102 | ], 103 | "source": [ 104 | "list in 1/1, \n", 105 | "clean noobs;" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "id": "791ca9a7-9b85-41a0-a773-1df3cd6f3a93", 111 | "metadata": {}, 112 | "source": [ 113 | "The cell below won't work because the kernel is still set to `#delimit;`:" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 7, 119 | "id": "69187d6e-c519-4a31-ba2e-46c3d3c903ae", 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "\u001b[31mWarning: Code cell (with #delimit; in effect) does not end in ';'. Exported .do script may behave differently from notebook. In v1.0, nbstata may trigger an error instead of just a warning.\u001b[0m\n" 127 | ] 128 | } 129 | ], 130 | "source": [ 131 | "disp 1\n", 132 | "disp 2" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 8, 138 | "id": "a6cf1862-241b-49fe-8b73-de286d4c77fa", 139 | "metadata": {}, 140 | "outputs": [ 141 | { 142 | "name": "stdout", 143 | "output_type": "stream", 144 | "text": [ 145 | "Current Stata command delimiter: ;\n" 146 | ] 147 | } 148 | ], 149 | "source": [ 150 | "*%delimit" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 9, 156 | "id": "e6052bf4-9aee-4c95-83f4-43920d18274f", 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "name": "stdout", 161 | "output_type": "stream", 162 | "text": [ 163 | "1\n", 164 | "2\n" 165 | ] 166 | } 167 | ], 168 | "source": [ 169 | "#delimit cr\n", 170 | "disp 1\n", 171 | "disp 2" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 10, 177 | "id": "7cb2dd35-36e7-4663-8e58-28135058fcce", 178 | "metadata": {}, 179 | "outputs": [ 180 | { 181 | "name": "stdout", 182 | "output_type": "stream", 183 | "text": [ 184 | "Current Stata command delimiter: cr\n" 185 | ] 186 | } 187 | ], 188 | "source": [ 189 | "*%delimit" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 11, 195 | "id": "e2c406b2-9638-4d92-ae47-f555813ed9bf", 196 | "metadata": {}, 197 | "outputs": [ 198 | { 199 | "name": "stdout", 200 | "output_type": "stream", 201 | "text": [ 202 | "1\n", 203 | "2\n" 204 | ] 205 | } 206 | ], 207 | "source": [ 208 | "disp 1\n", 209 | "disp 2" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 12, 215 | "id": "653f3075-1732-4b96-94a5-eddb4a1de8d6", 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [ 219 | "#delimit ;" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 13, 225 | "id": "cc628a8c-0af2-4faf-8ac7-4aff28c755c8", 226 | "metadata": {}, 227 | "outputs": [ 228 | { 229 | "name": "stdout", 230 | "output_type": "stream", 231 | "text": [ 232 | "Current Stata command delimiter: ;\n" 233 | ] 234 | } 235 | ], 236 | "source": [ 237 | "*%delimit" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 14, 243 | "id": "623bd383-429a-434f-a984-aaf2262fdf80", 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "name": "stdout", 248 | "output_type": "stream", 249 | "text": [ 250 | "\u001b[31mWarning: Code cell (with #delimit; in effect) does not end in ';'. Exported .do script may behave differently from notebook. In v1.0, nbstata may trigger an error instead of just a warning.\u001b[0m\n" 251 | ] 252 | } 253 | ], 254 | "source": [ 255 | "disp 3\n", 256 | "#delimit cr\n", 257 | "disp 1\n", 258 | "disp 2" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 15, 264 | "id": "63aefc89-9187-4f3a-94d7-9197954c3c56", 265 | "metadata": {}, 266 | "outputs": [ 267 | { 268 | "name": "stdout", 269 | "output_type": "stream", 270 | "text": [ 271 | "Current Stata command delimiter: ;\n" 272 | ] 273 | } 274 | ], 275 | "source": [ 276 | "*%delimit" 277 | ] 278 | } 279 | ], 280 | "metadata": { 281 | "kernelspec": { 282 | "display_name": "Stata (nbstata)", 283 | "language": "stata", 284 | "name": "nbstata" 285 | }, 286 | "language_info": { 287 | "file_extension": ".do", 288 | "mimetype": "text/x-stata", 289 | "name": "stata", 290 | "version": "17" 291 | } 292 | }, 293 | "nbformat": 4, 294 | "nbformat_minor": 5 295 | } 296 | -------------------------------------------------------------------------------- /manual_test_nbs/nbstata manual tests.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "462a5e62-0d52-413c-a30a-8f05462e8557", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "data": { 11 | "text/html": [ 12 | "" 13 | ], 14 | "text/plain": [ 15 | "" 16 | ] 17 | }, 18 | "metadata": {}, 19 | "output_type": "display_data" 20 | }, 21 | { 22 | "name": "stdout", 23 | "output_type": "stream", 24 | "text": [ 25 | "(U.S. life expectancy, 1900-1940)\n" 26 | ] 27 | } 28 | ], 29 | "source": [ 30 | "sysuse uslifeexp2, clear" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "id": "6f903656-7b4f-49d2-8326-c7bdedaeff07", 37 | "metadata": {}, 38 | "outputs": [ 39 | { 40 | "name": "stdout", 41 | "output_type": "stream", 42 | "text": [ 43 | "\n" 44 | ] 45 | } 46 | ], 47 | "source": [ 48 | "*%%quietly\n", 49 | "list in 1/1, noobs clean\n", 50 | "disp \"hello\"" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 3, 56 | "id": "435a273e-ec66-4630-995f-a3ba45a12c4c", 57 | "metadata": {}, 58 | "outputs": [ 59 | { 60 | "name": "stdout", 61 | "output_type": "stream", 62 | "text": [ 63 | "\n", 64 | ". list in 1/1, noobs clean\n", 65 | "\n", 66 | " year le \n", 67 | " 1900 47.3 \n", 68 | "\n", 69 | ". disp \"hello\"\n", 70 | "hello\n", 71 | "\n", 72 | ". \n" 73 | ] 74 | } 75 | ], 76 | "source": [ 77 | "*%%echo\n", 78 | "list in 1/1, noobs clean\n", 79 | "disp \"hello\"" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 4, 85 | "id": "5ad2e788-819c-4c65-a9ac-98dee887fe18", 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "\n", 93 | " year le \n", 94 | " 1900 47.3 \n", 95 | "\n", 96 | " year le \n", 97 | " 1901 49.1 \n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "forval i = 1/2 {\n", 103 | " list in `i', noobs clean\n", 104 | "}" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 5, 110 | "id": "14bb98ae-acd7-4f24-832c-bc7cc5237862", 111 | "metadata": {}, 112 | "outputs": [ 113 | { 114 | "name": "stdout", 115 | "output_type": "stream", 116 | "text": [ 117 | "\n" 118 | ] 119 | } 120 | ], 121 | "source": [ 122 | "program define display1\n", 123 | " list in 1/1, noobs clean\n", 124 | "end" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 6, 130 | "id": "4ecdf630-f4ea-4840-b81c-6d29855c2f30", 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "name": "stdout", 135 | "output_type": "stream", 136 | "text": [ 137 | "\n", 138 | " year le \n", 139 | " 1900 47.3 \n", 140 | "\n", 141 | "display1:\n", 142 | " 1. list in 1/1, noobs clean\n" 143 | ] 144 | } 145 | ], 146 | "source": [ 147 | "display1\n", 148 | "program list display1" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 7, 154 | "id": "746cea6f-510b-427b-a737-f94b7f4e093e", 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stdout", 159 | "output_type": "stream", 160 | "text": [ 161 | "\n" 162 | ] 163 | } 164 | ], 165 | "source": [ 166 | "program drop display1\n", 167 | "program display1\n", 168 | " list in 1/1, noobs clean\n", 169 | "end" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 8, 175 | "id": "1d78cf90-d4e7-4065-8031-eabd63549d70", 176 | "metadata": {}, 177 | "outputs": [ 178 | { 179 | "name": "stdout", 180 | "output_type": "stream", 181 | "text": [ 182 | "\n" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "program drop display1\n", 188 | "pr display1\n", 189 | " list in 1/1, noobs clean\n", 190 | "end" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 9, 196 | "id": "0d8da0e5-96de-4b46-8d57-9693a8359de9", 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "name": "stdout", 201 | "output_type": "stream", 202 | "text": [ 203 | " 1. list in 1/1, noobs clean\n", 204 | " 2. end\n", 205 | "\n" 206 | ] 207 | } 208 | ], 209 | "source": [ 210 | "program drop display1\n", 211 | "n program define display1\n", 212 | " list in 1/1, noobs clean\n", 213 | "end" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 10, 219 | "id": "95c62480-e85f-4caa-9eae-ea31713b082c", 220 | "metadata": {}, 221 | "outputs": [ 222 | { 223 | "name": "stdout", 224 | "output_type": "stream", 225 | "text": [ 226 | "\n", 227 | "display1:\n", 228 | " 1. list in 1/1, noobs clean\n" 229 | ] 230 | } 231 | ], 232 | "source": [ 233 | "pr l display1" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 11, 239 | "id": "7af91d6a-8b3b-48c0-a023-f071c44ac471", 240 | "metadata": {}, 241 | "outputs": [ 242 | { 243 | "name": "stdout", 244 | "output_type": "stream", 245 | "text": [ 246 | "\n", 247 | "\n", 248 | "\n", 249 | " year le \n", 250 | " 1901 49.1 \n" 251 | ] 252 | } 253 | ], 254 | "source": [ 255 | "capture program drop ender\n", 256 | "program define ender\n", 257 | " list in 2/2, noobs clean\n", 258 | "end\n", 259 | "capture program drop display2\n", 260 | "program define display2\n", 261 | " ender\n", 262 | "end\n", 263 | "display2" 264 | ] 265 | }, 266 | { 267 | "cell_type": "code", 268 | "execution_count": 12, 269 | "id": "4205ccae-607f-4d2c-8a8d-3df4a3315c22", 270 | "metadata": {}, 271 | "outputs": [ 272 | { 273 | "name": "stdout", 274 | "output_type": "stream", 275 | "text": [ 276 | "\n", 277 | " +-------------+\n", 278 | " | year le |\n", 279 | " |-------------|\n", 280 | " 1. | 1900 47.3 |\n", 281 | " +-------------+\n", 282 | "hello\n" 283 | ] 284 | } 285 | ], 286 | "source": [ 287 | "list in 1/1\n", 288 | "python: print(\"hello\")" 289 | ] 290 | }, 291 | { 292 | "cell_type": "code", 293 | "execution_count": 13, 294 | "id": "bd510409-eb47-4132-9e88-43ca387ce449", 295 | "metadata": {}, 296 | "outputs": [ 297 | { 298 | "name": "stdout", 299 | "output_type": "stream", 300 | "text": [ 301 | "This no longer raises an IndentationError.\n", 302 | "\n" 303 | ] 304 | } 305 | ], 306 | "source": [ 307 | "python:\n", 308 | "try:\n", 309 | " print(\"This no longer raises an IndentationError.\")\n", 310 | "except:\n", 311 | " pass\n", 312 | "end" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 14, 318 | "id": "15ef2172-c813-499c-8507-6d4da35d11ec", 319 | "metadata": {}, 320 | "outputs": [ 321 | { 322 | "name": "stdout", 323 | "output_type": "stream", 324 | "text": [ 325 | "hello\n", 326 | "\n", 327 | " +-------------+\n", 328 | " | year le |\n", 329 | " |-------------|\n", 330 | " 1. | 1900 47.3 |\n", 331 | " +-------------+\n" 332 | ] 333 | } 334 | ], 335 | "source": [ 336 | "mata: display(\"hello\")\n", 337 | "list in 1/1" 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": 15, 343 | "id": "ae3bbbcc-0234-4093-a08b-db870f3d5701", 344 | "metadata": {}, 345 | "outputs": [ 346 | { 347 | "name": "stdout", 348 | "output_type": "stream", 349 | "text": [ 350 | "\n", 351 | ". mata:\n", 352 | "------------------------------------------------- mata (type end to exit) -----\n", 353 | ": display(\"hello\")\n", 354 | "hello\n", 355 | "\n", 356 | ": end\n", 357 | "-------------------------------------------------------------------------------\n", 358 | "\n", 359 | ". \n" 360 | ] 361 | } 362 | ], 363 | "source": [ 364 | "mata:\n", 365 | "display(\"hello\")\n", 366 | "end" 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "execution_count": 16, 372 | "id": "b6b0e0ee-beea-4739-9473-3ee0446f653c", 373 | "metadata": {}, 374 | "outputs": [ 375 | { 376 | "name": "stdout", 377 | "output_type": "stream", 378 | "text": [ 379 | "start\n", 380 | "hello\n", 381 | "hello2\n", 382 | "hello2a\n", 383 | "hello3\n", 384 | "hello4\n" 385 | ] 386 | } 387 | ], 388 | "source": [ 389 | "disp \"start\"\n", 390 | "#delimit;\n", 391 | "disp \"hello\"; disp \"hello2\";\n", 392 | "disp \n", 393 | " \"hello2a\";\n", 394 | "#delimit cr\n", 395 | "disp \"hello3\"\n", 396 | "disp \"hello4\"\n", 397 | "#delimit;" 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": 17, 403 | "id": "09517bdc-e4df-4423-886d-50a7fc095c3c", 404 | "metadata": {}, 405 | "outputs": [ 406 | { 407 | "name": "stdout", 408 | "output_type": "stream", 409 | "text": [ 410 | "hello\n" 411 | ] 412 | } 413 | ], 414 | "source": [ 415 | "#delimit ;\n", 416 | "disp \"hello\";\n", 417 | "#delimit cr" 418 | ] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "execution_count": 18, 423 | "id": "4a55cce7-9723-4f09-b162-d71d5d786b5a", 424 | "metadata": {}, 425 | "outputs": [], 426 | "source": [ 427 | "* Should have no output\n", 428 | "quietly {\n", 429 | " list in 1/1, noobs clean\n", 430 | "}" 431 | ] 432 | }, 433 | { 434 | "cell_type": "code", 435 | "execution_count": 19, 436 | "id": "23744b00-c56d-4f28-a253-0cd3984618bd", 437 | "metadata": {}, 438 | "outputs": [], 439 | "source": [ 440 | "* Should have no output\n", 441 | "capture {\n", 442 | " list in 1/1, noobs clean\n", 443 | "}" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": 20, 449 | "id": "52f24921-d4cb-4968-8f09-90f3bbe72036", 450 | "metadata": {}, 451 | "outputs": [ 452 | { 453 | "name": "stdout", 454 | "output_type": "stream", 455 | "text": [ 456 | "\n" 457 | ] 458 | } 459 | ], 460 | "source": [ 461 | "capture program drop display1\n", 462 | "program display1\n", 463 | "#delimit;\n", 464 | " list in 1/1, noobs clean;\n", 465 | "end;\n", 466 | "display1;" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": 21, 472 | "id": "419d13a7-3539-459c-9433-297659347473", 473 | "metadata": {}, 474 | "outputs": [ 475 | { 476 | "name": "stdout", 477 | "output_type": "stream", 478 | "text": [ 479 | "test output\n" 480 | ] 481 | } 482 | ], 483 | "source": [ 484 | "#delimit cr\n", 485 | "disp \"test output\"" 486 | ] 487 | }, 488 | { 489 | "cell_type": "code", 490 | "execution_count": 22, 491 | "id": "acb057b1-801b-4303-ae30-981ae735cfda", 492 | "metadata": {}, 493 | "outputs": [ 494 | { 495 | "data": { 496 | "text/html": [ 497 | "
\n", 498 | "\n", 511 | "\n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | " \n", 612 | " \n", 613 | " \n", 614 | " \n", 615 | " \n", 616 | " \n", 617 | " \n", 618 | " \n", 619 | " \n", 620 | " \n", 621 | " \n", 622 | " \n", 623 | " \n", 624 | " \n", 625 | " \n", 626 | " \n", 627 | " \n", 628 | " \n", 629 | " \n", 630 | " \n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | " \n", 711 | " \n", 712 | " \n", 713 | " \n", 714 | " \n", 715 | " \n", 716 | " \n", 717 | " \n", 718 | " \n", 719 | " \n", 720 | " \n", 721 | " \n", 722 | " \n", 723 | " \n", 724 | " \n", 725 | " \n", 726 | "
yearle
1190047.3
2190149.1
3190251.5
4190350.5
5190447.6
6190548.7
7190648.7
8190747.6
9190851.1
10190952.1
11191050
12191152.6
13191253.5
14191352.5
15191454.2
16191554.5
17191651.7
18191750.9
19191839.1
20191954.7
21192054.1
22192160.8
23192259.6
24192357.2
25192459.7
26192559
27192656.7
28192760.4
29192856.8
30192957.1
31193059.7
32193161.1
33193262.1
34193363.3
35193461.1
36193561.7
37193658.5
38193760
39193863.5
40193963.7
41194062.9
\n", 727 | "
" 728 | ] 729 | }, 730 | "metadata": {}, 731 | "output_type": "display_data" 732 | } 733 | ], 734 | "source": [ 735 | "*%head 50" 736 | ] 737 | } 738 | ], 739 | "metadata": { 740 | "kernelspec": { 741 | "display_name": "Stata (nbstata)", 742 | "language": "stata", 743 | "name": "nbstata" 744 | }, 745 | "language_info": { 746 | "file_extension": ".do", 747 | "mimetype": "text/x-stata", 748 | "name": "stata", 749 | "version": "17" 750 | } 751 | }, 752 | "nbformat": 4, 753 | "nbformat_minor": 5 754 | } 755 | -------------------------------------------------------------------------------- /nbs/.gitignore: -------------------------------------------------------------------------------- 1 | /.quarto/ 2 | -------------------------------------------------------------------------------- /nbs/00_misc_utils.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2c184729", 6 | "metadata": {}, 7 | "source": [ 8 | "# misc_utils\n", 9 | "\n", 10 | "> General helper functions with no Jupyter or pystata dependence\n", 11 | "- order: 0" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "c7fb586a", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "#| default_exp misc_utils\n", 22 | "%load_ext autoreload\n", 23 | "%autoreload 2" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "026b00b7", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "#| hide\n", 34 | "from nbdev.showdoc import *" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "id": "4b35edd5", 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "#| export\n", 45 | "import time" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "id": "dd9d03cf", 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "#| export\n", 56 | "class Timer():\n", 57 | " text = \"Elapsed time: {:0.4f} seconds\"\n", 58 | " logger = print\n", 59 | " _start_time = None\n", 60 | "\n", 61 | " def start(self):\n", 62 | " self._start_time = time.perf_counter()\n", 63 | "\n", 64 | " def stop(self):\n", 65 | " elapsed_time = time.perf_counter() - self._start_time\n", 66 | " self._start_time = None\n", 67 | " if self.logger:\n", 68 | " self.logger(self.text.format(elapsed_time))\n", 69 | "\n", 70 | " def __enter__(self):\n", 71 | " self.start()\n", 72 | " return self\n", 73 | "\n", 74 | " def __exit__(self, *exc_info):\n", 75 | " self.stop()" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "id": "148a1690", 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "name": "stdout", 86 | "output_type": "stream", 87 | "text": [ 88 | "\n", 89 | "Elapsed time: 0.0001 seconds\n" 90 | ] 91 | } 92 | ], 93 | "source": [ 94 | "with Timer():\n", 95 | " print()" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "17baa321", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "#| export\n", 106 | "def print_red(text):\n", 107 | " print(f\"\\x1b[31m{text}\\x1b[0m\")" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "id": "c72d051b", 113 | "metadata": {}, 114 | "source": [ 115 | "`print_red` source: https://stackoverflow.com/a/16816874/10637373" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "id": "66170ae7", 122 | "metadata": {}, 123 | "outputs": [ 124 | { 125 | "name": "stdout", 126 | "output_type": "stream", 127 | "text": [ 128 | "\u001b[31mtest_red\u001b[0m\n" 129 | ] 130 | } 131 | ], 132 | "source": [ 133 | "print_red(\"test_red\")" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "id": "1c6dd777", 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "#| hide\n", 144 | "import nbdev; nbdev.nbdev_export()" 145 | ] 146 | } 147 | ], 148 | "metadata": { 149 | "kernelspec": { 150 | "display_name": "python3", 151 | "language": "python", 152 | "name": "python3" 153 | } 154 | }, 155 | "nbformat": 4, 156 | "nbformat_minor": 5 157 | } 158 | -------------------------------------------------------------------------------- /nbs/11_completions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2c184729", 6 | "metadata": {}, 7 | "source": [ 8 | "# completions\n", 9 | "\n", 10 | "> Autocomplete functionality\n", 11 | "- order: 11" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "id": "e2014567", 17 | "metadata": {}, 18 | "source": [ 19 | "Adapted from the [stata_kernel version](https://github.com/kylebarron/stata_kernel/blob/master/stata_kernel/completions.py)." 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "id": "c7fb586a", 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "#| default_exp completions\n", 30 | "%load_ext autoreload\n", 31 | "%autoreload 2" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "id": "026b00b7", 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "#| hide\n", 42 | "from nbdev.showdoc import *" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "id": "e335395e", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "#| export\n", 53 | "from nbstata.stata import get_global, pwd\n", 54 | "from nbstata.stata_session import StataSession\n", 55 | "from nbstata.magics import StataMagics\n", 56 | "from nbstata.completion_env import CompletionEnv, Env\n", 57 | "from fastcore.basics import patch_to\n", 58 | "import os\n", 59 | "import re\n", 60 | "import platform" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "id": "cdfde140", 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "#| export\n", 71 | "class CompletionsManager():\n", 72 | " def __init__(self, stata_session: StataSession):\n", 73 | " \"\"\"\"\"\"\n", 74 | " self.stata_session = stata_session\n", 75 | " self.available_magics = list(StataMagics.available_magics.keys())\n", 76 | " self.env_helper = CompletionEnv()" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "id": "9d085610", 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "#| hide\n", 87 | "from unittest.mock import Mock" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "id": "8a4015a3", 94 | "metadata": {}, 95 | "outputs": [ 96 | { 97 | "data": { 98 | "text/plain": [ 99 | "['%browse',\n", 100 | " '%head',\n", 101 | " '%tail',\n", 102 | " '%locals',\n", 103 | " '%delimit',\n", 104 | " '%help',\n", 105 | " '%%quietly',\n", 106 | " '%%noecho',\n", 107 | " '%%echo']" 108 | ] 109 | }, 110 | "execution_count": null, 111 | "metadata": {}, 112 | "output_type": "execute_result" 113 | } 114 | ], 115 | "source": [ 116 | "#| hide\n", 117 | "test_instance = CompletionsManager(Mock())\n", 118 | "test_instance.available_magics" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "id": "f1c728ba", 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "#| export\n", 129 | "@patch_to(CompletionsManager)\n", 130 | "def get_globals(self):\n", 131 | " if self.stata_session.suggestions:\n", 132 | " return {k: get_global(k) for k in self.stata_session.suggestions['globals']}\n", 133 | " else:\n", 134 | " return {}" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "id": "99382f7f", 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "#| export\n", 145 | "@patch_to(CompletionsManager)\n", 146 | "def get_file_paths(self, chunk):\n", 147 | " \"\"\"Get file paths based on chunk\n", 148 | " Args:\n", 149 | " chunk (str): chunk of text after last space. Doesn't include string\n", 150 | " punctuation characters\n", 151 | " Returns:\n", 152 | " (List[str]): folders and files at that location\n", 153 | " \"\"\"\n", 154 | " # If local exists, return empty list\n", 155 | " if re.search(r'[`\\']', chunk):\n", 156 | " return []\n", 157 | "\n", 158 | " # Define directory separator\n", 159 | " dir_sep = '/'\n", 160 | " if platform.system() == 'Windows':\n", 161 | " if '/' not in chunk:\n", 162 | " dir_sep = '\\\\'\n", 163 | "\n", 164 | " # Get directory without ending file, and without / or \\\n", 165 | " if any(x in chunk for x in ['/', '\\\\']):\n", 166 | " ind = max(chunk.rfind('/'), chunk.rfind('\\\\'))\n", 167 | " user_folder = chunk[:ind + 1]\n", 168 | " user_starts = chunk[ind + 1:]\n", 169 | "\n", 170 | " # Replace multiple consecutive / with a single /\n", 171 | " user_folder = re.sub(r'/+', '/', user_folder)\n", 172 | " user_folder = re.sub(r'\\\\+', r'\\\\', user_folder)\n", 173 | "\n", 174 | " else:\n", 175 | " user_folder = ''\n", 176 | " user_starts = chunk\n", 177 | "\n", 178 | " # Replace globals with their values\n", 179 | " globals_re = r'\\$\\{?((?![0-9_])\\w{1,32})\\}?'\n", 180 | " try:\n", 181 | " folder = re.sub(\n", 182 | " globals_re, \n", 183 | " lambda x: self.get_globals()[x.group(1)], \n", 184 | " user_folder\n", 185 | " )\n", 186 | " except KeyError:\n", 187 | " # If the global doesn't exist (aka it hasn't been defined in \n", 188 | " # the Stata environment yet), then there are no paths to check\n", 189 | " return []\n", 190 | "\n", 191 | " # Use Stata's relative path\n", 192 | " abspath = re.search(r'^([/~]|[a-zA-Z]:)', folder)\n", 193 | " if not abspath:\n", 194 | " folder = pwd() + '/' + folder\n", 195 | "\n", 196 | " try:\n", 197 | " top_dir, dirs, files = next(os.walk(os.path.expanduser(folder)))\n", 198 | " results = [x + dir_sep for x in dirs] + files\n", 199 | " results = [\n", 200 | " user_folder + x for x in results if not x.startswith('.')\n", 201 | " and re.match(re.escape(user_starts), x, re.I)]\n", 202 | "\n", 203 | " except StopIteration:\n", 204 | " results = []\n", 205 | "\n", 206 | " return sorted(results)" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "id": "4b362150", 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "#| eval: false\n", 217 | "from nbstata.config import launch_stata\n", 218 | "from nbstata.stata_more import run_sfi" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "id": "bacb5efe", 225 | "metadata": {}, 226 | "outputs": [ 227 | { 228 | "data": { 229 | "text/plain": [ 230 | "['00_misc_utils.ipynb',\n", 231 | " '01_config.ipynb',\n", 232 | " '02_stata.ipynb',\n", 233 | " '03_stata_more.ipynb',\n", 234 | " '04_code_utils.ipynb',\n", 235 | " '05_noecho.ipynb',\n", 236 | " '06_pandas.ipynb',\n", 237 | " '07_browse.ipynb',\n", 238 | " '08_stata_session.ipynb',\n", 239 | " '09_magics.ipynb']" 240 | ] 241 | }, 242 | "execution_count": null, 243 | "metadata": {}, 244 | "output_type": "execute_result" 245 | } 246 | ], 247 | "source": [ 248 | "#| eval: false\n", 249 | "launch_stata(splash=False)\n", 250 | "test_stata = StataSession()\n", 251 | "test_instance = CompletionsManager(test_stata)\n", 252 | "test_instance.get_file_paths(\"0\")" 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": null, 258 | "id": "1f98847d", 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "data": { 263 | "text/plain": [ 264 | "['$in_path/completion_env.py', '$in_path/completions.py']" 265 | ] 266 | }, 267 | "execution_count": null, 268 | "metadata": {}, 269 | "output_type": "execute_result" 270 | } 271 | ], 272 | "source": [ 273 | "#| eval: false\n", 274 | "run_sfi('global in_path \"../nbstata\"')\n", 275 | "test_instance.stata_session.refresh_suggestions()\n", 276 | "test_instance.get_file_paths(\"$in_path/com\")" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "id": "e320f350", 283 | "metadata": {}, 284 | "outputs": [], 285 | "source": [ 286 | "#| export\n", 287 | "\n", 288 | "relevant_suggestion_keys = {\n", 289 | " Env.NONE: [],\n", 290 | " Env.GENERAL: ['varlist', 'scalars'],\n", 291 | " Env.LOCAL: ['locals'],\n", 292 | " Env.GLOBAL: ['globals'],\n", 293 | " Env.SCALAR: ['scalars'],\n", 294 | " Env.MATRIX: ['matrices'],\n", 295 | " Env.SCALAR_VAR: ['scalars', 'varlist'],\n", 296 | " Env.MATRIX_VAR: ['matrices', 'varlist'],\n", 297 | " Env.STRING: [],\n", 298 | "}\n", 299 | "\n", 300 | "@patch_to(CompletionsManager)\n", 301 | "def get(self, starts, env, rcomp):\n", 302 | " \"\"\"Return environment-aware completions list.\"\"\"\n", 303 | " if env is Env.MAGIC:\n", 304 | " candidate_suggestions = self.available_magics\n", 305 | " else:\n", 306 | " candidate_suggestions = [suggestion\n", 307 | " for key in relevant_suggestion_keys[env]\n", 308 | " for suggestion in self.stata_session.suggestions[key]]\n", 309 | " relevant_suggestions = [candidate + rcomp \n", 310 | " for candidate in candidate_suggestions\n", 311 | " if candidate.startswith(starts)]\n", 312 | " if env in [Env.GENERAL, Env.STRING]:\n", 313 | " relevant_suggestions += self.get_file_paths(starts)\n", 314 | " return relevant_suggestions\n", 315 | "\n", 316 | "# elif env == 9:\n", 317 | "# if len(starts) > 1:\n", 318 | "# builtins = [\n", 319 | "# var for var in mata_builtins if var.startswith(starts)]\n", 320 | "# else:\n", 321 | "# builtins = []\n", 322 | "\n", 323 | "# if re.search(r'[/\\\\]', starts):\n", 324 | "# paths = self.get_file_paths(starts)\n", 325 | "# else:\n", 326 | "# paths = []\n", 327 | "\n", 328 | "# return [\n", 329 | "# var for var in self.stata_session.suggestions['mata']\n", 330 | "# if var.startswith(starts)] + builtins + paths" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": null, 336 | "id": "0e92736b", 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [ 340 | "#| export\n", 341 | "@patch_to(CompletionsManager)\n", 342 | "def do(self, code, cursor_pos):\n", 343 | " if self.stata_session.suggestions is None:\n", 344 | " self.stata_session.refresh_suggestions()\n", 345 | " env, pos, chunk, rcomp = self.env_helper.get_env(\n", 346 | " code[:cursor_pos], \n", 347 | " code[cursor_pos:(cursor_pos + 2)],\n", 348 | " self.stata_session.sc_delimiter,\n", 349 | " )\n", 350 | " return pos, cursor_pos, self.get(chunk, env, rcomp)" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": null, 356 | "id": "19528a4b", 357 | "metadata": {}, 358 | "outputs": [], 359 | "source": [ 360 | "#| eval: False\n", 361 | "from fastcore.test import test_eq" 362 | ] 363 | }, 364 | { 365 | "cell_type": "code", 366 | "execution_count": null, 367 | "id": "647e2c0d", 368 | "metadata": {}, 369 | "outputs": [], 370 | "source": [ 371 | "#| eval: False\n", 372 | "def completions_test_setup(code):\n", 373 | " global test_instance\n", 374 | " run_sfi(\"clear all\")\n", 375 | " run_sfi(code)\n", 376 | " test_instance.stata_session.clear_suggestions()\n", 377 | " \n", 378 | "\n", 379 | "def _complete(code, cursor_pos):\n", 380 | " _, _, matches = test_instance.do(code, cursor_pos)\n", 381 | " return matches" 382 | ] 383 | }, 384 | { 385 | "cell_type": "code", 386 | "execution_count": null, 387 | "id": "b1ff4e8b", 388 | "metadata": {}, 389 | "outputs": [], 390 | "source": [ 391 | "#| eval: False\n", 392 | "completions_test_setup(\"gen var1 = 1\")\n", 393 | "code = \"list va\"\n", 394 | "cursor_pos = 7\n", 395 | "\n", 396 | "test_eq(\n", 397 | " test_instance.env_helper.get_env(\n", 398 | " code[:cursor_pos], code[cursor_pos:(cursor_pos + 2)],\n", 399 | " False),\n", 400 | " (0, 5, 'va', ''),\n", 401 | ") \n", 402 | "test_eq(\n", 403 | " _complete(code, cursor_pos),\n", 404 | " ['var1'],\n", 405 | ")" 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": null, 411 | "id": "9c9abdf5", 412 | "metadata": {}, 413 | "outputs": [], 414 | "source": [ 415 | "#| hide\n", 416 | "#| eval: False\n", 417 | "completions_test_setup('')\n", 418 | "test_eq(\n", 419 | " _complete(\"use sideb\", 9),\n", 420 | " [\"sidebar.yml\"],\n", 421 | ")" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": null, 427 | "id": "a8304d09", 428 | "metadata": {}, 429 | "outputs": [], 430 | "source": [ 431 | "#| hide\n", 432 | "#| eval: False\n", 433 | "code = 'use \"../manual_test_nbs/delimit t'\n", 434 | "test_eq(\n", 435 | " _complete(code, len(code)),\n", 436 | " [\"../manual_test_nbs/delimit tests.ipynb\"],\n", 437 | ")" 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": null, 443 | "id": "b0a9caae", 444 | "metadata": {}, 445 | "outputs": [], 446 | "source": [ 447 | "#| hide\n", 448 | "#| eval: False\n", 449 | "completions_test_setup('global indir \"../manual_test_nbs\"')\n", 450 | "code = 'use \"$indir/delimit t'\n", 451 | "test_eq(\n", 452 | " _complete(code, len(code)),\n", 453 | " [\"$indir/delimit tests.ipynb\"],\n", 454 | ")" 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": null, 460 | "id": "99955c3f", 461 | "metadata": {}, 462 | "outputs": [], 463 | "source": [ 464 | "#| eval: False\n", 465 | "completions_test_setup('local test_local \"test value\"')\n", 466 | "test_eq(\n", 467 | " _complete(\"list `t\", 7),\n", 468 | " [\"test_local'\"],\n", 469 | ")\n", 470 | "run_sfi('local test_local \"\"')" 471 | ] 472 | }, 473 | { 474 | "cell_type": "code", 475 | "execution_count": null, 476 | "id": "7aaae188", 477 | "metadata": {}, 478 | "outputs": [], 479 | "source": [ 480 | "#| hide\n", 481 | "#| eval: False\n", 482 | "test_eq(\n", 483 | " _complete(\"list `t'\", 7),\n", 484 | " [\"test_local\"],\n", 485 | ")\n", 486 | "test_eq(\n", 487 | " _complete(\"list `t'\", 8),\n", 488 | " [],\n", 489 | ")" 490 | ] 491 | }, 492 | { 493 | "cell_type": "code", 494 | "execution_count": null, 495 | "id": "62abc389", 496 | "metadata": {}, 497 | "outputs": [], 498 | "source": [ 499 | "#| eval: False\n", 500 | "completions_test_setup('global test_global \"test value\"')\n", 501 | "test_eq(\n", 502 | " _complete(\"list ${tes}\", 10),\n", 503 | " ['test_global'],\n", 504 | ")" 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "execution_count": null, 510 | "id": "fc5bf252", 511 | "metadata": {}, 512 | "outputs": [], 513 | "source": [ 514 | "#| hide\n", 515 | "#| eval: False\n", 516 | "test_eq(\n", 517 | " _complete(\"list $tes\", 9),\n", 518 | " ['test_global'],\n", 519 | ")\n", 520 | "test_eq(\n", 521 | " _complete(\"list ${tes\", 10),\n", 522 | " ['test_global}'],\n", 523 | ")" 524 | ] 525 | }, 526 | { 527 | "cell_type": "code", 528 | "execution_count": null, 529 | "id": "56d21ba9", 530 | "metadata": {}, 531 | "outputs": [], 532 | "source": [ 533 | "#| hide\n", 534 | "#| eval: False\n", 535 | "completions_test_setup('scalar test_scalar = 5')\n", 536 | "test_eq(\n", 537 | " _complete(\"disp scalar(tes\", 15),\n", 538 | " ['test_scalar)'],\n", 539 | ")\n", 540 | "test_eq(\n", 541 | " _complete(\"disp scalar(tes)\", 15),\n", 542 | " ['test_scalar'],\n", 543 | ")\n", 544 | "test_eq(\n", 545 | " _complete(\"disp `=scalar(tes\", 17),\n", 546 | " [\"test_scalar)\"],\n", 547 | ")\n", 548 | "test_eq(\n", 549 | " _complete(\"disp `=scalar(tes'\", 17),\n", 550 | " [\"test_scalar)\"],\n", 551 | ")\n", 552 | "test_eq(\n", 553 | " _complete(\"disp `=tes\", 10),\n", 554 | " [\"test_scalar\"],\n", 555 | ")" 556 | ] 557 | }, 558 | { 559 | "cell_type": "code", 560 | "execution_count": null, 561 | "id": "1578e279", 562 | "metadata": {}, 563 | "outputs": [], 564 | "source": [ 565 | "#| hide\n", 566 | "#| eval: False\n", 567 | "completions_test_setup('gen var1 = 1')\n", 568 | "test_eq(\n", 569 | " _complete(\"disp `=va\", 10),\n", 570 | " [\"var1\"],\n", 571 | ")" 572 | ] 573 | }, 574 | { 575 | "cell_type": "code", 576 | "execution_count": null, 577 | "id": "d389357a", 578 | "metadata": {}, 579 | "outputs": [], 580 | "source": [ 581 | "#| hide\n", 582 | "#| eval: False\n", 583 | "completions_test_setup(r'matrix test_matrix = (1,2,3\\4,5,6)')\n", 584 | "test_eq(\n", 585 | " _complete(\"matrix A = tes\", 14),\n", 586 | " ['test_matrix'],\n", 587 | ")" 588 | ] 589 | }, 590 | { 591 | "cell_type": "code", 592 | "execution_count": null, 593 | "id": "1f46ab09", 594 | "metadata": {}, 595 | "outputs": [], 596 | "source": [ 597 | "#| hide\n", 598 | "#| eval: False\n", 599 | "completions_test_setup('gen x1 = 1 \\n scalar x2 = 2')\n", 600 | "code = \"\"\"\\\n", 601 | "#delimit;\n", 602 | "scalar\n", 603 | "list x\"\"\"\n", 604 | "test_eq(\n", 605 | " _complete(code, len(code)),\n", 606 | " ['x2'],\n", 607 | ")" 608 | ] 609 | }, 610 | { 611 | "cell_type": "code", 612 | "execution_count": null, 613 | "id": "8fb8a58c", 614 | "metadata": {}, 615 | "outputs": [], 616 | "source": [ 617 | "#| hide\n", 618 | "#| eval: False\n", 619 | "code = \"*%br\"\n", 620 | "test_eq(\n", 621 | " _complete(code, len(code)),\n", 622 | " [\"%browse\"],\n", 623 | ")" 624 | ] 625 | }, 626 | { 627 | "cell_type": "code", 628 | "execution_count": null, 629 | "id": "d410ba32", 630 | "metadata": {}, 631 | "outputs": [], 632 | "source": [ 633 | "#| hide\n", 634 | "#| eval: False\n", 635 | "code = \"*%%ec\"\n", 636 | "test_eq(\n", 637 | " _complete(code, len(code)),\n", 638 | " [\"%%echo\"],\n", 639 | ")" 640 | ] 641 | }, 642 | { 643 | "cell_type": "code", 644 | "execution_count": null, 645 | "id": "1c6dd777", 646 | "metadata": {}, 647 | "outputs": [], 648 | "source": [ 649 | "#| hide\n", 650 | "import nbdev; nbdev.nbdev_export()" 651 | ] 652 | } 653 | ], 654 | "metadata": { 655 | "kernelspec": { 656 | "display_name": "python3", 657 | "language": "python", 658 | "name": "python3" 659 | } 660 | }, 661 | "nbformat": 4, 662 | "nbformat_minor": 5 663 | } 664 | -------------------------------------------------------------------------------- /nbs/12_inspect.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2c184729", 6 | "metadata": {}, 7 | "source": [ 8 | "# inspect\n", 9 | "\n", 10 | "> Provides output for kernel.do_inspect()\n", 11 | "- order: 12" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "c7fb586a", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "#| default_exp inspect\n", 22 | "%load_ext autoreload\n", 23 | "%autoreload 2" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "026b00b7", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "#| hide\n", 34 | "from nbdev.showdoc import *" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "id": "4b35edd5", 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "#| export\n", 45 | "from nbstata.stata_more import run_as_program, run_sfi, diverted_stata_output\n", 46 | "import functools" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "id": "21aadf85", 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "#| hide\n", 57 | "#| eval: false\n", 58 | "from nbstata.config import launch_stata" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "id": "88842fa8", 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "#| hide\n", 69 | "from fastcore.test import test_eq" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "id": "3a30220b", 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "#| export\n", 80 | "def get_inspect(code=\"\", cursor_pos=0, detail_level=0, omit_sections=()):\n", 81 | " runner = functools.partial(run_as_program, prog_def_option_code=\"rclass\")\n", 82 | " inspect_code = \"\"\"\n", 83 | " disp _newline \"*** Stored results:\"\n", 84 | " return list\n", 85 | " ereturn list\n", 86 | " return add\n", 87 | " display \"*** Last updated `c(current_time)' `c(current_date)' ***\"\n", 88 | " describe, fullnames\n", 89 | " \"\"\"\n", 90 | " raw_output = diverted_stata_output(inspect_code, runner=runner)\n", 91 | " desc_start = raw_output.find('*** Last updated ')\n", 92 | " out = raw_output[desc_start:]\n", 93 | " if desc_start > 21:\n", 94 | " out += raw_output[:desc_start]\n", 95 | " return out" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "b6044d98", 102 | "metadata": {}, 103 | "outputs": [ 104 | { 105 | "name": "stdout", 106 | "output_type": "stream", 107 | "text": [ 108 | "\n", 109 | "Contains data\n", 110 | " Observations: 5 \n", 111 | " Variables: 1 \n", 112 | "-------------------------------------------------------------------------------\n", 113 | "Variable Storage Display Value\n", 114 | " name type format label Variable label\n", 115 | "-------------------------------------------------------------------------------\n", 116 | "var1 float %9.0g \n", 117 | "-------------------------------------------------------------------------------\n", 118 | "Sorted by: \n", 119 | " Note: Dataset has changed since last saved.\n" 120 | ] 121 | } 122 | ], 123 | "source": [ 124 | "#| eval: false\n", 125 | "launch_stata(splash=False)\n", 126 | "run_sfi(\"\"\"\\\n", 127 | "quietly set obs 5\n", 128 | "quietly gen var1 = _n > 3\n", 129 | "desc\"\"\")" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "id": "5aca08d7", 136 | "metadata": {}, 137 | "outputs": [ 138 | { 139 | "name": "stdout", 140 | "output_type": "stream", 141 | "text": [ 142 | "*** Last updated 13:09:56 4 Feb 2023 ***\n", 143 | "\n", 144 | "Contains data\n", 145 | " Observations: 5 \n", 146 | " Variables: 1 \n", 147 | "-------------------------------------------------------------------------------\n", 148 | "Variable Storage Display Value\n", 149 | " name type format label Variable label\n", 150 | "-------------------------------------------------------------------------------\n", 151 | "var1 float %9.0g \n", 152 | "-------------------------------------------------------------------------------\n", 153 | "Sorted by: \n", 154 | " Note: Dataset has changed since last saved.\n", 155 | "\n", 156 | "*** Stored results:\n", 157 | "\n", 158 | "scalars:\n", 159 | " r(changed) = 1\n", 160 | " r(width) = 4\n", 161 | " r(k) = 1\n", 162 | " r(N) = 5\n", 163 | "\n" 164 | ] 165 | } 166 | ], 167 | "source": [ 168 | "#| eval: false\n", 169 | "print(get_inspect())" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "id": "f309af8c", 176 | "metadata": {}, 177 | "outputs": [ 178 | { 179 | "name": "stdout", 180 | "output_type": "stream", 181 | "text": [ 182 | "\n", 183 | "scalars:\n", 184 | " r(N) = 5\n", 185 | " r(sum_w) = 5\n", 186 | " r(mean) = .4\n", 187 | " r(Var) = .3\n", 188 | " r(sd) = .5477225575051662\n", 189 | " r(min) = 0\n", 190 | " r(max) = 1\n", 191 | " r(sum) = 2\n" 192 | ] 193 | } 194 | ], 195 | "source": [ 196 | "#| hide\n", 197 | "#| eval: false\n", 198 | "run_sfi('quietly sum')\n", 199 | "run_sfi('return list')" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "id": "5b62f62b", 205 | "metadata": {}, 206 | "source": [ 207 | "#| hide\n", 208 | "\n", 209 | "The `r()` `return list` values remain unchanged, except that the display order is reversed for some reason--there seems to be no way to avoid that." 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "id": "dc34470f", 216 | "metadata": {}, 217 | "outputs": [ 218 | { 219 | "name": "stdout", 220 | "output_type": "stream", 221 | "text": [ 222 | "*** Last updated 13:09:57 4 Feb 2023 ***\n", 223 | "\n", 224 | "Contains data\n", 225 | " Observations: 5 \n", 226 | " Variables: 1 \n", 227 | "-------------------------------------------------------------------------------\n", 228 | "Variable Storage Display Value\n", 229 | " name type format label Variable label\n", 230 | "-------------------------------------------------------------------------------\n", 231 | "var1 float %9.0g \n", 232 | "-------------------------------------------------------------------------------\n", 233 | "Sorted by: \n", 234 | " Note: Dataset has changed since last saved.\n", 235 | "\n", 236 | "*** Stored results:\n", 237 | "\n", 238 | "scalars:\n", 239 | " r(sum) = 2\n", 240 | " r(max) = 1\n", 241 | " r(min) = 0\n", 242 | " r(sd) = .5477225575051662\n", 243 | " r(Var) = .3\n", 244 | " r(mean) = .4\n", 245 | " r(sum_w) = 5\n", 246 | " r(N) = 5\n", 247 | "\n" 248 | ] 249 | } 250 | ], 251 | "source": [ 252 | "#| eval: false\n", 253 | "run_sfi('quietly sum')\n", 254 | "print(get_inspect())" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "id": "04aa748f", 261 | "metadata": {}, 262 | "outputs": [ 263 | { 264 | "name": "stdout", 265 | "output_type": "stream", 266 | "text": [ 267 | "\n", 268 | "scalars:\n", 269 | " r(sum) = 2\n", 270 | " r(max) = 1\n", 271 | " r(min) = 0\n", 272 | " r(sd) = .5477225575051662\n", 273 | " r(Var) = .3\n", 274 | " r(mean) = .4\n", 275 | " r(sum_w) = 5\n", 276 | " r(N) = 5\n" 277 | ] 278 | } 279 | ], 280 | "source": [ 281 | "#| hide\n", 282 | "#| eval: false\n", 283 | "run_sfi('return list')" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": null, 289 | "id": "5c9e9f90", 290 | "metadata": {}, 291 | "outputs": [ 292 | { 293 | "name": "stdout", 294 | "output_type": "stream", 295 | "text": [ 296 | "*** Last updated 13:09:58 4 Feb 2023 ***\n", 297 | "\n", 298 | "Contains data\n", 299 | " Observations: 5 \n", 300 | " Variables: 1 \n", 301 | "-------------------------------------------------------------------------------\n", 302 | "Variable Storage Display Value\n", 303 | " name type format label Variable label\n", 304 | "-------------------------------------------------------------------------------\n", 305 | "var1 float %9.0g \n", 306 | "-------------------------------------------------------------------------------\n", 307 | "Sorted by: \n", 308 | " Note: Dataset has changed since last saved.\n", 309 | "\n", 310 | "*** Stored results:\n", 311 | "\n", 312 | "matrices:\n", 313 | " r(table) : 9 x 1\n", 314 | "\n", 315 | "scalars:\n", 316 | " e(N) = 5\n", 317 | " e(df_m) = 0\n", 318 | " e(df_r) = 4\n", 319 | " e(F) = 0\n", 320 | " e(r2) = 0\n", 321 | " e(rmse) = .5477225575051662\n", 322 | " e(mss) = 0\n", 323 | " e(rss) = 1.2\n", 324 | " e(r2_a) = 0\n", 325 | " e(ll) = -3.526901776922999\n", 326 | " e(ll_0) = -3.526901776922999\n", 327 | " e(rank) = 1\n", 328 | "\n", 329 | "macros:\n", 330 | " e(cmdline) : \"regress var1\"\n", 331 | " e(title) : \"Linear regression\"\n", 332 | " e(marginsok) : \"XB default\"\n", 333 | " e(vce) : \"ols\"\n", 334 | " e(depvar) : \"var1\"\n", 335 | " e(cmd) : \"regress\"\n", 336 | " e(properties) : \"b V\"\n", 337 | " e(predict) : \"regres_p\"\n", 338 | " e(model) : \"ols\"\n", 339 | " e(estat_cmd) : \"regress_estat\"\n", 340 | "\n", 341 | "matrices:\n", 342 | " e(b) : 1 x 1\n", 343 | " e(V) : 1 x 1\n", 344 | "\n", 345 | "functions:\n", 346 | " e(sample) \n", 347 | "\n" 348 | ] 349 | } 350 | ], 351 | "source": [ 352 | "#| eval: false\n", 353 | "run_sfi('quietly reg var1')\n", 354 | "print(get_inspect())" 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": null, 360 | "id": "5f5dc4cb", 361 | "metadata": {}, 362 | "outputs": [], 363 | "source": [ 364 | "#| eval: false\n", 365 | "run_sfi('clear all')\n", 366 | "test_eq(get_inspect()[43:],\"\"\"\\\n", 367 | "Contains data\n", 368 | " Observations: 0 \n", 369 | " Variables: 0 \n", 370 | "Sorted by: \n", 371 | "\"\"\")" 372 | ] 373 | }, 374 | { 375 | "cell_type": "code", 376 | "execution_count": null, 377 | "id": "1c6dd777", 378 | "metadata": {}, 379 | "outputs": [], 380 | "source": [ 381 | "#| hide\n", 382 | "import nbdev; nbdev.nbdev_export()" 383 | ] 384 | } 385 | ], 386 | "metadata": { 387 | "kernelspec": { 388 | "display_name": "python3", 389 | "language": "python", 390 | "name": "python3" 391 | } 392 | }, 393 | "nbformat": 4, 394 | "nbformat_minor": 5 395 | } 396 | -------------------------------------------------------------------------------- /nbs/13_cell.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# cell\n", 8 | "\n", 9 | "> Class representing a single code cell\n", 10 | "- order: 13" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "#| default_exp cell\n", 20 | "%load_ext autoreload\n", 21 | "%autoreload 2" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "#| hide\n", 31 | "from nbdev.showdoc import *" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "#| export\n", 41 | "from nbstata.stata_session import StataSession\n", 42 | "from nbstata.magics import StataMagics\n", 43 | "from fastcore.basics import patch_to" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "#| hide\n", 53 | "#| export\n", 54 | "magic_handler = StataMagics()" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "#| export\n", 64 | "class Cell:\n", 65 | " \"\"\"A class for managing execution of a single code cell\"\"\" \n", 66 | " def __init__(self, kernel, code_w_magics, silent=False):\n", 67 | " self.noecho = kernel.nbstata_config.noecho\n", 68 | " self.echo = kernel.nbstata_config.echo\n", 69 | " self.quietly = silent\n", 70 | " self.stata_session = kernel.stata_session\n", 71 | " self.code = magic_handler.magic(code_w_magics, kernel, self)\n", 72 | " \n", 73 | " def run(self):\n", 74 | " if not self.code:\n", 75 | " return\n", 76 | " self.stata_session.dispatch_run(self.code, \n", 77 | " quietly=self.quietly, echo=self.echo, noecho=self.noecho)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "Some `Cell` functionality can be tested apart from a kernel:" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "from nbstata.config import launch_stata, Config\n", 94 | "from fastcore.test import test_eq\n", 95 | "from textwrap import dedent\n", 96 | "from unittest.mock import Mock" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [ 104 | { 105 | "data": { 106 | "text/plain": [ 107 | "'disp \"test output\"'" 108 | ] 109 | }, 110 | "execution_count": null, 111 | "metadata": {}, 112 | "output_type": "execute_result" 113 | } 114 | ], 115 | "source": [ 116 | "kernel1 = Mock()\n", 117 | "kernel1.nbstata_config = Config()\n", 118 | "\n", 119 | "code_w_magics = '''disp \"test output\"'''\n", 120 | "cell1 = Cell(kernel1, code_w_magics)\n", 121 | "cell1.code" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "name": "stdout", 131 | "output_type": "stream", 132 | "text": [ 133 | "test output\n" 134 | ] 135 | } 136 | ], 137 | "source": [ 138 | "#| eval: false\n", 139 | "launch_stata(splash=False)\n", 140 | "kernel1.stata_session = StataSession()\n", 141 | "cell1a = Cell(kernel1, code_w_magics)\n", 142 | "cell1a.run()" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "code_w_magics = dedent('''\\\n", 152 | " *%%quietly\n", 153 | " disp \"test output\"\n", 154 | " ''')\n", 155 | "cell2 = Cell(kernel1, code_w_magics)\n", 156 | "test_eq(cell2.quietly, True)" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": null, 162 | "metadata": {}, 163 | "outputs": [], 164 | "source": [ 165 | "#| eval: false\n", 166 | "cell2.run()" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "kernel1.nbstata_config.env['echo'] = 'True'\n", 176 | "\n", 177 | "code_w_magics = '''disp \"test output\"'''\n", 178 | "cell3 = Cell(kernel1, code_w_magics)\n", 179 | "test_eq(cell3.noecho, False)" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": null, 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "name": "stdout", 189 | "output_type": "stream", 190 | "text": [ 191 | ". disp \"test output\"\n", 192 | "test output\n" 193 | ] 194 | } 195 | ], 196 | "source": [ 197 | "#| eval: false\n", 198 | "cell3.run()" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "code_w_magics = dedent('''\\\n", 208 | " *%%noecho\n", 209 | " #delimit cr\n", 210 | " disp \"test output\"\n", 211 | " ''')\n", 212 | "cell4 = Cell(kernel1, code_w_magics)\n", 213 | "test_eq(cell4.noecho, True)" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "metadata": {}, 220 | "outputs": [ 221 | { 222 | "name": "stdout", 223 | "output_type": "stream", 224 | "text": [ 225 | "test output\n" 226 | ] 227 | } 228 | ], 229 | "source": [ 230 | "#| eval: false\n", 231 | "cell4.run()" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": null, 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "#| hide\n", 241 | "import nbdev; nbdev.nbdev_export()" 242 | ] 243 | } 244 | ], 245 | "metadata": { 246 | "kernelspec": { 247 | "display_name": "python3", 248 | "language": "python", 249 | "name": "python3" 250 | } 251 | }, 252 | "nbformat": 4, 253 | "nbformat_minor": 4 254 | } 255 | -------------------------------------------------------------------------------- /nbs/15_install.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "2c184729", 6 | "metadata": {}, 7 | "source": [ 8 | "# install\n", 9 | "\n", 10 | "> nbstata kernel install script\n", 11 | "- order: 15" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "c7fb586a", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "#| default_exp install\n", 22 | "%load_ext autoreload\n", 23 | "%autoreload 2" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "026b00b7", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "#| hide\n", 34 | "from nbdev.showdoc import *" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "id": "4b35edd5", 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "#| export\n", 45 | "import argparse\n", 46 | "import json\n", 47 | "import os\n", 48 | "import sys\n", 49 | "\n", 50 | "from jupyter_client.kernelspec import KernelSpecManager\n", 51 | "from IPython.utils.tempdir import TemporaryDirectory\n", 52 | "from importlib import resources\n", 53 | "from shutil import copyfile\n", 54 | "from pathlib import Path\n", 55 | "from textwrap import dedent\n", 56 | "from fastcore.basics import IN_NOTEBOOK" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "id": "12331a1c-bb11-4986-a408-34ec9e4f87ba", 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "#| hide\n", 67 | "from fastcore.test import test_eq" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "id": "dd9d03cf", 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "#| export\n", 78 | "kernel_json = {\n", 79 | " \"argv\": [sys.executable, \"-m\", \"nbstata\", \"-f\", \"{connection_file}\"],\n", 80 | " \"display_name\": \"Stata (nbstata)\",\n", 81 | " \"language\": \"stata\",\n", 82 | "}\n", 83 | "\n", 84 | "def install_kernel_spec(user=True, prefix=None):\n", 85 | " with TemporaryDirectory() as td:\n", 86 | " os.chmod(td, 0o755) # Starts off as 700, not user readable\n", 87 | " with open(os.path.join(td, 'kernel.json'), 'w') as f:\n", 88 | " json.dump(kernel_json, f, sort_keys=True)\n", 89 | "\n", 90 | " # Copy logo to tempdir to be installed with kernelspec\n", 91 | " logo_path = resources.files('nbstata').joinpath('logo-64x64.png')\n", 92 | " copyfile(logo_path, os.path.join(td, 'logo-64x64.png'))\n", 93 | "\n", 94 | " print('Installing Jupyter kernel spec')\n", 95 | " KernelSpecManager().install_kernel_spec(td, 'nbstata', user=user, prefix=prefix)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "7571a012-6b15-4d01-91c1-0ebcbc1e1fa9", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "#| export\n", 106 | "def _find_stata():\n", 107 | " # By avoiding an import of .config until we need it, we can\n", 108 | " # complete the installation process in virtual environments\n", 109 | " # without needing this submodule nor its downstream imports.\n", 110 | " from nbstata.config import find_dir_edition\n", 111 | " try:\n", 112 | " stata_dir, stata_ed = find_dir_edition()\n", 113 | " except OSError as err:\n", 114 | " stata_dir, stata_ed = \"\", \"\"\n", 115 | " return stata_dir, stata_ed" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "id": "cbe6dbc7-9399-48d6-b36d-3ea2228d57a8", 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "#| export\n", 126 | "def _conf_default(stata_dir='', stata_ed=''):\n", 127 | " from nbstata.config import Config\n", 128 | " conf_default = dedent(\n", 129 | " f\"\"\"\\\n", 130 | " [nbstata]\n", 131 | " stata_dir = {stata_dir}\n", 132 | " edition = {stata_ed}\n", 133 | " \"\"\"\n", 134 | " )\n", 135 | " for key in Config.env.keys():\n", 136 | " if key not in {'stata_dir', 'edition'}:\n", 137 | " conf_default += f\"{key} = {Config.env[key]}\\n\"\n", 138 | " return conf_default" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "id": "bd472989-154c-4081-b0ed-8319f563d714", 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "#| export \n", 149 | "def create_conf_if_needed(conf_path, conf_file_requested=True):\n", 150 | " \"\"\"Create config file if requested or if Stata not found automatically\"\"\"\n", 151 | " if conf_path.is_file():\n", 152 | " if conf_file_requested:\n", 153 | " print(\"Configuration file already exists at:\")\n", 154 | " print(str(conf_path))\n", 155 | " return\n", 156 | " stata_dir, stata_ed = _find_stata()\n", 157 | " if not stata_ed:\n", 158 | " msg = \"\"\"\\\n", 159 | " WARNING: Could not find Stata path.\n", 160 | " Please specify it manually in configuration file.\n", 161 | " \"\"\"\n", 162 | " print(dedent(msg))\n", 163 | " if stata_ed and not conf_file_requested:\n", 164 | " return\n", 165 | " try:\n", 166 | " conf_dir = Path(os.path.dirname(conf_path))\n", 167 | " if not conf_dir.is_dir():\n", 168 | " os.makedirs(conf_dir)\n", 169 | " with conf_path.open('w') as f:\n", 170 | " f.write(_conf_default(stata_dir, stata_ed))\n", 171 | " print(\"Configuration file created at:\")\n", 172 | " print(str(conf_path))\n", 173 | " except Exception as err:\n", 174 | " print(f\"Attempt to create a configuration file at {str(conf_path)} failed.\")\n", 175 | " raise(err)" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "id": "e0c475a9-dabb-4e8c-81e2-e44cf6f76059", 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "name": "stdout", 186 | "output_type": "stream", 187 | "text": [ 188 | "[nbstata]\n", 189 | "stata_dir = \n", 190 | "edition = \n", 191 | "splash = False\n", 192 | "graph_format = png\n", 193 | "graph_width = 5.5in\n", 194 | "graph_height = 4in\n", 195 | "echo = None\n", 196 | "missing = .\n", 197 | "browse_auto_height = True\n", 198 | "\n" 199 | ] 200 | } 201 | ], 202 | "source": [ 203 | "print(_conf_default())" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "id": "87bcb3d3-dc1d-4f45-9a7f-94d807fb6310", 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [ 213 | "#| export\n", 214 | "def _is_root():\n", 215 | " try:\n", 216 | " return os.geteuid() == 0\n", 217 | " except AttributeError:\n", 218 | " return False # assume not an admin on non-Unix platforms" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "id": "37f8910b-f9da-441e-b678-f05c25cbc458", 225 | "metadata": {}, 226 | "outputs": [ 227 | { 228 | "data": { 229 | "text/plain": [ 230 | "False" 231 | ] 232 | }, 233 | "execution_count": null, 234 | "metadata": {}, 235 | "output_type": "execute_result" 236 | } 237 | ], 238 | "source": [ 239 | "#| hide\n", 240 | "_is_root()" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "id": "e1a689d6-85c1-42ce-9524-425cbc9570c4", 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "#| export\n", 251 | "def _conf_path(user, prefix):\n", 252 | " from nbstata.config import old_user_config_path, xdg_user_config_path\n", 253 | " if user:\n", 254 | " alt_conf_path = old_user_config_path()\n", 255 | " if alt_conf_path.is_file():\n", 256 | " return alt_conf_path\n", 257 | " else:\n", 258 | " return xdg_user_config_path()\n", 259 | " else:\n", 260 | " return Path(os.path.join(prefix, 'etc/nbstata.conf'))" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "id": "5091b653-79e8-4b59-8d9b-d1d66be77cbd", 267 | "metadata": {}, 268 | "outputs": [ 269 | { 270 | "data": { 271 | "text/plain": [ 272 | "Path('C:/Users/tjhuegerich/.config/nbstata/nbstata.conf')" 273 | ] 274 | }, 275 | "execution_count": null, 276 | "metadata": {}, 277 | "output_type": "execute_result" 278 | } 279 | ], 280 | "source": [ 281 | "#| hide\n", 282 | "test_eq(_conf_path(user=False, prefix='[prefix]'), Path('[prefix]/etc/nbstata.conf'))\n", 283 | "_conf_path(user=True, prefix='[prefix]')" 284 | ] 285 | }, 286 | { 287 | "cell_type": "code", 288 | "execution_count": null, 289 | "id": "2ab1d1be-a9c8-4454-9a6c-7d0c0f695371", 290 | "metadata": {}, 291 | "outputs": [], 292 | "source": [ 293 | "#| export\n", 294 | "def main(argv=None):\n", 295 | " ap = argparse.ArgumentParser()\n", 296 | " ap.add_argument('--user', action='store_true',\n", 297 | " help=\"Install to the per-user kernels registry. Default if not root.\")\n", 298 | " ap.add_argument('--sys-prefix', action='store_true',\n", 299 | " help=\"Install to sys.prefix (e.g. a virtualenv or conda env)\")\n", 300 | " ap.add_argument('--prefix',\n", 301 | " help=\"Install to the given prefix. \"\n", 302 | " \"Kernelspec will be installed in {PREFIX}/share/jupyter/kernels/\")\n", 303 | " ap.add_argument('--conf-file', action='store_true',\n", 304 | " help=\"Create a configuration file.\")\n", 305 | " args = ap.parse_args(argv)\n", 306 | "\n", 307 | " if args.sys_prefix:\n", 308 | " args.prefix = sys.prefix\n", 309 | " if not args.prefix and not _is_root():\n", 310 | " args.user = True\n", 311 | "\n", 312 | " install_kernel_spec(user=args.user, prefix=args.prefix)\n", 313 | " conf_path = _conf_path(user=args.user, prefix=sys.prefix)\n", 314 | " create_conf_if_needed(conf_path, conf_file_requested=args.conf_file)" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": null, 320 | "id": "a1cc02dd-3e38-4324-866e-f19a130e38fd", 321 | "metadata": {}, 322 | "outputs": [], 323 | "source": [ 324 | "#| export\n", 325 | "#|eval: false\n", 326 | "if __name__ == \"__main__\" and not IN_NOTEBOOK:\n", 327 | " main()" 328 | ] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": null, 333 | "id": "2c1dedf6-cda9-41e1-9eb9-34a78f22b305", 334 | "metadata": {}, 335 | "outputs": [ 336 | { 337 | "data": { 338 | "text/plain": [ 339 | "True" 340 | ] 341 | }, 342 | "execution_count": null, 343 | "metadata": {}, 344 | "output_type": "execute_result" 345 | } 346 | ], 347 | "source": [ 348 | "#| hide\n", 349 | "IN_NOTEBOOK" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": null, 355 | "id": "a42239e6-298c-4c1f-acf3-80c3fd143919", 356 | "metadata": {}, 357 | "outputs": [ 358 | { 359 | "data": { 360 | "text/plain": [ 361 | "'__main__'" 362 | ] 363 | }, 364 | "execution_count": null, 365 | "metadata": {}, 366 | "output_type": "execute_result" 367 | } 368 | ], 369 | "source": [ 370 | "#| hide\n", 371 | "__name__" 372 | ] 373 | }, 374 | { 375 | "cell_type": "code", 376 | "execution_count": null, 377 | "id": "34bf788c-34bb-49a3-8eee-7768c2091eb4", 378 | "metadata": {}, 379 | "outputs": [ 380 | { 381 | "data": { 382 | "text/plain": [ 383 | "False" 384 | ] 385 | }, 386 | "execution_count": null, 387 | "metadata": {}, 388 | "output_type": "execute_result" 389 | } 390 | ], 391 | "source": [ 392 | "#| hide\n", 393 | "__name__ == \"__main__\" and not IN_NOTEBOOK" 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": null, 399 | "id": "1c6dd777", 400 | "metadata": {}, 401 | "outputs": [], 402 | "source": [ 403 | "#| hide\n", 404 | "import nbdev; nbdev.nbdev_export()" 405 | ] 406 | } 407 | ], 408 | "metadata": { 409 | "kernelspec": { 410 | "display_name": "python3", 411 | "language": "python", 412 | "name": "python3" 413 | } 414 | }, 415 | "nbformat": 4, 416 | "nbformat_minor": 5 417 | } 418 | -------------------------------------------------------------------------------- /nbs/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | type: website 3 | 4 | format: 5 | html: 6 | theme: cosmo 7 | css: styles.css 8 | toc: true 9 | 10 | website: 11 | twitter-card: true 12 | open-graph: true 13 | repo-actions: [issue] 14 | navbar: 15 | background: primary 16 | search: true 17 | sidebar: 18 | style: floating 19 | 20 | metadata-files: [nbdev.yml, sidebar.yml, custom.yml] 21 | -------------------------------------------------------------------------------- /nbs/custom.yml: -------------------------------------------------------------------------------- 1 | website: 2 | google-analytics: 3 | tracking-id: "G-1XLFCC9FMF" 4 | storage: none 5 | sidebar: 6 | collapse-level: 1 7 | style: docked 8 | navbar: 9 | right: 10 | - icon: github 11 | href: "https://github.com/hugetim/nbstata" 12 | format: 13 | html: 14 | reference-location: section 15 | -------------------------------------------------------------------------------- /nbs/dev_docs_index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Developer Docs 3 | listing: 4 | fields: [title, description] 5 | type: table 6 | sort-ui: false 7 | filter-ui: false 8 | contents: 9 | - 00_misc_utils.ipynb 10 | - 01_config.ipynb 11 | - 02_stata.ipynb 12 | - 03_stata_more.ipynb 13 | - 04_code_utils.ipynb 14 | - 05_noecho.ipynb 15 | - 06_pandas.ipynb 16 | - 07_browse.ipynb 17 | - 08_stata_session.ipynb 18 | - 09_magics.ipynb 19 | - 10_completion_env.ipynb 20 | - 11_completions.ipynb 21 | - 12_inspect.ipynb 22 | - 13_cell.ipynb 23 | - 14_kernel.ipynb 24 | - 15_install.ipynb 25 | --- 26 | For information on how to help improve the *nbstata* code, see: [CONTRIBUTING.md](https://github.com/hugetim/nbstata/blob/master/CONTRIBUTING.md) 27 | 28 | The main dependencies among the principal nbstata modules: 29 | ```{mermaid} 30 | flowchart TB 31 | A[kernel] -.-> I[config] 32 | A -.-> B[cell] 33 | B -.-> E[magics] 34 | B -.-> D[stata_session] 35 | D -.-> C[noecho] 36 | E -.-> L[browse] 37 | A -.-> F[completions] 38 | F -.-> D 39 | F -.-> G[completion_env] 40 | A -.-> N[inspect] 41 | N -.-> H[stata_more] 42 | D -.-> H 43 | L -.-> M[pandas] 44 | L -.-> H 45 | M -.-> H 46 | C -.-> H 47 | click A "https://hugetim.github.io/nbstata/kernel.html" 48 | click B "https://hugetim.github.io/nbstata/cell.html" 49 | click C "https://hugetim.github.io/nbstata/noecho.html" 50 | click D "https://hugetim.github.io/nbstata/stata_session.html" 51 | click E "https://hugetim.github.io/nbstata/magics.html" 52 | click F "https://hugetim.github.io/nbstata/completions.html" 53 | click G "https://hugetim.github.io/nbstata/completion_env.html" 54 | click H "https://hugetim.github.io/nbstata/stata_more.html" 55 | click I "https://hugetim.github.io/nbstata/config.html" 56 | click L "https://hugetim.github.io/nbstata/browse.html" 57 | click M "https://hugetim.github.io/nbstata/pandas.html" 58 | click N "https://hugetim.github.io/nbstata/inspect.html" 59 | ``` 60 | The complete list of modules: -------------------------------------------------------------------------------- /nbs/images/status example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugetim/nbstata/a22d0a111cdf76d3452b779941d56cb5d094fd27/nbs/images/status example.png -------------------------------------------------------------------------------- /nbs/index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# nbstata: a new Stata kernel\n", 8 | "\n", 9 | "> A Jupyter kernel for Stata built on pystata" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | " \n", 17 | "\n", 18 | "*nbstata* is a [Jupyter kernel](https://docs.jupyter.org/en/latest/projects/kernels.html) for [Stata](https://www.stata.com/why-use-stata/) built on top of [pystata](https://www.stata.com/python/pystata18/index.html)." 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "**[*For the User Guide, click here.*](https://hugetim.github.io/nbstata/user_guide.html)**\n", 26 | "\n", 27 | " " 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "## What is Jupyter?" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "A Jupyter notebook allows you to combine interactive code and results with [Markdown](https://daringfireball.net/projects/markdown/basics) in a single document. Though it is named after the three core programming languages it supports (Julia, Python, and R), it can be used with with a wide variety of languages. \n", 42 | "\n", 43 | "*nbstata* allows you to create Stata notebooks (as opposed to [using Stata within a *Python* notebook](https://www.stata.com/python/pystata18/notebook/Example2.html), which is needlessly clunky if you are working primarily with Stata)." 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "### Key *nbstata* features\n", 51 | "\n", 52 | "- [x] [Easy setup](https://hugetim.github.io/nbstata/user_guide.html#install)\n", 53 | "- [x] Works with Stata 17+ (only).\n", 54 | "- [x] DataGrid widget with `browse`-like capabilities (e.g., interactive filtering)\n", 55 | "- [x] Variable and data properties available in a 'contextual help' side panel\n", 56 | "- [x] Quarto [inline code](https://quarto.org/docs/computations/inline-code.html) support\n", 57 | "\n", 58 | "Users of Stata 17 or 18.0 also get these features only built-in natively to Stata 18.5+:\n", 59 | "\n", 60 | "- Displays Stata output without the redundant 'echo' of (multi-line) commands\n", 61 | "- Autocompletion for variables, macros, matrices, and file paths\n", 62 | "- Interactive/richtext help files accessible within notebook\n", 63 | "- `#delimit ;` interactive support (along with all types of comments)" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "The video below demonstrates using Stata in a Jupyter notebook. In addition to the [NBClassic](https://nbclassic.readthedocs.io/en/stable/notebook.html) application shown there, *nbstata* can also be used with [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/getting_started/overview.html), [VS Code](https://code.visualstudio.com/docs/datascience/jupyter-notebooks), or [Quarto](https://quarto.org/).\n", 71 | "\n", 72 | "\"Animated" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "### What can you do with Stata notebooks...\n", 80 | "...that you can't do with the [official Stata interface](https://www.stata.com/features/overview/graphical-user-interface/)?\n", 81 | "\n", 82 | "* Exploratory analysis that is both:\n", 83 | " * interactive\n", 84 | " * preserved for future reference/editing" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "* Present results in a way that interweaves:[^1]\n", 92 | " * code\n", 93 | " * results (including graphs)\n", 94 | " * rich text: \n", 95 | " 1. lists\n", 96 | " 2. **Headings**\n", 97 | " 3. \"WordArt\n", 98 | " 4. [links](https://hugetim.github.io/nbstata/) \n", 99 | " 5. math: $y_{it}=\\beta_0+\\varepsilon_{it}$\n", 100 | "\n", 101 | "[^1]: Stata [dynamic documents](https://www.stata.com/manuals/rptdynamicdocumentsintro.pdf) can do this part, though with a less interactive workflow. (See also: [markstat](https://grodri.github.io/markstat/), [stmd](https://www.ssc.wisc.edu/~hemken/Stataworkshops/stmd/Usage/stmdusage.html), and [Statamarkdown](https://ssc.wisc.edu/~hemken/Stataworkshops/Statamarkdown/stata-and-r-markdown.html)) Using *nbstata* with [Quarto](https://www.statalist.org/forums/forum/general-stata-discussion/general/1703835-ado-files-and-literate-programming) instead gives you a similar workflow, with greater flexibility of output." 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "## Contributing" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "*nbstata* is being developed using [nbdev](https://nbdev.fast.ai/blog/posts/2022-07-28-nbdev2/#whats-nbdev). The `/nbs` directory is where edits to the source code should be made. (The python code is then exported to the `/nbdev` library folder.)\n", 116 | "\n", 117 | "For more, see [CONTRIBUTING.md](https://github.com/hugetim/nbstata/blob/master/CONTRIBUTING.md)." 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "## Acknowledgements" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "Kyle Barron authored the original *stata_kernel*, which works for older versions of Stata. Vinci Chow created a Stata kernel that instead uses [pystata](https://www.stata.com/python/pystata18/), which first became available with Stata 17. *nbstata* was originally derived from his [*pystata-kernel*](https://github.com/ticoneva/pystata-kernel), but much of the docs and newer features are derived from *stata_kernel*." 132 | ] 133 | } 134 | ], 135 | "metadata": { 136 | "kernelspec": { 137 | "display_name": "python3", 138 | "language": "python", 139 | "name": "python3" 140 | } 141 | }, 142 | "nbformat": 4, 143 | "nbformat_minor": 4 144 | } 145 | -------------------------------------------------------------------------------- /nbs/nbdev.yml: -------------------------------------------------------------------------------- 1 | project: 2 | output-dir: _docs 3 | 4 | website: 5 | title: "nbstata" 6 | site-url: "https://hugetim.github.io/nbstata" 7 | description: "Jupyter kernel for Stata built on pystata" 8 | repo-branch: master 9 | repo-url: "https://github.com/hugetim/nbstata" 10 | -------------------------------------------------------------------------------- /nbs/sidebar.yml: -------------------------------------------------------------------------------- 1 | website: 2 | page-navigation: true 3 | sidebar: 4 | contents: 5 | - index.ipynb 6 | - user_guide.ipynb 7 | - text: "---" 8 | - href: dev_docs_index.qmd 9 | contents: 10 | - 00_misc_utils.ipynb 11 | - 01_config.ipynb 12 | - 02_stata.ipynb 13 | - 03_stata_more.ipynb 14 | - 04_code_utils.ipynb 15 | - 05_noecho.ipynb 16 | - 06_pandas.ipynb 17 | - 07_browse.ipynb 18 | - 08_stata_session.ipynb 19 | - 09_magics.ipynb 20 | - 10_completion_env.ipynb 21 | - 11_completions.ipynb 22 | - 12_inspect.ipynb 23 | - 13_cell.ipynb 24 | - 14_kernel.ipynb 25 | - 15_install.ipynb 26 | -------------------------------------------------------------------------------- /nbs/styles.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | margin-bottom: 1rem; 3 | } 4 | 5 | .cell > .sourceCode { 6 | margin-bottom: 0; 7 | } 8 | 9 | .cell-output > pre { 10 | margin-bottom: 0; 11 | } 12 | 13 | .cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { 14 | margin-left: 0.8rem; 15 | margin-top: 0; 16 | background: none; 17 | border-left: 2px solid lightsalmon; 18 | border-top-left-radius: 0; 19 | border-top-right-radius: 0; 20 | } 21 | 22 | .cell-output > .sourceCode { 23 | border: none; 24 | } 25 | 26 | .cell-output > .sourceCode { 27 | background: none; 28 | margin-top: 0; 29 | } 30 | 31 | div.description { 32 | padding-left: 2px; 33 | padding-top: 5px; 34 | font-style: italic; 35 | font-size: 135%; 36 | opacity: 70%; 37 | } 38 | -------------------------------------------------------------------------------- /nbstata/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.8.2" 2 | 3 | from .config import launch_stata, set_graph_format 4 | from . import misc_utils, config, stata, stata_more, code_utils, noecho, pandas 5 | -------------------------------------------------------------------------------- /nbstata/__main__.py: -------------------------------------------------------------------------------- 1 | from ipykernel.kernelapp import IPKernelApp 2 | from .kernel import PyStataKernel 3 | 4 | IPKernelApp.launch_instance(kernel_class=PyStataKernel) 5 | -------------------------------------------------------------------------------- /nbstata/browse.py: -------------------------------------------------------------------------------- 1 | """Helpers for browse, head, and tail magics""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/07_browse.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['matchparts', 'in_range', 'parse_browse_magic', 'get_df', 'headtail_df_params', 'headtail_get_df', 'browse_df_params', 7 | 'set_ipydatagrid_height', 'display_df_as_ipydatagrid'] 8 | 9 | # %% ../nbs/07_browse.ipynb 3 10 | from .misc_utils import print_red 11 | from .stata import run_single 12 | from .stata_more import SelectVar, run_direct_cleaned, diverted_stata_output_quicker, run_sfi 13 | from .pandas import better_pdataframe_from_data 14 | from fastcore.basics import patch_to 15 | import re 16 | 17 | # %% ../nbs/07_browse.ipynb 6 18 | def _get_start_end_strs(stata_in_code): 19 | stata_range_code = stata_in_code.replace('in ','').strip() 20 | slash_pos = stata_range_code.find('/') 21 | if slash_pos != -1: 22 | start_str = stata_range_code[:slash_pos] 23 | end_str = stata_range_code[slash_pos+1:] 24 | else: 25 | start_str = "1" 26 | end_str = stata_range_code 27 | return start_str, end_str 28 | 29 | # %% ../nbs/07_browse.ipynb 8 30 | def _get_pos_stata_obs_num(in_obs_str, count): 31 | temp_str = in_obs_str.strip().upper() 32 | if temp_str == 'F': 33 | in_obs = 1 34 | elif temp_str == 'L': 35 | in_obs = count 36 | else: 37 | try: 38 | in_obs = int(in_obs_str) 39 | except ValueError as e: 40 | raise ValueError(f"{in_obs_str} invalid observation number") 41 | if in_obs < 0: in_obs += count + 1 42 | if in_obs < 1 or in_obs > count: 43 | raise ValueError(f"{in_obs_str} invalid observation number") 44 | return in_obs 45 | 46 | # %% ../nbs/07_browse.ipynb 10 47 | def in_range(stata_in_code, count): 48 | """Return in-statement range""" 49 | if not stata_in_code.strip(): 50 | return (None, None) 51 | start, end = (_get_pos_stata_obs_num(in_str, count) 52 | for in_str in _get_start_end_strs(stata_in_code)) 53 | if start > end: 54 | raise ValueError("observations numbers out of range") 55 | return (start-1, end) 56 | 57 | # %% ../nbs/07_browse.ipynb 15 58 | def _parse_browse_magic_syntax(code): 59 | _program_name = "temp_nbstata_syntax_name" 60 | run_direct_cleaned(( 61 | f"program define {_program_name}\n" 62 | """ syntax [varlist(default=none)] [if] [in] [, noLabels noFormat] 63 | disp "%varlist%" 64 | foreach var in `varlist' { 65 | disp "`var'" 66 | } 67 | disp "%if%" 68 | disp `"`if'"' 69 | disp "%in%" 70 | disp `"`in'"' 71 | disp "%nolabels%" 72 | disp "`labels'" 73 | disp "%noformat%" 74 | disp "`format'" 75 | disp "%end%" 76 | end 77 | """), quietly=True) 78 | try: 79 | output = diverted_stata_output_quicker(f"""\ 80 | {_program_name} {code} 81 | program drop {_program_name} 82 | """).strip() 83 | except Exception as e: 84 | run_sfi(f"capture program drop {_program_name}") 85 | raise(e) 86 | return output.replace("\n> ", "") #[c.strip() for c in var_code.split() if c] if var_code else None 87 | 88 | # %% ../nbs/07_browse.ipynb 20 89 | matchparts = re.compile( 90 | r"\A.*?" 91 | r"^%varlist%(?P.*?)" 92 | r"%if%(?P.*?)" 93 | r"%in%(?P.*?)" 94 | r"%nolabels%(?P.*?)" 95 | r"%noformat%(?P.*?)%end%", #"(\Z|---+\s*end)", 96 | flags=re.DOTALL + re.MULTILINE).match 97 | 98 | # %% ../nbs/07_browse.ipynb 22 99 | def parse_browse_magic(code): 100 | N = None 101 | m = re.match(r"\A(?P[0-9]+)?[\s]*(?P.*?)\Z", code.strip()) 102 | if m is None: 103 | raise ValueError(f"syntax error: this magic must be in a code by itself on a single line") 104 | if m.group('N'): 105 | N = int(m.group('N')) 106 | code_minus_N = m.group('remainder') 107 | args = matchparts(_parse_browse_magic_syntax(code_minus_N)).groupdict() 108 | _var = [c.strip() for c in args['varlist'].split() if c] 109 | var = _var if _var else None 110 | if_code = args['if'].strip() 111 | in_code = args['in'].strip() 112 | nolabels = args['nolabels'].strip() 113 | noformat = args['noformat'].strip() 114 | return N, var, if_code, in_code, nolabels, noformat 115 | 116 | # %% ../nbs/07_browse.ipynb 26 117 | def _parse_df_params(code, count, browse=False, tail=False): 118 | from numpy import inf 119 | N, var, if_code, in_code, nolabels, noformat = parse_browse_magic(code) 120 | sformat = not noformat 121 | valuelabel = not nolabels 122 | 123 | N_max = inf if browse else 5 124 | if N is not None: 125 | if browse: 126 | print_red("Warning: '%browse [N]' syntax is deprecated " 127 | "and may be removed in v1.0.") 128 | N_max = N 129 | 130 | # Obs range 131 | obs_range = None 132 | if browse: 133 | start, end = in_range(in_code, count) 134 | if start != None and end != None: 135 | obs_range = range(start, end) 136 | elif count > N_max: 137 | obs_range = range(0, N_max) 138 | else: 139 | if in_code: 140 | print_red(f"Note: [in] not allowed for {'tail' if tail else 'head'} " 141 | "magic and is ignored." 142 | ) 143 | if count > N_max: 144 | obs_range = range(count - N_max, count) if tail else range(0, N_max) 145 | 146 | return obs_range, var, if_code, valuelabel, sformat 147 | 148 | # %% ../nbs/07_browse.ipynb 27 149 | def get_df(obs_range, var, stata_if_code, missingval, valuelabel, sformat): 150 | with SelectVar(stata_if_code) as sel_varname: 151 | df = better_pdataframe_from_data(obs=obs_range, 152 | var=var, 153 | selectvar=sel_varname, 154 | missingval=missingval, 155 | valuelabel=valuelabel, 156 | sformat=sformat, 157 | ) 158 | if not var and sel_varname is not None and sel_varname in df: 159 | df = df.drop([sel_varname], axis=1) 160 | return df 161 | 162 | # %% ../nbs/07_browse.ipynb 29 163 | def headtail_df_params(code, count, missing_config, tail=False): 164 | from numpy import nan 165 | custom_missingval = missing_config != 'pandas' 166 | missingval = missing_config if custom_missingval else nan 167 | obs_range, var, stata_if_code, valuelabel, sformat = ( 168 | _parse_df_params(code, count, tail=tail) 169 | ) 170 | return obs_range, var, stata_if_code, missingval, valuelabel, sformat 171 | 172 | # %% ../nbs/07_browse.ipynb 33 173 | def headtail_get_df(obs_range, var, stata_if_code, missingval, valuelabel, sformat): 174 | if not stata_if_code: 175 | return get_df(obs_range, var, stata_if_code, missingval, valuelabel, sformat) 176 | N_max = len(obs_range) 177 | tail = obs_range[0] != 0 178 | with SelectVar(stata_if_code) as sel_varname: 179 | df = better_pdataframe_from_data(obs=None, 180 | var=var, 181 | selectvar=sel_varname, 182 | missingval=missingval, 183 | valuelabel=valuelabel, 184 | sformat=sformat, 185 | ) 186 | if not var and sel_varname is not None and sel_varname in df: 187 | df = df.drop([sel_varname], axis=1) 188 | return df.tail(N_max) if tail else df.head(N_max) 189 | 190 | # %% ../nbs/07_browse.ipynb 44 191 | def browse_df_params(code, count, missing_config): 192 | from numpy import nan 193 | custom_missingval = missing_config != 'pandas' 194 | missingval = missing_config if custom_missingval else nan 195 | obs_range, var, stata_if_code, valuelabel, sformat = ( 196 | _parse_df_params(code, count, browse=True) 197 | ) 198 | return obs_range, var, stata_if_code, missingval, valuelabel, sformat 199 | 200 | # %% ../nbs/07_browse.ipynb 52 201 | def set_ipydatagrid_height(): 202 | from IPython.core.display import HTML 203 | display(HTML("")) 204 | 205 | # %% ../nbs/07_browse.ipynb 53 206 | def display_df_as_ipydatagrid(df, auto_height=True): 207 | from ipydatagrid import DataGrid, TextRenderer 208 | i_renderer = TextRenderer(horizontal_alignment="right", background_color="rgb(243, 243, 243)") 209 | d_renderer = TextRenderer(horizontal_alignment="right") 210 | h_renderer = TextRenderer(horizontal_alignment="center") 211 | column_widths = {} 212 | temp_head = df.head(20) 213 | char_px_width = 6.05 214 | def name_width(name_len): 215 | if name_len <= 15: 216 | return 43 + char_px_width*name_len 217 | elif name_len > 22 or name_len%2 == 0: 218 | return 43 + char_px_width*15.3 219 | else: 220 | return 43 + char_px_width*14.3 221 | 222 | for name in list(df): 223 | column_widths[name] = max( 224 | name_width(len(name)), 225 | 20 + char_px_width*max((len(str(value)) for value in temp_head[name])), 226 | ) 227 | column_widths[" "] = 20 + char_px_width*len(str(max(df.index.values))) 228 | g_kwargs = dict( 229 | index_name=" ", 230 | editable=False, 231 | selection_mode='cell', 232 | base_row_size=21, 233 | auto_fit_columns=False, 234 | default_renderer=d_renderer, 235 | header_renderer=h_renderer, 236 | renderers={" ": i_renderer}, 237 | column_widths=column_widths, 238 | ) 239 | if auto_height: 240 | g_kwargs['layout'] = {"height": "100%"} 241 | g = DataGrid(df, **g_kwargs) 242 | g.grid_style = { 243 | "background_color": "rgb(255, 255, 255)", 244 | "header_background_color": "rgb(243, 243, 243)", 245 | "header_grid_line_color": "rgb(229, 229, 229)", 246 | "vertical_grid_line_color": "rgb(229, 229, 229)", 247 | "horizontal_grid_line_color": "rgb(229, 229, 229)", 248 | "selection_fill_color": "rgb(59, 135, 195, .25)", # 206, 225, 240 249 | "selection_border_color": "rgb(229, 229, 229)", 250 | "header_selection_fill_color": "rgb(99, 99, 99, .25)", #216 251 | "header_selection_border_color": "rgb(229, 229, 229)", 252 | "cursor_fill_color": "rgb(255, 255, 255, 0)", 253 | "cursor_border_color": "rgb(40, 40, 40)", 254 | "scroll_shadow": {'size': 0, 'color1': "white", 'color2': "white", 'color3': "white"}, 255 | } 256 | display(g) 257 | -------------------------------------------------------------------------------- /nbstata/cell.py: -------------------------------------------------------------------------------- 1 | """Class representing a single code cell""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/13_cell.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['magic_handler', 'Cell'] 7 | 8 | # %% ../nbs/13_cell.ipynb 3 9 | from .stata_session import StataSession 10 | from .magics import StataMagics 11 | from fastcore.basics import patch_to 12 | 13 | # %% ../nbs/13_cell.ipynb 4 14 | magic_handler = StataMagics() 15 | 16 | # %% ../nbs/13_cell.ipynb 5 17 | class Cell: 18 | """A class for managing execution of a single code cell""" 19 | def __init__(self, kernel, code_w_magics, silent=False): 20 | self.noecho = kernel.nbstata_config.noecho 21 | self.echo = kernel.nbstata_config.echo 22 | self.quietly = silent 23 | self.stata_session = kernel.stata_session 24 | self.code = magic_handler.magic(code_w_magics, kernel, self) 25 | 26 | def run(self): 27 | if not self.code: 28 | return 29 | self.stata_session.dispatch_run(self.code, 30 | quietly=self.quietly, echo=self.echo, noecho=self.noecho) 31 | -------------------------------------------------------------------------------- /nbstata/code_utils.py: -------------------------------------------------------------------------------- 1 | """Stata-related helper functions with no Jupyter or pystata dependence""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/04_code_utils.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['stata_lexer', 'delimit_regex', 'multi_regex', 'pre', 'kwargs', 'local_def_in', 'preserve_restore_in', 7 | 'remove_comments', 'ends_in_comment_block', 'valid_single_line_code', 'ending_sc_delimiter', 8 | 'standardize_code', 'ending_code_version', 'is_start_of_program_block', 'break_out_prog_blocks'] 9 | 10 | # %% ../nbs/04_code_utils.ipynb 4 11 | import re 12 | from decimal import Decimal 13 | 14 | # %% ../nbs/04_code_utils.ipynb 5 15 | from pygments import lexers 16 | from pygments.token import Comment 17 | 18 | # %% ../nbs/04_code_utils.ipynb 8 19 | stata_lexer = lexers.get_lexer_by_name('stata') 20 | 21 | def _lex_tokens(code): 22 | return stata_lexer.get_tokens_unprocessed(code) 23 | 24 | # %% ../nbs/04_code_utils.ipynb 10 25 | def remove_comments(code): 26 | return "".join(token[2] for token in _lex_tokens(code) if token[1] not in Comment) 27 | 28 | # %% ../nbs/04_code_utils.ipynb 18 29 | def _end_block_followed_by_non_comment_block(code): 30 | return ( 31 | code.rfind('*/') != -1 32 | and not ends_in_comment_block(code[code.rfind('*/')+2:]) 33 | ) 34 | 35 | # %% ../nbs/04_code_utils.ipynb 19 36 | def ends_in_comment_block(code): 37 | last_token = list(_lex_tokens(code))[-1] 38 | last_token_type = last_token[1] 39 | return ( 40 | last_token_type == Comment.Multiline 41 | and code.strip()[-2:] != "*/" 42 | and not _end_block_followed_by_non_comment_block(code) 43 | ) 44 | 45 | # %% ../nbs/04_code_utils.ipynb 23 46 | def _is_not_cr_delimiter(delimiter): 47 | return delimiter != 'cr' 48 | 49 | # %% ../nbs/04_code_utils.ipynb 24 50 | delimit_regex = re.compile(r'^[ \t]*#delimit(.*$)', flags=re.MULTILINE) 51 | def _replace_delimiter(code, sc_delimiter=False): 52 | # Recursively replace custom delimiter with newline 53 | 54 | split = delimit_regex.split(code.strip(), maxsplit=1) 55 | 56 | if len(split) == 3: 57 | before = split[0] 58 | after = _replace_delimiter(split[2], _is_not_cr_delimiter(split[1].strip())) 59 | else: 60 | before = code 61 | after = '' 62 | 63 | if sc_delimiter: 64 | before_last_sc_pos = before.rfind(';') 65 | if before_last_sc_pos < len(before.strip()) - 1: 66 | before = before[:before_last_sc_pos+1] 67 | if len(split) > 1: 68 | after = _replace_delimiter(before[before_last_sc_pos+1:]+" ".join(split[1:]), sc_delimiter=True) 69 | before = before.replace('\r', ' ').replace('\n', ' ') 70 | before = before.replace(';','\n') 71 | 72 | return before + after 73 | 74 | # %% ../nbs/04_code_utils.ipynb 33 75 | def _replace_tabs(code): 76 | return code.replace("\t", " ") 77 | 78 | # %% ../nbs/04_code_utils.ipynb 35 79 | def valid_single_line_code(code): 80 | code = _replace_tabs(remove_comments(code)) 81 | if delimit_regex.match(code): 82 | return "" 83 | else: 84 | return code 85 | 86 | # %% ../nbs/04_code_utils.ipynb 37 87 | def ending_sc_delimiter(code, sc_delimiter=False): 88 | code = remove_comments(code) 89 | # Recursively determine ending delimiter 90 | split = delimit_regex.split(code.strip(),maxsplit=1) 91 | 92 | if len(split) == 3: 93 | before = split[0] 94 | else: 95 | before = code 96 | if sc_delimiter: 97 | before_last_sc_pos = before.rfind(';') 98 | if before_last_sc_pos < len(before.strip()) - 1: 99 | if len(split) > 1: 100 | return ending_sc_delimiter(before[before_last_sc_pos+1:]+" ".join(split[1:]), sc_delimiter=True) 101 | 102 | if len(split) == 3: 103 | sc_delimiter = ending_sc_delimiter(split[2], _is_not_cr_delimiter(split[1].strip())) 104 | elif len(split) == 2: 105 | sc_delimiter = _is_not_cr_delimiter(split[1].strip()) 106 | 107 | return sc_delimiter 108 | 109 | # %% ../nbs/04_code_utils.ipynb 44 110 | # Detect Multiple whitespace 111 | multi_regex = re.compile(r'(?P\S) +') 112 | 113 | def standardize_code(code, sc_delimiter=False): 114 | """Remove comments spanning multiple lines and replace custom delimiters""" 115 | code = remove_comments(code) 116 | 117 | # After removing multi-line comments, which could include "#delimit;" 118 | code = _replace_delimiter(code, sc_delimiter) 119 | 120 | # Replace multiple interior whitespace with one 121 | code = multi_regex.sub('\g ',code) 122 | 123 | # Delete blank lines and whitespace at end of lines 124 | code_lines = code.splitlines() 125 | std_lines = [] 126 | for code_line in code_lines: 127 | cs = code_line.rstrip() 128 | if cs: 129 | std_lines.append(cs) 130 | return '\n'.join(std_lines) 131 | 132 | # %% ../nbs/04_code_utils.ipynb 57 133 | def _startswith_stata_abbrev(string, full_command, shortest_abbrev): 134 | for j in range(len(shortest_abbrev), len(full_command)+1): 135 | if string.startswith(full_command[0:j] + ' '): 136 | return True 137 | return False 138 | 139 | # %% ../nbs/04_code_utils.ipynb 59 140 | def _remove_prefixes(std_code_line): 141 | std_code_line = std_code_line.lstrip() 142 | if (_startswith_stata_abbrev(std_code_line, 'quietly', 'qui') 143 | or std_code_line.startswith('capture ') 144 | or _startswith_stata_abbrev(std_code_line, 'noisily', 'n')): 145 | return _remove_prefixes(std_code_line.split(None, maxsplit=1)[1]) 146 | else: 147 | return std_code_line 148 | 149 | # %% ../nbs/04_code_utils.ipynb 63 150 | def ending_code_version(code, sc_delimiter=False, code_version=None, stata_version='17.0'): 151 | if 'version' not in code: 152 | return code_version 153 | std_code = standardize_code(code, sc_delimiter) 154 | for std_code_line in reversed(std_code.splitlines()): 155 | if 'version ' not in std_code_line: 156 | continue 157 | m = re.match(r'\A\s*version ([0-9]+(?:\.[0-9][0-9]?)?)\Z', _remove_prefixes(std_code_line)) 158 | if m: 159 | _version = Decimal(m.group(1)).normalize() 160 | if Decimal('1') <= _version <= Decimal(stata_version): 161 | code_version = None if _version == Decimal(stata_version).normalize() else str(_version) 162 | break 163 | return code_version 164 | 165 | # %% ../nbs/04_code_utils.ipynb 67 166 | pre = ( 167 | r'(cap(t|tu|tur|ture)?' 168 | r'|qui(e|et|etl|etly)?' 169 | r'|n(o|oi|ois|oisi|oisil|oisily)?)') 170 | kwargs = {'flags': re.MULTILINE} 171 | local_def_in = re.compile( 172 | r"(^\s*({0} )*(loc(a|al)?|tempname|tempvar|tempfile|gettoken|token(i|iz|ize)?|levelsof)\s)|st_local\(".format(pre), 173 | **kwargs, 174 | ).search 175 | 176 | # %% ../nbs/04_code_utils.ipynb 69 177 | preserve_restore_in = re.compile( 178 | r"(^({0} )*(preserve|restore)[,\s]?\.*?$)|(;({0} )*(preserve|restore)[,\s]?\.*?$)".format(pre), 179 | **kwargs, 180 | ).search 181 | 182 | # %% ../nbs/04_code_utils.ipynb 72 183 | def is_start_of_program_block(std_code_line): 184 | cs = _remove_prefixes(std_code_line) 185 | _starts_program = (_startswith_stata_abbrev(cs, 'program', 'pr') 186 | and not (cs.split()[1] in ['di', 'dir', 'drop', 'l', 'li', 'lis', 'list'])) 187 | return (_starts_program 188 | or (cs in {'mata', 'mata:'}) 189 | or (cs in {'python', 'python:'})) 190 | 191 | # %% ../nbs/04_code_utils.ipynb 74 192 | def _prog_blocks(std_code_lines): 193 | next_block_lines = [] 194 | in_program = False 195 | for std_code_line in std_code_lines: 196 | if is_start_of_program_block(std_code_line): 197 | if next_block_lines: # previous lines 198 | yield _block(next_block_lines, is_prog=in_program) 199 | next_block_lines = [] 200 | in_program = True 201 | next_block_lines.append(std_code_line) 202 | if std_code_line == 'end': # regardless of whether in_program 203 | yield _block(next_block_lines, is_prog=True) 204 | next_block_lines = [] 205 | in_program = False 206 | if next_block_lines: 207 | yield _block(next_block_lines, in_program) 208 | 209 | 210 | def _block(block_lines, is_prog): 211 | return {"is_prog": is_prog, "std_code": '\n'.join(block_lines)} 212 | 213 | # %% ../nbs/04_code_utils.ipynb 75 214 | def break_out_prog_blocks(code, sc_delimiter=False): 215 | std_code_lines = standardize_code(code, sc_delimiter).splitlines() 216 | return list(_prog_blocks(std_code_lines)) 217 | -------------------------------------------------------------------------------- /nbstata/completion_env.py: -------------------------------------------------------------------------------- 1 | """Autocomplete helper: determine context of the token to be autocompleted""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/10_completion_env.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['stata_lexer', 'CompletionEnv', 'Env'] 7 | 8 | # %% ../nbs/10_completion_env.ipynb 4 9 | from .code_utils import ending_sc_delimiter 10 | from fastcore.basics import patch_to 11 | from enum import IntEnum 12 | from typing import Tuple 13 | import re 14 | 15 | # %% ../nbs/10_completion_env.ipynb 5 16 | from pygments import lexers 17 | from pygments.token import Comment, Keyword, Name, Number, \ 18 | String, Text, Operator 19 | 20 | # %% ../nbs/10_completion_env.ipynb 7 21 | stata_lexer = lexers.get_lexer_by_name('stata') 22 | 23 | def _lex_tokens(code): 24 | return list(stata_lexer.get_tokens_unprocessed(code)) 25 | 26 | # %% ../nbs/10_completion_env.ipynb 8 27 | def _last_token(code): 28 | tokens = _lex_tokens(code) 29 | last_tokentype = tokens[-1][1] 30 | tokens_to_combine = [] 31 | for token in reversed(tokens): 32 | if token[1] is last_tokentype: 33 | tokens_to_combine.append(token) 34 | else: 35 | break 36 | tokens_to_combine = list(reversed(tokens_to_combine)) 37 | return (min(tokens_to_combine, key=lambda t: t[0])[0], last_tokentype, "".join([t[2] for t in tokens_to_combine])) 38 | 39 | # %% ../nbs/10_completion_env.ipynb 9 40 | def _last_token_full_string(code, sc_delimiter=False): 41 | if not code: 42 | return (0, None, "") 43 | prefix = "" 44 | if sc_delimiter: 45 | prefix = "#delimit;\n" 46 | orig_code = code 47 | code = prefix + orig_code 48 | tokens = _lex_tokens(code) 49 | last_tokentype = tokens[-1][1] 50 | tokens_to_combine = [] 51 | for token in reversed(tokens): 52 | if token[1] is last_tokentype: 53 | tokens_to_combine.append(token) 54 | else: 55 | break 56 | tokens_to_combine = list(reversed(tokens_to_combine)) 57 | index = min(tokens_to_combine, key=lambda t: t[0])[0] 58 | value = "".join([t[2] for t in tokens_to_combine]) 59 | while last_tokentype == String and value[0] != '"' and value[0:2] != '`"': 60 | tokens_to_combine = list(reversed(tokens_to_combine)) 61 | reversed_remaining_tokens = list(reversed(_lex_tokens(code[:index]))) 62 | for i, token in enumerate(reversed_remaining_tokens): 63 | if (token[1] is not String 64 | and reversed_remaining_tokens[i-1][1] is String): 65 | break 66 | elif token[1] in [Comment.Single, Comment.Multiline, Comment.Special]: 67 | break 68 | tokens_to_combine.append(token) 69 | tokens_to_combine = list(reversed(tokens_to_combine)) 70 | index = min(tokens_to_combine, key=lambda t: t[0])[0] 71 | value = "".join([t[2] for t in tokens_to_combine]) 72 | index = index-len(prefix) 73 | if index < 0: 74 | value = value[-index:] 75 | index = 0 76 | return (index, last_tokentype, value) 77 | 78 | # %% ../nbs/10_completion_env.ipynb 29 79 | class CompletionEnv(): 80 | def __init__(self): 81 | """""" 82 | self.last_word = re.compile( 83 | r'\W\w*?\Z', flags=re.MULTILINE).search 84 | 85 | # any non-space/"/= 'chunk' at the end of the string after the last ", =, or white space 86 | self.last_chunk = re.compile( 87 | r'[\s"=][^\s"=]*?\Z', flags=re.MULTILINE).search 88 | 89 | # Path completion 90 | self.path_search = re.compile( 91 | r'^(?P.*")(?P[^"]*)\Z').search 92 | 93 | # Magic completion 94 | self.magic_completion = re.compile( 95 | r'\A\*?(?P%%?\S*)\Z', flags=re.DOTALL + re.MULTILINE).match 96 | 97 | # Match context; this is used to determine if the line starts 98 | # with matrix or scalar. It also matches constructs like 99 | # 100 | # (`=)?scalar( 101 | pre = ( 102 | r'(cap(t|tu|tur|ture)?' 103 | r'|qui(e|et|etl|etly)?' 104 | r'|n(o|oi|ois|oisi|oisil|oisily)?)') 105 | kwargs = {'flags': re.MULTILINE} 106 | self.fcontext = { 107 | 'function': 108 | re.compile( 109 | r"(\s+|\=|`=)\s*(?P\w+?)" 110 | r"\([^\)]*?(?P\w*)\Z", **kwargs).search, 111 | } 112 | self.context = { 113 | 'line': 114 | re.compile( 115 | r"^(?P\s*({0}\s+)*(?P\S+) .*?)\Z".format(pre), 116 | **kwargs).search, 117 | 'delimit_line': 118 | re.compile( 119 | r"(?:\A|;)(?P\s*({0}\s+)*(?P[^\s;]+)\s[^;]*?)\Z".format(pre), 120 | **kwargs).search 121 | } 122 | # self.last_line = { 123 | # 'line': 124 | # re.compile( 125 | # r"^(?P.*)\Z", 126 | # **kwargs).search, 127 | # 'delimit_line': 128 | # re.compile( 129 | # r"(?:\A|;)(?P[^;]*)\Z", 130 | # **kwargs).search 131 | # } 132 | 133 | # self.ends_in_a_comment = re.compile( 134 | # r'(' 135 | # r'(^((\s*\*)|((.*( |\t))?\/\/)).*)' # last line starting with '*' or containing ' //' 136 | # r'|(\/\*)([^\*\/]|\*(?!\/)|\/(? Tuple[Env, int, str, str]: 222 | """Returns completions environment 223 | 224 | Returns 225 | ------- 226 | env : Env 227 | pos : int 228 | Where the completions start. This is set to the start of the word to be completed. 229 | out_chunk : str 230 | Word to match. 231 | rcomp : str 232 | How to finish the completion (defaulting to nothing): 233 | locals: ' 234 | globals (if start with ${): } 235 | scalars: ) 236 | scalars (if start with `): )' 237 | """ 238 | rcomp = "" 239 | 240 | lcode = code.lstrip() 241 | if self.magic_completion(lcode): 242 | pos = code.find("%") 243 | env = Env.MAGIC 244 | return env, pos, code[pos:], rcomp 245 | 246 | sc_delimiter = ending_sc_delimiter(code, sc_delimiter) 247 | env = Env.GENERAL 248 | 249 | pos = self._start_of_last_word(code) 250 | 251 | if _ends_in_a_comment(code, sc_delimiter): 252 | return env, pos, code[pos:], rcomp 253 | 254 | last_token_index, last_token_type, last_token_value = _last_token_full_string(code, sc_delimiter) 255 | 256 | if last_token_type is String: 257 | if (not _ends_in_string_literal(code + " ", sc_delimiter) 258 | or not (last_token_value.startswith('"') 259 | or last_token_value.startswith('`"'))): 260 | return Env.NONE, len(code)-1, rcomp 261 | if last_token_value.startswith('"'): 262 | opening_marker_length = 1 263 | rcomp = "" if r2chars[0:1] == '"' else '"' 264 | elif last_token_value.startswith('`"'): 265 | opening_marker_length = 2 266 | rcomp = "" if r2chars[0:2] == "\"'" else "\"'" 267 | pos = last_token_index + opening_marker_length 268 | env = Env.STRING 269 | else: 270 | # Figure out if this is a local or global; env = 0 (default) 271 | # will suggest variables in memory. 272 | cpos = self._start_of_last_chunk(code) # last "chunk" delimited by white space, a double-quote, or =. 273 | chunk = code[cpos:] 274 | lfind = chunk.rfind('`') 275 | gfind = chunk.rfind('$') 276 | path_chars = any(x in chunk for x in ['/', '\\', '~']) 277 | 278 | if lfind >= 0 and (lfind > gfind): 279 | pos = cpos + lfind + 1 280 | env = Env.LOCAL 281 | rcomp = "" if r2chars[0:1] == "'" else "'" 282 | elif gfind >= 0 and not path_chars: 283 | bfind = chunk.rfind('{') 284 | if bfind >= 0 and (bfind == gfind+1): 285 | pos = cpos + bfind + 1 286 | env = Env.GLOBAL 287 | rcomp = "" if r2chars[0:1] == "}" else "}" 288 | else: 289 | env = Env.GLOBAL 290 | pos = cpos + gfind + 1 291 | 292 | if pos == 0: 293 | env = Env.NONE # to-do: auto-complete commands here 294 | else: 295 | # Figure out if current statement is a matrix or scalar 296 | # statement. If so, will add them to completions list. 297 | last_line, first_word = self._last_line_first_word(code, sc_delimiter) 298 | if first_word: 299 | equals_present = (last_line.find('=') > 0) 300 | if re.match(r'^sca(lar|la|l)?$', first_word): #.strip() 301 | env = Env.SCALAR_VAR if equals_present else Env.SCALAR 302 | elif re.match(r'^mat(rix|ri|r)?$', first_word): #.strip() 303 | env = Env.MATRIX_VAR if equals_present else Env.MATRIX 304 | 305 | # Constructs of the form scalar(x will be filled only 306 | # with scalars. This can be preceded by = or `= 307 | if env in [Env.GENERAL, Env.STRING]: 308 | scalar_f, new_pos, new_rcomp = self._scalar_f_pos_rcomp(code, r2chars) 309 | if scalar_f: 310 | env = Env.SCALAR 311 | pos = new_pos 312 | rcomp = new_rcomp 313 | 314 | out_chunk = code[pos:] 315 | return env, pos, out_chunk, rcomp 316 | -------------------------------------------------------------------------------- /nbstata/completions.py: -------------------------------------------------------------------------------- 1 | """Autocomplete functionality""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/11_completions.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['relevant_suggestion_keys', 'CompletionsManager'] 7 | 8 | # %% ../nbs/11_completions.ipynb 4 9 | from .stata import get_global, pwd 10 | from .stata_session import StataSession 11 | from .magics import StataMagics 12 | from .completion_env import CompletionEnv, Env 13 | from fastcore.basics import patch_to 14 | import os 15 | import re 16 | import platform 17 | 18 | # %% ../nbs/11_completions.ipynb 5 19 | class CompletionsManager(): 20 | def __init__(self, stata_session: StataSession): 21 | """""" 22 | self.stata_session = stata_session 23 | self.available_magics = list(StataMagics.available_magics.keys()) 24 | self.env_helper = CompletionEnv() 25 | 26 | # %% ../nbs/11_completions.ipynb 8 27 | @patch_to(CompletionsManager) 28 | def get_globals(self): 29 | if self.stata_session.suggestions: 30 | return {k: get_global(k) for k in self.stata_session.suggestions['globals']} 31 | else: 32 | return {} 33 | 34 | # %% ../nbs/11_completions.ipynb 9 35 | @patch_to(CompletionsManager) 36 | def get_file_paths(self, chunk): 37 | """Get file paths based on chunk 38 | Args: 39 | chunk (str): chunk of text after last space. Doesn't include string 40 | punctuation characters 41 | Returns: 42 | (List[str]): folders and files at that location 43 | """ 44 | # If local exists, return empty list 45 | if re.search(r'[`\']', chunk): 46 | return [] 47 | 48 | # Define directory separator 49 | dir_sep = '/' 50 | if platform.system() == 'Windows': 51 | if '/' not in chunk: 52 | dir_sep = '\\' 53 | 54 | # Get directory without ending file, and without / or \ 55 | if any(x in chunk for x in ['/', '\\']): 56 | ind = max(chunk.rfind('/'), chunk.rfind('\\')) 57 | user_folder = chunk[:ind + 1] 58 | user_starts = chunk[ind + 1:] 59 | 60 | # Replace multiple consecutive / with a single / 61 | user_folder = re.sub(r'/+', '/', user_folder) 62 | user_folder = re.sub(r'\\+', r'\\', user_folder) 63 | 64 | else: 65 | user_folder = '' 66 | user_starts = chunk 67 | 68 | # Replace globals with their values 69 | globals_re = r'\$\{?((?![0-9_])\w{1,32})\}?' 70 | try: 71 | folder = re.sub( 72 | globals_re, 73 | lambda x: self.get_globals()[x.group(1)], 74 | user_folder 75 | ) 76 | except KeyError: 77 | # If the global doesn't exist (aka it hasn't been defined in 78 | # the Stata environment yet), then there are no paths to check 79 | return [] 80 | 81 | # Use Stata's relative path 82 | abspath = re.search(r'^([/~]|[a-zA-Z]:)', folder) 83 | if not abspath: 84 | folder = pwd() + '/' + folder 85 | 86 | try: 87 | top_dir, dirs, files = next(os.walk(os.path.expanduser(folder))) 88 | results = [x + dir_sep for x in dirs] + files 89 | results = [ 90 | user_folder + x for x in results if not x.startswith('.') 91 | and re.match(re.escape(user_starts), x, re.I)] 92 | 93 | except StopIteration: 94 | results = [] 95 | 96 | return sorted(results) 97 | 98 | # %% ../nbs/11_completions.ipynb 13 99 | relevant_suggestion_keys = { 100 | Env.NONE: [], 101 | Env.GENERAL: ['varlist', 'scalars'], 102 | Env.LOCAL: ['locals'], 103 | Env.GLOBAL: ['globals'], 104 | Env.SCALAR: ['scalars'], 105 | Env.MATRIX: ['matrices'], 106 | Env.SCALAR_VAR: ['scalars', 'varlist'], 107 | Env.MATRIX_VAR: ['matrices', 'varlist'], 108 | Env.STRING: [], 109 | } 110 | 111 | @patch_to(CompletionsManager) 112 | def get(self, starts, env, rcomp): 113 | """Return environment-aware completions list.""" 114 | if env is Env.MAGIC: 115 | candidate_suggestions = self.available_magics 116 | else: 117 | candidate_suggestions = [suggestion 118 | for key in relevant_suggestion_keys[env] 119 | for suggestion in self.stata_session.suggestions[key]] 120 | relevant_suggestions = [candidate + rcomp 121 | for candidate in candidate_suggestions 122 | if candidate.startswith(starts)] 123 | if env in [Env.GENERAL, Env.STRING]: 124 | relevant_suggestions += self.get_file_paths(starts) 125 | return relevant_suggestions 126 | 127 | # elif env == 9: 128 | # if len(starts) > 1: 129 | # builtins = [ 130 | # var for var in mata_builtins if var.startswith(starts)] 131 | # else: 132 | # builtins = [] 133 | 134 | # if re.search(r'[/\\]', starts): 135 | # paths = self.get_file_paths(starts) 136 | # else: 137 | # paths = [] 138 | 139 | # return [ 140 | # var for var in self.stata_session.suggestions['mata'] 141 | # if var.startswith(starts)] + builtins + paths 142 | 143 | # %% ../nbs/11_completions.ipynb 14 144 | @patch_to(CompletionsManager) 145 | def do(self, code, cursor_pos): 146 | if self.stata_session.suggestions is None: 147 | self.stata_session.refresh_suggestions() 148 | env, pos, chunk, rcomp = self.env_helper.get_env( 149 | code[:cursor_pos], 150 | code[cursor_pos:(cursor_pos + 2)], 151 | self.stata_session.sc_delimiter, 152 | ) 153 | return pos, cursor_pos, self.get(chunk, env, rcomp) 154 | -------------------------------------------------------------------------------- /nbstata/config.py: -------------------------------------------------------------------------------- 1 | """Utilities for loading Stata and nbstata""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_config.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['find_dir_edition', 'find_edition', 'set_pystata_path', 'launch_stata', 'set_graph_format', 'xdg_user_config_path', 7 | 'old_user_config_path', 'Config'] 8 | 9 | # %% ../nbs/01_config.ipynb 5 10 | from .misc_utils import print_red 11 | from fastcore.basics import patch_to 12 | import os 13 | import sys 14 | import platform 15 | from shutil import which 16 | from pathlib import Path 17 | from packaging import version 18 | import configparser 19 | 20 | # %% ../nbs/01_config.ipynb 8 21 | def _win_find_path(_dir=None): 22 | if _dir is None: 23 | dirs = [r'C:\Program Files\Stata19', 24 | r'C:\Program Files\Stata18', 25 | r'C:\Program Files\Stata17'] 26 | else: 27 | dirs = [_dir] 28 | for this_dir in dirs: 29 | path = Path(this_dir) 30 | if os.path.exists(path): 31 | executables = [exe for exe in path.glob("Stata*.exe") if exe not in set(path.glob("Stata*_old.exe"))] 32 | if executables: 33 | return str(executables[0]) 34 | # Otherwise, try old way 35 | import winreg 36 | reg = winreg.ConnectRegistry(None, winreg.HKEY_CLASSES_ROOT) 37 | subkey = r'Stata17Do\shell\do\command' 38 | try: 39 | key = winreg.OpenKey(reg, subkey) 40 | return winreg.QueryValue(key, None).split('"')[1] 41 | except FileNotFoundError: 42 | return '' 43 | 44 | # %% ../nbs/01_config.ipynb 10 45 | def _mac_find_path(_dir=None): 46 | """ 47 | Attempt to find Stata path on macOS when not on user's PATH. 48 | Modified from stata_kernel's original to only location "Applications/Stata". 49 | 50 | Returns: 51 | (str): Path to Stata. Empty string if not found. 52 | """ 53 | if _dir is None: 54 | _dir = '/Applications/Stata' 55 | path = Path(_dir) 56 | if not os.path.exists(path): 57 | return '' 58 | else: 59 | try: 60 | # find the application with the suffix .app 61 | # example path: /Applications/Stata/StataMP.app 62 | return str(next(path.glob("Stata*.app"))) 63 | except StopIteration: 64 | return '' 65 | 66 | # %% ../nbs/01_config.ipynb 12 67 | def _other_find_path(): 68 | for i in ['stata-mp', 'stata-se', 'stata']: 69 | stata_path = which(i) 70 | if stata_path: 71 | return stata_path 72 | return '' 73 | 74 | # %% ../nbs/01_config.ipynb 14 75 | def _find_path(_dir=None): 76 | if os.getenv('CONTINUOUS_INTEGRATION'): 77 | print('WARNING: Running as CI; Stata path not set correctly') 78 | return 'stata' 79 | path = '' 80 | if platform.system() == 'Windows': 81 | path = _win_find_path(_dir) 82 | elif platform.system() == 'Darwin': 83 | path = _mac_find_path(_dir) 84 | return path if path else _other_find_path() 85 | 86 | # %% ../nbs/01_config.ipynb 16 87 | def _edition(stata_exe): 88 | edition = 'be' 89 | for e in ('be', 'se', 'mp'): 90 | if stata_exe.find(e) > -1: 91 | edition = e 92 | break 93 | return edition 94 | 95 | # %% ../nbs/01_config.ipynb 18 96 | def find_dir_edition(stata_path=None): 97 | if stata_path is None: 98 | stata_path = _find_path() 99 | if not stata_path: 100 | raise OSError("Stata path not found.") 101 | stata_dir = str(os.path.dirname(stata_path)) 102 | stata_exe = str(os.path.basename(stata_path)).lower() 103 | return stata_dir, _edition(stata_exe) 104 | 105 | # %% ../nbs/01_config.ipynb 20 106 | def find_edition(stata_dir): 107 | stata_path = _find_path(stata_dir) 108 | stata_exe = str(os.path.basename(stata_path)).lower() 109 | return _edition(stata_exe) 110 | 111 | # %% ../nbs/01_config.ipynb 25 112 | def set_pystata_path(stata_dir=None): 113 | if stata_dir is None: 114 | stata_dir, _ = find_dir_edition() 115 | if not os.path.isdir(stata_dir): 116 | raise OSError(f'Specified stata_dir, "{stata_dir}", is not a valid directory path') 117 | if not os.path.isdir(os.path.join(stata_dir, 'utilities')): 118 | raise OSError(f'Specified stata_dir, "{stata_dir}", is not Stata\'s installation path') 119 | sys.path.append(os.path.join(stata_dir, 'utilities')) 120 | 121 | # %% ../nbs/01_config.ipynb 29 122 | def launch_stata(stata_dir=None, edition=None, splash=True): 123 | """ 124 | We modify stata_setup to make splash screen optional 125 | """ 126 | if stata_dir is None: 127 | stata_dir, edition_found = find_dir_edition() 128 | edition = edition_found if edition is None else edition 129 | elif edition is None: 130 | edition = find_edition(stata_dir) 131 | set_pystata_path(stata_dir) 132 | import pystata 133 | try: 134 | if version.parse(pystata.__version__) >= version.parse("0.1.1"): 135 | # Splash message control is a new feature of pystata-0.1.1 136 | pystata.config.init(edition, splash=splash) 137 | else: 138 | pystata.config.init(edition) 139 | except FileNotFoundError as err: 140 | raise OSError(f'Specified edition, "{edition}", is not present at "{stata_dir}"') 141 | 142 | # %% ../nbs/01_config.ipynb 35 143 | def set_graph_format(gformat): 144 | import pystata 145 | if gformat == 'pystata': 146 | gformat = 'svg' # pystata default 147 | pystata.config.set_graph_format(gformat) 148 | 149 | # %% ../nbs/01_config.ipynb 37 150 | def _set_graph_size(width, height): 151 | import pystata 152 | pystata.config.set_graph_size(width, height) 153 | 154 | # %% ../nbs/01_config.ipynb 41 155 | def _get_config_settings(cpath): 156 | parser = configparser.ConfigParser( 157 | empty_lines_in_values=False, 158 | comment_prefixes=('*','//'), 159 | inline_comment_prefixes=('//',), 160 | ) 161 | parser.read(str(cpath)) 162 | return dict(parser.items('nbstata')) 163 | 164 | # %% ../nbs/01_config.ipynb 42 165 | def xdg_user_config_path(): 166 | xdg_config_home = Path(os.environ.get('XDG_CONFIG_HOME', Path.home() / '.config')) 167 | return xdg_config_home / 'nbstata/nbstata.conf' 168 | 169 | def old_user_config_path(): 170 | return Path('~/.nbstata.conf').expanduser() 171 | 172 | # %% ../nbs/01_config.ipynb 45 173 | class Config: 174 | "nbstata configuration" 175 | env = {'stata_dir': None, 176 | 'edition': None, 177 | 'splash': 'False', 178 | 'graph_format': 'png', 179 | 'graph_width': '5.5in', 180 | 'graph_height': '4in', 181 | 'echo': 'None', 182 | 'missing': '.', 183 | 'browse_auto_height': 'True', 184 | } 185 | valid_values_of = dict( 186 | edition={None, 'mp', 'se', 'be'}, 187 | graph_format={'pystata', 'svg', 'png', 'pdf'}, 188 | echo={'True', 'False', 'None'}, 189 | splash={'True', 'False'}, 190 | browse_auto_height={'True', 'False'}, 191 | ) 192 | 193 | @property 194 | def splash(self): 195 | return False if self.env['splash'] == 'False' else True 196 | 197 | @property 198 | def browse_auto_height(self): 199 | return False if self.env['browse_auto_height'] == 'False' else True 200 | 201 | @property 202 | def noecho(self): 203 | return self.env['echo'] == 'None' 204 | 205 | @property 206 | def echo(self): 207 | return self.env['echo'] == 'True' 208 | 209 | def display_status(self): 210 | import pystata 211 | pystata.config.status() 212 | print(f""" 213 | echo {self.env['echo']} 214 | missing {self.env['missing']} 215 | browse_auto_height {self.env['browse_auto_height']} 216 | config file path {self.config_path}""") 217 | 218 | def __init__(self): 219 | """First check if a configuration file exists. If not, try `find_dir_edition`.""" 220 | self.errors = [] 221 | self._update_backup_graph_size() 222 | self.config_path = None 223 | 224 | def _update_backup_graph_size(self): 225 | self.backup_graph_size = {key: self.env[key] for key in {'graph_width', 'graph_height'}} 226 | 227 | def process_config_file(self): 228 | global_config_path = Path(os.path.join(sys.prefix, 'etc', 'nbstata.conf')) 229 | for cpath in (xdg_user_config_path(), old_user_config_path(), global_config_path): 230 | if cpath.is_file(): 231 | self._get_config_env(cpath) 232 | break 233 | 234 | def _get_config_env(self, cpath): 235 | try: 236 | settings = _get_config_settings(cpath) 237 | except configparser.Error as err: 238 | print_red(f"Configuration error in {cpath}:\n" 239 | f" {str(err)}") 240 | else: 241 | self.config_path = str(cpath) 242 | self.update( 243 | settings, 244 | init=True, 245 | error_header=f"Configuration errors in {self.config_path}:" 246 | ) 247 | 248 | def update(self, env, init=False, error_header="%set error(s):"): 249 | init_only_settings = {'stata_dir','edition','splash'} 250 | allowed_settings = self.env if init else set(self.env)-init_only_settings 251 | for key in list(env): 252 | if key not in allowed_settings: 253 | explanation = ( 254 | "is only allowed in a configuration file." if key in init_only_settings 255 | else "is not a valid setting." 256 | ) 257 | self.errors.append(f" '{key}' {explanation}") 258 | env.pop(key) 259 | elif key in self.valid_values_of and env[key] not in self.valid_values_of[key]: 260 | self.errors.append( 261 | f" '{key}' configuration invalid: '{env[key]}' is not a valid value. " 262 | f"Reverting to: {key} = {self.env[key]}" 263 | ) 264 | env.pop(key) 265 | self._display_and_clear_update_errors(error_header) 266 | for key in set(env)-{'graph_width', 'graph_height'}: 267 | if not init: print(f"{key} was {self.env[key]}, is now {env[key]}") 268 | self.env.update(env) 269 | 270 | def _display_and_clear_update_errors(self, error_header): 271 | if self.errors: 272 | print_red(error_header) 273 | for message in self.errors: 274 | print_red(message) 275 | self.errors = [] 276 | 277 | # %% ../nbs/01_config.ipynb 53 278 | @patch_to(Config) 279 | def set_graph_size(self, init=False): 280 | try: 281 | _set_graph_size(self.env['graph_width'], self.env['graph_height']) 282 | except ValueError as err: 283 | self.env.update(self.backup_graph_size) 284 | print_red(f"Configuration error: {str(err)}. Graph size not changed.") 285 | if init: self.set_graph_size() # ensures set to definite measures rather than "default" 286 | else: 287 | if {key: self.env[key] for key in {'graph_width', 'graph_height'}} != self.backup_graph_size: 288 | if not init: 289 | print(f"graph size was ({self.backup_graph_size['graph_width']}, " 290 | f"{self.backup_graph_size['graph_height']}), " 291 | f"is now ({self.env['graph_width']}, {self.env['graph_height']}).") 292 | self._update_backup_graph_size() 293 | 294 | # %% ../nbs/01_config.ipynb 59 295 | @patch_to(Config) 296 | def update_graph_config(self, init=False): 297 | graph_format = self.env['graph_format'] 298 | if graph_format == 'pystata': 299 | graph_format = 'svg' 300 | set_graph_format(graph_format) 301 | self.set_graph_size(init) 302 | 303 | # %% ../nbs/01_config.ipynb 61 304 | @patch_to(Config) 305 | def init_stata(self): 306 | launch_stata(self.env['stata_dir'], 307 | self.env['edition'], 308 | self.splash, 309 | ) 310 | self.update_graph_config(init=True) 311 | -------------------------------------------------------------------------------- /nbstata/css/_StataKernelHelpDefault.css: -------------------------------------------------------------------------------- 1 | h2 { 2 | font-family: Arial,Helvetica,Helv,sans-serif; 3 | color: #000000; 4 | } 5 | 6 | pre { 7 | margin: 10px; 8 | } 9 | 10 | table { 11 | background-color: transparent; 12 | border-color: transparent; 13 | bgcolor: transparent; 14 | } 15 | 16 | tr { 17 | background-color: transparent; 18 | border-color: transparent; 19 | bgcolor: transparent; 20 | } 21 | 22 | body { 23 | background-color: transparent; 24 | border-color: transparent; 25 | margin: 0px; 26 | } 27 | 28 | div { 29 | background-color: transparent; 30 | border-color: transparent; 31 | } 32 | -------------------------------------------------------------------------------- /nbstata/inspect.py: -------------------------------------------------------------------------------- 1 | """Provides output for kernel.do_inspect()""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/12_inspect.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['get_inspect'] 7 | 8 | # %% ../nbs/12_inspect.ipynb 3 9 | from .stata_more import run_as_program, run_sfi, diverted_stata_output 10 | import functools 11 | 12 | # %% ../nbs/12_inspect.ipynb 6 13 | def get_inspect(code="", cursor_pos=0, detail_level=0, omit_sections=()): 14 | runner = functools.partial(run_as_program, prog_def_option_code="rclass") 15 | inspect_code = """ 16 | disp _newline "*** Stored results:" 17 | return list 18 | ereturn list 19 | return add 20 | display "*** Last updated `c(current_time)' `c(current_date)' ***" 21 | describe, fullnames 22 | """ 23 | raw_output = diverted_stata_output(inspect_code, runner=runner) 24 | desc_start = raw_output.find('*** Last updated ') 25 | out = raw_output[desc_start:] 26 | if desc_start > 21: 27 | out += raw_output[:desc_start] 28 | return out 29 | -------------------------------------------------------------------------------- /nbstata/install.py: -------------------------------------------------------------------------------- 1 | """nbstata kernel install script""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/15_install.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['kernel_json', 'install_kernel_spec', 'create_conf_if_needed', 'main'] 7 | 8 | # %% ../nbs/15_install.ipynb 3 9 | import argparse 10 | import json 11 | import os 12 | import sys 13 | 14 | from jupyter_client.kernelspec import KernelSpecManager 15 | from IPython.utils.tempdir import TemporaryDirectory 16 | from importlib import resources 17 | from shutil import copyfile 18 | from pathlib import Path 19 | from textwrap import dedent 20 | from fastcore.basics import IN_NOTEBOOK 21 | 22 | # %% ../nbs/15_install.ipynb 5 23 | kernel_json = { 24 | "argv": [sys.executable, "-m", "nbstata", "-f", "{connection_file}"], 25 | "display_name": "Stata (nbstata)", 26 | "language": "stata", 27 | } 28 | 29 | def install_kernel_spec(user=True, prefix=None): 30 | with TemporaryDirectory() as td: 31 | os.chmod(td, 0o755) # Starts off as 700, not user readable 32 | with open(os.path.join(td, 'kernel.json'), 'w') as f: 33 | json.dump(kernel_json, f, sort_keys=True) 34 | 35 | # Copy logo to tempdir to be installed with kernelspec 36 | logo_path = resources.files('nbstata').joinpath('logo-64x64.png') 37 | copyfile(logo_path, os.path.join(td, 'logo-64x64.png')) 38 | 39 | print('Installing Jupyter kernel spec') 40 | KernelSpecManager().install_kernel_spec(td, 'nbstata', user=user, prefix=prefix) 41 | 42 | # %% ../nbs/15_install.ipynb 6 43 | def _find_stata(): 44 | # By avoiding an import of .config until we need it, we can 45 | # complete the installation process in virtual environments 46 | # without needing this submodule nor its downstream imports. 47 | from nbstata.config import find_dir_edition 48 | try: 49 | stata_dir, stata_ed = find_dir_edition() 50 | except OSError as err: 51 | stata_dir, stata_ed = "", "" 52 | return stata_dir, stata_ed 53 | 54 | # %% ../nbs/15_install.ipynb 7 55 | def _conf_default(stata_dir='', stata_ed=''): 56 | from nbstata.config import Config 57 | conf_default = dedent( 58 | f"""\ 59 | [nbstata] 60 | stata_dir = {stata_dir} 61 | edition = {stata_ed} 62 | """ 63 | ) 64 | for key in Config.env.keys(): 65 | if key not in {'stata_dir', 'edition'}: 66 | conf_default += f"{key} = {Config.env[key]}\n" 67 | return conf_default 68 | 69 | # %% ../nbs/15_install.ipynb 8 70 | def create_conf_if_needed(conf_path, conf_file_requested=True): 71 | """Create config file if requested or if Stata not found automatically""" 72 | if conf_path.is_file(): 73 | if conf_file_requested: 74 | print("Configuration file already exists at:") 75 | print(str(conf_path)) 76 | return 77 | stata_dir, stata_ed = _find_stata() 78 | if not stata_ed: 79 | msg = """\ 80 | WARNING: Could not find Stata path. 81 | Please specify it manually in configuration file. 82 | """ 83 | print(dedent(msg)) 84 | if stata_ed and not conf_file_requested: 85 | return 86 | try: 87 | conf_dir = Path(os.path.dirname(conf_path)) 88 | if not conf_dir.is_dir(): 89 | os.makedirs(conf_dir) 90 | with conf_path.open('w') as f: 91 | f.write(_conf_default(stata_dir, stata_ed)) 92 | print("Configuration file created at:") 93 | print(str(conf_path)) 94 | except Exception as err: 95 | print(f"Attempt to create a configuration file at {str(conf_path)} failed.") 96 | raise(err) 97 | 98 | # %% ../nbs/15_install.ipynb 10 99 | def _is_root(): 100 | try: 101 | return os.geteuid() == 0 102 | except AttributeError: 103 | return False # assume not an admin on non-Unix platforms 104 | 105 | # %% ../nbs/15_install.ipynb 12 106 | def _conf_path(user, prefix): 107 | from nbstata.config import old_user_config_path, xdg_user_config_path 108 | if user: 109 | alt_conf_path = old_user_config_path() 110 | if alt_conf_path.is_file(): 111 | return alt_conf_path 112 | else: 113 | return xdg_user_config_path() 114 | else: 115 | return Path(os.path.join(prefix, 'etc/nbstata.conf')) 116 | 117 | # %% ../nbs/15_install.ipynb 14 118 | def main(argv=None): 119 | ap = argparse.ArgumentParser() 120 | ap.add_argument('--user', action='store_true', 121 | help="Install to the per-user kernels registry. Default if not root.") 122 | ap.add_argument('--sys-prefix', action='store_true', 123 | help="Install to sys.prefix (e.g. a virtualenv or conda env)") 124 | ap.add_argument('--prefix', 125 | help="Install to the given prefix. " 126 | "Kernelspec will be installed in {PREFIX}/share/jupyter/kernels/") 127 | ap.add_argument('--conf-file', action='store_true', 128 | help="Create a configuration file.") 129 | args = ap.parse_args(argv) 130 | 131 | if args.sys_prefix: 132 | args.prefix = sys.prefix 133 | if not args.prefix and not _is_root(): 134 | args.user = True 135 | 136 | install_kernel_spec(user=args.user, prefix=args.prefix) 137 | conf_path = _conf_path(user=args.user, prefix=sys.prefix) 138 | create_conf_if_needed(conf_path, conf_file_requested=args.conf_file) 139 | 140 | # %% ../nbs/15_install.ipynb 15 141 | #|eval: false 142 | if __name__ == "__main__" and not IN_NOTEBOOK: 143 | main() 144 | -------------------------------------------------------------------------------- /nbstata/kernel.py: -------------------------------------------------------------------------------- 1 | """IPythonKernel based on pystata""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/14_kernel.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['PyStataKernel', 'print_stata_error'] 7 | 8 | # %% ../nbs/14_kernel.ipynb 4 9 | from .config import Config 10 | from .misc_utils import print_red 11 | from .stata_more import user_expression 12 | from .inspect import get_inspect 13 | from .stata_session import StataSession 14 | from .completions import CompletionsManager 15 | from .cell import Cell 16 | import nbstata # for __version__ 17 | from fastcore.basics import patch_to 18 | from ipykernel.ipkernel import IPythonKernel 19 | 20 | # %% ../nbs/14_kernel.ipynb 6 21 | class PyStataKernel(IPythonKernel): 22 | """A jupyter kernel based on pystata""" 23 | implementation = 'nbstata' 24 | implementation_version = nbstata.__version__ 25 | language_info = { 26 | 'name': 'stata', 27 | 'version': '17', 28 | 'mimetype': 'text/x-stata', 29 | 'file_extension': '.do', 30 | } 31 | banner = "nbstata: a Jupyter kernel for Stata based on pystata" 32 | help_links = [ 33 | { 34 | "text": "Stata Documentation", 35 | "url": "https://www.stata.com/features/documentation/", 36 | }, 37 | { 38 | "text": "nbstata Help", 39 | "url": "https://hugetim.github.io/nbstata/", 40 | }, 41 | ] 42 | 43 | def __init__(self, **kwargs): 44 | super().__init__(**kwargs) 45 | self.stata_ready = False 46 | self.ipydatagrid_height_set = False 47 | self.shell.execution_count = 0 48 | self.inspect_output = "Stata not yet initialized." 49 | self.nbstata_config = Config() 50 | self.stata_session = StataSession() 51 | self.completions = CompletionsManager(self.stata_session) 52 | self.inspect_output = "" 53 | 54 | # %% ../nbs/14_kernel.ipynb 8 55 | @patch_to(PyStataKernel) 56 | def init_session(self): 57 | self.nbstata_config.process_config_file() 58 | self.nbstata_config.init_stata() 59 | self.stata_ready = True 60 | 61 | # %% ../nbs/14_kernel.ipynb 9 62 | def _stata_error_reply(ename, evalue, execution_count=None): 63 | reply_content = { 64 | 'status': "error", 65 | "traceback": [], 66 | "ename": ename, 67 | "evalue": evalue, 68 | } 69 | if execution_count is not None: 70 | reply_content['execution_count'] = execution_count 71 | return reply_content 72 | 73 | # %% ../nbs/14_kernel.ipynb 10 74 | _missing_stata_message = ( 75 | "pystata path not found\n" 76 | "A Stata 17+ installation is required to use the nbstata Stata kernel. " 77 | "If you already have Stata 17+ installed, " 78 | "please specify its path in your configuration file." 79 | ) 80 | 81 | # %% ../nbs/14_kernel.ipynb 12 82 | def _handle_stata_import_error(err, silent, execution_count): 83 | if not silent: 84 | print_red(f"ModuleNotFoundError: {_missing_stata_message}") 85 | return _stata_error_reply( 86 | ename = "ModuleNotFoundError", 87 | evalue = _missing_stata_message, 88 | execution_count = execution_count, 89 | ) 90 | 91 | # %% ../nbs/14_kernel.ipynb 13 92 | def _handle_stata_init_error(err, silent, execution_count): 93 | reply_content = _stata_error_reply( 94 | ename = "Stata init error", 95 | evalue = str(err), 96 | execution_count = execution_count, 97 | ) 98 | if not silent: 99 | print_red(reply_content['evalue']) 100 | return reply_content 101 | 102 | # %% ../nbs/14_kernel.ipynb 17 103 | def print_stata_error(text): 104 | lines = text.splitlines() 105 | if len(lines) >= 2 and lines[-2] == lines[-1]: 106 | lines.pop(-1) # remove duplicate error code glitch in pystata.stata.run multi-line (ex. below) 107 | if len(lines) > 2: 108 | print("\n".join(lines[:-2])) 109 | print_red("\n".join(lines[-2:])) 110 | 111 | # %% ../nbs/14_kernel.ipynb 23 112 | def _handle_stata_error(err, silent=False, execution_count=None): 113 | reply_content = _stata_error_reply( 114 | ename = "Stata error", 115 | evalue = str(err), 116 | execution_count = execution_count, 117 | ) 118 | if not silent: 119 | print_stata_error(reply_content['evalue']) 120 | return reply_content 121 | 122 | # %% ../nbs/14_kernel.ipynb 24 123 | def _format_user_obj(user_expr_output): 124 | return dict( 125 | status='ok', 126 | data={'text/plain': user_expr_output}, 127 | metadata={}, 128 | ) 129 | 130 | # %% ../nbs/14_kernel.ipynb 25 131 | def _user_expressions(expressions): 132 | results = {} 133 | for key, expr in expressions.items(): 134 | try: 135 | value = _format_user_obj(user_expression(expr)) 136 | except Exception as err: 137 | value = _stata_error_reply( 138 | ename = "Stata user expression error", 139 | evalue = str(err) 140 | ) 141 | print_red(value['evalue']) 142 | results[key] = value 143 | return results 144 | 145 | # %% ../nbs/14_kernel.ipynb 29 146 | @patch_to(PyStataKernel) 147 | def post_do_hook(self): 148 | self.inspect_output = "" 149 | 150 | # %% ../nbs/14_kernel.ipynb 30 151 | @patch_to(PyStataKernel) 152 | def do_execute(self, code, silent, 153 | store_history=True, user_expressions=None, allow_stdin=False): 154 | """Execute Stata code cell""" 155 | if not self.stata_ready: 156 | try: 157 | self.init_session() # do this here so config error messages displayed in notebook 158 | except OSError as err: 159 | return _handle_stata_init_error(err, silent, self.execution_count) 160 | except ModuleNotFoundError as err: # this should almost always be preempted by OSErrors now 161 | return _handle_stata_import_error(err, silent, self.execution_count) 162 | 163 | self.shell.execution_count += 1 164 | code_cell = Cell(self, code, silent) 165 | try: 166 | code_cell.run() 167 | except SystemError as err: 168 | return _handle_stata_error(err, silent, self.execution_count) 169 | self.post_do_hook() 170 | return { 171 | 'status': "ok", 172 | 'execution_count': self.execution_count, 173 | 'payload': [], 174 | 'user_expressions': _user_expressions(user_expressions or {}), 175 | } 176 | 177 | # %% ../nbs/14_kernel.ipynb 32 178 | @patch_to(PyStataKernel) 179 | def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): 180 | """Display Stata 'describe' output (regardless of cursor position)""" 181 | if self.stata_ready: 182 | if not self.inspect_output: 183 | self.inspect_output = get_inspect(code, cursor_pos, detail_level, omit_sections) 184 | data = {'text/plain': self.inspect_output} 185 | else: 186 | data = {} 187 | return {"status": "ok", "data": data, "metadata": {}, "found": True} 188 | 189 | # %% ../nbs/14_kernel.ipynb 33 190 | @patch_to(PyStataKernel) 191 | def do_complete(self, code, cursor_pos): 192 | """Provide context-aware tab-autocomplete suggestions""" 193 | if self.stata_ready: 194 | cursor_start, cursor_end, matches = self.completions.do( 195 | code, 196 | cursor_pos, 197 | ) 198 | else: 199 | cursor_start = cursor_end = cursor_pos 200 | matches = [] 201 | return { 202 | 'status': "ok", 203 | 'cursor_start': cursor_start, 204 | 'cursor_end': cursor_end, 205 | 'metadata': {}, 206 | 'matches': matches, 207 | } 208 | 209 | # %% ../nbs/14_kernel.ipynb 34 210 | @patch_to(PyStataKernel) 211 | def do_is_complete(self, code): 212 | """Overrides IPythonKernel with kernelbase default""" 213 | return {"status": "unknown"} 214 | 215 | # %% ../nbs/14_kernel.ipynb 35 216 | @patch_to(PyStataKernel) 217 | def do_history( 218 | self, 219 | hist_access_type, 220 | output, 221 | raw, 222 | session=None, 223 | start=None, 224 | stop=None, 225 | n=None, 226 | pattern=None, 227 | unique=False, 228 | ): 229 | """Overrides IPythonKernel with kernelbase default""" 230 | return {"status": "ok", "history": []} 231 | -------------------------------------------------------------------------------- /nbstata/logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugetim/nbstata/a22d0a111cdf76d3452b779941d56cb5d094fd27/nbstata/logo-64x64.png -------------------------------------------------------------------------------- /nbstata/magics.py: -------------------------------------------------------------------------------- 1 | """IPython magics for nbstata""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/09_magics.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['print_kernel', 'StataMagics', 'Frame'] 7 | 8 | # %% ../nbs/09_magics.ipynb 3 9 | from .config import Config 10 | from .misc_utils import print_red 11 | from .stata import obs_count, macro_expand, get_global 12 | from .stata_session import warn_re_unclosed_comment_block_if_needed 13 | import nbstata.browse as browse 14 | from fastcore.basics import patch_to 15 | import re 16 | import urllib 17 | from pkg_resources import resource_filename 18 | from bs4 import BeautifulSoup as bs 19 | import configparser 20 | 21 | # %% ../nbs/09_magics.ipynb 4 22 | def print_kernel(msg, kernel): 23 | msg = re.sub(r'$', r'\r\n', msg, flags=re.MULTILINE) 24 | msg = re.sub(r'[\r\n]{1,2}[\r\n]{1,2}', r'\r\n', msg, flags=re.MULTILINE) 25 | stream_content = {'text': msg, 'name': 'stdout'} 26 | kernel.send_response(kernel.iopub_socket, 'stream', stream_content) 27 | 28 | # %% ../nbs/09_magics.ipynb 5 29 | def _construct_abbrev_dict(): 30 | def _all_abbrevs(full_command, shortest_abbrev): 31 | for j in range(len(shortest_abbrev), len(full_command)): 32 | yield full_command[0:j] 33 | out = {} 34 | abbrev_list = [ 35 | ('browse', 'br'), 36 | ('frbrowse', 'frbr'), 37 | ('help', 'h'), 38 | ('quietly', 'q'), 39 | ] 40 | for full_command, shortest_abbrev in abbrev_list: 41 | out.update( 42 | {abbrev: full_command 43 | for abbrev in _all_abbrevs(full_command, shortest_abbrev)} 44 | ) 45 | return out 46 | 47 | # %% ../nbs/09_magics.ipynb 7 48 | class StataMagics(): 49 | """Class for handling magics""" 50 | magic_regex = re.compile( 51 | r'\A\*?(?P%%?\w+?)(?P[\s,]+.*?)?\Z', flags=re.DOTALL + re.MULTILINE) 52 | 53 | # Format: magic_name: help_content 54 | available_magics = { 55 | '%browse': '{} [-h] [varlist] [if] [in] [, nolabel noformat]', 56 | '%head': '{} [-h] [N] [varlist] [if] [, nolabel noformat]', 57 | '%tail': '{} [-h] [N] [varlist] [if] [, nolabel noformat]', 58 | '%frbrowse': '{} [-h] framename: [varlist] [if] [in] [, nolabel noformat]', 59 | '%frhead': '{} [-h] framename: [N] [varlist] [if] [, nolabel noformat]', 60 | '%frtail': '{} [-h] framename: [N] [varlist] [if] [, nolabel noformat]', 61 | '%locals': '', 62 | '%delimit': '', 63 | '%help': '{} [-h] command_or_topic_name', 64 | '%set': '{} [-h] key = value', 65 | '%%set': '{} [-h]\nkey1 = value1\n[key2 = value2]\n[...]', 66 | '%status': '', 67 | '%%quietly': '', 68 | '%%noecho': '', 69 | '%%echo': '', 70 | } 71 | 72 | abbrev_dict = _construct_abbrev_dict() 73 | 74 | csshelp_default = resource_filename( 75 | 'nbstata', 'css/_StataKernelHelpDefault.css' 76 | ) 77 | 78 | def magic_quietly(self, code, kernel, cell): 79 | """Suppress all display for the current cell.""" 80 | cell.quietly = True 81 | return code 82 | 83 | def magic_noecho(self, code, kernel, cell): 84 | """Suppress echo for the current cell.""" 85 | cell.noecho = True 86 | cell.echo = False 87 | return code 88 | 89 | def magic_echo(self, code, kernel, cell): 90 | """Suppress echo for the current cell.""" 91 | cell.noecho = False 92 | cell.echo = True 93 | return code 94 | 95 | def magic_delimit(self, code, kernel, cell): 96 | delim = ';' if kernel.stata_session.sc_delimiter else 'cr' 97 | print_kernel(f'Current Stata command delimiter: {delim}', kernel) 98 | return '' 99 | 100 | def magic_status(self, code, kernel, cell): 101 | kernel.nbstata_config.display_status() 102 | return '' 103 | 104 | # %% ../nbs/09_magics.ipynb 8 105 | @patch_to(StataMagics) 106 | def _unabbrev_magic_name(self, raw_name): 107 | last_percent = raw_name.rfind('%') 108 | percent_part = raw_name[:last_percent+1] 109 | raw_name_part = raw_name[last_percent+1:] 110 | if raw_name_part in self.abbrev_dict: 111 | name_part = self.abbrev_dict[raw_name_part] 112 | else: 113 | name_part = raw_name_part 114 | return percent_part + name_part 115 | 116 | # %% ../nbs/09_magics.ipynb 11 117 | def _parse_magic_name_code(match): 118 | v = match.groupdict() 119 | for k in v: 120 | v[k] = v[k] if v[k] is not None else '' 121 | name = v['magic'].strip() 122 | code = v['code'].strip() 123 | return name, code 124 | 125 | # %% ../nbs/09_magics.ipynb 12 126 | @patch_to(StataMagics) 127 | def _parse_code_for_magic(self, code): 128 | match = self.magic_regex.match(code.strip()) 129 | if match: 130 | raw_name, mcode = _parse_magic_name_code(match) 131 | name = self._unabbrev_magic_name(raw_name) 132 | if name in {'%quietly', '%noecho', '%echo'}: 133 | print_red( 134 | f"Warning: The correct syntax for a cell magic is '%{name}', not '{name}'. " 135 | "In v1.0, nbstata may trigger an error instead of just a warning." 136 | ) 137 | name = '%' + name 138 | elif name == "%set" and len(code.splitlines()) > 1: 139 | print_red( 140 | f"Warning: The correct syntax for the multi-line 'set' magic is '%{name}', not '{name}'. " 141 | "In v1.0, nbstata may trigger an error instead of just a warning." 142 | ) 143 | name = '%' + name 144 | elif name == "%%set" and len(code.splitlines()) == 1: 145 | print_red( 146 | f"Warning: The correct syntax for the single-line 'set' magic is '%set', not '{name}'. " 147 | "In v1.0, nbstata may trigger an error instead of just a warning." 148 | ) 149 | name = '%set' 150 | elif name not in self.available_magics: 151 | raise ValueError(f"Unknown magic {name}.") 152 | return name, mcode 153 | else: 154 | return None, code 155 | 156 | # %% ../nbs/09_magics.ipynb 19 157 | @patch_to(StataMagics) 158 | def _do_magic(self, name, code, kernel, cell): 159 | if code.startswith('-h') or code.startswith('--help') or (name == "%help" and (not code or code.isspace())): 160 | print_kernel(self.available_magics[name].format(name), kernel) 161 | return '' 162 | else: 163 | return getattr(self, "magic_" + name.lstrip('%'))(code, kernel, cell) 164 | 165 | # %% ../nbs/09_magics.ipynb 21 166 | @patch_to(StataMagics) 167 | def magic(self, code, kernel, cell): 168 | try: 169 | if kernel.nbstata_config.browse_auto_height and not kernel.ipydatagrid_height_set: 170 | browse.set_ipydatagrid_height() 171 | kernel.ipydatagrid_height_set = True 172 | name, code = self._parse_code_for_magic(code) 173 | except ValueError as e: 174 | print_kernel(str(e), kernel) 175 | else: 176 | if name: 177 | code = self._do_magic(name, code, kernel, cell) 178 | return code 179 | 180 | # %% ../nbs/09_magics.ipynb 22 181 | def _formatted_local_list(local_dict): 182 | std_len = 14 183 | str_reps = [] 184 | for n in local_dict: 185 | if len(n) <= std_len: 186 | str_reps.append(f"{n}:{' '*(std_len-len(n))} {local_dict[n]}") 187 | else: 188 | str_reps.append(f"{n}:\n{' '*std_len} {local_dict[n]}") 189 | return "\n".join(str_reps) 190 | 191 | # %% ../nbs/09_magics.ipynb 25 192 | @patch_to(StataMagics) 193 | def magic_locals(self, code, kernel, cell): 194 | local_dict = kernel.stata_session.get_local_dict() 195 | print_kernel(_formatted_local_list(local_dict), kernel) 196 | return '' 197 | 198 | # %% ../nbs/09_magics.ipynb 27 199 | def _get_new_settings(code): 200 | parser = configparser.ConfigParser( 201 | empty_lines_in_values=False, 202 | comment_prefixes=('*','//', '/*'), # '/*': to not cause error when commenting out for Stata purposes only 203 | inline_comment_prefixes=('//',), 204 | ) 205 | parser.read_string("[set]\n" + code.strip(), source="set") 206 | return dict(parser.items('set')) 207 | 208 | # %% ../nbs/09_magics.ipynb 31 209 | def _clean_error_message(err_str): 210 | return (err_str 211 | .replace("While reading from 'set'", "") 212 | .replace(" in section 'set' already exists", " already set above") 213 | .replace("Source contains ", "") 214 | .replace(" 'set'\n", "\n") 215 | ) 216 | 217 | # %% ../nbs/09_magics.ipynb 32 218 | def _process_new_settings(settings, kernel): 219 | kernel.nbstata_config.update(settings) 220 | kernel.nbstata_config.update_graph_config() 221 | 222 | # %% ../nbs/09_magics.ipynb 33 223 | @patch_to(StataMagics) 224 | def magic_set(self, code, kernel, cell): 225 | try: 226 | settings = _get_new_settings(code) 227 | except configparser.Error as err: 228 | print_red(f"set error:\n {_clean_error_message(str(err))}") 229 | else: 230 | _process_new_settings(settings, kernel) 231 | warn_re_unclosed_comment_block_if_needed(code) 232 | 233 | # %% ../nbs/09_magics.ipynb 38 234 | @patch_to(StataMagics) 235 | def magic_browse(self, code, kernel, cell): 236 | """Display data interactively.""" 237 | try: 238 | expanded_code = macro_expand(code) 239 | params = browse.browse_df_params( 240 | expanded_code, obs_count(), kernel.nbstata_config.env['missing'], 241 | ) 242 | sformat = params[-1] 243 | df = browse.get_df(*params) 244 | browse.display_df_as_ipydatagrid(df, kernel.nbstata_config.browse_auto_height) 245 | except Exception as e: 246 | print_kernel(f"browse failed.\r\n{e}", kernel) 247 | return '' 248 | 249 | # %% ../nbs/09_magics.ipynb 39 250 | class Frame(): 251 | """Class for generating Stata select_var for getAsDict""" 252 | def __init__(self, framename): 253 | self.original_framename = get_global('c(frame)') 254 | self.framename = framename 255 | 256 | def __enter__(self): 257 | import sfi 258 | try: 259 | frame = sfi.Frame.connect(self.framename) 260 | except sfi.FrameError: 261 | raise ValueError(f"frame {self.framename} not found") 262 | else: 263 | frame.changeToCWF() 264 | 265 | def __exit__(self, exc_type, exc_value, exc_tb): 266 | import sfi 267 | orig_frame = sfi.Frame.connect(self.original_framename) 268 | orig_frame.changeToCWF() 269 | 270 | # %% ../nbs/09_magics.ipynb 40 271 | def _parse_frame_prefix(code): 272 | pattern = re.compile( 273 | r'\A(?P\w+)[ \t]*(?:\:[ \t]*(?P.*?))?\Z', flags=re.DOTALL) 274 | match = pattern.match(code) 275 | if not match: 276 | raise ValueError("invalid syntax: missing framename or colon?") 277 | v = match.groupdict() 278 | for k in v: 279 | v[k] = v[k] if v[k] is not None else '' 280 | framename = v['frame'].strip() 281 | main_code = v['code'].strip() 282 | return framename, main_code 283 | 284 | # %% ../nbs/09_magics.ipynb 42 285 | @patch_to(StataMagics) 286 | def magic_frbrowse(self, code, kernel, cell): 287 | """Display frame interactively.""" 288 | try: 289 | framename, main_code = _parse_frame_prefix(code) 290 | with Frame(framename): 291 | self.magic_browse(main_code, kernel, cell) 292 | except Exception as e: 293 | print_kernel(f"frbrowse failed.\r\n{e}", kernel) 294 | return '' 295 | 296 | # %% ../nbs/09_magics.ipynb 46 297 | def _get_html_data(df): 298 | html = df.convert_dtypes().to_html(notebook=True) 299 | return {'text/html': html} 300 | 301 | # %% ../nbs/09_magics.ipynb 47 302 | @patch_to(StataMagics) 303 | def _headtail_html(self, df, kernel): 304 | content = { 305 | 'data': _get_html_data(df), 306 | 'metadata': {}, 307 | } 308 | kernel.send_response(kernel.iopub_socket, 'display_data', content) 309 | 310 | # %% ../nbs/09_magics.ipynb 48 311 | @patch_to(StataMagics) 312 | def _magic_headtail(self, code, kernel, cell, tail=False): 313 | try: 314 | expanded_code = macro_expand(code) 315 | df = browse.headtail_get_df(*browse.headtail_df_params( 316 | expanded_code, obs_count(), kernel.nbstata_config.env['missing'], tail=tail 317 | )) 318 | self._headtail_html(df, kernel) 319 | except Exception as e: 320 | print_kernel(f"{'tail' if tail else 'head'} failed.\r\n{e}", kernel) 321 | return '' 322 | 323 | # %% ../nbs/09_magics.ipynb 49 324 | @patch_to(StataMagics) 325 | def magic_head(self, code, kernel, cell): 326 | """Display data in a nicely-formatted table.""" 327 | return self._magic_headtail(code, kernel, cell, tail=False) 328 | 329 | # %% ../nbs/09_magics.ipynb 50 330 | @patch_to(StataMagics) 331 | def magic_frhead(self, code, kernel, cell): 332 | """Display data in a nicely-formatted table.""" 333 | return self._magic_frheadtail(code, kernel, cell, tail=False) 334 | 335 | # %% ../nbs/09_magics.ipynb 51 336 | @patch_to(StataMagics) 337 | def magic_tail(self, code, kernel, cell): 338 | """Display data in a nicely-formatted table.""" 339 | return self._magic_headtail(code, kernel, cell, tail=True) 340 | 341 | # %% ../nbs/09_magics.ipynb 52 342 | @patch_to(StataMagics) 343 | def magic_frtail(self, code, kernel, cell): 344 | """Display data in a nicely-formatted table.""" 345 | return self._magic_frheadtail(code, kernel, cell, tail=True) 346 | 347 | # %% ../nbs/09_magics.ipynb 53 348 | @patch_to(StataMagics) 349 | def _magic_frheadtail(self, code, kernel, cell, tail): 350 | """Display frame interactively.""" 351 | try: 352 | framename, main_code = _parse_frame_prefix(code) 353 | with Frame(framename): 354 | self._magic_headtail(main_code, kernel, cell, tail) 355 | except Exception as e: 356 | print_kernel(f"{'tail' if tail else 'head'} failed.\r\n{e}", kernel) 357 | return '' 358 | 359 | # %% ../nbs/09_magics.ipynb 55 360 | @patch_to(StataMagics) 361 | def _get_help_html(self, code): 362 | html_base = "https://www.stata.com" 363 | html_help = urllib.parse.urljoin(html_base, "help.cgi?{}") 364 | url_safe_code = urllib.parse.quote(code) 365 | headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"} 366 | request = urllib.request.Request(html_help.format(url_safe_code), headers=headers) 367 | reply = urllib.request.urlopen(request) 368 | html = reply.read().decode("utf-8") 369 | 370 | # Remove excessive extra lines (Note css: "white-space: pre-wrap") 371 | edited_html = html.replace("

\n", "

") 372 | soup = bs(edited_html, 'html.parser') 373 | 374 | # Set root for links to https://www.stata.com 375 | for a in soup.find_all('a', href=True): 376 | href = a.get('href') 377 | match = re.search(r'{}(.*?)#'.format(code), href) 378 | if match: 379 | hrelative = href.find('#') 380 | a['href'] = href[hrelative:] 381 | elif not href.startswith('http'): 382 | link = a['href'] 383 | match = re.search(r'/help.cgi\?(.+)$', link) 384 | # URL encode bad characters like % 385 | if match: 386 | link = '/help.cgi?' 387 | link += urllib.parse.quote_plus(match.group(1)) 388 | a['href'] = urllib.parse.urljoin(html_base, link) 389 | a['target'] = '_blank' 390 | 391 | # Remove header 'Stata 15 help for ...' 392 | stata_header = soup.find('h2') 393 | if stata_header: 394 | stata_header.decompose() 395 | 396 | # Remove Stata help menu 397 | soup.find('div', id='menu').decompose() 398 | 399 | # Remove Copyright notice 400 | copyright = soup.find(string=re.compile(r".*Copyright.*", flags=re.DOTALL)) 401 | copyright.find_parent("table").decompose() 402 | 403 | # Remove last hrule 404 | soup.find_all('hr')[-1].decompose() 405 | 406 | # Remove last br 407 | soup.find_all('br')[-1].decompose() 408 | 409 | # Remove last empty paragraph, empty space 410 | empty_paragraphs = soup.find_all('p', string="") 411 | if str(empty_paragraphs[-1]) == "

": 412 | empty_paragraphs[-1].decompose() 413 | 414 | # Set all the backgrounds to transparent 415 | for color in ['#ffffff', '#FFFFFF']: 416 | for bg in ['bgcolor', 'background', 'background-color']: 417 | for tag in soup.find_all(attrs={bg: color}): 418 | if tag.get(bg): 419 | tag[bg] = 'transparent' 420 | 421 | # Set html 422 | css = soup.find('style', {'type': 'text/css'}) 423 | with open(self.csshelp_default, 'r') as default: 424 | css.string = default.read() 425 | 426 | return str(soup) 427 | 428 | # %% ../nbs/09_magics.ipynb 56 429 | @patch_to(StataMagics) 430 | def magic_help(self, code, kernel, cell): 431 | """Show help file from stata.com/help.cgi?\{\}""" 432 | try: 433 | html_help = self._get_help_html(code) 434 | except Exception as e: # original: (urllib.error.HTTPError, urllib.error.URLError) 435 | msg = "Failed to fetch HTML help.\r\n{0}" 436 | print_kernel(msg.format(e), kernel) 437 | else: 438 | fallback = 'This front-end cannot display HTML help.' 439 | resp = { 440 | 'data': { 441 | 'text/html': html_help, 442 | 'text/plain': fallback}, 443 | 'metadata': {}} 444 | kernel.send_response(kernel.iopub_socket, 'display_data', resp) 445 | return '' 446 | -------------------------------------------------------------------------------- /nbstata/misc_utils.py: -------------------------------------------------------------------------------- 1 | """General helper functions with no Jupyter or pystata dependence""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_misc_utils.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['Timer', 'print_red'] 7 | 8 | # %% ../nbs/00_misc_utils.ipynb 3 9 | import time 10 | 11 | # %% ../nbs/00_misc_utils.ipynb 4 12 | class Timer(): 13 | text = "Elapsed time: {:0.4f} seconds" 14 | logger = print 15 | _start_time = None 16 | 17 | def start(self): 18 | self._start_time = time.perf_counter() 19 | 20 | def stop(self): 21 | elapsed_time = time.perf_counter() - self._start_time 22 | self._start_time = None 23 | if self.logger: 24 | self.logger(self.text.format(elapsed_time)) 25 | 26 | def __enter__(self): 27 | self.start() 28 | return self 29 | 30 | def __exit__(self, *exc_info): 31 | self.stop() 32 | 33 | # %% ../nbs/00_misc_utils.ipynb 6 34 | def print_red(text): 35 | print(f"\x1b[31m{text}\x1b[0m") 36 | -------------------------------------------------------------------------------- /nbstata/noecho.py: -------------------------------------------------------------------------------- 1 | """For running multi-line Stata code without echoing the commands""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/05_noecho.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['parse_sreturn', 'run_as_program_w_locals', 'run_non_prog_noecho', 'run_prog_noecho', 'run_noecho'] 7 | 8 | # %% ../nbs/05_noecho.ipynb 4 9 | from .code_utils import break_out_prog_blocks, valid_single_line_code, local_def_in, preserve_restore_in 10 | from .stata import run_direct, set_local, run_single, get_global 11 | from . import stata_more as sm 12 | from textwrap import dedent 13 | import re 14 | 15 | # %% ../nbs/05_noecho.ipynb 7 16 | def _run_as_program_w_locals_sreturned(std_code): 17 | sreturn_code = dedent("""\ 18 | 19 | mata : st_local("temp_nbstata_all_locals", invtokens(st_dir("local", "macro", "*")')) 20 | foreach lname in `temp_nbstata_all_locals' { 21 | sreturn local `lname' "``lname''" 22 | } 23 | """) 24 | store_new_locals_code = ("sreturn clear\n" 25 | + std_code 26 | + sreturn_code) 27 | sm.run_as_program(store_new_locals_code, "sclass") 28 | 29 | # %% ../nbs/05_noecho.ipynb 11 30 | parse_sreturn = re.compile( 31 | r'^\s*?(?:\ss\((?P\w+)\) : )', flags=re.MULTILINE 32 | ).findall 33 | 34 | # %% ../nbs/05_noecho.ipynb 13 35 | def _local_names_from_sreturn(sreturn_output): 36 | matches = parse_sreturn(sreturn_output) 37 | return matches 38 | 39 | # %% ../nbs/05_noecho.ipynb 15 40 | def _after_local_dict(): 41 | sreturn_output = sm.diverted_stata_output_quicker("sreturn list") 42 | _local_names = _local_names_from_sreturn(sreturn_output) 43 | return {name: get_global(f"s({name})") for name in _local_names} 44 | 45 | # %% ../nbs/05_noecho.ipynb 20 46 | def _restore_locals_and_clear_sreturn(): 47 | for lname, value in _after_local_dict().items(): 48 | set_local(lname, value) 49 | run_single("sreturn clear") 50 | 51 | # %% ../nbs/05_noecho.ipynb 22 52 | def run_as_program_w_locals(std_code, local_dict=None): 53 | if local_dict is None: 54 | local_dict = sm.get_local_dict() 55 | locals_code = sm.locals_code_from_dict(local_dict) 56 | if not local_def_in(std_code): 57 | sm.run_as_program(f"""{locals_code}\n{std_code}""") 58 | else: 59 | _run_as_program_w_locals_sreturned(f"""{locals_code}\n{std_code}""") 60 | _restore_locals_and_clear_sreturn() 61 | 62 | # %% ../nbs/05_noecho.ipynb 25 63 | def run_non_prog_noecho(std_non_prog_code, run_as_prog=run_as_program_w_locals): 64 | if len(std_non_prog_code.splitlines()) <= 1: # to keep it simple when we can 65 | run_direct(valid_single_line_code(std_non_prog_code), 66 | quietly=False, inline=True, echo=False) 67 | elif preserve_restore_in(std_non_prog_code): 68 | print("(Note: Below code run with echo to enable preserve/restore functionality.)") 69 | run_direct(std_non_prog_code, 70 | quietly=False, inline=True, echo=False) 71 | else: 72 | run_as_prog(std_non_prog_code) 73 | 74 | # %% ../nbs/05_noecho.ipynb 32 75 | def run_prog_noecho(std_prog_code): 76 | if std_prog_code.splitlines()[0] in {'mata', 'mata:'}: # b/c 'quietly' blocks mata output 77 | run_direct(std_prog_code, quietly=False, inline=True, echo=False) 78 | else: 79 | run_direct(std_prog_code, quietly=True, inline=True, echo=False) 80 | 81 | # %% ../nbs/05_noecho.ipynb 38 82 | def run_noecho(code, sc_delimiter=False, run_as_prog=run_as_program_w_locals): 83 | """After `break_out_prog_blocks`, run each prog and non-prog block noecho""" 84 | for block in break_out_prog_blocks(code, sc_delimiter): 85 | if block['is_prog']: 86 | run_prog_noecho(block['std_code']) 87 | else: 88 | run_non_prog_noecho(block['std_code'], run_as_prog=run_as_prog) 89 | -------------------------------------------------------------------------------- /nbstata/pandas.py: -------------------------------------------------------------------------------- 1 | """Stata-to-pandas utilities, used in `nbstata.browse`""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/06_pandas.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['better_dataframe_from_stata', 'better_pdataframe_from_data', 'better_pdataframe_from_frame'] 7 | 8 | # %% ../nbs/06_pandas.ipynb 3 9 | from .stata import stata_formatted 10 | from .stata_more import IndexVar 11 | 12 | # %% ../nbs/06_pandas.ipynb 6 13 | def _better_dataframe(hdl, var, obs, selectvar, valuelabel, missingval): 14 | import pandas as pd 15 | with IndexVar() as idx_var: 16 | data = hdl.getAsDict(var, obs, selectvar, valuelabel, missingval) 17 | if not data: 18 | return pd.DataFrame() 19 | 20 | if idx_var in data: 21 | idx = data.pop(idx_var) 22 | else: 23 | temp_var = [idx_var, selectvar] if selectvar else idx_var 24 | idx = hdl.getAsDict(temp_var, obs, selectvar, valuelabel, missingval).pop(idx_var) 25 | idx = pd.array(idx, dtype='int64') 26 | 27 | return pd.DataFrame(data=data, index=idx) 28 | 29 | # %% ../nbs/06_pandas.ipynb 17 30 | def _simple_dataframe_from_stata(stfr, var, valuelabel, missingval): 31 | from pystata import stata 32 | if stfr is None: 33 | df = stata.pdataframe_from_data(var=var, valuelabel=valuelabel, missingval=missingval) 34 | else: 35 | df = stata.pdataframe_from_frame(stfr, var=var, valuelabel=valuelabel, missingval=missingval) 36 | df.index += 1 37 | return df 38 | 39 | # %% ../nbs/06_pandas.ipynb 20 40 | def better_dataframe_from_stata(stfr, var, obs, selectvar, valuelabel, missingval, sformat): 41 | from numpy import nan 42 | import pandas as pd 43 | import sfi 44 | hdl = sfi.Data if stfr is None else sfi.Frame.connect(stfr) 45 | custom_index_not_needed = obs is None and not selectvar 46 | if custom_index_not_needed: 47 | df = _simple_dataframe_from_stata(stfr, var, valuelabel, missingval) 48 | else: 49 | if hdl.getObsTotal() <= 0: 50 | return pd.DataFrame() 51 | df = _better_dataframe(hdl, var, obs, selectvar, valuelabel, missingval) 52 | if sformat: 53 | for v in list(df.columns): 54 | if hdl.isVarTypeString(v) or (valuelabel and missingval==nan 55 | and not pd.api.types.is_numeric_dtype(df[v])): 56 | continue 57 | v_format = hdl.getVarFormat(v) 58 | if missingval != nan and not pd.api.types.is_numeric_dtype(df[v]): 59 | def format_value(x): 60 | return stata_formatted(x, v_format).lstrip() if type(x)!=str else x 61 | else: 62 | def format_value(x): 63 | return stata_formatted(x, v_format).lstrip() 64 | df[v] = df[v].apply(format_value) 65 | return df 66 | 67 | # %% ../nbs/06_pandas.ipynb 21 68 | def better_pdataframe_from_data(var=None, obs=None, selectvar=None, valuelabel=False, missingval=None, sformat=False): 69 | from numpy import nan 70 | if missingval is None: 71 | missingval = nan 72 | return better_dataframe_from_stata(None, var, obs, selectvar, valuelabel, missingval, sformat) 73 | 74 | # %% ../nbs/06_pandas.ipynb 22 75 | def better_pdataframe_from_frame(stfr, var=None, obs=None, selectvar=None, valuelabel=False, missingval=None, sformat=False): 76 | from numpy import nan 77 | if missingval is None: 78 | missingval = nan 79 | return better_dataframe_from_stata(stfr, var, obs, selectvar, valuelabel, missingval, sformat) 80 | -------------------------------------------------------------------------------- /nbstata/stata.py: -------------------------------------------------------------------------------- 1 | """Simple wrappers for `pystata`/`sfi` functionality""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_stata.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['get_local', 'set_local', 'get_global', 'get_scalar', 'stata_formatted', 'variable_names', 'drop_var', 'obs_count', 7 | 'pwd', 'macro_expand', 'run_direct', 'run_single'] 8 | 9 | # %% ../nbs/02_stata.ipynb 5 10 | from .misc_utils import print_red 11 | from contextlib import redirect_stdout 12 | from io import StringIO 13 | 14 | # %% ../nbs/02_stata.ipynb 9 15 | def get_local(name): 16 | import sfi 17 | return sfi.Macro.getLocal(name) 18 | 19 | # %% ../nbs/02_stata.ipynb 11 20 | def set_local(name, value): 21 | import sfi 22 | return sfi.Macro.setLocal(name, value) 23 | 24 | # %% ../nbs/02_stata.ipynb 13 25 | def get_global(name): 26 | import sfi 27 | return sfi.Macro.getGlobal(name) 28 | 29 | # %% ../nbs/02_stata.ipynb 15 30 | def get_scalar(name): 31 | import sfi 32 | return sfi.Scalar.getValue(name) 33 | 34 | # %% ../nbs/02_stata.ipynb 17 35 | def stata_formatted(value, s_format): 36 | import sfi 37 | return sfi.SFIToolkit.formatValue(value, s_format) 38 | 39 | # %% ../nbs/02_stata.ipynb 19 40 | def variable_names(): 41 | from sfi import Data 42 | return [Data.getVarName(i) for i in range(Data.getVarCount())] 43 | 44 | # %% ../nbs/02_stata.ipynb 23 45 | def drop_var(name): 46 | import sfi 47 | sfi.Data.dropVar(name) 48 | 49 | # %% ../nbs/02_stata.ipynb 26 50 | def obs_count(): 51 | """Count the number of observations""" 52 | import sfi 53 | return sfi.Data.getObsTotal() 54 | 55 | # %% ../nbs/02_stata.ipynb 29 56 | def pwd(): 57 | from sfi import SFIToolkit 58 | return SFIToolkit.getWorkingDir() 59 | 60 | # %% ../nbs/02_stata.ipynb 32 61 | def macro_expand(s): 62 | from sfi import SFIToolkit 63 | return SFIToolkit.macroExpand(s) 64 | 65 | # %% ../nbs/02_stata.ipynb 35 66 | def run_direct(cmds, quietly=False, echo=False, inline=True): 67 | import pystata 68 | return pystata.stata.run(cmds, quietly, echo, inline) 69 | 70 | # %% ../nbs/02_stata.ipynb 43 71 | def run_single(cmd, echo=False): 72 | import sfi 73 | try: 74 | sfi.SFIToolkit.stata(cmd, echo) 75 | except Exception as e: 76 | with redirect_stdout(StringIO()) as diverted: 77 | sfi.SFIToolkit.stata("", echo) 78 | raise SyntaxError(diverted.getvalue()) 79 | -------------------------------------------------------------------------------- /nbstata/stata_more.py: -------------------------------------------------------------------------------- 1 | """Helper functions that expand on `pystata`/`sfi` functionality""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_stata_more.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['run_direct_cleaned', 'run_sfi', 'SelectVar', 'IndexVar', 'run_as_program', 'diverted_stata_output', 7 | 'diverted_stata_output_quicker', 'var_from_varlist', 'local_names', 'get_local_dict', 8 | 'locals_code_from_dict', 'user_expression'] 9 | 10 | # %% ../nbs/03_stata_more.ipynb 4 11 | from .misc_utils import print_red 12 | from .stata import run_direct, run_single, get_local, set_local, drop_var, stata_formatted 13 | from textwrap import dedent 14 | import functools 15 | from contextlib import redirect_stdout 16 | from io import StringIO 17 | import re 18 | 19 | # %% ../nbs/03_stata_more.ipynb 8 20 | def run_direct_cleaned(cmds, quietly=False, echo=False, inline=True): 21 | if quietly: 22 | with redirect_stdout(StringIO()) as diverted: # to prevent blank line output, as with `program define` 23 | out = run_direct(cmds, quietly, echo, inline) 24 | prints = diverted.getvalue() 25 | for line in prints.splitlines(): 26 | if line.strip(): 27 | print(line) 28 | return out 29 | elif len(cmds.splitlines()) > 1: 30 | with redirect_stdout(StringIO()) as diverted: 31 | run_direct(cmds, quietly, echo, inline) 32 | output_lines = diverted.getvalue().splitlines() 33 | if (len(output_lines) >= 2 34 | and not output_lines[0].strip() 35 | and "\n".join(output_lines[-2:]).strip() == "."): 36 | print("\n".join(output_lines[1:-2])) 37 | else: 38 | print("\n".join(output_lines)) 39 | else: 40 | return run_direct(cmds, quietly, echo, inline) 41 | 42 | # %% ../nbs/03_stata_more.ipynb 30 43 | def run_sfi(std_code, echo=False, show_exc_warning=True): 44 | import sfi 45 | cmds = std_code.splitlines() 46 | for i, cmd in enumerate(cmds): 47 | try: 48 | sfi.SFIToolkit.stata(cmd, echo) 49 | except Exception as e: 50 | if show_exc_warning: 51 | print_red(f"run_sfi (sfi.SFIToolkit.stata) error: {repr(e)}") 52 | remaining_code = "\n".join(cmds[i:]) 53 | run_direct(remaining_code, echo=echo) 54 | break 55 | 56 | # %% ../nbs/03_stata_more.ipynb 35 57 | class SelectVar(): 58 | """Class for generating Stata select_var for getAsDict""" 59 | varname = None 60 | 61 | def __init__(self, stata_if_code): 62 | import sfi 63 | condition = stata_if_code.replace('if ', '', 1).strip() 64 | if condition: 65 | self.varname = sfi.SFIToolkit.getTempName() 66 | cmd = f"quietly gen {self.varname} = cond({condition},1,0)" 67 | run_single(cmd) 68 | 69 | def clear(self): 70 | """Remove temporary select_var from Stata dataset""" 71 | if self.varname: 72 | drop_var(self.varname) 73 | 74 | def __enter__(self): 75 | return self.varname 76 | 77 | def __exit__(self, exc_type, exc_value, exc_tb): 78 | self.clear() 79 | 80 | # %% ../nbs/03_stata_more.ipynb 38 81 | class IndexVar: 82 | """Class for generating Stata index var for use with pandas""" 83 | def __enter__(self): 84 | import sfi 85 | self.idx_var = sfi.SFIToolkit.getTempName() 86 | run_single(f"gen {self.idx_var} = _n") 87 | return self.idx_var 88 | 89 | def __exit__(self, exc_type, exc_value, exc_tb): 90 | drop_var(self.idx_var) 91 | 92 | # %% ../nbs/03_stata_more.ipynb 51 93 | def run_as_program(std_non_prog_code, prog_def_option_code=""): 94 | _program_name = "temp_nbstata_program_name" 95 | _options = f", {prog_def_option_code}" if prog_def_option_code else "" 96 | _program_define_code = ( 97 | f"program {_program_name}{_options}\n" 98 | f"{std_non_prog_code}\n" 99 | "end\n" 100 | ) 101 | try: 102 | run_direct_cleaned(_program_define_code, quietly=True) 103 | run_direct(_program_name, quietly=False, inline=True, echo=False) 104 | finally: 105 | run_single(f"capture program drop {_program_name}") 106 | 107 | # %% ../nbs/03_stata_more.ipynb 65 108 | def diverted_stata_output(std_code, runner=None): 109 | if runner is None: 110 | runner = functools.partial(run_direct, quietly=False, inline=True, echo=False) 111 | with redirect_stdout(StringIO()) as diverted: 112 | run_as_program("return add\ncapture log off", prog_def_option_code="rclass") 113 | try: 114 | runner(std_code) 115 | finally: 116 | run_as_program("return add\ncapture log on", prog_def_option_code="rclass") 117 | out = diverted.getvalue() 118 | return out 119 | 120 | # %% ../nbs/03_stata_more.ipynb 71 121 | def diverted_stata_output_quicker(std_non_prog_code): 122 | with redirect_stdout(StringIO()) as diverted: 123 | code = f"return add\ncapture log off\n{std_non_prog_code}\ncapture log on""" 124 | try: 125 | run_as_program(code, prog_def_option_code="rclass") 126 | except SystemError as e: 127 | run_as_program("return add\ncapture log on", prog_def_option_code="rclass") 128 | raise(e) 129 | out = diverted.getvalue() 130 | return out 131 | 132 | # %% ../nbs/03_stata_more.ipynb 76 133 | def var_from_varlist(varlist, stfr=None): 134 | if stfr: 135 | var_code = varlist.strip() 136 | else: 137 | _program_name = "temp_nbstata_varlist_name" 138 | run_direct_cleaned(( 139 | f"program define {_program_name}\n" 140 | """ syntax [varlist(default=none)] 141 | foreach var in `varlist' { 142 | disp "`var'" 143 | } 144 | end 145 | """), quietly=True) 146 | try: 147 | var_code = diverted_stata_output_quicker(f"""\ 148 | {_program_name} {varlist} 149 | program drop {_program_name} 150 | """).strip() 151 | except Exception as e: 152 | run_sfi(f"capture program drop {_program_name}") 153 | raise(e) 154 | return [c.strip() for c in var_code.split() if c] if var_code else None 155 | 156 | # %% ../nbs/03_stata_more.ipynb 86 157 | def local_names(): 158 | run_single("""\ 159 | mata : st_local("temp_nbstata_all_locals", invtokens(st_dir("local", "macro", "*")'))""") 160 | out = get_local('temp_nbstata_all_locals') 161 | set_local('temp_nbstata_all_locals', "") 162 | return out.split() 163 | 164 | # %% ../nbs/03_stata_more.ipynb 90 165 | def get_local_dict(_local_names=None): 166 | if _local_names is None: 167 | _local_names = local_names() 168 | return {n: get_local(n) for n in _local_names} 169 | 170 | # %% ../nbs/03_stata_more.ipynb 92 171 | def locals_code_from_dict(preexisting_local_dict): 172 | local_defs = (f"""local {name} `"{preexisting_local_dict[name]}"'""" 173 | for name in preexisting_local_dict) 174 | return "\n".join(local_defs) 175 | 176 | # %% ../nbs/03_stata_more.ipynb 98 177 | def user_expression(input_str): 178 | run_single("tempname output") 179 | try: 180 | run_single(f"local `output': display {input_str}") 181 | except SyntaxError as e: 182 | combined_message = f"{str(e)}\nInvalid Stata '[%fmt] [=]exp' display expression: {input_str}" 183 | raise SyntaxError(combined_message) 184 | return get_local(get_local("output")) 185 | -------------------------------------------------------------------------------- /nbstata/stata_session.py: -------------------------------------------------------------------------------- 1 | """A class for representing a Stata session""" 2 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/08_stata_session.ipynb. 4 | 5 | # %% auto 0 6 | __all__ = ['StataSession', 'warn_re_unclosed_comment_block_if_needed'] 7 | 8 | # %% ../nbs/08_stata_session.ipynb 4 9 | from .misc_utils import print_red 10 | from .stata import run_direct, get_local, get_scalar 11 | from .stata_more import diverted_stata_output_quicker, local_names, run_sfi 12 | from .stata_more import get_local_dict as _get_local_dict 13 | from nbstata.code_utils import ( 14 | valid_single_line_code, 15 | ending_sc_delimiter, 16 | ends_in_comment_block, 17 | ending_code_version, 18 | ) 19 | from .noecho import run_as_program_w_locals, run_noecho 20 | from fastcore.basics import patch_to 21 | from textwrap import dedent 22 | import re 23 | 24 | # %% ../nbs/08_stata_session.ipynb 6 25 | class StataSession(): 26 | def __init__(self): 27 | """""" 28 | self.sc_delimiter = False 29 | self.code_version = None 30 | self.stata_version = None 31 | self.clear_suggestions() 32 | self._compile_re() 33 | 34 | def clear_suggestions(self): 35 | self.suggestions = None 36 | 37 | def _compile_re(self): 38 | self.matchall = re.compile( 39 | r"\A.*?" 40 | r"^%varlist%(?P.*?)" 41 | r"%globals%(?P.*?)" 42 | #r"%locals%(?P.*?)" 43 | r"%scalars%(?P.*?)" 44 | r"%matrices%(?P.*?)%end%", #"(\Z|---+\s*end)", 45 | flags=re.DOTALL + re.MULTILINE).match 46 | 47 | # Varlist-style matching; applies to most 48 | self.varlist = re.compile(r"(?:\s+)(\S+)", flags=re.MULTILINE) 49 | 50 | # file-style matching 51 | self.filelist = re.compile(r"[\r\n]{1,2}", flags=re.MULTILINE) 52 | 53 | # Clean line-breaks. 54 | self.varclean = re.compile( 55 | r"(?=\s*)[\r\n]{1,2}?^>\s", flags=re.MULTILINE).sub 56 | 57 | # # Match output from mata mata desc 58 | # self.matadesc = re.compile( 59 | # r"(\A.*?---+|---+[\r\n]*\Z)", flags=re.MULTILINE + re.DOTALL) 60 | 61 | # self.matalist = re.compile( 62 | # r"(?:.*?)\s(\S+)\s*$", flags=re.MULTILINE + re.DOTALL) 63 | 64 | # self.mataclean = re.compile(r"\W.*?(\b|$)") 65 | # self.matasearch = re.compile(r"(?P\w.*?(?=\W|\b|$))").search 66 | 67 | # %% ../nbs/08_stata_session.ipynb 7 68 | @patch_to(StataSession) 69 | def refresh_suggestions(self): 70 | self.suggestions = self.get_suggestions() 71 | 72 | # %% ../nbs/08_stata_session.ipynb 8 73 | @patch_to(StataSession) 74 | def _completions(self): 75 | return diverted_stata_output_quicker(dedent("""\ 76 | local _temp_completions_while_local_ = 1 77 | while `_temp_completions_while_local_' { 78 | set more off 79 | set trace off 80 | if `"`varlist'"' != "" { 81 | local _temp_completions_varlist_loc_ `"`varlist'"' 82 | } 83 | syntax [varlist] 84 | disp "%varlist%" 85 | disp `"`varlist'"' 86 | macro drop _varlist __temp_completions_while_local_ 87 | if `"`_temp_completions_varlist_loc_'"' != "" { 88 | local varlist `"`_temp_completions_varlist_loc_'"' 89 | macro drop __temp_completions_varlist_loc_ 90 | } 91 | disp "%globals%" 92 | disp `"`:all globals'"' 93 | *disp "%locals%" 94 | *mata : invtokens(st_dir("local", "macro", "*")') 95 | disp "%scalars%" 96 | disp `"`:all scalars'"' 97 | disp "%matrices%" 98 | disp `"`:all matrices'"' 99 | disp "%end%" 100 | local _temp_completions_while_local_ = 0 101 | } 102 | macro drop _temp_completions_while_local_ 103 | """)) 104 | 105 | # %% ../nbs/08_stata_session.ipynb 11 106 | @patch_to(StataSession) 107 | def _get_locals(self): 108 | return self.suggestions['locals'] if self.suggestions else local_names() 109 | 110 | # %% ../nbs/08_stata_session.ipynb 15 111 | @patch_to(StataSession) 112 | def get_suggestions(self): 113 | match = self.matchall(self._completions()) 114 | suggestions = match.groupdict() 115 | # suggestions['mata'] = self._parse_mata_desc(suggestions['mata']) 116 | # suggestions['programs'] = self._parse_programs_desc( 117 | # suggestions['programs']) 118 | for k, v in suggestions.items(): 119 | suggestions[k] = self.varlist.findall(self.varclean('', v)) 120 | suggestions['locals'] = self._get_locals() 121 | return suggestions 122 | 123 | # %% ../nbs/08_stata_session.ipynb 19 124 | @patch_to(StataSession) 125 | def get_local_dict(self): 126 | return _get_local_dict(self._get_locals()) 127 | 128 | # %% ../nbs/08_stata_session.ipynb 21 129 | @patch_to(StataSession) 130 | def _run_as_program_w_locals(self, std_code): 131 | """After `break_out_prog_blocks`, run noecho, inserting locals when needed""" 132 | return run_as_program_w_locals(std_code, local_dict=self.get_local_dict()) 133 | 134 | # %% ../nbs/08_stata_session.ipynb 26 135 | def _run_simple(code, quietly=False, echo=False, sc_delimiter=False): 136 | if sc_delimiter: 137 | code = "#delimit;\n" + code 138 | if len(code.splitlines()) == 1: 139 | code = valid_single_line_code(code) 140 | run_direct(code, quietly=quietly, inline=not quietly, echo=echo) 141 | 142 | # %% ../nbs/08_stata_session.ipynb 29 143 | _final_delimiter_warning = ( 144 | "Warning: Code cell (with #delimit; in effect) does not end in ';'. " 145 | "Exported .do script may behave differently from notebook. " 146 | "In v1.0, nbstata may trigger an error instead of just a warning." 147 | ) 148 | 149 | # %% ../nbs/08_stata_session.ipynb 30 150 | @patch_to(StataSession) 151 | def _update_ending_delimiter(self, code): 152 | self.sc_delimiter = ending_sc_delimiter(code, self.sc_delimiter) 153 | _final_character = code.strip()[-1] 154 | _code_missing_final_delimiter = (self.sc_delimiter 155 | and _final_character != ';') 156 | if _code_missing_final_delimiter: 157 | print_red(_final_delimiter_warning) 158 | 159 | # %% ../nbs/08_stata_session.ipynb 32 160 | def warn_re_unclosed_comment_block_if_needed(code): 161 | if ends_in_comment_block(code): 162 | print_red("Warning: Code cell ends in a comment block without a " 163 | "closing '*/'. Exported .do script may behave differently " 164 | "from notebook. In v1.0, nbstata may trigger an error " 165 | "instead of just a warning." 166 | ) 167 | 168 | # %% ../nbs/08_stata_session.ipynb 35 169 | @patch_to(StataSession) 170 | def _post_run_hook(self, code): 171 | self.clear_suggestions() 172 | if self.stata_version is None: 173 | self.stata_version = f"{get_scalar('c(stata_version)'):0.2f}" 174 | self.code_version = ending_code_version(code, self.sc_delimiter, self.code_version, self.stata_version) 175 | self._update_ending_delimiter(code) # after updating code_version (based on starting sc_delimiter) 176 | warn_re_unclosed_comment_block_if_needed(code) 177 | 178 | # %% ../nbs/08_stata_session.ipynb 36 179 | @patch_to(StataSession) 180 | def dispatch_run(self, code, quietly=False, echo=False, noecho=False): 181 | if self.code_version: 182 | version_prefix = "capture version " + self.code_version + (";" if self.sc_delimiter else "\n") 183 | code = version_prefix + code 184 | if noecho and not quietly: 185 | run_noecho(code, self.sc_delimiter, run_as_prog=self._run_as_program_w_locals) 186 | else: 187 | _run_simple(code, quietly, echo, self.sc_delimiter) 188 | self._post_run_hook(code) 189 | -------------------------------------------------------------------------------- /settings.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | repo = nbstata 3 | lib_name = nbstata 4 | version = 0.8.2 5 | min_python = 3.9 6 | license = gpl3 7 | doc_path = _docs 8 | lib_path = nbstata 9 | nbs_path = nbs 10 | recursive = True 11 | tst_flags = notest 12 | put_version_in_init = True 13 | branch = master 14 | custom_sidebar = True 15 | doc_host = https://hugetim.github.io 16 | doc_baseurl = /nbstata 17 | git_url = https://github.com/hugetim/nbstata 18 | title = nbstata 19 | audience = Science/Research 20 | author = Tim Huegerich 21 | author_email = hugetim@gmail.com 22 | copyright = 2022 onwards, Tim Huegerich 23 | description = Jupyter kernel for Stata built on pystata 24 | keywords = nbdev jupyter notebook python stata 25 | language = English 26 | status = 3 27 | user = hugetim 28 | requirements = jupyter-client ipython ipykernel packaging pandas numpy beautifulsoup4 fastcore pygments>=2.8 ipydatagrid<1.3 29 | black_formatting = False 30 | readme_nb = index.ipynb 31 | allowed_metadata_keys = 32 | allowed_cell_metadata_keys = 33 | jupyter_hooks = True 34 | clean_ids = True 35 | clear_all = False 36 | cell_number = True 37 | skip_procs = 38 | 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import parse_version 2 | from configparser import ConfigParser 3 | import setuptools 4 | assert parse_version(setuptools.__version__)>=parse_version('36.2') 5 | 6 | # note: all settings are in settings.ini; edit there, not here 7 | config = ConfigParser(delimiters=['=']) 8 | config.read('settings.ini') 9 | cfg = config['DEFAULT'] 10 | 11 | cfg_keys = 'version description keywords author author_email'.split() 12 | expected = cfg_keys + "lib_name user branch license status min_python audience language".split() 13 | for o in expected: assert o in cfg, "missing expected setting: {}".format(o) 14 | setup_cfg = {o:cfg[o] for o in cfg_keys} 15 | 16 | licenses = { 17 | 'apache2': ('Apache Software License 2.0','OSI Approved :: Apache Software License'), 18 | 'mit': ('MIT License', 'OSI Approved :: MIT License'), 19 | 'gpl2': ('GNU General Public License v2', 'OSI Approved :: GNU General Public License v2 (GPLv2)'), 20 | 'gpl3': ('GNU General Public License v3', 'OSI Approved :: GNU General Public License v3 (GPLv3)'), 21 | 'bsd3': ('BSD License', 'OSI Approved :: BSD License'), 22 | } 23 | statuses = [ '1 - Planning', '2 - Pre-Alpha', '3 - Alpha', 24 | '4 - Beta', '5 - Production/Stable', '6 - Mature', '7 - Inactive' ] 25 | py_versions = '3.6 3.7 3.8 3.9 3.10'.split() 26 | 27 | requirements = cfg.get('requirements','').split() 28 | if cfg.get('pip_requirements'): requirements += cfg.get('pip_requirements','').split() 29 | min_python = cfg['min_python'] 30 | lic = licenses.get(cfg['license'].lower(), (cfg['license'], None)) 31 | dev_requirements = (cfg.get('dev_requirements') or '').split() 32 | 33 | setuptools.setup( 34 | name = cfg['lib_name'], 35 | license = lic[0], 36 | classifiers = [ 37 | 'Development Status :: ' + statuses[int(cfg['status'])], 38 | 'Intended Audience :: ' + cfg['audience'].title(), 39 | 'Natural Language :: ' + cfg['language'].title(), 40 | ] + ['Programming Language :: Python :: '+o for o in py_versions[py_versions.index(min_python):]] + (['License :: ' + lic[1] ] if lic[1] else []), 41 | url = cfg['git_url'], 42 | packages = setuptools.find_packages(), 43 | include_package_data = True, 44 | install_requires = requirements, 45 | extras_require={ 'dev': dev_requirements }, 46 | setup_requires = ['wheel'], # https://stackoverflow.com/questions/34819221/why-is-python-setup-py-saying-invalid-command-bdist-wheel-on-travis-ci#comment99762029_54833684 47 | dependency_links = cfg.get('dep_links','').split(), 48 | python_requires = '>=' + cfg['min_python'], 49 | long_description = open('README.md').read(), 50 | long_description_content_type = 'text/markdown', 51 | zip_safe = False, 52 | entry_points = { 53 | 'console_scripts': cfg.get('console_scripts','').split(), 54 | 'nbdev': [f'{cfg.get("lib_path")}={cfg.get("lib_path")}._modidx:d'] 55 | }, 56 | **setup_cfg) 57 | --------------------------------------------------------------------------------