├── .appveyor.yml ├── .coveragerc ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .pylintrc ├── .travis.yml ├── AUTHORS.rst ├── CHANGELOG.rst ├── CODE_OF_CONDUCT.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── TODO.md ├── docs ├── Makefile ├── README.md ├── _static │ └── custom.css ├── authors.rst ├── conf.py ├── dev │ ├── index.rst │ ├── see.exceptions.rst │ ├── see.features.rst │ ├── see.inspector.rst │ ├── see.output.rst │ ├── see.term.rst │ └── see.tools.rst ├── index.rst ├── install.rst ├── license.rst ├── startup.rst └── usage.rst ├── extra ├── README.md ├── see.sublime-project └── vimrc ├── requirements.txt ├── see ├── __init__.py ├── exceptions.py ├── features.py ├── inspector.py ├── output.py ├── term.py └── tools.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── test_mod_inspector.py ├── test_mod_output.py ├── test_mod_term.py ├── test_mod_tools.py ├── test_see_result.py ├── test_term_width.py └── test_unicode.py /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Windows CI with AppVeyor.com 2 | 3 | build: off # skip the build step, just test 4 | 5 | matrix: 6 | fast_finish: true 7 | 8 | environment: 9 | matrix: 10 | - PYTHON: "C:\\Python27-x64" 11 | PYTHON_VERSION: "2.7.x" 12 | PYTHON_ARCH: "64" 13 | TOXENV: "py27" 14 | 15 | - PYTHON: "C:\\Python34-x64" 16 | PYTHON_VERSION: "3.4.x" 17 | PYTHON_ARCH: "64" 18 | TOXENV: "py34" 19 | 20 | - PYTHON: "C:\\Python35-x64" 21 | PYTHON_VERSION: "3.5.x" 22 | PYTHON_ARCH: "64" 23 | TOXENV: "py35" 24 | 25 | - PYTHON: "C:\\Python36-x64" 26 | PYTHON_VERSION: "3.6.x" 27 | PYTHON_ARCH: "64" 28 | TOXENV: "py36" 29 | 30 | - PYTHON: "C:\\Python37-x64" 31 | PYTHON_VERSION: "3.7.x" 32 | PYTHON_ARCH: "64" 33 | TOXENV: "py37" 34 | 35 | install: 36 | - python --version 37 | - pip install setuptools>=17.1 38 | - pip install mock>=2.0 39 | - pip install coverage 40 | 41 | test_script: 42 | - coverage run --source=see setup.py test 43 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # Coverage configuration 2 | # See: 3 | # http://coverage.readthedocs.org/ 4 | 5 | [report] 6 | # Regexes for lines to exclude from consideration 7 | exclude_lines = 8 | # Have to re-enable the standard pragma: 9 | pragma: no.?cover 10 | 11 | # Don't complain if non-runnable code isn't run: 12 | if 0: 13 | if __name__ == .__main__.: 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig http://editorconfig.org 2 | # See also: 3 | # extra/see.sublime-project 4 | # extra/vimrc 5 | 6 | # top-most editorconfig file 7 | root = true 8 | 9 | [*] 10 | end_of_line = lf 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.{py,rst}] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.yml] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Description of the issue] 4 | 5 | 6 | ## Steps to reproduce 7 | 8 | 1. [Step 1] 9 | 2. [Step 2] 10 | 3. [etc.] 11 | 12 | **Expected behaviour**: [...] 13 | 14 | **Actual behaviour**: [...] 15 | 16 | 17 | ## Version info 18 | 19 | * **See**: [see version] 20 | * **Python**: [python version] 21 | * **OS**: [operating system version] 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes #[issue number]. 2 | 3 | Changes proposed in this pull request: 4 | 5 | - [Bug fixed] 6 | - [Change in functionality] 7 | - [New feature] 8 | - [etc.] 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS?Store 2 | .*.swp 3 | *.sublime-workspace 4 | 5 | MANIFEST 6 | 7 | # ----------------------------------------------------------------------------- 8 | # https://github.com/github/gitignore/blob/master/Python.gitignore 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | env/ 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | .hypothesis/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # dotenv 92 | .env 93 | 94 | # virtualenv 95 | .venv 96 | venv/ 97 | ENV/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Add files or directories to the blacklist. They should be base names, not 11 | # paths. 12 | ignore=CVS 13 | 14 | # Add files or directories matching the regex patterns to the blacklist. The 15 | # regex matches against base names, not paths. 16 | ignore-patterns= 17 | 18 | # Pickle collected data for later comparisons. 19 | persistent=yes 20 | 21 | # List of plugins (as comma separated values of python modules names) to load, 22 | # usually to register additional checkers. 23 | load-plugins= 24 | 25 | # Use multiple processes to speed up Pylint. 26 | jobs=2 27 | 28 | # Allow loading of arbitrary C extensions. Extensions are imported into the 29 | # active Python interpreter and may run arbitrary code. 30 | unsafe-load-any-extension=no 31 | 32 | # A comma-separated list of package or module names from where C extensions may 33 | # be loaded. Extensions are loading into the active Python interpreter and may 34 | # run arbitrary code 35 | extension-pkg-whitelist= 36 | 37 | # Allow optimization of some AST trees. This will activate a peephole AST 38 | # optimizer, which will apply various small optimizations. For instance, it can 39 | # be used to obtain the result of joining multiple strings with the addition 40 | # operator. Joining a lot of strings can lead to a maximum recursion error in 41 | # Pylint and this flag can prevent that. It has one side effect, the resulting 42 | # AST will be different than the one from reality. This option is deprecated 43 | # and it will be removed in Pylint 2.0. 44 | optimize-ast=no 45 | 46 | 47 | [MESSAGES CONTROL] 48 | 49 | # Only show warnings with the listed confidence levels. Leave empty to show 50 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 51 | confidence= 52 | 53 | # Enable the message, report, category or checker with the given id(s). You can 54 | # either give multiple identifier separated by comma (,) or put this option 55 | # multiple time (only on the command line, not in the configuration file where 56 | # it should appear only once). See also the "--disable" option for examples. 57 | #enable= 58 | 59 | # Disable the message, report, category or checker with the given id(s). You 60 | # can either give multiple identifiers separated by comma (,) or put this 61 | # option multiple times (only on the command line, not in the configuration 62 | # file where it should appear only once).You can also use "--disable=all" to 63 | # disable everything first and then reenable specific checks. For example, if 64 | # you want to run only the similarities checker, you can use "--disable=all 65 | # --enable=similarities". If you want to run only the classes checker, but have 66 | # no Warning level messages displayed, use"--disable=all --enable=classes 67 | # --disable=W" 68 | disable=too-few-public-methods,no-else-return 69 | #disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,import-star-module-level,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,long-suffix,old-ne-operator,old-octal-literal,suppressed-message,useless-suppression 70 | 71 | 72 | [REPORTS] 73 | 74 | # Set the output format. Available formats are text, parseable, colorized, msvs 75 | # (visual studio) and html. You can also give a reporter class, eg 76 | # mypackage.mymodule.MyReporterClass. 77 | output-format=text 78 | 79 | # Put messages in a separate file for each module / package specified on the 80 | # command line instead of printing them on stdout. Reports (if any) will be 81 | # written in a file name "pylint_global.[txt|html]". This option is deprecated 82 | # and it will be removed in Pylint 2.0. 83 | files-output=no 84 | 85 | # Tells whether to display a full report or only the messages 86 | reports=no 87 | 88 | # Python expression which should return a note less than 10 (10 is the highest 89 | # note). You have access to the variables errors warning, statement which 90 | # respectively contain the number of errors / warnings messages and the total 91 | # number of statements analyzed. This is used by the global evaluation report 92 | # (RP0004). 93 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 94 | 95 | # Template used to display messages. This is a python new-style format string 96 | # used to format the message information. See doc for all details 97 | #msg-template= 98 | 99 | 100 | [BASIC] 101 | 102 | # Good variable names which should always be accepted, separated by a comma 103 | good-names=i,j,k,ex,Run,_ 104 | 105 | # Bad variable names which should always be refused, separated by a comma 106 | bad-names=foo,bar,baz,toto,tutu,tata 107 | 108 | # Colon-delimited sets of names that determine each other's naming style when 109 | # the name regexes allow several styles. 110 | name-group= 111 | 112 | # Include a hint for the correct naming format with invalid-name 113 | include-naming-hint=no 114 | 115 | # List of decorators that produce properties, such as abc.abstractproperty. Add 116 | # to this list to register other decorators that produce valid properties. 117 | property-classes=abc.abstractproperty 118 | 119 | # Regular expression matching correct module names 120 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 121 | 122 | # Naming hint for module names 123 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 124 | 125 | # Regular expression matching correct constant names 126 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 127 | 128 | # Naming hint for constant names 129 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 130 | 131 | # Regular expression matching correct class names 132 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 133 | 134 | # Naming hint for class names 135 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 136 | 137 | # Regular expression matching correct function names 138 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 139 | 140 | # Naming hint for function names 141 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 142 | 143 | # Regular expression matching correct method names 144 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 145 | 146 | # Naming hint for method names 147 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 148 | 149 | # Regular expression matching correct attribute names 150 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 151 | 152 | # Naming hint for attribute names 153 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 154 | 155 | # Regular expression matching correct argument names 156 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 157 | 158 | # Naming hint for argument names 159 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 160 | 161 | # Regular expression matching correct variable names 162 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 163 | 164 | # Naming hint for variable names 165 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 166 | 167 | # Regular expression matching correct class attribute names 168 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 169 | 170 | # Naming hint for class attribute names 171 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 172 | 173 | # Regular expression matching correct inline iteration names 174 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 175 | 176 | # Naming hint for inline iteration names 177 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 178 | 179 | # Regular expression which should only match function or class names that do 180 | # not require a docstring. 181 | no-docstring-rgx=^_ 182 | 183 | # Minimum line length for functions/classes that require docstrings, shorter 184 | # ones are exempt. 185 | docstring-min-length=-1 186 | 187 | 188 | [ELIF] 189 | 190 | # Maximum number of nested blocks for function / method body 191 | max-nested-blocks=5 192 | 193 | 194 | [FORMAT] 195 | 196 | # Maximum number of characters on a single line. 197 | max-line-length=100 198 | 199 | # Regexp for a line that is allowed to be longer than the limit. 200 | ignore-long-lines=^\s*(# )??$ 201 | 202 | # Allow the body of an if to be on the same line as the test if there is no 203 | # else. 204 | single-line-if-stmt=no 205 | 206 | # List of optional constructs for which whitespace checking is disabled. `dict- 207 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 208 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 209 | # `empty-line` allows space-only lines. 210 | no-space-check=trailing-comma,dict-separator 211 | 212 | # Maximum number of lines in a module 213 | max-module-lines=1000 214 | 215 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 216 | # tab). 217 | indent-string=' ' 218 | 219 | # Number of spaces of indent required inside a hanging or continued line. 220 | indent-after-paren=4 221 | 222 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 223 | expected-line-ending-format= 224 | 225 | 226 | [LOGGING] 227 | 228 | # Logging modules to check that the string format arguments are in logging 229 | # function parameter format 230 | logging-modules=logging 231 | 232 | 233 | [MISCELLANEOUS] 234 | 235 | # List of note tags to take in consideration, separated by a comma. 236 | notes=FIXME,XXX,TODO 237 | 238 | 239 | [SIMILARITIES] 240 | 241 | # Minimum lines number of a similarity. 242 | min-similarity-lines=4 243 | 244 | # Ignore comments when computing similarities. 245 | ignore-comments=yes 246 | 247 | # Ignore docstrings when computing similarities. 248 | ignore-docstrings=yes 249 | 250 | # Ignore imports when computing similarities. 251 | ignore-imports=no 252 | 253 | 254 | [SPELLING] 255 | 256 | # Spelling dictionary name. Available dictionaries: none. To make it working 257 | # install python-enchant package. 258 | spelling-dict= 259 | 260 | # List of comma separated words that should not be checked. 261 | spelling-ignore-words= 262 | 263 | # A path to a file that contains private dictionary; one word per line. 264 | spelling-private-dict-file= 265 | 266 | # Tells whether to store unknown words to indicated private dictionary in 267 | # --spelling-private-dict-file option instead of raising a message. 268 | spelling-store-unknown-words=no 269 | 270 | 271 | [TYPECHECK] 272 | 273 | # Tells whether missing members accessed in mixin class should be ignored. A 274 | # mixin class is detected if its name ends with "mixin" (case insensitive). 275 | ignore-mixin-members=yes 276 | 277 | # List of module names for which member attributes should not be checked 278 | # (useful for modules/projects where namespaces are manipulated during runtime 279 | # and thus existing member attributes cannot be deduced by static analysis. It 280 | # supports qualified module names, as well as Unix pattern matching. 281 | ignored-modules= 282 | 283 | # List of class names for which member attributes should not be checked (useful 284 | # for classes with dynamically set attributes). This supports the use of 285 | # qualified names. 286 | ignored-classes=optparse.Values,thread._local,_thread._local 287 | 288 | # List of members which are set dynamically and missed by pylint inference 289 | # system, and so shouldn't trigger E1101 when accessed. Python regular 290 | # expressions are accepted. 291 | generated-members= 292 | 293 | # List of decorators that produce context managers, such as 294 | # contextlib.contextmanager. Add to this list to register other decorators that 295 | # produce valid context managers. 296 | contextmanager-decorators=contextlib.contextmanager 297 | 298 | 299 | [VARIABLES] 300 | 301 | # Tells whether we should check for unused import in __init__ files. 302 | init-import=no 303 | 304 | # A regular expression matching the name of dummy variables (i.e. expectedly 305 | # not used). 306 | dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy 307 | 308 | # List of additional names supposed to be defined in builtins. Remember that 309 | # you should avoid to define new builtins when possible. 310 | additional-builtins= 311 | 312 | # List of strings which can identify a callback function by name. A callback 313 | # name must start or end with one of those strings. 314 | callbacks=cb_,_cb 315 | 316 | # List of qualified module names which can have objects that can redefine 317 | # builtins. 318 | redefining-builtins-modules=six.moves,future.builtins 319 | 320 | 321 | [CLASSES] 322 | 323 | # List of method names used to declare (i.e. assign) instance attributes. 324 | defining-attr-methods=__init__,__new__,setUp 325 | 326 | # List of valid names for the first argument in a class method. 327 | valid-classmethod-first-arg=cls 328 | 329 | # List of valid names for the first argument in a metaclass class method. 330 | valid-metaclass-classmethod-first-arg=mcs 331 | 332 | # List of member names, which should be excluded from the protected access 333 | # warning. 334 | exclude-protected=_asdict,_fields,_replace,_source,_make 335 | 336 | 337 | [DESIGN] 338 | 339 | # Maximum number of arguments for function / method 340 | max-args=5 341 | 342 | # Argument names that match this expression will be ignored. Default to name 343 | # with leading underscore 344 | ignored-argument-names=_.* 345 | 346 | # Maximum number of locals for function / method body 347 | max-locals=15 348 | 349 | # Maximum number of return / yield for function / method body 350 | max-returns=6 351 | 352 | # Maximum number of branch for function / method body 353 | max-branches=12 354 | 355 | # Maximum number of statements in function / method body 356 | max-statements=50 357 | 358 | # Maximum number of parents for a class (see R0901). 359 | max-parents=7 360 | 361 | # Maximum number of attributes for a class (see R0902). 362 | max-attributes=7 363 | 364 | # Minimum number of public methods for a class (see R0903). 365 | min-public-methods=2 366 | 367 | # Maximum number of public methods for a class (see R0904). 368 | max-public-methods=20 369 | 370 | # Maximum number of boolean expressions in a if statement 371 | max-bool-expr=5 372 | 373 | 374 | [IMPORTS] 375 | 376 | # Deprecated modules which should not be used, separated by a comma 377 | deprecated-modules=optparse 378 | 379 | # Create a graph of every (i.e. internal and external) dependencies in the 380 | # given file (report RP0402 must not be disabled) 381 | import-graph= 382 | 383 | # Create a graph of external dependencies in the given file (report RP0402 must 384 | # not be disabled) 385 | ext-import-graph= 386 | 387 | # Create a graph of internal dependencies in the given file (report RP0402 must 388 | # not be disabled) 389 | int-import-graph= 390 | 391 | # Force import order to recognize a module as part of the standard 392 | # compatibility libraries. 393 | known-standard-library= 394 | 395 | # Force import order to recognize a module as part of a third party library. 396 | known-third-party=enchant 397 | 398 | # Analyse import fallback blocks. This can be used to support both Python 2 and 399 | # 3 compatible code, which means that the block might have code that exists 400 | # only in one or another interpreter, leading to false positives when analysed. 401 | analyse-fallback-blocks=no 402 | 403 | 404 | [EXCEPTIONS] 405 | 406 | # Exceptions that will emit a warning when being caught. Defaults to 407 | # "Exception" 408 | overgeneral-exceptions=Exception 409 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI configuration for remote testing. 2 | # See: 3 | # https://docs.travis-ci.com/user/customizing-the-build/ 4 | # https://docs.travis-ci.com/user/languages/python 5 | # https://lint.travis-ci.org 6 | 7 | language: python 8 | 9 | python: 10 | - "pypy" 11 | - "pypy3" 12 | - "2.7" 13 | - "3.4" 14 | - "3.5" 15 | - "3.6" 16 | - "nightly" 17 | 18 | matrix: 19 | fast_finish: true 20 | allow_failures: 21 | - python: "pypy" 22 | - python: "pypy3" 23 | - python: "nightly" 24 | 25 | sudo: false 26 | 27 | install: 28 | - pip install 'setuptools>=17.1' 29 | - pip install 'mock>=2.0' 30 | - pip install coverage 31 | 32 | script: 33 | - coverage run --source=see setup.py test 34 | 35 | after_success: 36 | - pip install coveralls 37 | - coveralls 38 | 39 | after_script: 40 | - coverage report 41 | - pip install flake8 42 | - flake8 see 43 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | **see** is written and maintained by Liam Cooke, with features, patches, and 5 | suggestions from various contributors: 6 | 7 | - `Liam Cooke `__ 8 | - Ian Gowen 9 | - `Baishampayan Ghose `__ 10 | - `Jeremy Dunck `__ 11 | - Gabriel Genellina 12 | - `Eduardo de Oliveira Padoan `__ 13 | - `Guff `__ 14 | - `Bob Farrell `__ 15 | - `Ed Page `__ 16 | - Charlie Nolan 17 | - `Steve Losh `__ 18 | - `Adam Lloyd `__ 19 | - `Andrei Fokau `__ 20 | - `Christopher Toth `__ 21 | - `Elias Fotinis `__ 22 | - `Frederic De Groef `__ 23 | - `hugovk `__ 24 | - `dbaker `__ 25 | - `Rodrigo Ferreira de Souza `__ 26 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Release History 2 | =============== 3 | 4 | .. See http://keepachangelog.com/ 5 | 6 | All notable changes to this project will be documented in this file. This 7 | project adheres to `Semantic Versioning `__ as of v1.1.0. 8 | 9 | 10 | Unreleased_ 11 | -------------------- 12 | 13 | **Added** 14 | 15 | - ``.filter_ignoring_case()`` -- Like ``.filter()`` but case-insensitive. 16 | 17 | - ``.exclude()`` -- The opposite of ``.filter()``. Excludes results that match 18 | a regular expression. 19 | 20 | **Deprecated** 21 | 22 | - Dropped support for Python 2.6 and 3.3. 23 | 24 | 25 | v1.4.1_ / 2017-09-24 26 | -------------------- 27 | 28 | **Changed** 29 | 30 | - Limit the output indentation to a maximum of 4 spaces. 31 | 32 | **Fixed** 33 | 34 | - Fixed the function name in the 'deprecated' message that appears when you use 35 | the ``r`` argument. 36 | 37 | 38 | v1.4.0_ / 2017-05-21 39 | -------------------- 40 | 41 | **Added** 42 | 43 | - New API for filtering the output. Call ``see().filter()`` with either 44 | a shell-style wildcard pattern or a regular expression. 45 | 46 | - Test each of the ``is`` functions from the *inspect* module, such as 47 | ``isclass`` and ``isgenerator``, and include them in the result. 48 | 49 | - Documentation using Sphinx. 50 | 51 | **Deprecated** 52 | 53 | - Deprecated the ``pattern`` and ``r`` arguments. They are still usable (via 54 | ``*args`` and ``**kwargs``) but they will be removed in a later release. 55 | 56 | 57 | v1.3.2_ / 2016-04-30 58 | -------------------- 59 | 60 | **Fixed** 61 | 62 | - Misaligned columns with Unicode attribute names that include wide CJK 63 | characters or combining characters. 64 | 65 | 66 | v1.3.1_ / 2016-04-26 67 | -------------------- 68 | 69 | **Fixed** 70 | 71 | - Misaligned columns when some attributes have unusually long names. 72 | 73 | 74 | v1.3.0_ / 2016-04-24 75 | -------------------- 76 | 77 | **Added** 78 | 79 | - Unit tests, continuous integration with Travis, and coverage reports 80 | published on Coveralls.io. 81 | 82 | - For Windows, adjust the output to fit the terminal width as on other 83 | platforms. 84 | 85 | **Fixed** 86 | 87 | - Replaced one instance of ``dir`` with ``hasattr``. 88 | 89 | 90 | v1.2.0_ / 2016-04-17 91 | -------------------- 92 | 93 | **Added** 94 | 95 | - Support for Python 3.5's matrix multiplication operators. 96 | 97 | 98 | v1.1.1_ / 2015-04-17 99 | -------------------- 100 | 101 | **Fixed** 102 | 103 | - Broken on Windows due to a dependency on the fcntl module. 104 | 105 | 106 | v1.1.0_ / 2015-03-27 107 | -------------------- 108 | 109 | **Added** 110 | 111 | - Output is adjusted to fit the terminal width. 112 | - Print ``?`` after any attributes that raised an exception. 113 | 114 | **Fixed** 115 | 116 | - Unhandled exceptions when reading attributes. 117 | 118 | 119 | v1.0.1_ / 2010-10-17 120 | -------------------- 121 | 122 | **Changed** 123 | 124 | - License is now BSD (was GPL). 125 | 126 | 127 | v1.0_ / 2010-07-31 128 | ------------------ 129 | 130 | **Added** 131 | 132 | - Justified columns. 133 | 134 | **Changed** 135 | 136 | - Output is indented to line up with the prompt. For example, if the prompt 137 | is a single ``>`` followed by a space, the output will be indented by two 138 | spaces. 139 | 140 | **Fixed** 141 | 142 | - Exception raised when ``see()`` has nothing to display. 143 | 144 | 145 | v0.5.4_ / 2009-07-23 146 | -------------------- 147 | 148 | **Fixed** 149 | 150 | - Calling ``see()`` first with no arguments would return nothing. 151 | 152 | 153 | v0.5.3_ / 2009-04-12 154 | -------------------- 155 | 156 | **Added** 157 | 158 | - Running *see.py* as a script will show documentation, equivalent to 159 | ``help(see)``. 160 | - If you want to be lazy, you can ``from see import *``, and only ``see()`` 161 | will be imported. 162 | 163 | **Changed** 164 | 165 | - Results are spaced out more, and line up with the default interpreter prompt. 166 | - Unary operator symbols changed from ``+@`` and ``-@`` to ``+obj`` and 167 | ``-obj`` respectively. 168 | - Revised code documentation and examples. 169 | - New project homepage. 170 | 171 | **Fixed** 172 | 173 | - ``see()`` output could be modified, but would still print the original 174 | results. The output list now acts like a tuple. 175 | 176 | 177 | v0.5.2_ / 2009-03-16 178 | -------------------- 179 | 180 | **Added** 181 | 182 | - Calling ``see()`` without arguments shows local variables. 183 | 184 | 185 | v0.5.1_ / 2009-03-13 186 | -------------------- 187 | 188 | **Changed** 189 | 190 | - Filename pattern matching is now the default, e.g. ``see('', '.is*')``. 191 | Regular expression matching can still be done by using the ``r`` argument. 192 | 193 | **Fixed** 194 | 195 | - Python 3.0: After the first ``see()`` call, subsequent calls would give no 196 | output for some objects. 197 | - Python 3.0: Regular expression and filename pattern matching would also 198 | result in nothing being output. 199 | 200 | 201 | v0.5_ / 2009-03-07 202 | ------------------ 203 | 204 | **Added** 205 | 206 | - Now returns a list-like object, for iterating through the results, while 207 | still showing the human-readable output when run interactively. 208 | - Optional ``regex`` and ``fn`` arguments, for regular expression and filename 209 | pattern matching, respectively. 210 | 211 | 212 | v0.4.1_ / 2009-02-23 213 | -------------------- 214 | 215 | **Added** 216 | 217 | - New attributes: ``str()`` and ``repr()``. 218 | 219 | 220 | v0.4_ / 2009-02-19 221 | ------------------ 222 | 223 | **Added** 224 | 225 | - For Python 3.0, new attributes are included, and deprecated attributes are no 226 | longer shown. 227 | - Instructions added for using this with iPython. 228 | 229 | **Changed** 230 | 231 | - (Pseudo-)static variables moved outside the ``see()`` function. This may or 232 | may not be more efficient. 233 | - If the object has a docstring set, ``help()`` is shown in the list instead of 234 | ``?``. 235 | 236 | **Fixed** 237 | 238 | - AttributeError with Django class attributes fixed. 239 | - The correct symbols are now shown for objects implementing ``__divmod__``, 240 | ``__floordiv__`` and ``__cmp__``. 241 | 242 | 243 | v0.3.1_ / 2009-02-18 244 | -------------------- 245 | 246 | **Added** 247 | 248 | - Symbols for binary arithmetic operations using reflected (swapped) operands. 249 | - ``with`` and ``reversed()`` symbols. 250 | 251 | 252 | v0.3_ / 2009-02-18 253 | ------------------ 254 | 255 | **Added** 256 | 257 | - Rudimentary Python 3.0 support. 258 | - Created a *setup.py* installation script. 259 | 260 | **Fixed** 261 | 262 | - Outdated documentation link in the *README* file. 263 | 264 | 265 | v0.2 / 2009-02-17 266 | ----------------- 267 | 268 | **Added** 269 | 270 | - ``.*`` symbol for the ``__getattr__`` attribute. 271 | - ``help()``` documentation. 272 | 273 | **Changed** 274 | 275 | - Special attribute symbols reordered. 276 | - Unary addition and subtraction changed to ``+@`` and ``-@`` respectively. 277 | 278 | 279 | v0.1 / 2009-02-16 280 | ----------------- 281 | 282 | - Original release. 283 | 284 | 285 | .. _unreleased: https://github.com/ljcooke/see/compare/v1.4.1...develop 286 | 287 | .. _v1.4.1: https://github.com/ljcooke/see/compare/v1.4.0...v1.4.1 288 | .. _v1.4.0: https://github.com/ljcooke/see/compare/v1.3.2...v1.4.0 289 | .. _v1.3.2: https://github.com/ljcooke/see/compare/v1.3.1...v1.3.2 290 | .. _v1.3.1: https://github.com/ljcooke/see/compare/v1.3.0...v1.3.1 291 | .. _v1.3.0: https://github.com/ljcooke/see/compare/v1.2.0...v1.3.0 292 | .. _v1.2.0: https://github.com/ljcooke/see/compare/v1.1.1...v1.2.0 293 | .. _v1.1.1: https://github.com/ljcooke/see/compare/v1.1.0...v1.1.1 294 | .. _v1.1.0: https://github.com/ljcooke/see/compare/v1.0.1...v1.1.0 295 | 296 | .. _v1.0.1: https://github.com/ljcooke/see/compare/v1.0-fixed...v1.0.1 297 | .. _v1.0: https://github.com/ljcooke/see/compare/v0.5.4...v1.0-fixed 298 | .. _v0.5.4: https://github.com/ljcooke/see/compare/v0.5.3...v0.5.4 299 | .. _v0.5.3: https://github.com/ljcooke/see/compare/v0.5.2...v0.5.3 300 | .. _v0.5.2: https://github.com/ljcooke/see/compare/v0.5.1...v0.5.2 301 | .. _v0.5.1: https://github.com/ljcooke/see/compare/v0.5...v0.5.1 302 | .. _v0.5: https://github.com/ljcooke/see/compare/v0.4.1...v0.5 303 | .. _v0.4.1: https://github.com/ljcooke/see/compare/v0.4...v0.4.1 304 | .. _v0.4: https://github.com/ljcooke/see/compare/v0.3.1...v0.4 305 | .. _v0.3.1: https://github.com/ljcooke/see/compare/v0.3...v0.3.1 306 | .. _v0.3: https://github.com/ljcooke/see/compare/v0.2...v0.3 307 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at see@liamcooke.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2009-2018, Liam Cooke 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include see *.py 2 | recursive-include tests *.py 3 | 4 | include *.md *.rst LICENSE 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | python2 setup.py test 4 | python3 setup.py test 5 | 6 | .PHONY: coverage 7 | coverage: 8 | coverage run --source=see setup.py test 9 | coverage report 10 | coverage html 11 | flake8 see 12 | 13 | .PHONY: lint 14 | lint: 15 | flake8 see 16 | pylint see 17 | 18 | .PHONY: clean 19 | clean: 20 | rm -rf build 21 | rm -rf ./*.pyc see/*.pyc __pycache__ see/__pycache__ 22 | rm -rf .eggs *.egg-info 23 | (cd docs && make clean) 24 | 25 | .PHONY: docs 26 | docs: 27 | (cd docs && make html) 28 | 29 | .PHONY: dist 30 | dist: 31 | python3 setup.py sdist bdist_wheel 32 | 33 | .PHONY: publish 34 | publish: 35 | python3 setup.py sdist bdist_wheel upload # -r testpypi 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # see: dir for humans 2 | 3 | [![](https://img.shields.io/pypi/v/see.svg)](https://pypi.org/project/see/) 4 | 5 | **see** is an alternative to `dir()`, for Python 2.7 and 3.4+. 6 | 7 | It neatly summarises what you can do with an object. 8 | Use it to inspect your code or learn new APIs. 9 | 10 | 11 | ## Example 12 | 13 | 14 | 15 | Say you have an object which you'd like to know more about: 16 | 17 | ``` 18 | >>> from datetime import timedelta 19 | ``` 20 | 21 | Try inspecting the object with `see`: 22 | 23 | ``` 24 | >>> see(timedelta) 25 | isclass + - 26 | * / // 27 | % +obj -obj 28 | < <= == 29 | != > >= 30 | abs() bool() dir() 31 | divmod() hash() help() 32 | repr() str() .days 33 | .max .microseconds .min 34 | .resolution .seconds .total_seconds() 35 | ``` 36 | 37 | Here we can discover some things about it, such as: 38 | 39 | - The object is a class. 40 | - You can add something to it with the `+` operator. 41 | - It has a `seconds` attribute. 42 | - It has a `total_seconds` attribute which is a function. 43 | 44 | Compare with the output of `dir`: 45 | 46 | ``` 47 | >>> dir(timedelta) 48 | ['__abs__', '__add__', '__bool__', '__class__', '__delattr__', ' 49 | __dir__', '__divmod__', '__doc__', '__eq__', '__floordiv__', '__ 50 | format__', '__ge__', '__getattribute__', '__gt__', '__hash__', ' 51 | __init__', '__init_subclass__', '__le__', '__lt__', '__mod__', ' 52 | __mul__', '__ne__', '__neg__', '__new__', '__pos__', '__radd__', 53 | '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rf 54 | loordiv__', '__rmod__', '__rmul__', '__rsub__', '__rtruediv__', 55 | '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclassho 56 | ok__', '__truediv__', 'days', 'max', 'microseconds', 'min', 'res 57 | olution', 'seconds', 'total_seconds'] 58 | ``` 59 | 60 | You can filter the results of `see` using a wildcard pattern 61 | or a regular expression: 62 | 63 | ``` 64 | >>> see(timedelta).filter('*sec*') 65 | .microseconds .seconds .total_seconds() 66 | 67 | >>> see(timedelta).filter('/^d/') 68 | dir() divmod() 69 | ``` 70 | 71 | 72 | ## Documentation 73 | 74 | Documentation is available at . 75 | 76 | - [Installation](https://ljcooke.github.io/see/install.html) 77 | - [Usage](https://ljcooke.github.io/see/usage.html) 78 | - [Startup File](https://ljcooke.github.io/see/startup.html) 79 | - [Developer Reference](https://ljcooke.github.io/see/dev/index.html) 80 | 81 | 82 | ## Contributing 83 | 84 | The source code is available from [GitHub](https://github.com/ljcooke/see): 85 | 86 | ```sh 87 | git clone https://github.com/ljcooke/see.git 88 | ``` 89 | 90 | Contributions are welcome. 91 | 92 | - [Change Log](https://github.com/ljcooke/see/blob/develop/CHANGELOG.rst) 93 | - [Code of Conduct](https://github.com/ljcooke/see/blob/develop/CODE_OF_CONDUCT.md) 94 | - [Authors](https://github.com/ljcooke/see/blob/develop/AUTHORS.rst) 95 | - [License](https://github.com/ljcooke/see/blob/develop/LICENSE) 96 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # To do 2 | 3 | ## For v2.0.0 4 | 5 | - [ ] Remove the `pattern` and `r` arguments (see `handle_deprecated_args`). 6 | 7 | - [ ] Update `SeeResult.filter_ignoring_case` to support wildcard patterns 8 | and to require regular expression strings to be in the form `/pattern/`, 9 | to be consistent with `SeeResult.filter`. 10 | 11 | - [ ] Update `SeeResult.exclude` to support wildcard patterns 12 | and to require regular expression strings to be in the form `/pattern/`, 13 | to be consistent with `SeeResult.filter`. 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python3 -msphinx 7 | SPHINXPROJ = see 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | docs 2 | ==== 3 | 4 | This folder contains files for building the documentation with 5 | [Sphinx](http://www.sphinx-doc.org/en/stable/). 6 | 7 | Read the documentation at: 8 | [https://ljcooke.github.io/see/](https://ljcooke.github.io/see/) 9 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700|Ubuntu:400,700'); 2 | 3 | 4 | 5 | html, body, div.body { 6 | background: #fafafa; 7 | } 8 | 9 | 10 | 11 | dl.class, 12 | dl.exception, 13 | dl.function { 14 | background: #fff; 15 | border: 1px solid #e5e5e5; 16 | border-radius: 4px; 17 | margin-top: 1.5em; 18 | padding: 1em 1em 0.25em; 19 | } 20 | 21 | dl.class, 22 | dl.exception { 23 | border-width: 2px; 24 | } 25 | 26 | dl.class .property, 27 | dl.exception .property { 28 | font-style: inherit; 29 | font-weight: bold; 30 | } 31 | 32 | 33 | 34 | code.descclassname, 35 | code.descname, 36 | .sig-paren { 37 | font-size: 1em; 38 | } 39 | 40 | 41 | 42 | pre { 43 | border-radius: 4px; 44 | } 45 | 46 | 47 | 48 | div.admonition { 49 | border-radius: 4px; 50 | } 51 | 52 | div.admonition p.admonition-title { 53 | font-size: 1em; 54 | font-style: italic; 55 | } 56 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. index:: 2 | single: Authors 3 | 4 | .. include:: ../AUTHORS.rst 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # see documentation build configuration file, created by 5 | # sphinx-quickstart on Sat May 20 09:46:19 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.coverage', 37 | 'sphinx.ext.doctest', 38 | 'sphinx.ext.githubpages', 39 | ] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # The suffix(es) of source filenames. 45 | # You can specify multiple suffix as a list of string: 46 | # 47 | # source_suffix = ['.rst', '.md'] 48 | source_suffix = '.rst' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = 'see' 55 | copyright = '2009–2018, Liam Cooke' 56 | author = 'Liam Cooke' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = '1.4.1' 64 | # The full version, including alpha/beta/rc tags. 65 | release = version 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | # This patterns also effect to html_static_path and html_extra_path 77 | exclude_patterns = [ 78 | '_build', 'Thumbs.db', '.DS_Store', 79 | ] 80 | 81 | # The name of the Pygments (syntax highlighting) style to use. 82 | pygments_style = 'sphinx' 83 | 84 | # If true, `todo` and `todoList` produce output, else they produce nothing. 85 | todo_include_todos = False 86 | 87 | 88 | # -- Options for HTML output ---------------------------------------------- 89 | 90 | # The theme to use for HTML and HTML Help pages. See the documentation for 91 | # a list of builtin themes. 92 | # 93 | html_theme = 'alabaster' 94 | 95 | # Theme options are theme-specific and customize the look and feel of a theme 96 | # further. For a list of options available for each theme, see the 97 | # documentation. 98 | 99 | html_theme_options = { 100 | # This value must end with a trailing slash. 101 | #'canonical_url': '', 102 | 103 | 'fixed_sidebar': True, 104 | 'show_powered_by': False, 105 | 106 | # Fonts - see _static/custom.css 107 | 'font_family': '"Ubuntu", -apple-system, "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "Helvetica", sans-serif', 108 | 'font_size': '1em', 109 | 'code_font_family': 'Ubuntu Mono, Consolas, Menlo, "Deja Vu Sans Mono", "Bitstream Vera Sans Mono", monospace', 110 | 'code_font_size': '1em', 111 | 'body_text': '#333', 112 | 'footer_text': '#888', 113 | 'sidebar_text': '#888', 114 | } 115 | 116 | html_theme_options['caption_font_family'] = html_theme_options['font_family'] 117 | html_theme_options['head_font_family'] = html_theme_options['font_family'] 118 | 119 | # Add any paths that contain custom static files (such as style sheets) here, 120 | # relative to this directory. They are copied after the builtin static files, 121 | # so a file named "default.css" will overwrite the builtin "default.css". 122 | html_static_path = ['_static'] 123 | 124 | # http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 125 | html_sidebars = { 126 | '**': [ 127 | 'about.html', 128 | 'navigation.html', 129 | 'relations.html', 130 | 'searchbox.html', 131 | 'donate.html', 132 | ], 133 | } 134 | 135 | # If true, links to the reST sources are added to the pages. 136 | html_show_sourcelink = False 137 | 138 | # Path to custom.css 139 | html_static_path = ['_static'] 140 | 141 | 142 | 143 | # -- Options for HTMLHelp output ------------------------------------------ 144 | 145 | # Output file base name for HTML help builder. 146 | htmlhelp_basename = 'seedoc' 147 | 148 | 149 | # -- Options for LaTeX output --------------------------------------------- 150 | 151 | latex_elements = { 152 | # The paper size ('letterpaper' or 'a4paper'). 153 | # 154 | # 'papersize': 'letterpaper', 155 | 156 | # The font size ('10pt', '11pt' or '12pt'). 157 | # 158 | # 'pointsize': '10pt', 159 | 160 | # Additional stuff for the LaTeX preamble. 161 | # 162 | # 'preamble': '', 163 | 164 | # Latex figure (float) alignment 165 | # 166 | # 'figure_align': 'htbp', 167 | } 168 | 169 | # Grouping the document tree into LaTeX files. List of tuples 170 | # (source start file, target name, title, 171 | # author, documentclass [howto, manual, or own class]). 172 | latex_documents = [ 173 | (master_doc, 'see.tex', 'see Documentation', 174 | 'Liam Cooke', 'manual'), 175 | ] 176 | 177 | 178 | # -- Options for manual page output --------------------------------------- 179 | 180 | # One entry per manual page. List of tuples 181 | # (source start file, name, description, authors, manual section). 182 | man_pages = [ 183 | (master_doc, 'see', 'see Documentation', 184 | [author], 1) 185 | ] 186 | 187 | 188 | # -- Options for Texinfo output ------------------------------------------- 189 | 190 | # Grouping the document tree into Texinfo files. List of tuples 191 | # (source start file, target name, title, author, 192 | # dir menu entry, description, category) 193 | texinfo_documents = [ 194 | (master_doc, 'see', 'see Documentation', 195 | author, 'see', 'One line description of project.', 196 | 'Miscellaneous'), 197 | ] 198 | 199 | 200 | 201 | # -- Options for Epub output ---------------------------------------------- 202 | 203 | # Bibliographic Dublin Core info. 204 | epub_title = project 205 | epub_author = author 206 | epub_publisher = author 207 | epub_copyright = copyright 208 | 209 | # The unique identifier of the text. This can be a ISBN number 210 | # or the project homepage. 211 | # 212 | # epub_identifier = '' 213 | 214 | # A unique identification for the text. 215 | # 216 | # epub_uid = '' 217 | 218 | # A list of files that should not be packed into the epub file. 219 | epub_exclude_files = ['search.html'] 220 | 221 | 222 | -------------------------------------------------------------------------------- /docs/dev/index.rst: -------------------------------------------------------------------------------- 1 | .. API docs, modified from the output of: 2 | sphinx-apidoc -e -M -f -o docs/api see 3 | 4 | Contributing 5 | ============ 6 | 7 | Get the source code 8 | ------------------- 9 | 10 | The :index:`source code` is maintained 11 | `on GitHub `_. 12 | Contributions are welcome. 13 | 14 | * `Change Log `_ 15 | * `Code of Conduct `_ 16 | 17 | 18 | Continuous integration 19 | ---------------------- 20 | 21 | The code repository on GitHub is integrated with a few continuous integration 22 | services which come into effect each time code is pushed: 23 | 24 | * `Travis CI `_ runs the unit tests in 25 | a Linux environment for each supported Python release. 26 | * `AppVeyor `_ runs the unit tests 27 | in a Windows environment. 28 | * `Coveralls `_ tracks how much of the 29 | code is covered by the unit tests. This is updated by Travis when a test 30 | succeeds. 31 | 32 | 33 | Module reference 34 | ---------------- 35 | 36 | .. toctree:: 37 | 38 | see.exceptions 39 | see.features 40 | see.inspector 41 | see.output 42 | see.term 43 | see.tools 44 | -------------------------------------------------------------------------------- /docs/dev/see.exceptions.rst: -------------------------------------------------------------------------------- 1 | see\.exceptions module 2 | ====================== 3 | 4 | .. automodule:: see.exceptions 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/dev/see.features.rst: -------------------------------------------------------------------------------- 1 | see\.features module 2 | ==================== 3 | 4 | .. automodule:: see.features 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/dev/see.inspector.rst: -------------------------------------------------------------------------------- 1 | see\.inspector module 2 | ===================== 3 | 4 | .. automodule:: see.inspector 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/dev/see.output.rst: -------------------------------------------------------------------------------- 1 | see\.output module 2 | ================== 3 | 4 | .. automodule:: see.output 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/dev/see.term.rst: -------------------------------------------------------------------------------- 1 | see\.term module 2 | ================ 3 | 4 | .. automodule:: see.term 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/dev/see.tools.rst: -------------------------------------------------------------------------------- 1 | see\.tools module 2 | ================= 3 | 4 | .. automodule:: see.tools 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. see documentation master file. 2 | This should contain the root `toctree` directive. 3 | 4 | .. For Python examples, use the following: 5 | * 64-column terminal 6 | * sys.ps1 = '>>> ' 7 | * sys.ps2 = '... ' 8 | 9 | 10 | see: dir for humans 11 | =================== 12 | 13 | .. module:: see 14 | 15 | Release v\ |release|. 16 | 17 | **see** is an alternative to ``dir()``, for Python 2.7 and 3.4+. 18 | 19 | It neatly summarises what you can do with an object. 20 | Use it to inspect your code or learn new APIs. 21 | 22 | To get started, see the :doc:`install` and :doc:`usage` pages. 23 | 24 | 25 | Example 26 | ------- 27 | 28 | Say you have an object which you'd like to know more about:: 29 | 30 | >>> from datetime import timedelta 31 | 32 | Try inspecting the object with ``see``:: 33 | 34 | >>> see(timedelta) 35 | isclass + - 36 | * / // 37 | % +obj -obj 38 | < <= == 39 | != > >= 40 | abs() bool() dir() 41 | divmod() hash() help() 42 | repr() str() .days 43 | .max .microseconds .min 44 | .resolution .seconds .total_seconds() 45 | 46 | Here we can discover some things about it, such as: 47 | 48 | * The object is a class. 49 | * You can add something to it with the ``+`` operator. 50 | * It has a ``seconds`` attribute. 51 | * It has a ``total_seconds`` attribute which is a function. 52 | 53 | Compare with the output of ``dir``:: 54 | 55 | >>> dir(timedelta) 56 | ['__abs__', '__add__', '__bool__', '__class__', '__delattr__', ' 57 | __dir__', '__divmod__', '__doc__', '__eq__', '__floordiv__', '__ 58 | format__', '__ge__', '__getattribute__', '__gt__', '__hash__', ' 59 | __init__', '__init_subclass__', '__le__', '__lt__', '__mod__', ' 60 | __mul__', '__ne__', '__neg__', '__new__', '__pos__', '__radd__', 61 | '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rf 62 | loordiv__', '__rmod__', '__rmul__', '__rsub__', '__rtruediv__', 63 | '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclassho 64 | ok__', '__truediv__', 'days', 'max', 'microseconds', 'min', 'res 65 | olution', 'seconds', 'total_seconds'] 66 | 67 | You can filter the results of ``see`` using a wildcard pattern 68 | or a regular expression:: 69 | 70 | >>> see(timedelta).filter('*sec*') 71 | .microseconds .seconds .total_seconds() 72 | 73 | >>> see(timedelta).filter('/^d/') 74 | dir() divmod() 75 | 76 | 77 | Contents 78 | -------- 79 | 80 | .. toctree:: 81 | :maxdepth: 2 82 | 83 | install 84 | usage 85 | startup 86 | dev/index 87 | authors 88 | license 89 | 90 | 91 | Indices and tables 92 | ------------------ 93 | 94 | - :ref:`genindex` 95 | - :ref:`modindex` 96 | - :ref:`search` 97 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. index:: 2 | single: Installation 3 | 4 | Installation 5 | ============ 6 | 7 | **see** supports Python 2.7 and 3.4+. 8 | 9 | The latest release can be found on the 10 | `Python Package Index `_. 11 | 12 | 13 | Install with Pip 14 | ---------------- 15 | 16 | Run the following command in your terminal to install the latest release: 17 | 18 | .. code-block:: shell 19 | 20 | $ pip3 install --upgrade see 21 | 22 | For Python 2, change ``pip3`` to ``pip2``. 23 | 24 | 25 | After installing 26 | ---------------- 27 | 28 | Once **see** is installed, launch an interactive Python interpreter and try 29 | importing the ``see`` function: 30 | 31 | .. code-block:: shell 32 | 33 | $ python3 # or python2 34 | 35 | .. code-block:: python 36 | 37 | >>> from see import see 38 | >>> see('hello') 39 | 40 | .. see/docs 41 | 42 | You can use a :doc:`startup file ` to ensure that ``see`` is always 43 | imported when you start Python. 44 | 45 | .. see/docs 46 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | .. index:: 2 | single: Licensing Information 3 | 4 | Licensing Information 5 | ===================== 6 | 7 | **see** is licensed under the BSD 3-Clause License. 8 | 9 | Copyright (c) 2009--2018, Liam Cooke. 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | * Neither the name of the copyright holder nor the names of its 23 | contributors may be used to endorse or promote products derived from 24 | this software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | -------------------------------------------------------------------------------- /docs/startup.rst: -------------------------------------------------------------------------------- 1 | .. index:: 2 | single: Startup File 3 | 4 | Startup File 5 | ============ 6 | 7 | Create a startup file 8 | --------------------- 9 | 10 | You can use a Python startup file to ensure that the ``see`` function is 11 | available every time you run Python. The following example uses a file named 12 | ``.pythonrc.py`` in the user's home directory: 13 | 14 | 1. Create the Python startup file, if it does not already exist: 15 | 16 | .. code-block:: shell 17 | 18 | $ touch ~/.pythonrc.py 19 | 20 | 2. Open this file in your preferred editor. Add the following line:: 21 | 22 | from see import see 23 | 24 | 3. Set the following environment variable: 25 | 26 | .. code-block:: shell 27 | 28 | $ export PYTHONSTARTUP="$HOME/.pythonrc.py" 29 | 30 | For example, with the Bash shell, you can add this to the ``~/.bashrc`` 31 | file. 32 | 33 | Now you can use ``see`` immediately after running the Python interpreter, 34 | without having to manually import it. 35 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | .. index:: 2 | single: Usage 3 | 4 | Usage 5 | ===== 6 | 7 | .. module:: see 8 | 9 | 10 | Import see 11 | ---------- 12 | 13 | The function named ``see`` is all you need. 14 | In the Python interpreter, run:: 15 | 16 | >>> from see import see 17 | 18 | .. include:: install.rst 19 | :start-after: see/docs 20 | :end-before: see/docs 21 | 22 | 23 | Inspect an object 24 | ----------------- 25 | 26 | .. autofunction:: see 27 | :noindex: 28 | 29 | 30 | Examine the results 31 | ------------------- 32 | 33 | .. autoclass:: see.output.SeeResult 34 | :noindex: 35 | :members: 36 | :undoc-members: 37 | :inherited-members: 38 | 39 | 40 | Symbols 41 | ------- 42 | 43 | Some special symbols are used in the output from ``see`` to show the features 44 | of the object. 45 | 46 | ``()`` 47 | | Object is a function or may be called like a function. 48 | | Example: ``obj()`` 49 | ``.*`` 50 | | Object implements ``__getattr__``, so it may allow you to access 51 | attributes that are not defined. 52 | | Example: ``obj.anything``. 53 | ``[]`` 54 | | Object supports the ``[]`` syntax. 55 | | Example: ``obj[index]`` 56 | ``with`` 57 | | Object can be used in a ``with`` statement. 58 | | Example: ``with obj as target`` 59 | ``in`` 60 | | Object supports the ``in`` operator. 61 | | Example: ``for item in obj`` 62 | ``+ - * / // % **`` 63 | | Object supports these arithmetic operators. 64 | | Example: ``obj + 1`` 65 | ``<< >> & ^ |`` 66 | | Object supports these bitwise operators. 67 | | Example: ``obj << 1`` 68 | ``+obj -obj`` 69 | | Object supports the unary arithmetic operators ``+`` (positive) 70 | and ``-`` (negative) respectively. 71 | | Example: ``+1``, ``-1`` 72 | ``~`` 73 | | Object supports the unary bitwise operator ``~`` (invert). 74 | | Example: ``~1`` 75 | ``< <= == != > >=`` 76 | | Object supports these comparison operators. 77 | | Example: ``obj << 1`` 78 | ``@`` 79 | | Object supports the ``@`` operator (matrix multiplication), 80 | introduced in Python 3.5. 81 | | Example: ``obj @ matrix`` 82 | -------------------------------------------------------------------------------- /extra/README.md: -------------------------------------------------------------------------------- 1 | extra 2 | ===== 3 | 4 | This folder contains some files which may be useful when contributing to 5 | developing `see`. 6 | 7 | * *see.sublime-project*: 8 | A project file for the [Sublime Text](https://www.sublimetext.com) 9 | text editor. 10 | 11 | * *vimrc*: 12 | Recommended settings for the [vim](http://www.vim.org) text editor. 13 | -------------------------------------------------------------------------------- /extra/see.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "..", 6 | "folder_exclude_patterns": [ 7 | "__pycache__", 8 | ".eggs", 9 | "*.egg-info", 10 | "_build", 11 | "build", 12 | "htmlcov" 13 | ] 14 | } 15 | ], 16 | "settings": 17 | { 18 | "ensure_newline_at_eof_on_save": true, 19 | "indent_guide_options": ["draw_normal", "draw_active"], 20 | "rulers": [80], 21 | "tab_size": 4, 22 | "translate_tabs_to_spaces": true, 23 | "trim_trailing_white_space_on_save": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /extra/vimrc: -------------------------------------------------------------------------------- 1 | set tabstop=4 2 | set softtabstop=4 3 | set shiftwidth=4 4 | 5 | set list 6 | set expandtab 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Requirements for local development & testing. 2 | # To install, run: pip3 install -r requirements.txt 3 | # See also: .travis.yml 4 | 5 | coverage==4.4.1 6 | flake8==3.3.0 7 | pylint==1.7.1 8 | Sphinx==1.6.1 9 | virtualenv==15.1.0 10 | -------------------------------------------------------------------------------- /see/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | see: dir for humans. 3 | 4 | Documentation is available at https://ljcooke.github.io/see/ 5 | 6 | """ 7 | from .inspector import see 8 | from .output import SeeResult 9 | 10 | 11 | __all__ = ['see', 'SeeResult'] 12 | 13 | __author__ = 'Liam Cooke' 14 | __contributors__ = 'See AUTHORS.rst' 15 | __version__ = '1.4.1' 16 | __copyright__ = 'Copyright (c) 2009-2018 Liam Cooke' 17 | __license__ = 'BSD License' 18 | -------------------------------------------------------------------------------- /see/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exception classes. 3 | 4 | """ 5 | 6 | 7 | class SeeError(Exception): 8 | """ 9 | This is used internally by :func:`see.see` to indicate an invalid 10 | attribute, such as one that raises an exception when it is accessed. 11 | """ 12 | pass 13 | -------------------------------------------------------------------------------- /see/features.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python feature definitions. 3 | 4 | Reference links: 5 | 6 | * `What's New in Python 7 | `__ 8 | 9 | * `Data model 10 | `__ 11 | 12 | * `Standard operators as functions 13 | `__ 14 | 15 | """ 16 | # ----------------------------------------------------------------------------- 17 | # Relevant Python changes 18 | # 19 | # 3.5 20 | # - Introduced the @ operator and __matmul__, __imatmul__ 21 | # - Introduced __await__, __aiter__, __anext__, __aenter__, __aexit__ 22 | # (see PEP 492) 23 | # 24 | # 3.0-3.3 25 | # - Dropped __cmp__ 26 | # - Dropped __div__ 27 | # ----------------------------------------------------------------------------- 28 | 29 | import sys 30 | 31 | from .tools import compact 32 | 33 | 34 | PY_VERSION = sys.version_info 35 | 36 | if (PY_VERSION < (2, 7)) or ((3, 0) <= PY_VERSION < (3, 4)): # pragma: nocover 37 | sys.stderr.write('Warning: see() is not supported here. ' 38 | 'Please use Python 3.4+ or 2.7.\n') 39 | 40 | PY2 = PY_VERSION < (3, 0) 41 | PY3 = not PY2 42 | PY3_5 = (3, 5) <= PY_VERSION 43 | 44 | 45 | class Feature(object): 46 | """ 47 | Definition of a Python feature that an object might support (such as 48 | performing an arithmetic operation or returning a length), and a symbol 49 | to indicate this in the output from :func:`see.see`. 50 | 51 | >>> add_feature = Feature(symbol='+', attrs=[ 52 | ... '__add__', 53 | ... '__radd__', 54 | ... ]) 55 | 56 | Support for this feature is detected by checking for one or more special 57 | attributes on the object. 58 | """ 59 | 60 | def __init__(self, symbol, attrs=None): 61 | self.symbol = symbol 62 | self.attrs = set(compact(attrs)) 63 | 64 | def match(self, obj, attrs): 65 | """ 66 | Return whether the feature represented by this symbol is supported by 67 | a given object and its attributes. 68 | """ 69 | # pylint: disable=unused-argument 70 | return not self.attrs.isdisjoint(attrs) 71 | 72 | 73 | class HelpFeature(Feature): 74 | """ 75 | Help feature. 76 | """ 77 | 78 | def match(self, obj, attrs): 79 | """ 80 | Only match if the object contains a non-empty docstring. 81 | """ 82 | if '__doc__' in attrs: 83 | lstrip = getattr(obj.__doc__, 'lstrip', False) 84 | return lstrip and any(lstrip()) 85 | 86 | 87 | FEATURES = compact(( 88 | 89 | # ------------------------------------------------------------------------- 90 | # Callable 91 | 92 | Feature(symbol='()', attrs=['__call__']), 93 | 94 | # ------------------------------------------------------------------------- 95 | # Element/attribute access 96 | 97 | Feature(symbol='.*', attrs=['__getattr__']), 98 | Feature(symbol='[]', attrs=['__getitem__', '__setitem__', '__delitem__']), 99 | 100 | # ------------------------------------------------------------------------- 101 | # Iteration 102 | 103 | Feature(symbol='with', attrs=['__enter__', '__exit__']), 104 | Feature(symbol='in', attrs=['__contains__']), 105 | 106 | # ------------------------------------------------------------------------- 107 | # Operators 108 | 109 | Feature(symbol='+', attrs=['__add__', '__radd__']), 110 | Feature(symbol='+=', attrs=['__iadd__']), 111 | 112 | Feature(symbol='-', attrs=['__sub__', '__rsub__']), 113 | Feature(symbol='-=', attrs=['__isub__']), 114 | 115 | Feature(symbol='*', attrs=['__mul__', '__rmul__']), 116 | Feature(symbol='*=', attrs=['__imul__']), 117 | 118 | Feature(symbol='@', attrs=['__matmul__', '__rmatmul__']) if PY3_5 else 0, 119 | Feature(symbol='@=', attrs=['__imatmul__']) if PY3_5 else 0, 120 | 121 | # Python 2.x uses __div__ for a/b, but will look for __truediv__ instead 122 | # if this is enabled with `from __future__ import division`. 123 | Feature(symbol='/', attrs=[ 124 | '__truediv__', 125 | '__rtruediv__', 126 | PY2 and '__div__', 127 | PY2 and '__rdiv__', 128 | ]), 129 | Feature(symbol='/=', attrs=[ 130 | '__itruediv__', 131 | PY2 and '__idiv__', 132 | ]), 133 | 134 | Feature(symbol='//', attrs=['__floordiv__', '__rfloordiv__']), 135 | Feature(symbol='//=', attrs=['__ifloordiv__']), 136 | 137 | Feature(symbol='%', attrs=['__mod__', '__rmod__', '__divmod__']), 138 | Feature(symbol='%=', attrs=['__imod__']), 139 | 140 | Feature(symbol='**', attrs=['__pow__', '__rpow__']), 141 | Feature(symbol='**=', attrs=['__ipow__']), 142 | 143 | Feature(symbol='<<', attrs=['__lshift__', '__rlshift__']), 144 | Feature(symbol='<<=', attrs=['__ilshift__']), 145 | 146 | Feature(symbol='>>', attrs=['__rshift__', '__rrshift__']), 147 | Feature(symbol='>>=', attrs=['__irshift__']), 148 | 149 | Feature(symbol='&', attrs=['__and__', '__rand__']), 150 | Feature(symbol='&=', attrs=['__iand__']), 151 | 152 | Feature(symbol='^', attrs=['__xor__', '__rxor__']), 153 | Feature(symbol='^=', attrs=['__ixor__']), 154 | 155 | Feature(symbol='|', attrs=['__or__', '__ror__']), 156 | Feature(symbol='|=', attrs=['__ior__']), 157 | 158 | Feature(symbol='+obj', attrs=['__pos__']), 159 | Feature(symbol='-obj', attrs=['__neg__']), 160 | Feature(symbol='~', attrs=['__invert__']), 161 | 162 | Feature(symbol='<', attrs=['__lt__', PY2 and '__cmp__']), 163 | Feature(symbol='<=', attrs=['__le__', PY2 and '__cmp__']), 164 | Feature(symbol='==', attrs=['__eq__', PY2 and '__cmp__']), 165 | Feature(symbol='!=', attrs=['__ne__', PY2 and '__cmp__']), 166 | Feature(symbol='>', attrs=['__gt__', PY2 and '__cmp__']), 167 | Feature(symbol='>=', attrs=['__ge__', PY2 and '__cmp__']), 168 | 169 | # ------------------------------------------------------------------------- 170 | # Builtin functions 171 | 172 | Feature(symbol='abs()', attrs=['__abs__']), 173 | Feature(symbol='bool()', attrs=[ 174 | '__bool__' if PY3 else '__nonzero__' 175 | ]), 176 | Feature(symbol='complex()', attrs=['__complex__']), 177 | Feature(symbol='dir()', attrs=['__dir__']) if PY3 else 0, 178 | Feature(symbol='divmod()', attrs=['__divmod__', '__rdivmod__']), 179 | Feature(symbol='float()', attrs=['__float__']), 180 | Feature(symbol='hash()', attrs=['__hash__']), 181 | HelpFeature(symbol='help()'), 182 | Feature(symbol='hex()', attrs=[ 183 | '__index__' if PY3 else '__hex__' 184 | ]), 185 | Feature(symbol='int()', attrs=['__int__']), 186 | Feature(symbol='iter()', attrs=['__iter__']), 187 | Feature(symbol='len()', attrs=['__len__']), 188 | Feature(symbol='long()', attrs=['__long__']) if PY2 else 0, 189 | Feature(symbol='oct()', attrs=[ 190 | '__index__' if PY3 else '__oct__' 191 | ]), 192 | Feature(symbol='repr()', attrs=['__repr__']), 193 | Feature(symbol='reversed()', attrs=['__reversed__']), 194 | Feature(symbol='round()', attrs=['__round__']) if PY3 else 0, 195 | Feature(symbol='str()', attrs=['__str__']), 196 | Feature(symbol='unicode()', attrs=['__unicode__']) if PY3 else 0, 197 | 198 | )) 199 | -------------------------------------------------------------------------------- /see/inspector.py: -------------------------------------------------------------------------------- 1 | """ 2 | Object inspector. 3 | 4 | """ 5 | import inspect 6 | import re 7 | import sys 8 | 9 | from . import output, tools 10 | from .exceptions import SeeError 11 | from .features import FEATURES 12 | 13 | 14 | class DefaultArg(object): 15 | """ 16 | A global instance of this class is used as the default argument to 17 | :func:`see.see`. This allows for a distinction between calling ``see()`` 18 | without arguments and calling it with a falsey argument like ``see(None)``. 19 | """ 20 | def __repr__(self): 21 | return 'anything' 22 | 23 | 24 | DEFAULT_ARG = DefaultArg() 25 | 26 | 27 | class Namespace(object): 28 | """ 29 | An object whose attributes are initialised with a dictionary. 30 | Similar to the SimpleNamespace_ class introduced in Python 3. 31 | 32 | .. _SimpleNamespace: https:// 33 | docs.python.org/3/library/types.html#types.SimpleNamespace 34 | """ 35 | def __init__(self, attrs): 36 | self.__dict__.update(attrs) 37 | 38 | 39 | # Get all the 'is' functions from the inspect module. 40 | INSPECT_FUNCS = tuple((name, getattr(inspect, name)) 41 | for name in dir(inspect) if name.startswith('is')) 42 | 43 | 44 | def handle_deprecated_args(tokens, args, kwargs): 45 | """ 46 | Backwards compatibility with deprecated arguments ``pattern`` and ``r``. 47 | """ 48 | num_args = len(args) 49 | pattern = args[0] if num_args else kwargs.get('pattern', None) 50 | regex = args[1] if num_args > 1 else kwargs.get('r', None) 51 | 52 | if pattern is not None: 53 | tokens = tools.filter_wildcard(tokens, pattern) 54 | sys.stderr.write( 55 | 'Please use see().match() now. The "pattern" argument is ' 56 | 'deprecated and will be removed in a later release. \n') 57 | 58 | if regex is not None: 59 | tokens = tools.filter_regex(tokens, re.compile(regex)) 60 | sys.stderr.write( 61 | 'Please use see().match() now. The "r" argument is ' 62 | 'deprecated and will be removed in a later release. \n') 63 | 64 | return tokens 65 | 66 | 67 | def see(obj=DEFAULT_ARG, *args, **kwargs): 68 | """ 69 | see(obj=anything) 70 | 71 | Show the features and attributes of an object. 72 | 73 | This function takes a single argument, ``obj``, which can be of any type. 74 | A summary of the object is printed immediately in the Python interpreter. 75 | For example:: 76 | 77 | >>> see([]) 78 | [] in + += * 79 | *= < <= == != 80 | > >= dir() hash() 81 | help() iter() len() repr() 82 | reversed() str() .append() .clear() 83 | .copy() .count() .extend() .index() 84 | .insert() .pop() .remove() .reverse() 85 | .sort() 86 | 87 | If this function is run without arguments, it will instead list the objects 88 | that are available in the current scope. :: 89 | 90 | >>> see() 91 | os random see() sys 92 | 93 | The return value is an instance of :class:`SeeResult`. 94 | """ 95 | use_locals = obj is DEFAULT_ARG 96 | 97 | if use_locals: 98 | # Get the local scope from the caller's stack frame. 99 | # Typically this is the scope of an interactive Python session. 100 | obj = Namespace(inspect.currentframe().f_back.f_locals) 101 | 102 | tokens = [] 103 | attrs = dir(obj) 104 | 105 | if not use_locals: 106 | 107 | for name, func in INSPECT_FUNCS: 108 | if func(obj): 109 | tokens.append(name) 110 | 111 | for feature in FEATURES: 112 | if feature.match(obj, attrs): 113 | tokens.append(feature.symbol) 114 | 115 | for attr in filter(lambda a: not a.startswith('_'), attrs): 116 | try: 117 | prop = getattr(obj, attr) 118 | except (AttributeError, Exception): # pylint: disable=broad-except 119 | prop = SeeError() 120 | action = output.display_name(name=attr, obj=prop, local=use_locals) 121 | tokens.append(action) 122 | 123 | if args or kwargs: 124 | tokens = handle_deprecated_args(tokens, args, kwargs) 125 | 126 | return output.SeeResult(tokens) 127 | -------------------------------------------------------------------------------- /see/output.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manipulating strings for output. 3 | 4 | """ 5 | import math 6 | import re 7 | import sys 8 | import textwrap 9 | 10 | from . import term, tools 11 | from .exceptions import SeeError 12 | from .features import PY3 13 | 14 | 15 | REGEX_TYPE = type(re.compile('.')) 16 | 17 | MAX_INDENT = 4 18 | 19 | 20 | class SeeResult(object): 21 | """ 22 | The output of the :func:`see` function. 23 | 24 | Acts like a tuple of strings, so you can iterate over the output:: 25 | 26 | >>> first = see()[0] 27 | 28 | >>> for string in see([]): 29 | ... print(string) 30 | 31 | """ 32 | def __init__(self, tokens): 33 | self._tokens = tuple(tokens) 34 | 35 | def __repr__(self): 36 | col_width = column_width(self) 37 | padded = [justify_token(tok, col_width) for tok in self] 38 | 39 | ps1 = getattr(sys, 'ps1', None) 40 | if ps1: 41 | get_len = tools.display_len if PY3 else len 42 | indent = ' ' * min(get_len(ps1), MAX_INDENT) 43 | else: 44 | indent = ' ' * MAX_INDENT 45 | 46 | return textwrap.fill(''.join(padded), term.line_width(), 47 | initial_indent=indent, 48 | subsequent_indent=indent) 49 | 50 | def __eq__(self, other): 51 | return tuple(self) == tuple(other) 52 | 53 | def __iter__(self): 54 | return iter(self._tokens) 55 | 56 | def __len__(self): 57 | return len(self._tokens) 58 | 59 | def __getitem__(self, index): 60 | return self._tokens[index] 61 | 62 | def filter(self, pattern): 63 | """ 64 | Filter the results using a pattern. 65 | 66 | This accepts a shell-style wildcard pattern (as used by the fnmatch_ 67 | module):: 68 | 69 | >>> see([]).filter('*op*') 70 | .copy() .pop() 71 | 72 | It also accepts a regular expression. This may be a compiled regular 73 | expression (from the re_ module) or a string that starts with a ``/`` 74 | (forward slash) character:: 75 | 76 | >>> see([]).filter('/[aeiou]{2}/') 77 | .clear() .count() 78 | 79 | .. _fnmatch: https://docs.python.org/3/library/fnmatch.html 80 | .. _re: https://docs.python.org/3/library/re.html 81 | """ 82 | if isinstance(pattern, REGEX_TYPE): 83 | func = tools.filter_regex 84 | elif pattern.startswith('/'): 85 | pattern = re.compile(pattern.strip('/')) 86 | func = tools.filter_regex 87 | else: 88 | func = tools.filter_wildcard 89 | 90 | return SeeResult(func(self, pattern)) 91 | 92 | def filter_ignoring_case(self, pattern): 93 | """ 94 | Like ``filter`` but case-insensitive. 95 | 96 | Expects a regular expression string without the surrounding ``/`` 97 | characters. 98 | 99 | >>> see().filter('^my', ignore_case=True) 100 | MyClass() 101 | 102 | """ 103 | return self.filter(re.compile(pattern, re.I)) 104 | 105 | def exclude(self, pattern): 106 | """ 107 | The opposite of ``filter``: excludes items from the result that match 108 | a given pattern. 109 | 110 | Expects a regular expression string without the surrounding ``/`` 111 | characters. 112 | """ 113 | # See https://stackoverflow.com/a/957581 114 | return self.filter(re.compile('^((?!{0}).)*$'.format(pattern))) 115 | 116 | 117 | def column_width(tokens): 118 | """ 119 | Return a suitable column width to display one or more strings. 120 | """ 121 | get_len = tools.display_len if PY3 else len 122 | lens = sorted(map(get_len, tokens or [])) or [0] 123 | width = lens[-1] 124 | 125 | # adjust for disproportionately long strings 126 | if width >= 18: 127 | most = lens[int(len(lens) * 0.9)] 128 | if most < width + 6: 129 | return most 130 | 131 | return width 132 | 133 | 134 | def justify_token(tok, col_width): 135 | """ 136 | Justify a string to fill one or more columns. 137 | """ 138 | get_len = tools.display_len if PY3 else len 139 | tok_len = get_len(tok) 140 | diff_len = tok_len - len(tok) if PY3 else 0 141 | 142 | cols = (int(math.ceil(float(tok_len) / col_width)) 143 | if col_width < tok_len + 4 else 1) 144 | 145 | if cols > 1: 146 | return tok.ljust((col_width * cols) + (4 * cols) - diff_len) 147 | else: 148 | return tok.ljust(col_width + 4 - diff_len) 149 | 150 | 151 | def display_name(name, obj, local): 152 | """ 153 | Get the display name of an object. 154 | 155 | Keyword arguments (all required): 156 | 157 | * ``name`` -- the name of the object as a string. 158 | * ``obj`` -- the object itself. 159 | * ``local`` -- a boolean value indicating whether the object is in local 160 | scope or owned by an object. 161 | 162 | """ 163 | prefix = '' if local else '.' 164 | 165 | if isinstance(obj, SeeError): 166 | suffix = '?' 167 | elif hasattr(obj, '__call__'): 168 | suffix = '()' 169 | else: 170 | suffix = '' 171 | 172 | return ''.join((prefix, name, suffix)) 173 | -------------------------------------------------------------------------------- /see/term.py: -------------------------------------------------------------------------------- 1 | """ 2 | Terminal info. 3 | 4 | """ 5 | import platform 6 | import struct 7 | 8 | # pylint: disable=invalid-name 9 | 10 | try: 11 | if platform.system() == 'Windows': # pragma: no cover (windows) 12 | from ctypes import windll, create_string_buffer 13 | fcntl, termios = None, None 14 | else: 15 | import fcntl 16 | import termios 17 | windll, create_string_buffer = None, None 18 | except ImportError: 19 | fcntl, termios = None, None 20 | windll, create_string_buffer = None, None 21 | 22 | 23 | DEFAULT_LINE_WIDTH = 78 24 | MAX_LINE_WIDTH = 120 25 | 26 | 27 | def term_width(): 28 | """ 29 | Return the column width of the terminal, or ``None`` if it can't be 30 | determined. 31 | """ 32 | if fcntl and termios: 33 | try: 34 | winsize = fcntl.ioctl(0, termios.TIOCGWINSZ, ' ') 35 | _, width = struct.unpack('hh', winsize) 36 | return width 37 | except IOError: 38 | pass 39 | elif windll and create_string_buffer: # pragma: no cover (windows) 40 | stderr_handle, struct_size = -12, 22 41 | handle = windll.kernel32.GetStdHandle(stderr_handle) 42 | csbi = create_string_buffer(struct_size) 43 | res = windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi) 44 | if res: 45 | (_, _, _, _, _, left, _, right, _, 46 | _, _) = struct.unpack('hhhhHhhhhhh', csbi.raw) 47 | return right - left + 1 48 | else: 49 | return 0 # console screen buffer not available 50 | 51 | 52 | def line_width(default_width=DEFAULT_LINE_WIDTH, max_width=MAX_LINE_WIDTH): 53 | """ 54 | Return the ideal column width for the output from :func:`see.see`, taking 55 | the terminal width into account to avoid wrapping. 56 | """ 57 | width = term_width() 58 | if width: # pragma: no cover (no terminal info in Travis CI) 59 | return min(width, max_width) 60 | else: 61 | return default_width 62 | -------------------------------------------------------------------------------- /see/tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | Filtering and other tasks. 3 | 4 | """ 5 | import fnmatch 6 | import unicodedata 7 | 8 | 9 | def compact(objects): 10 | """ 11 | Filter out any falsey objects in a sequence. 12 | """ 13 | return tuple(filter(bool, objects or [])) 14 | 15 | 16 | def char_width(char): 17 | """ 18 | Get the display length of a unicode character. 19 | """ 20 | if ord(char) < 128: 21 | return 1 22 | elif unicodedata.east_asian_width(char) in ('F', 'W'): 23 | return 2 24 | elif unicodedata.category(char) in ('Mn',): 25 | return 0 26 | else: 27 | return 1 28 | 29 | 30 | def display_len(text): 31 | """ 32 | Get the display length of a string. This can differ from the character 33 | length if the string contains wide characters. 34 | """ 35 | text = unicodedata.normalize('NFD', text) 36 | return sum(char_width(char) for char in text) 37 | 38 | 39 | def filter_regex(names, regex): 40 | """ 41 | Return a tuple of strings that match the regular expression pattern. 42 | """ 43 | return tuple(name for name in names 44 | if regex.search(name) is not None) 45 | 46 | 47 | def filter_wildcard(names, pattern): 48 | """ 49 | Return a tuple of strings that match a shell-style wildcard pattern. 50 | """ 51 | return tuple(name for name in names 52 | if fnmatch.fnmatch(name, pattern)) 53 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import sys 3 | from setuptools import setup 4 | 5 | VERSION = '1.4.1' 6 | 7 | INSTALL_REQUIRES = [] 8 | TESTS_REQUIRE = [ 9 | 'mock>=2.0.0' if sys.version_info.major == 2 else None, 10 | ] 11 | 12 | with codecs.open('README.md', encoding='utf-8') as file: 13 | README = file.read() 14 | 15 | setup(name='see', 16 | version=VERSION, 17 | description='dir for humans', 18 | long_description=README, 19 | author='Liam Cooke', 20 | author_email='see@liamcooke.com', 21 | license='BSD License', 22 | platforms=['any'], 23 | url='https://ljcooke.github.io/see/', 24 | packages=['see'], 25 | python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', 26 | install_requires=list(filter(bool, INSTALL_REQUIRES)), 27 | test_suite='tests', 28 | tests_require=list(filter(bool, TESTS_REQUIRE)), 29 | zip_safe=True, 30 | keywords='see dir alternative inspect human readable pretty'.split(), 31 | classifiers=[ 32 | 'Development Status :: 5 - Production/Stable', 33 | 'Environment :: Console', 34 | 'Intended Audience :: Developers', 35 | 'License :: Freely Distributable', 36 | 'License :: OSI Approved :: BSD License', 37 | 'Natural Language :: English', 38 | 'Operating System :: OS Independent', 39 | 'Programming Language :: Python :: 2', 40 | 'Programming Language :: Python :: 2.7', 41 | 'Programming Language :: Python :: 3', 42 | 'Programming Language :: Python :: 3.4', 43 | 'Programming Language :: Python :: 3.5', 44 | 'Programming Language :: Python :: 3.6', 45 | 'Programming Language :: Python :: 3.7', 46 | 'Programming Language :: Python :: Implementation :: CPython', 47 | 'Programming Language :: Python :: Implementation :: PyPy', 48 | 'Topic :: Software Development', 49 | ]) 50 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljcooke/see/4616144b0b7d8a300e311f00712ffbe23c49bd66/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_mod_inspector.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for the see.inspector module. 3 | 4 | """ 5 | import unittest 6 | 7 | from see import inspector, see 8 | 9 | 10 | class ObjectWithAttributeError(object): 11 | 12 | @property 13 | def bad_attribute(self): 14 | raise Exception 15 | 16 | 17 | class ObjectWithDocstring(object): 18 | """ 19 | Hello, world 20 | """ 21 | pass 22 | 23 | 24 | class ObjectWithEmptyDocstring(object): 25 | """ 26 | """ 27 | pass 28 | 29 | 30 | class TestDefaultArgClass(unittest.TestCase): 31 | 32 | def test_global_instance(self): 33 | self.assertIsInstance(inspector.DEFAULT_ARG, inspector.DefaultArg) 34 | 35 | def test_repr(self): 36 | arg = inspector.DefaultArg() 37 | 38 | self.assertEqual(repr(arg), 'anything') 39 | 40 | 41 | class TestNamespaceClass(unittest.TestCase): 42 | 43 | def test_namespace(self): 44 | attributes = { 45 | 'hello': 'hello world', 46 | } 47 | 48 | namespace = inspector.Namespace(attributes) 49 | 50 | self.assertEqual(namespace.hello, 'hello world') 51 | self.assertIn('hello', dir(namespace)) 52 | self.assertIn('.hello', see(namespace)) 53 | 54 | 55 | class TestSeeFunction(unittest.TestCase): 56 | 57 | def test_see_with_args(self): 58 | result = see([]) 59 | 60 | self.assertTrue(any(result)) 61 | 62 | def test_see_without_args(self): 63 | result = see() 64 | 65 | self.assertTrue(any(result)) 66 | 67 | def test_see_with_new_default_arg_instance(self): 68 | result_with_obj = see(inspector.DefaultArg()) 69 | result_without_args = see() 70 | 71 | self.assertNotEqual(result_with_obj, result_without_args) 72 | 73 | def test_see_accessor_raises_exception(self): 74 | normal_obj = 1 75 | err_obj = ObjectWithAttributeError() 76 | 77 | normal_result = see(normal_obj) 78 | err_result = see(err_obj) 79 | 80 | self.assertTrue(not any(attr.endswith('?') 81 | for attr in normal_result)) 82 | self.assertTrue(any(attr.endswith('?') 83 | for attr in err_result)) 84 | self.assertIn('.bad_attribute?', err_result) 85 | 86 | def test_see_object_has_help(self): 87 | obj_help = ObjectWithDocstring() 88 | obj_nohelp = ObjectWithEmptyDocstring() 89 | 90 | out_help = see(obj_help) 91 | out_nohelp = see(obj_nohelp) 92 | 93 | self.assertTrue('help()' in out_help) 94 | self.assertFalse('help()' in out_nohelp) 95 | 96 | # Deprecated filtering API -- see test_mod_output 97 | 98 | def test_see_deprecated_r_arg(self): 99 | obj = [] 100 | pattern = '[aeiou]{2}' 101 | 102 | filtered_result = see(obj, r=pattern) 103 | filtered_result_pos_arg = see(obj, '*', pattern) 104 | 105 | # Assert 106 | self.assertEqual(filtered_result, filtered_result_pos_arg) 107 | self.assertIn('.count()', filtered_result) 108 | self.assertNotIn('.pop()', filtered_result) 109 | 110 | def test_see_deprecated_pattern_arg(self): 111 | obj = [] 112 | pattern = '*()' 113 | 114 | filtered_result = see(obj, pattern=pattern) 115 | filtered_result_pos_arg = see(obj, pattern) 116 | 117 | # Assert 118 | self.assertEqual(filtered_result, filtered_result_pos_arg) 119 | self.assertIn('.count()', filtered_result) 120 | self.assertNotIn('+=', filtered_result) 121 | -------------------------------------------------------------------------------- /tests/test_mod_output.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for the see.output module. 3 | 4 | """ 5 | import re 6 | import unittest 7 | 8 | from see import output, see 9 | from see.features import PY3 10 | 11 | 12 | class ObjectWithLongAttrName(dict): 13 | 14 | def lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit(self): 15 | pass 16 | 17 | 18 | class TestSeeResultClass(unittest.TestCase): 19 | 20 | def test_see_returns_result_instance(self): 21 | result_for_obj = see([]) 22 | result_for_locals = see() 23 | 24 | isinstance(result_for_obj, output.SeeResult) 25 | isinstance(result_for_locals, output.SeeResult) 26 | 27 | def test_repr(self): 28 | result = see() 29 | 30 | self.assertEqual(str(result), result.__repr__()) 31 | 32 | def test_acts_like_tuple(self): 33 | result = see([]) 34 | 35 | self.assertIsNotNone(tuple(result)) 36 | self.assertIsNotNone(result[0]) 37 | self.assertIsNotNone(result[-1]) 38 | self.assertTrue(any(result)) 39 | self.assertTrue(len(result) > 0) 40 | 41 | def test_justify_attributes(self): 42 | obj = ObjectWithLongAttrName() 43 | 44 | result = see(obj) 45 | col_width = output.column_width(result) 46 | padded = [output.justify_token(tok, col_width) for tok in result] 47 | lens = sorted(map(len, padded)) 48 | factors = tuple(float(num) / lens[0] for num in lens[1:]) 49 | 50 | # Assert 51 | self.assertNotEqual(lens[0], lens[-1], 52 | 'Expected differing column widths') 53 | self.assertTrue(any(factors)) 54 | self.assertTrue(all(int(factor) == factor for factor in factors), 55 | 'Irregular column widths') 56 | 57 | def test_filter_with_wildcard(self): 58 | obj = [] 59 | pattern = '*op*' 60 | expected = ('.copy()', '.pop()') if PY3 else ('.pop()',) 61 | 62 | filtered_result = see(obj).filter(pattern) 63 | 64 | self.assertIsInstance(filtered_result, output.SeeResult) 65 | self.assertEqual(expected, filtered_result) 66 | 67 | def test_filter_with_regex_string(self): 68 | obj = [] 69 | pattern = '/[aeiou]{2}/' 70 | expected = ('.clear()', '.count()') if PY3 else ('.count()',) 71 | 72 | filtered_result = see(obj).filter(pattern) 73 | 74 | self.assertIsInstance(filtered_result, output.SeeResult) 75 | self.assertEqual(expected, filtered_result) 76 | 77 | def test_filter_with_regex_object(self): 78 | obj = [] 79 | pattern = re.compile('[aeiou]{2}') 80 | expected = ('.clear()', '.count()') if PY3 else ('.count()',) 81 | 82 | filtered_result = see(obj).filter(pattern) 83 | 84 | self.assertIsInstance(filtered_result, output.SeeResult) 85 | self.assertEqual(expected, filtered_result) 86 | 87 | @unittest.skip('Not implemented') 88 | def test_filter_ignoring_case_with_wildcard(self): 89 | pass 90 | 91 | def test_filter_ignoring_case_with_regex_string(self): 92 | obj = [] 93 | pattern = '[AEIOU]{2}' # TODO: Change this to '/[AEIOU]{2}/' 94 | expected = ('.clear()', '.count()') if PY3 else ('.count()',) 95 | 96 | filtered_result = see(obj).filter_ignoring_case(pattern) 97 | 98 | self.assertIsInstance(filtered_result, output.SeeResult) 99 | self.assertEqual(expected, filtered_result) 100 | 101 | @unittest.skip('Not implemented') 102 | def test_exclude_with_wildcard(self): 103 | pass 104 | 105 | def test_exclude_with_regex_string(self): 106 | obj = [] 107 | pattern = '[aeiou]{2}' # TODO: Change this to '/[aeiou]{2}/' 108 | excluded = set(('.clear()', '.count()')) if PY3 else set(('.count()',)) 109 | 110 | unfiltered_result = see(obj) 111 | filtered_result = see(obj).exclude(pattern) 112 | 113 | self.assertIsInstance(filtered_result, output.SeeResult) 114 | self.assertEqual(set(unfiltered_result).difference(filtered_result), 115 | excluded) 116 | self.assertFalse(set(filtered_result).difference(unfiltered_result)) 117 | -------------------------------------------------------------------------------- /tests/test_mod_term.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for the see.term module. 3 | 4 | """ 5 | import unittest 6 | 7 | from see import term 8 | 9 | 10 | class TestTermModule(unittest.TestCase): 11 | 12 | def test_line_width(self): 13 | # Arrange 14 | default_width = 1 15 | max_width = 1 16 | 17 | # Act 18 | width = term.line_width(default_width, max_width) 19 | width_no_args = term.line_width() 20 | 21 | # Assert 22 | self.assertIsInstance(width, int) 23 | self.assertEqual(width, 1) 24 | self.assertLessEqual(width_no_args, term.MAX_LINE_WIDTH) 25 | -------------------------------------------------------------------------------- /tests/test_mod_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for the see.tools module. 3 | 4 | """ 5 | import re 6 | import unittest 7 | 8 | from see import tools 9 | 10 | 11 | class TestToolsModule(unittest.TestCase): 12 | 13 | def test_compact(self): 14 | # Arrange 15 | items = [0, 1, '', 'foo', True, False, [], ['a'], {}, {1: 2}] 16 | expected = (1, 'foo', True, ['a'], {1: 2}) 17 | 18 | # Act 19 | compacted = tools.compact(items) 20 | 21 | # Assert 22 | self.assertEqual(compacted, expected) 23 | 24 | def test_filter_regex(self): 25 | # Arrange 26 | names = ["george", "helen"] 27 | pat_wildcard = re.compile("e.*g") 28 | pat_start = re.compile("^h") 29 | pat_end = re.compile("n$") 30 | 31 | # Act 32 | out_wildcard = tools.filter_regex(names, pat_wildcard) 33 | out_start = tools.filter_regex(names, pat_start) 34 | out_end = tools.filter_regex(names, pat_end) 35 | 36 | # Assert 37 | self.assertIsInstance(out_wildcard, tuple) 38 | self.assertIsInstance(out_start, tuple) 39 | self.assertIsInstance(out_end, tuple) 40 | self.assertEqual(out_wildcard, ("george",)) 41 | self.assertEqual(out_start, ("helen",)) 42 | self.assertEqual(out_end, ("helen",)) 43 | 44 | def test_filter_wildcard(self): 45 | # Arrange 46 | names = ["george", "helen"] 47 | pat_wildcard = "*or*" 48 | pat_single = "h?l?n" 49 | pat_partial = "e*" 50 | 51 | # Act 52 | out_wildcard = tools.filter_wildcard(names, pat_wildcard) 53 | out_single = tools.filter_wildcard(names, pat_single) 54 | out_partial = tools.filter_wildcard(names, pat_partial) 55 | 56 | # Assert 57 | self.assertIsInstance(out_wildcard, tuple) 58 | self.assertIsInstance(out_single, tuple) 59 | self.assertIsInstance(out_partial, tuple) 60 | self.assertEqual(out_wildcard, ("george",)) 61 | self.assertEqual(out_single, ("helen",)) 62 | self.assertEqual(out_partial, ()) 63 | -------------------------------------------------------------------------------- /tests/test_see_result.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for the result of calling see() with various types of object. 3 | 4 | """ 5 | import itertools 6 | import unittest 7 | 8 | from see import see 9 | 10 | 11 | def union(*sets): 12 | return set(itertools.chain(*sets)) 13 | 14 | 15 | SIGN_OPS = {'+obj', '-obj'} 16 | 17 | NUMBER_OPS = set('+ - * / // % **'.split()) 18 | NUMBER_ASSIGN_OPS = set() 19 | 20 | BITWISE_OPS = set('<< >> & ^ | ~'.split()) 21 | BITWISE_ASSIGN_OPS = {op + '=' for op in BITWISE_OPS} 22 | 23 | COMPARE_OPS = set('< <= == != > >='.split()) 24 | 25 | MATRIX_OPS = {'@'} 26 | MATRIX_ASSIGN_OPS = {'@='} 27 | 28 | ALL_OPS = union( 29 | SIGN_OPS, 30 | NUMBER_OPS, 31 | NUMBER_ASSIGN_OPS, 32 | BITWISE_OPS, 33 | BITWISE_ASSIGN_OPS, 34 | COMPARE_OPS, 35 | MATRIX_OPS, 36 | MATRIX_ASSIGN_OPS, 37 | ) 38 | 39 | 40 | class TestSeeResult(unittest.TestCase): 41 | 42 | def check_ops(self, obj_type, expected_ops, see_output): 43 | for op in ALL_OPS: 44 | if op in expected_ops: 45 | self.assertIn( 46 | op, see_output, 47 | 'expected %s to support %s' % (obj_type, op)) 48 | else: 49 | self.assertNotIn( 50 | op, see_output, 51 | 'expected %s not to support %s' % (obj_type, op)) 52 | 53 | def test_int(self): 54 | obj = 1 55 | lit_ops = union( 56 | SIGN_OPS, 57 | NUMBER_OPS, 58 | BITWISE_OPS, 59 | COMPARE_OPS, 60 | ) 61 | obj_ops = union( 62 | lit_ops, 63 | ) 64 | 65 | lit_see = see(1) 66 | obj_see = see(obj) 67 | 68 | self.check_ops('int literal', lit_ops, lit_see) 69 | self.check_ops('int object', obj_ops, obj_see) 70 | 71 | def test_float(self): 72 | obj = 1.0 73 | lit_ops = union( 74 | SIGN_OPS, 75 | NUMBER_OPS, 76 | COMPARE_OPS, 77 | ) 78 | obj_ops = union( 79 | lit_ops, 80 | ) 81 | 82 | lit_see = see(1.0) 83 | obj_see = see(obj) 84 | 85 | self.check_ops('float literal', lit_ops, lit_see) 86 | self.check_ops('float object', obj_ops, obj_see) 87 | 88 | def test_isfunction(self): 89 | 90 | def a_function(): 91 | pass 92 | 93 | output = see(a_function) 94 | 95 | self.assertIn('isfunction', output) 96 | self.assertIn('()', output) 97 | -------------------------------------------------------------------------------- /tests/test_term_width.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for getting the terminal width. 3 | 4 | There are separate test cases to simulate unsupported environments (not 5 | Unixlike or Windows), where information about the terminal is not easily 6 | available. To do this, we prevent Python from importing some modules while it 7 | loads see. 8 | 9 | """ 10 | import platform 11 | import sys 12 | from imp import reload 13 | import unittest 14 | 15 | try: 16 | import unittest.mock as mock # Python 3.3+ 17 | except ImportError: 18 | import mock # Python 2 19 | 20 | try: 21 | import builtins # Python 3 22 | except ImportError: 23 | import __builtin__ as builtins # Python 2 24 | 25 | import see 26 | from see import term 27 | 28 | 29 | MOCK_EXCLUDE_MODULES = ( 30 | 'ctypes', 31 | 'fcntl', 32 | 'termios', 33 | ) 34 | 35 | PY3 = sys.version_info >= (3, 0) 36 | 37 | REAL_IMPORT = builtins.__import__ 38 | 39 | 40 | def mock_import(name, 41 | globals=None if PY3 else {}, 42 | locals=None if PY3 else {}, 43 | fromlist=None if PY3 else [], 44 | level=0 if PY3 else -1): 45 | if name in MOCK_EXCLUDE_MODULES: 46 | raise ImportError 47 | return REAL_IMPORT(name, globals, locals, fromlist, level) 48 | 49 | 50 | class TestSupportedTerminal(unittest.TestCase): 51 | 52 | def setUp(self): 53 | self.system = platform.system() 54 | self.windows = (self.system == 'Windows') 55 | 56 | def tearDown(self): 57 | if hasattr(sys, 'ps1'): 58 | del sys.ps1 59 | 60 | def test_system(self): 61 | self.assertTrue(self.system, 'System/OS name could not be determined') 62 | 63 | def test_import_success(self): 64 | if self.windows: 65 | self.assertIsNone(term.fcntl) 66 | self.assertIsNone(term.termios) 67 | self.assertIsNotNone(term.windll) 68 | self.assertIsNotNone(term.create_string_buffer) 69 | else: 70 | self.assertIsNotNone(term.fcntl) 71 | self.assertIsNotNone(term.termios) 72 | self.assertIsNone(term.windll) 73 | self.assertIsNone(term.create_string_buffer) 74 | 75 | def test_term_width(self): 76 | width = term.term_width() 77 | 78 | self.assertIsNotNone(width) 79 | 80 | # Note: terminal info is not available in Travis or AppVeyor 81 | # self.assertGreater(width, 0) 82 | 83 | def test_ioctl_fail(self): 84 | if self.windows: 85 | return 86 | 87 | package = 'see.term.fcntl.ioctl' 88 | with mock.patch(package, side_effect=IOError('')): 89 | width = term.term_width() 90 | 91 | self.assertIsNone(width) 92 | 93 | def test_reduce_indent_to_narrow_prompt(self): 94 | if hasattr(sys, 'ps1'): 95 | self.fail('expected sys.ps1 to be absent during unit testing') 96 | 97 | # Arrange 98 | sys.ps1 = '> ' 99 | 100 | # Act 101 | out = see.see() 102 | indent = len(str(out)) - len(str(out).lstrip()) 103 | 104 | # Assert 105 | self.assertEqual(indent, len(sys.ps1)) 106 | 107 | def test_maximum_indent(self): 108 | if hasattr(sys, 'ps1'): 109 | self.fail('expected sys.ps1 to be absent during unit testing') 110 | 111 | # Arrange 112 | sys.ps1 = '[arbitrary prompt string]' 113 | 114 | # Act 115 | out = see.see() 116 | indent = len(str(out)) - len(str(out).lstrip()) 117 | 118 | # Assert 119 | self.assertEqual(indent, 4) 120 | 121 | 122 | class TestMockWindowsTerminal(unittest.TestCase): 123 | 124 | def setUp(self): 125 | builtins.__import__ = mock_import 126 | reload(term) 127 | 128 | def tearDown(self): 129 | builtins.__import__ = REAL_IMPORT 130 | reload(term) 131 | 132 | 133 | class TestMockUnsupportedTerminal(unittest.TestCase): 134 | 135 | def setUp(self): 136 | builtins.__import__ = mock_import 137 | reload(term) 138 | 139 | def tearDown(self): 140 | builtins.__import__ = REAL_IMPORT 141 | reload(term) 142 | 143 | def test_import_fail(self): 144 | self.assertIsNone(term.fcntl) 145 | self.assertIsNone(term.termios) 146 | 147 | def test_term_width_not_available(self): 148 | term_width = term.term_width() 149 | 150 | self.assertIsNone(term_width) 151 | 152 | def test_default_line_width(self): 153 | line_width = term.line_width() 154 | 155 | self.assertEqual(line_width, term.DEFAULT_LINE_WIDTH) 156 | -------------------------------------------------------------------------------- /tests/test_unicode.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from __future__ import unicode_literals 3 | """ 4 | Unit tests for Unicode issues. 5 | 6 | This requires Unicode string literals in Python 2. 7 | 8 | """ 9 | import unittest 10 | 11 | from see import output, tools 12 | from see.features import PY3 13 | 14 | 15 | class TestSeeUnicode(unittest.TestCase): 16 | 17 | def test_char_width(self): 18 | # Arrange 19 | char_ascii = 'a' # narrow char 20 | char_accent = 'á' # narrow char 21 | char_combo = 'q̇' # narrow char + zero-width combining char 22 | char_cjk = '猫' # wide character 23 | 24 | # Act 25 | len_ascii = len(char_ascii) 26 | len_accent = len(char_accent) 27 | len_combo = len(char_combo) 28 | len_cjk = len(char_cjk) 29 | width_ascii = tools.display_len(char_ascii) 30 | width_accent = tools.display_len(char_accent) 31 | width_combo = tools.display_len(char_combo) 32 | width_cjk = tools.display_len(char_cjk) 33 | 34 | # Assert 35 | self.assertEqual(len_ascii, 1) 36 | self.assertEqual(len_accent, 1) 37 | self.assertEqual(len_combo, 2) 38 | self.assertEqual(len_cjk, 1) 39 | self.assertEqual(width_ascii, 1) 40 | self.assertEqual(width_accent, 1) 41 | self.assertEqual(width_combo, 1) 42 | self.assertEqual(width_cjk, 2) 43 | 44 | def test_display_len(self): 45 | # Arrange 46 | attr_ascii = '.hello_world()' 47 | attr_cyrillic = '.hello_мир()' 48 | attr_cjk = '.hello_世界()' 49 | attr_combo = '.hello_q̇()' 50 | diff_cjk = 2 if PY3 else 0 51 | diff_combo = -1 if PY3 else 0 52 | 53 | # Act 54 | width_ascii = tools.display_len(attr_ascii) 55 | width_cyrillic = tools.display_len(attr_cyrillic) 56 | width_cjk = tools.display_len(attr_cjk) 57 | width_combo = tools.display_len(attr_combo) 58 | justify_ascii = len(output.justify_token(attr_ascii, 20)) 59 | justify_cyrillic = len(output.justify_token(attr_cyrillic, 20)) 60 | justify_cjk = len(output.justify_token(attr_cjk, 20)) 61 | justify_combo = len(output.justify_token(attr_combo, 20)) 62 | 63 | # Assert 64 | self.assertEqual(width_ascii, 14) 65 | self.assertEqual(width_cyrillic, 12) 66 | self.assertEqual(width_cjk, 13) 67 | self.assertEqual(width_combo, 10) 68 | self.assertEqual(justify_cyrillic, justify_ascii) 69 | self.assertEqual(justify_cjk, justify_ascii - diff_cjk) 70 | self.assertEqual(justify_combo, justify_ascii - diff_combo) 71 | --------------------------------------------------------------------------------