├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .yarnrc.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── binder ├── environment.yml └── postBuild ├── docs └── limit-output.png ├── install.json ├── jupyterlab_limit_output ├── __init__.py └── _version.py ├── notebooks └── index.ipynb ├── package.json ├── pyproject.toml ├── schema └── settings.json ├── setup.py ├── src ├── formatters.ts ├── index.ts └── renders.ts ├── style ├── base.css ├── index.css └── index.js ├── tests ├── formatter_spec.ts └── tsconfig.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | jupyterlab_limit_output 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: ['tsconfig.json', 'tests/tsconfig.json'], 11 | sourceType: 'module', 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | '@typescript-eslint/naming-convention': [ 16 | 'error', 17 | { 18 | selector: 'interface', 19 | format: ['PascalCase'], 20 | custom: { 21 | regex: '^I[A-Z]', 22 | match: true, 23 | }, 24 | }, 25 | ], 26 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/explicit-function-return-type': 'off', 29 | '@typescript-eslint/no-namespace': 'off', 30 | '@typescript-eslint/no-use-before-define': 'off', 31 | '@typescript-eslint/quotes': [ 32 | 'error', 33 | 'single', 34 | { avoidEscape: true, allowTemplateLiterals: false }, 35 | ], 36 | curly: ['error', 'all'], 37 | eqeqeq: 'error', 38 | 'prefer-arrow-callback': 'error', 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json binary 2 | yarn.lock binary 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: master 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | - name: Install node 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '18.x' 19 | - name: Install Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: '3.11' 23 | architecture: 'x64' 24 | - name: Install dependencies 25 | run: python -m pip install jupyterlab==4.0.0a37 26 | - name: Build the extension 27 | run: | 28 | jlpm 29 | jlpm run eslint:check 30 | python -m pip install . 31 | 32 | jupyter labextension list 2>&1 | grep -ie "jupyterlab-limit-output.*OK" 33 | python -m jupyterlab.browser_check 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.egg-info/ 5 | .ipynb_checkpoints 6 | *.tsbuildinfo 7 | jupyterlab_limit_output/labextension 8 | 9 | # Created by https://www.gitignore.io/api/python 10 | # Edit at https://www.gitignore.io/?templates=python 11 | 12 | ### Python ### 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | .spyproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # Mr Developer 94 | .mr.developer.cfg 95 | .project 96 | .pydevproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | .dmypy.json 104 | dmypy.json 105 | 106 | # Pyre type checker 107 | .pyre/ 108 | 109 | # End of https://www.gitignore.io/api/python 110 | 111 | # OSX files 112 | .DS_Store 113 | .yarn 114 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | npm run test 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | jupyterlab_limit_output 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.0](https://github.com/deshaw/jupyterlab-limit-output/compare/v1.0.1...v2.0.0) (2023-05-18) 2 | 3 | ### Changed 4 | 5 | - **Breaking**: Ported to JupyterLab 4.x. 6 | - Updated the `build` github workflow to use the latest versions of the github 7 | actions; the latest version of python (3.11); and latest version (18.x) 8 | version of node. 9 | 10 | ## [1.0.1](https://github.com/deshaw/jupyterlab-limit-output/compare/v1.0.0...v1.0.1) (2023-03-27) 11 | 12 | ### Fixed 13 | 14 | - Fixed memory leak [#3](https://github.com/deshaw/jupyterlab-limit-output/issues/3) 15 | 16 | ## [1.0.0](https://github.com/deshaw/jupyterlab-limit-output/compare/v1.0.0...v1.0.0) (2022-02-18) 17 | 18 | ### Added 19 | 20 | - First version 21 | 22 | The project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and 23 | this CHANGELOG follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) standard. 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We love contributions! Before you can contribute, please [sign and submit this Contributor License Agreement (CLA)](https://www.deshaw.com/oss/cla), 4 | declaring that you have both the rights to your contribution and you grant us us the rights to use your contribution. 5 | This CLA is in place to protect all users of this project. You only need to sign this once for all projects using our CLA. 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 D. E. Shaw & Co., L.P. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.md 3 | include pyproject.toml 4 | recursive-include jupyter-config *.json 5 | 6 | include package.json 7 | include install.json 8 | include ts*.json 9 | include yarn.lock 10 | 11 | graft jupyterlab_limit_output/labextension 12 | 13 | # Javascript files 14 | graft src 15 | graft style 16 | prune **/node_modules 17 | prune lib 18 | prune binder 19 | 20 | # Patterns to exclude from any directory 21 | global-exclude *~ 22 | global-exclude *.pyc 23 | global-exclude *.pyo 24 | global-exclude .git 25 | global-exclude .ipynb_checkpoints 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jupyterlab-limit-output 2 | 3 | [![PyPI version][pypi-image]][pypi-url] [![PyPI DM][pypi-dm-image]][pypi-url] 4 | [![Github Actions Status][github-status-image]][github-status-url] [![Binder][binder-image]][binder-url] 5 | 6 | Limit long text output for a single mime render. 7 | 8 | ![Limit OutputScreenshot](https://github.com/deshaw/jupyterlab-limit-output/blob/main/docs/limit-output.png?raw=true) 9 | 10 | This is inspired by the notebook version [here](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/blob/master/src/jupyter_contrib_nbextensions/nbextensions/limit_output). 11 | 12 | ## Settings 13 | 14 | Use `Settings->Advanced User Settings->Limit Output` to set head and/or tail limits. You can also limit by characters instead of lines. 15 | This extension can also be enabled/disabled to allow for site-wide installations. 16 | 17 | Note: This works on a per mime render basis. For example, imagine cell that outputs a large number alternating lines of stdout and stderr. 18 | This extension won't help because each line would be it's own mime renderer (lab itself will help by limiting the number of renders shown). 19 | 20 | ## Requirements 21 | 22 | - JupyterLab >= 3.0 23 | 24 | ## Install 25 | 26 | To install this package with [`pip`](https://pip.pypa.io/en/stable/) run 27 | 28 | ```bash 29 | pip install jupyterlab_limit_output 30 | ``` 31 | 32 | To install this package with [`conda`](https://docs.conda.io/en/latest/) run 33 | 34 | ```bash 35 | conda install -c conda-forge jupyterlab_limit_output 36 | ``` 37 | 38 | ## Contributing 39 | 40 | ### Development install 41 | 42 | Note: You will need NodeJS to build the extension package. 43 | 44 | The `jlpm` command is JupyterLab's pinned version of 45 | [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 46 | `yarn` or `npm` in lieu of `jlpm` below. 47 | 48 | ```bash 49 | # Clone the repo to your local environment 50 | # Change directory to the jupyterlab_limit_output directory 51 | # Install package in development mode 52 | pip install -e . 53 | # Link your development version of the extension with JupyterLab 54 | jupyter labextension develop . --overwrite 55 | # Rebuild extension Typescript source after making changes 56 | jlpm run build 57 | ``` 58 | 59 | You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. 60 | 61 | ```bash 62 | # Watch the source directory in one terminal, automatically rebuilding when needed 63 | jlpm run watch 64 | # Run JupyterLab in another terminal 65 | jupyter lab 66 | ``` 67 | 68 | With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). 69 | 70 | By default, the `jlpm run build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: 71 | 72 | ```bash 73 | jupyter lab build --minimize=False 74 | ``` 75 | 76 | #### Publishing 77 | 78 | Before starting, you'll need to have run: `pip install twine jupyter_packaging` 79 | 80 | 1. Update the version in `package.json` and update the release date in `CHANGELOG.md` 81 | 2. Commit the change in step 1, tag it, then push it 82 | 83 | ``` 84 | git commit -am 85 | git tag vX.Z.Y 86 | git push && git push --tags 87 | ``` 88 | 89 | 3. Create the artifacts 90 | 91 | ``` 92 | rm -rf dist jupyterlab_limit_output/labextension 93 | jlpm run build 94 | python setup.py sdist bdist_wheel 95 | ``` 96 | 97 | 4. Test this against the test pypi. You can then install from here to test as well: 98 | 99 | ``` 100 | twine upload --repository-url https://test.pypi.org/legacy/ dist/* 101 | # In a new venv 102 | pip install --index-url https://test.pypi.org/simple/ jupyterlab_limit_output 103 | ``` 104 | 105 | 5. Upload this to pypi: 106 | 107 | ``` 108 | twine upload dist/* 109 | ``` 110 | 111 | ### Uninstall 112 | 113 | ```bash 114 | pip uninstall jupyterlab_limit_output 115 | ``` 116 | 117 | ## History 118 | 119 | This plugin was contributed back to the community by the [D. E. Shaw group](https://www.deshaw.com/). 120 | 121 |

122 | 123 | D. E. Shaw Logo 124 | 125 |

126 | 127 | ## License 128 | 129 | This project is released under a [BSD-3-Clause license](https://github.com/deshaw/jupyterlab-limit-output/blob/main/LICENSE.txt). 130 | 131 | We love contributions! Before you can contribute, please sign and submit this [Contributor License Agreement (CLA)](https://www.deshaw.com/oss/cla). 132 | This CLA is in place to protect all users of this project. 133 | 134 | "Jupyter" is a trademark of the NumFOCUS foundation, of which Project Jupyter is a part. 135 | 136 | [pypi-url]: https://pypi.org/project/jupyterlab-limit-output 137 | [pypi-image]: https://img.shields.io/pypi/v/jupyterlab-limit-output 138 | [pypi-dm-image]: https://img.shields.io/pypi/dm/jupyterlab-limit-output 139 | [github-status-image]: https://github.com/deshaw/jupyterlab-limit-output/workflows/Build/badge.svg 140 | [github-status-url]: https://github.com/deshaw/jupyterlab-limit-output/actions?query=workflow%3ABuild 141 | [binder-image]: https://mybinder.org/badge_logo.svg 142 | [binder-url]: https://mybinder.org/v2/gh/deshaw/jupyterlab-limit-output.git/main?urlpath=lab%2Ftree%2Fnotebooks%2Findex.ipynb 143 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | # a mybinder.org-ready environment for demoing jupyterlab_limit_output 2 | # this environment may also be used locally on Linux/MacOS/Windows, e.g. 3 | # 4 | # conda env update --file binder/environment.yml 5 | # conda activate jupyterlab_limit_output-demo 6 | # 7 | name: jupyterlab_limit_output-demo 8 | 9 | channels: 10 | - conda-forge 11 | 12 | dependencies: 13 | # runtime dependencies 14 | - python >=3.8,<4.0.0a0 15 | - jupyterlab >=3.0.16,<4.0.0a0 16 | # labextension build dependencies 17 | - nodejs >=14,<15 18 | - wheel 19 | - pip 20 | - yarn 21 | # additional packages for demos 22 | # - ipywidgets 23 | -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ perform a development install of jupyterlab_limit_output 3 | 4 | On Binder, this will run _after_ the environment has been fully created from 5 | the environment.yml in this directory. 6 | 7 | This script should also run locally on Linux/MacOS/Windows: 8 | 9 | python3 binder/postBuild 10 | """ 11 | import subprocess 12 | import sys 13 | from os.path import expanduser 14 | from pathlib import Path 15 | 16 | 17 | ROOT = Path.cwd() 18 | 19 | 20 | def _(*args, **kwargs): 21 | """Run a command, echoing the args 22 | 23 | fails hard if something goes wrong 24 | """ 25 | print("\n\t", " ".join(args), "\n") 26 | return_code = subprocess.call(args, **kwargs) 27 | if return_code != 0: 28 | print("\nERROR", return_code, " ".join(args)) 29 | sys.exit(return_code) 30 | 31 | 32 | # verify the environment is self-consistent before even starting 33 | _(sys.executable, "-m", "pip", "check") 34 | 35 | # install the labextension in develop mode 36 | _(sys.executable, "-m", "pip", "install", "-e", ".") 37 | _(sys.executable, "-m", "jupyter", "labextension", "develop", "--overwrite", ".") 38 | 39 | # verify the environment the extension didn't break anything 40 | _(sys.executable, "-m", "pip", "check") 41 | 42 | # list the extensions 43 | _("jupyter", "server", "extension", "list") 44 | 45 | # initially list installed extensions to determine if there are any surprises 46 | _("jupyter", "labextension", "list") 47 | 48 | user = expanduser("~") 49 | # Ensure the right settings 50 | _( 51 | "mkdir", 52 | "-p", 53 | "{}/.jupyter/lab/user-settings/@jupyterlab/notebook-extension/".format(user), 54 | ) 55 | 56 | # We may not need this after https://github.com/jupyterlab/jupyterlab/pull/9561 57 | with open( 58 | "{}/.jupyter/lab/user-settings/jupyterlab-limit-output/settings.jupyterlab-settings".format( 59 | user 60 | ), 61 | "w", 62 | ) as f: 63 | f.write('{"enabled": true}') 64 | 65 | print("JupyterLab with jupyterlab_limit_output is ready to run with:\n") 66 | print("\tjupyter lab\n") 67 | -------------------------------------------------------------------------------- /docs/limit-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deshaw/jupyterlab-limit-output/71cccc92be388176e15da2f7a62acd87037b6ea5/docs/limit-output.png -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "jupyterlab_limit_output", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupyterlab_limit_output" 5 | } 6 | -------------------------------------------------------------------------------- /jupyterlab_limit_output/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import os.path as osp 4 | 5 | from ._version import __version__ 6 | 7 | HERE = osp.abspath(osp.dirname(__file__)) 8 | 9 | with open(osp.join(HERE, 'labextension', 'package.json')) as fid: 10 | data = json.load(fid) 11 | 12 | def _jupyter_labextension_paths(): 13 | return [{ 14 | 'src': 'labextension', 15 | 'dest': data['name'] 16 | }] 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /jupyterlab_limit_output/_version.py: -------------------------------------------------------------------------------- 1 | __all__ = ['__version__'] 2 | 3 | def _fetchVersion(): 4 | import json 5 | import os 6 | 7 | HERE = os.path.abspath(os.path.dirname(__file__)) 8 | 9 | for d, _, _ in os.walk(HERE): 10 | try: 11 | with open(os.path.join(d, 'package.json')) as f: 12 | return json.load(f)['version'] 13 | except FileNotFoundError: 14 | pass 15 | 16 | raise FileNotFoundError('Could not find package.json under dir {}'.format(HERE)) 17 | 18 | __version__ = _fetchVersion() 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jupyterlab-limit-output", 3 | "version": "2.0.0", 4 | "description": "Limit output text mime-renders", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension", 9 | "limit output" 10 | ], 11 | "homepage": "https://github.com/deshaw/jupyterlab-limit-output", 12 | "bugs": { 13 | "url": "https://github.com/deshaw/jupyterlab-limit-output/issues" 14 | }, 15 | "license": "BSD-3-Clause", 16 | "files": [ 17 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 18 | "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", 19 | "schema/*.json", 20 | "style/index.js" 21 | ], 22 | "sideEffects": [ 23 | "style/*.css", 24 | "style/index.js" 25 | ], 26 | "jupyterlab": { 27 | "extension": true, 28 | "schemaDir": "schema", 29 | "outputDir": "jupyterlab_limit_output/labextension" 30 | }, 31 | "main": "lib/index.js", 32 | "types": "lib/index.d.ts", 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/deshaw/jupyterlab-limit-output.git" 36 | }, 37 | "scripts": { 38 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 39 | "build:labextension": "jupyter-labextension build .", 40 | "build:labextension:dev": "jupyter-labextension build --development True .", 41 | "build:lib": "tsc", 42 | "build:prod": "jlpm run clean && jlpm run build:lib && jlpm run build:labextension", 43 | "clean": "jlpm run clean:lib", 44 | "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", 45 | "clean:labextension": "rimraf jupyterlab_limit_output/labextension", 46 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 47 | "eslint": "eslint . --ext .ts,.tsx --fix", 48 | "eslint:check": "eslint . --ext .ts,.tsx", 49 | "install:extension": "jlpm run build", 50 | "lint": "npm run prettier && npm run tslint", 51 | "prepare": "jlpm run clean && jlpm run build:prod && husky install", 52 | "prettier": "prettier --write '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}'", 53 | "test": "ts-mocha -p tests/tsconfig.json tests/**/*_spec.ts", 54 | "tslint": "tslint --fix -c tslint.json --project tsconfig.json '**/*{.ts,.tsx}'", 55 | "watch": "run-p watch:src watch:labextension", 56 | "watch:labextension": "jupyter-labextension watch .", 57 | "watch:src": "tsc -w --sourceMap" 58 | }, 59 | "dependencies": { 60 | "@jupyterlab/application": "^4.0.0", 61 | "@jupyterlab/apputils": "^4.0.0", 62 | "@jupyterlab/rendermime": "^4.0.0", 63 | "@jupyterlab/settingregistry": "^4.0.0" 64 | }, 65 | "devDependencies": { 66 | "@jupyterlab/builder": "^4.0.0", 67 | "@types/chai": "^4.3.4", 68 | "@types/mocha": "^10.0.1", 69 | "@typescript-eslint/eslint-plugin": "^5.55.0", 70 | "@typescript-eslint/parser": "^5.55.0", 71 | "chai": "^4.3.7", 72 | "eslint": "^7.32.0", 73 | "eslint-config-prettier": "^8.7.0", 74 | "eslint-plugin-prettier": "^4.2.1", 75 | "husky": "^8.0.0", 76 | "mocha": "^10.2.0", 77 | "npm-run-all": "^4.1.5", 78 | "prettier": "^2.8.7", 79 | "rimraf": "^4.4.1", 80 | "ts-mocha": "^10.0.0", 81 | "tslint": "^6.1.3", 82 | "tslint-config-prettier": "^1.18.0", 83 | "tslint-plugin-prettier": "^2.3.0", 84 | "typescript": "~5.0.2" 85 | }, 86 | "styleModule": "style/index.js" 87 | } 88 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["jupyter_packaging~=0.10,<2", "jupyterlab~=4.0.0"] 3 | build-backend = "jupyter_packaging.build_api" 4 | 5 | [tool.jupyter-packaging.builder] 6 | factory = "jupyter_packaging.npm_builder" 7 | 8 | [tool.jupyter-packaging.build-args] 9 | build_cmd = "build:prod" 10 | npm = ["jlpm"] 11 | -------------------------------------------------------------------------------- /schema/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Limit Output", 3 | "description": "Limit number of a characters in a output (per mime-bundle)", 4 | "type": "object", 5 | "definitions": { 6 | "limitMethod": { 7 | "type": "string", 8 | "enum": ["lines", "characters"] 9 | } 10 | }, 11 | "properties": { 12 | "enabled": { 13 | "type": "boolean", 14 | "title": "Enabled", 15 | "description": "Enable limit output mime render", 16 | "default": true 17 | }, 18 | "head": { 19 | "type": "number", 20 | "title": "Head of output limit", 21 | "description": "Number of characters or lines to show from the top of an output.", 22 | "default": 50 23 | }, 24 | "tail": { 25 | "type": "number", 26 | "title": "Tail of output limit", 27 | "description": "Number of characters or lines to show from the bottom of an output.", 28 | "default": 50 29 | }, 30 | "method": { 31 | "title": "Limit Method", 32 | "description": "Limit output based on head/tail 'lines' or 'characters'", 33 | "$ref": "#/definitions/limitMethod", 34 | "default": "lines" 35 | } 36 | }, 37 | "additionalProperties": false 38 | } 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | jupyterlab_limit_output setup. 3 | """ 4 | import json 5 | from pathlib import Path 6 | import setuptools 7 | from jupyter_packaging import wrap_installers, npm_builder, get_data_files 8 | 9 | HERE = Path(__file__).parent.resolve() 10 | 11 | # The name of the project 12 | name = "jupyterlab_limit_output" 13 | 14 | lab_path = HERE / name.replace("-", "_") / "labextension" 15 | 16 | # Representative files that should exist after a successful build 17 | ensured_targets = [str(lab_path / "package.json"), str(lab_path / "static/style.js")] 18 | 19 | labext_name = "jupyterlab-limit-output" 20 | 21 | data_files_spec = [ 22 | ( 23 | "share/jupyter/labextensions/%s" % labext_name, 24 | str(lab_path.relative_to(HERE)), 25 | "**", 26 | ), 27 | ("share/jupyter/labextensions/%s" % labext_name, str("."), "install.json"), 28 | ] 29 | long_description = (HERE / "README.md").read_text() 30 | 31 | # Get the package info from package.json 32 | pkg_json = json.loads((HERE / "package.json").read_bytes()) 33 | 34 | setup_args = dict( 35 | name=name, 36 | version=pkg_json["version"], 37 | url=pkg_json["homepage"], 38 | author="Marc Udoff", 39 | description=pkg_json["description"], 40 | license=pkg_json["license"], 41 | long_description=long_description, 42 | long_description_content_type="text/markdown", 43 | packages=setuptools.find_namespace_packages(), 44 | install_requires=["jupyter_server>=2.0.1,<3"], 45 | zip_safe=False, 46 | include_package_data=True, 47 | python_requires=">=3.7", 48 | platforms="Linux, Mac OS X, Windows", 49 | keywords=["Jupyter", "JupyterLab", "JupyterLab4"], 50 | classifiers=[ 51 | "License :: OSI Approved :: BSD License", 52 | "Programming Language :: Python", 53 | "Programming Language :: Python :: 3", 54 | "Programming Language :: Python :: 3.7", 55 | "Programming Language :: Python :: 3.8", 56 | "Programming Language :: Python :: 3.9", 57 | "Framework :: Jupyter", 58 | "Framework :: Jupyter :: JupyterLab", 59 | "Framework :: Jupyter :: JupyterLab :: 4", 60 | "Framework :: Jupyter :: JupyterLab :: Extensions", 61 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 62 | ], 63 | ) 64 | 65 | 66 | post_develop = npm_builder( 67 | build_cmd="install:extension", source_dir="src", build_dir=lab_path, npm="jlpm" 68 | ) 69 | setup_args["cmdclass"] = wrap_installers( 70 | post_develop=post_develop, ensured_targets=ensured_targets 71 | ) 72 | setup_args["data_files"] = get_data_files(data_files_spec) 73 | 74 | if __name__ == "__main__": 75 | setuptools.setup(**setup_args) 76 | -------------------------------------------------------------------------------- /src/formatters.ts: -------------------------------------------------------------------------------- 1 | const NEW_LINE = '\n'; 2 | const SPACER = '\n\n\n'; 3 | 4 | /** 5 | * Return a string with at most head starting characters and tail ending (plus a warning) 6 | */ 7 | export const limitByCharacters = ( 8 | text: string, 9 | head: number, 10 | tail: number 11 | ): string => { 12 | const maxChars = head + tail; 13 | if (text.length > maxChars) { 14 | const headstr = text.substring(0, head); 15 | const tailstr = text.substring(text.length - tail); 16 | let msg = ''; 17 | if (head) { 18 | msg = ` first ${head}`; 19 | } 20 | if (tail) { 21 | msg += `${head ? ' and' : ''} last ${tail}`; 22 | } 23 | return `${headstr}${ 24 | head ? SPACER : '' 25 | }WARNING: Output limited. Showing${msg} characters.${ 26 | tail ? SPACER : '' 27 | }${tailstr}`; 28 | } 29 | return text; 30 | }; 31 | 32 | /** 33 | * Find the nth index of the newline character 34 | */ 35 | function _nthNewLineIndex(text: string, n: number): number | null { 36 | let idx = 0; 37 | while (n-- > 0 && idx++ < text.length) { 38 | idx = text.indexOf(NEW_LINE, idx); 39 | // Not found before we ran out of n 40 | if (idx < 0) { 41 | return null; 42 | } 43 | } 44 | return idx; 45 | } 46 | 47 | /** 48 | * Find the nth newline from the end of the string (excluding a possible final new line) 49 | */ 50 | function _nthNewLineFromLastIndex(text: string, n: number): number | null { 51 | let idx = text.length - 1; // Ignore a possible final trailing \n 52 | while (n-- > 0 && idx-- >= 0) { 53 | idx = text.lastIndexOf(NEW_LINE, idx); 54 | // Not found before we ran out of n 55 | if (idx < 0) { 56 | return null; 57 | } 58 | } 59 | return idx; 60 | } 61 | 62 | /** 63 | * Return a string with at most head starting lines and tail ending (plus a warning) 64 | */ 65 | export const limitByLines = ( 66 | text: string, 67 | head: number, 68 | tail: number 69 | ): string => { 70 | const headEndPos = head > 0 ? _nthNewLineIndex(text, head) : -1; 71 | if (headEndPos === null) { 72 | return text; 73 | } 74 | const tailStartPos = 75 | tail > 0 ? _nthNewLineFromLastIndex(text, tail) : text.length; 76 | if (tailStartPos === null) { 77 | return text; 78 | } 79 | if (tailStartPos <= headEndPos) { 80 | return text; 81 | } 82 | const headstr = text.substring(0, headEndPos); 83 | const tailstr = text.substring(tailStartPos); 84 | let msg = ''; 85 | if (head) { 86 | msg = ` first ${head}`; 87 | } 88 | if (tail) { 89 | msg += `${head ? ' and' : ''} last ${tail}`; 90 | } 91 | return `${headstr}${ 92 | head ? SPACER : '' 93 | }WARNING: Output limited. Showing${msg} lines.${ 94 | tail ? SPACER : '' 95 | }${tailstr}`; 96 | }; 97 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { IRenderMime, IRenderMimeRegistry } from '@jupyterlab/rendermime'; 2 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 3 | import { 4 | rendererFactory, 5 | updateLimitOutputSettings, 6 | ISettings as RenderISettings, 7 | } from './renders'; 8 | import { 9 | JupyterFrontEnd, 10 | JupyterFrontEndPlugin, 11 | } from '@jupyterlab/application'; 12 | 13 | const PLUGIN_NAME = 'jupyterlab-limit-output'; 14 | 15 | const extension: IRenderMime.IExtension = { 16 | id: `${PLUGIN_NAME}:rendertext`, 17 | rendererFactory, 18 | // This number is NOT random. It's just lower (more preferred) than https://github.com/jupyterlab/jupyterlab/blob/0cbfcbe8c09d2c1fbfd1912f4d36c12479893946/packages/rendermime/src/factories.ts#L68 19 | // Setting the rank too low makes the text version of renders too preferred (e.g. show text instead of the widget render) 20 | rank: 119, 21 | dataType: 'string', 22 | }; 23 | 24 | const RenderExtension: JupyterFrontEndPlugin = { 25 | id: `${PLUGIN_NAME}:renders`, 26 | autoStart: true, 27 | requires: [IRenderMimeRegistry, ISettingRegistry], 28 | activate: function ( 29 | app: JupyterFrontEnd, 30 | rendermime: IRenderMimeRegistry, 31 | settingRegistry: ISettingRegistry 32 | ) { 33 | // eslint-disable-next-line no-console 34 | console.log('JupyterLab extension jupyterlab-limit-output is activated!'); 35 | 36 | rendermime.addFactory(extension.rendererFactory, extension.rank); 37 | 38 | function updateSettings(settings: ISettingRegistry.ISettings) { 39 | const head = settings.get('head').composite as number; 40 | const tail = settings.get('tail').composite as number; 41 | const enabled = settings.get('enabled').composite as boolean; 42 | const method = settings.get('method') 43 | .composite as RenderISettings['method']; 44 | updateLimitOutputSettings({ head, tail, method, enabled }); 45 | } 46 | 47 | settingRegistry.load(`${PLUGIN_NAME}:settings`).then( 48 | (settings: ISettingRegistry.ISettings) => { 49 | updateSettings(settings); 50 | settings.changed.connect(updateSettings); 51 | }, 52 | (err: Error) => { 53 | console.error( 54 | `Could not load settings, so did not activate ${PLUGIN_NAME}: ${err}` 55 | ); 56 | } 57 | ); 58 | }, 59 | }; 60 | 61 | export default RenderExtension; 62 | -------------------------------------------------------------------------------- /src/renders.ts: -------------------------------------------------------------------------------- 1 | import { IRenderMime, RenderedText, renderText } from '@jupyterlab/rendermime'; 2 | import { limitByCharacters, limitByLines } from './formatters'; 3 | import { showDialog } from '@jupyterlab/apputils'; 4 | 5 | const WARN_BEFORE_EXPANDING_SOURCE_LENGTH_CH = 100000; 6 | const WARN_BEFORE_EXPANDING_SOURCE_LENGTH_LINES = 1000; 7 | 8 | export interface ISettings { 9 | head: number; 10 | tail: number; 11 | method: 'lines' | 'characters'; 12 | enabled: boolean; 13 | } 14 | 15 | let limitSettings: ISettings = { 16 | head: 50, 17 | tail: 50, 18 | method: 'lines', 19 | enabled: true, 20 | }; 21 | 22 | export const updateLimitOutputSettings = (settings: ISettings): void => { 23 | limitSettings = settings; 24 | if (limitSettings.head < 0) { 25 | limitSettings.head = 0; 26 | } 27 | if (limitSettings.tail < 0) { 28 | limitSettings.tail = 0; 29 | } 30 | if (limitSettings.tail === 0 && limitSettings.head === 0) { 31 | limitSettings.enabled = false; 32 | } 33 | if ( 34 | limitSettings.method !== 'lines' && 35 | limitSettings.method !== 'characters' 36 | ) { 37 | limitSettings.method = 'lines'; 38 | } 39 | }; 40 | 41 | const limitOutputRenderText = async ( 42 | options: renderText.IRenderOptions, 43 | _head = 0, 44 | _tail = 0, 45 | _cleanupButtonFn: () => void = null 46 | ) => { 47 | if (limitSettings.enabled) { 48 | // We have to clone so that we can both keep track of number of head/tail 49 | // shown as well as keep the original options unchanged 50 | const clonedOptions = { 51 | ...options, 52 | head: _head || limitSettings.head, 53 | tail: _tail || limitSettings.tail, 54 | }; 55 | 56 | if (limitSettings.method === 'characters') { 57 | clonedOptions.source = limitByCharacters( 58 | options.source, 59 | clonedOptions.head, 60 | clonedOptions.tail 61 | ); 62 | } else { 63 | clonedOptions.source = limitByLines( 64 | options.source, 65 | clonedOptions.head, 66 | clonedOptions.tail 67 | ); 68 | } 69 | // Add a div so we can easily remove output 70 | const div = document.createElement('div'); 71 | options.host.append(div); 72 | clonedOptions.host = div; 73 | // Wait for text to render so that we can add our buttons after it 74 | const ret = await renderText(clonedOptions); 75 | // If we need to, add buttons for expanding output 76 | if ( 77 | _cleanupButtonFn === null && 78 | clonedOptions.source.length !== options.source.length 79 | ) { 80 | const expandLines = Math.max(limitSettings.tail, limitSettings.head); 81 | const span = document.createElement('span'); 82 | [ 83 | // label, expand head, expand tail, warn on click 84 | [ 85 | `↑ Show ${expandLines} ${limitSettings.method}`, 86 | expandLines, 87 | 0, 88 | false, 89 | ], 90 | [`Show all ${limitSettings.method}`, Infinity, Infinity, true], 91 | [ 92 | `↓ Show ${expandLines} ${limitSettings.method}`, 93 | 0, 94 | expandLines, 95 | false, 96 | ], 97 | ].map((b) => { 98 | const [label, expandUp, expandDown, warnOnClick] = b; 99 | const button = document.createElement('button'); 100 | button.innerText = label as string; 101 | button.className = 'bp3-button jp-Button limit-output-button'; 102 | const cleanup = () => span.remove(); 103 | button.onclick = async () => { 104 | if (warnOnClick) { 105 | let warningLabel; 106 | if (limitSettings.method === 'lines') { 107 | let count = 0; 108 | for (let i = 0; i < options.source.length; ++i) { 109 | if (options.source[i] === '\n') { 110 | count++; 111 | } 112 | } 113 | if (count > WARN_BEFORE_EXPANDING_SOURCE_LENGTH_LINES) { 114 | warningLabel = `${count.toLocaleString()} lines`; 115 | } 116 | } else { 117 | if ( 118 | options.source.length > WARN_BEFORE_EXPANDING_SOURCE_LENGTH_CH 119 | ) { 120 | warningLabel = `${options.source.length.toLocaleString()} characters`; 121 | } 122 | } 123 | if (warningLabel) { 124 | const result = await showDialog({ 125 | title: 'Show all', 126 | body: `Do you really want to show all ${warningLabel}?`, 127 | }); 128 | if (!result.button.accept) { 129 | return; 130 | } 131 | } 132 | } 133 | // This binds the first clonedOptions call 134 | // i.e. future calls will updated clonedOptions but this onclick won't change 135 | clonedOptions.head += expandUp as number; 136 | clonedOptions.tail += expandDown as number; 137 | await limitOutputRenderText( 138 | { 139 | ...options, 140 | host: clonedOptions.host, 141 | }, 142 | clonedOptions.head, 143 | clonedOptions.tail, 144 | cleanup 145 | ); 146 | // Not the best design, but we know the prev element added is the renderText one 147 | // so we remove it before we redisplay 148 | clonedOptions.host.childNodes.forEach((n) => n.remove()); 149 | }; 150 | span.appendChild(button); 151 | }); 152 | options.host.append(span); 153 | // We are fully expanded! 154 | } else if ( 155 | clonedOptions.source.length === options.source.length && 156 | _cleanupButtonFn 157 | ) { 158 | _cleanupButtonFn(); 159 | } 160 | return ret; 161 | } 162 | return renderText(options); 163 | }; 164 | 165 | export class MyRenderedText extends RenderedText { 166 | /** 167 | * Render a mime model. 168 | * 169 | * @param model - The mime model to render. 170 | * 171 | * @returns A promise which resolves when rendering is complete. 172 | */ 173 | render(model: IRenderMime.IMimeModel): Promise { 174 | return limitOutputRenderText({ 175 | host: this.node, 176 | sanitizer: this.sanitizer, 177 | source: String(model.data[this.mimeType]), 178 | translator: this.translator, 179 | }); 180 | } 181 | 182 | /** 183 | * Dispose the contents of node to contain potential memory leak. 184 | * 185 | * **Notes**: when user attempts to clean the output using context menu 186 | * they invoke `JupyterFrontEnd.evtContextMenu` which caches the event 187 | * to enable commands and extensions to access it later; this leads to 188 | * a memory leak as the event holds the target node reference. 189 | */ 190 | dispose(): void { 191 | // TODO: remove ts-ignore during JupyterLab 4.0/TypeScript 5.0 migration 192 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 193 | // @ts-ignore 194 | this.node.replaceChildren(); 195 | super.dispose(); 196 | } 197 | } 198 | 199 | export const rendererFactory: IRenderMime.IRendererFactory = { 200 | safe: true, 201 | mimeTypes: [ 202 | 'text/plain', 203 | 'application/vnd.jupyter.stdout', 204 | 'application/vnd.jupyter.stderr', 205 | ], 206 | createRenderer: (options) => new MyRenderedText(options), 207 | }; 208 | -------------------------------------------------------------------------------- /style/base.css: -------------------------------------------------------------------------------- 1 | .limit-output-button { 2 | color: var(--jp-content-link-color) !important; 3 | background: var(--jp-layout-color1) !important; 4 | } 5 | 6 | .limit-output-button:hover { 7 | background: var(--jp-layout-color2) !important; 8 | } 9 | 10 | .limit-output-button:active { 11 | background: var(--jp-layout-color3) !important; 12 | } 13 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /tests/formatter_spec.ts: -------------------------------------------------------------------------------- 1 | import { limitByCharacters, limitByLines } from '../src/formatters'; 2 | import { expect } from 'chai'; 3 | 4 | describe('renders utils', () => { 5 | describe('limit by characters', () => { 6 | const splitter = 7 | /^(?.*?)\n{0,3}WARNING:.+characters.\n{0,3}(?.*?)$/s; 8 | const str = 'abc\n\ndefg\nhijkl\nnopqrs\ntuvwxyz012345\n789'; 9 | it('limits by starting characters', () => { 10 | const splitOutput = splitter.exec(limitByCharacters(str, 7, 0)); 11 | expect(splitOutput).to.exist; 12 | expect(splitOutput.groups.head).to.equal('abc\n\nde'); 13 | expect(splitOutput.groups.tail).to.equal(''); 14 | }); 15 | it('limits by ending characters', () => { 16 | const splitOutput = splitter.exec(limitByCharacters(str, 0, 6)); 17 | expect(splitOutput).to.exist; 18 | expect(splitOutput.groups.head).to.equal(''); 19 | expect(splitOutput.groups.tail).to.equal('45\n789'); 20 | }); 21 | it('limits by both starting and ending characters', () => { 22 | const splitOutput = splitter.exec(limitByCharacters(str, 7, 6)); 23 | expect(splitOutput).to.exist; 24 | expect(splitOutput.groups.head).to.equal('abc\n\nde'); 25 | expect(splitOutput.groups.tail).to.equal('45\n789'); 26 | }); 27 | it('does not limit if input is small', () => { 28 | const output = limitByCharacters(str, 700, 600); 29 | expect(output).to.equal(str); 30 | }); 31 | }); 32 | describe('limit by lines', () => { 33 | const splitter = 34 | /^(?.*?)\n{0,3}WARNING:.+lines.\n{0,3}(?.*?)$/s; 35 | const str = 'abc\n\ndefg\nhijkl\nnopqrs\ntuvwxyz012345\n789'; 36 | it('limits by starting lines', () => { 37 | const splitOutput = splitter.exec(limitByLines(str, 3, 0)); 38 | expect(splitOutput).to.exist; 39 | expect(splitOutput.groups.head).to.equal('abc\n\ndefg'); 40 | expect(splitOutput.groups.tail).to.equal(''); 41 | }); 42 | it('limits by ending lines', () => { 43 | const splitOutput = splitter.exec(limitByLines(str, 0, 2)); 44 | expect(splitOutput).to.exist; 45 | expect(splitOutput.groups.head).to.equal(''); 46 | expect(splitOutput.groups.tail).to.equal('\ntuvwxyz012345\n789'); 47 | }); 48 | it('limits by both starting and ending lines', () => { 49 | const splitOutput = splitter.exec(limitByLines(str, 3, 2)); 50 | expect(splitOutput).to.exist; 51 | expect(splitOutput.groups.head).to.equal('abc\n\ndefg'); 52 | expect(splitOutput.groups.tail).to.equal('\ntuvwxyz012345\n789'); 53 | }); 54 | it('does not limit if input is small', () => { 55 | const output = limitByLines(str, 700, 600); 56 | expect(output).to.equal(str); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "outDir": "lib", 17 | "rootDir": "src", 18 | "strict": true, 19 | "strictNullChecks": false, 20 | "target": "es2017", 21 | "types": [] 22 | }, 23 | "include": ["*"] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "outDir": "lib", 17 | "rootDir": "src", 18 | "strict": true, 19 | "strictNullChecks": false, 20 | "target": "es2018", 21 | "types": [] 22 | }, 23 | "include": ["src/*"] 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["tslint-plugin-prettier"], 3 | "rules": { 4 | "prettier": [true, { "singleQuote": true }], 5 | "align": [true, "parameters", "statements"], 6 | "await-promise": true, 7 | "ban": [ 8 | true, 9 | ["_", "forEach"], 10 | ["_", "each"], 11 | ["$", "each"], 12 | ["angular", "forEach"] 13 | ], 14 | "class-name": true, 15 | "comment-format": [true, "check-space"], 16 | "curly": true, 17 | "eofline": true, 18 | "forin": false, 19 | "indent": [true, "spaces", 2], 20 | "interface-name": [true, "always-prefix"], 21 | "jsdoc-format": true, 22 | "label-position": true, 23 | "max-line-length": [false], 24 | "member-access": false, 25 | "member-ordering": [false], 26 | "new-parens": true, 27 | "no-angle-bracket-type-assertion": true, 28 | "no-any": false, 29 | "no-arg": true, 30 | "no-bitwise": true, 31 | "no-conditional-assignment": true, 32 | "no-consecutive-blank-lines": false, 33 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 34 | "no-construct": true, 35 | "no-debugger": true, 36 | "no-default-export": false, 37 | "no-duplicate-variable": true, 38 | "no-empty": true, 39 | "no-eval": true, 40 | "no-floating-promises": true, 41 | "no-inferrable-types": false, 42 | "no-internal-module": true, 43 | "no-invalid-this": [true, "check-function-in-method"], 44 | "no-null-keyword": false, 45 | "no-reference": true, 46 | "no-require-imports": false, 47 | "no-shadowed-variable": false, 48 | "no-string-literal": false, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": true, 51 | "no-use-before-declare": false, 52 | "no-var-keyword": true, 53 | "no-var-requires": true, 54 | "object-literal-sort-keys": false, 55 | "one-line": [ 56 | true, 57 | "check-open-brace", 58 | "check-catch", 59 | "check-else", 60 | "check-finally", 61 | "check-whitespace" 62 | ], 63 | "one-variable-per-declaration": [true, "ignore-for-loop"], 64 | "quotemark": { 65 | "options": [true, "single", "avoid-escape"], 66 | "severity": "off" 67 | }, 68 | "radix": true, 69 | "semicolon": [true, "always", "ignore-bound-class-methods"], 70 | "switch-default": true, 71 | "trailing-comma": [ 72 | false, 73 | { 74 | "multiline": "never", 75 | "singleline": "never" 76 | } 77 | ], 78 | "triple-equals": [true, "allow-null-check", "allow-undefined-check"], 79 | "typedef": [false], 80 | "typedef-whitespace": [ 81 | false, 82 | { 83 | "call-signature": "nospace", 84 | "index-signature": "nospace", 85 | "parameter": "nospace", 86 | "property-declaration": "nospace", 87 | "variable-declaration": "nospace" 88 | }, 89 | { 90 | "call-signature": "space", 91 | "index-signature": "space", 92 | "parameter": "space", 93 | "property-declaration": "space", 94 | "variable-declaration": "space" 95 | } 96 | ], 97 | "use-isnan": true, 98 | "use-strict": [false], 99 | "variable-name": [ 100 | true, 101 | "check-format", 102 | "allow-leading-underscore", 103 | "ban-keywords", 104 | "allow-pascal-case" 105 | ], 106 | "whitespace": [ 107 | true, 108 | "check-branch", 109 | "check-operator", 110 | "check-separator", 111 | "check-type" 112 | ] 113 | }, 114 | "linterOptions": { 115 | "exclude": ["node_modules/**/*.ts", "node_modules/**/*.tsx", "**/*.d.ts"] 116 | } 117 | } 118 | --------------------------------------------------------------------------------