Showing preview only (239K chars total). Download the full file or copy to clipboard to get everything.
Repository: awslabs/aws-shell
Branch: master
Commit: 74d95a74fde8
Files: 75
Total size: 221.5 KB
Directory structure:
gitextract_8vvun9tj/
├── .changes/
│ ├── 0.2.0.json
│ ├── 0.2.1.json
│ └── 0.2.2.json
├── .gitignore
├── .pylintrc
├── .travis.yml
├── CHANGELOG.rst
├── LICENSE.txt
├── MANIFEST.in
├── Makefile
├── NOTICE.txt
├── README.rst
├── awsshell/
│ ├── __init__.py
│ ├── app.py
│ ├── autocomplete.py
│ ├── awsshellrc
│ ├── compat.py
│ ├── config.py
│ ├── data/
│ │ ├── cloudformation/
│ │ │ └── 2010-05-15/
│ │ │ └── completions-1.json
│ │ ├── dynamodb/
│ │ │ └── 2012-08-10/
│ │ │ └── completions-1.json
│ │ ├── ec2/
│ │ │ └── 2015-04-15/
│ │ │ └── completions-1.json
│ │ ├── elb/
│ │ │ └── 2012-06-01/
│ │ │ └── completions-1.json
│ │ ├── glacier/
│ │ │ └── 2012-06-01/
│ │ │ └── completions-1.json
│ │ ├── iam/
│ │ │ └── 2010-05-08/
│ │ │ └── completions-1.json
│ │ ├── kinesis/
│ │ │ └── 2013-12-02/
│ │ │ └── completions-1.json
│ │ ├── opsworks/
│ │ │ └── 2013-02-18/
│ │ │ └── completions-1.json
│ │ ├── s3/
│ │ │ └── 2006-03-01/
│ │ │ └── completions-1.json
│ │ ├── sns/
│ │ │ └── 2010-03-31/
│ │ │ └── completions-1.json
│ │ └── sqs/
│ │ └── 2012-11-05/
│ │ └── completions-1.json
│ ├── db.py
│ ├── docs.py
│ ├── fuzzy.py
│ ├── index/
│ │ ├── __init__.py
│ │ └── completion.py
│ ├── keys.py
│ ├── lexer.py
│ ├── loaders.py
│ ├── makeindex.py
│ ├── resource/
│ │ ├── __init__.py
│ │ └── index.py
│ ├── shellcomplete.py
│ ├── style.py
│ ├── substring.py
│ ├── toolbar.py
│ ├── ui.py
│ └── utils.py
├── requirements-dev.txt
├── requirements-test.txt
├── scripts/
│ ├── ci/
│ │ ├── install
│ │ ├── run-integ-tests
│ │ └── run-tests
│ └── new-change
├── setup.cfg
├── setup.py
├── tests/
│ ├── __init__.py
│ ├── compat.py
│ ├── integration/
│ │ ├── __init__.py
│ │ ├── test_config.py
│ │ ├── test_db.py
│ │ ├── test_keys.py
│ │ └── test_makeindex.py
│ └── unit/
│ ├── __init__.py
│ ├── index/
│ │ ├── __init__.py
│ │ └── test_completions.py
│ ├── test_app.py
│ ├── test_autocomplete.py
│ ├── test_docs.py
│ ├── test_fuzzy.py
│ ├── test_load_completions.py
│ ├── test_makeindex.py
│ ├── test_resources.py
│ ├── test_substring.py
│ ├── test_toolbar.py
│ └── test_utils.py
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .changes/0.2.0.json
================================================
{
"schema-version": "1.0",
"changes": [
{
"category": "Command History",
"description": "Ensure aws prefix is not prepended twice in command history. Fixes `#157 <https://github.com/awslabs/aws-shell/issues/157>`__",
"type": "bugfix"
},
{
"category": "Keybinding",
"description": "Switching between emacs/vi keybindings now functions properly",
"type": "bugfix"
},
{
"category": "Documentation",
"description": "The documentation pane can now be focused and navigated. Fixes `#74 <https://github.com/awslabs/aws-shell/issues/74>`__, `#159 <https://github.com/awslabs/aws-shell/issues/159>`__",
"type": "enhancement"
}
]
}
================================================
FILE: .changes/0.2.1.json
================================================
{
"schema-version": "1.0",
"changes": [
{
"category": "AWS CLI",
"description": "Fixes `#208 <https://github.com/awslabs/aws-shell/issues/208>`__. Update the AWS Shell to support the latest version of the AWS CLI.",
"type": "bugfix"
}
]
}
================================================
FILE: .changes/0.2.2.json
================================================
{
"schema-version": "1.0",
"changes": [
{
"type": "bugfix",
"category": "Dependency",
"description": "Fix bcdoc import errors. Fixes `#247 <https://github.com/awslabs/aws-shell/issues/247>`__."
}
]
}
================================================
FILE: .gitignore
================================================
*.py[co]
*.DS_Store
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
.cache
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Emacs backup files
*~
# Eclipse IDE
/.project
/.pydevproject
# IDEA IDE
.idea*
src/
# Completions Index
completions.idx
================================================
FILE: .pylintrc
================================================
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=compat.py
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
jobs=1
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=
# Allow optimization of some AST trees. This will activate a peephole AST
# optimizer, which will apply various small optimizations. For instance, it can
# be used to obtain the result of joining multiple strings with the addition
# operator. Joining a lot of strings can lead to a maximum recursion error in
# Pylint and this flag can prevent that. It has one side effect, the resulting
# AST will be different than the one from reality.
optimize-ast=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=E1608,R0201,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W1609,W1608,W1607,W1606,W1605,W1604,W1603,W1602,W1601,W1639,W0613,W1640,I0021,W1638,I0020,C0111,W1618,W1619,W1630,W1626,W1637,W1634,W1635,W1610,W1611,W1612,W1613,W1614,W1615,W1616,W1617,W1632,R0903,W1633,W0231,W0704,W0232,W1628,W1629,W1636
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions=apply,reduce
# Good variable names which should always be accepted, separated by a comma
good-names=e,i,j,k,n,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,50}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{0,50}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct constant names
const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{1,50}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{0,50}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,50}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=.*
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=3
# Ignore comments when computing similarities.
ignore-comments=no
# Ignore docstrings when computing similarities.
ignore-docstrings=no
# Ignore imports when computing similarities.
ignore-imports=no
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent,objects,DoesNotExist,md5,sha1,sha224,sha256,sha384,sha512
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_|dummy|ignore
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=30
# Maximum number of parents for a class (see R0901).
max-parents=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=0
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec,UserDict
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
================================================
FILE: .travis.yml
================================================
language: python
matrix:
include:
- python: 2.7
env: TEST_TYPE=test
- python: 2.7
env: TEST_TYPE=check
- python: 3.4
env: TEST_TYPE=test
- python: 3.5
env: TEST_TYPE=test
- python: 3.6
env: TEST_TYPE=test
sudo: false
install:
- if [[ $TEST_TYPE == 'check' ]]; then pip install -r requirements-dev.txt; fi
- python scripts/ci/install
script:
- make $TEST_TYPE
================================================
FILE: CHANGELOG.rst
================================================
=========
CHANGELOG
=========
0.2.2
=====
* bugfix:Dependency: Fix bcdoc import errors. Fixes `#247 <https://github.com/awslabs/aws-shell/issues/247>`__.
0.2.1
=====
* bugfix:AWS CLI: Fixes `#208 <https://github.com/awslabs/aws-shell/issues/208>`__. Update the AWS Shell to support the latest version of the AWS CLI.
0.2.0
=====
* bugfix:Command History: Ensure aws prefix is not prepended twice in command history.
Fixes `#157 <https://github.com/awslabs/aws-shell/issues/157>`__
* bugfix:Keybinding: Switching between emacs/vi keybindings now functions properly
* enhancement:Documentation: The documentation pane can now be focused and navigated.
Fixes `#74 <https://github.com/awslabs/aws-shell/issues/74>`__, `#159 <https://github.com/awslabs/aws-shell/issues/159>`__
0.1.1
=====
* bugfix:AWS CLI: Fix issue with latest version of the AWS CLI
that would cause the AWS Shell to raise an exception on startup.
The minimum version of the AWS CLI has been bumped to 1.10.30.
(`issue 118 <https://github.com/awslabs/aws-shell/issues/118>`__)
0.1.0
=====
* feature:Dot Commands: Add ``.exit/.quit`` dot commands
(`issue 97 <https://github.com/awslabs/aws-shell/pull/97>`__)
* feature:Documentation: Show documentation for global arguments
(`issue 51 <https://github.com/awslabs/aws-shell/issues/51>`__)
* feature:Dot Commands: Add ``.cd`` dot command
(`issue 97 <https://github.com/awslabs/aws-shell/issues/76>`__)
* feature:Dot Commands: Add ``.profile`` dot command
(`issue 97 <https://github.com/awslabs/aws-shell/issues/9>`__)
* feature:Command Line Arguments: Add ``--profile`` command line
option (`issue 89 <https://github.com/awslabs/aws-shell/issues/89>`__)
* bugfix:Completer: Fix crash when attempting server side completion
with no region configured option
(`issue 84 <https://github.com/awslabs/aws-shell/issues/84>`__)
* feature:Lexer: Add lexer/syntax highlighting
(`issue 27 <https://github.com/awslabs/aws-shell/issues/27>`__)
* feature:Server Side Completion: Add server side completion for
Elastic Load Balancing
(`issue 79 <https://github.com/awslabs/aws-shell/pull/79>`__)
* feature:Server Side Completion: Add server side completion for
Amazon Kinesis
(`issue 73 <https://github.com/awslabs/aws-shell/pull/73>`__)
* bugfix:Windows: Fix crash when using ``.edit`` on Windows
(`issue 55 <https://github.com/awslabs/aws-shell/pull/55>`__)
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MANIFEST.in
================================================
include README.rst
include LICENSE.txt
include NOTICE.txt
include awsshell/awsshellrc
recursive-include awsshell/data *.json
graft tests
================================================
FILE: Makefile
================================================
# Eventually I'll add:
# py.test --cov awsshell --cov-report term-missing --cov-fail-under 95 tests/
# which will fail if tests are under 95%
check:
###### FLAKE8 #####
# No unused imports, no undefined vars,
# follow pep8, and max cyclomatic complexity of 15.
# I'd eventually like to lower this down to < 10.
flake8 --ignore=E731,W503 --exclude awsshell/compat.py --max-complexity 15 awsshell/*.py
#
#
##### DOC8 ######
# Correct rst formatting for docstrings
#
##
doc8 awsshell/*.py
#
#
#
# Proper docstring conventions according to pep257
#
#
pep257 --add-ignore=D100,D101,D102,D103,D104,D105,D204
#
#
#
###### PYLINT ERRORS ONLY ######
#
#
#
pylint --rcfile .pylintrc -E awsshell
test:
python scripts/ci/run-tests
pylint:
###### PYLINT ######
# Python linter. This will generally not have clean output.
# So you'll need to manually verify this output.
#
#
pylint --rcfile .pylintrc awsshell
coverage:
py.test --cov awsshell --cov-report term-missing tests/
================================================
FILE: NOTICE.txt
================================================
AWS Shell
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
================================================
FILE: README.rst
================================================
aws-shell - The interactive productivity booster for the AWS CLI
================================================================
.. image:: https://aws-developer-blog-media.s3-us-west-2.amazonaws.com/cli/Super-Charge-Your-AWS-Command-Line-Experience-with-aws-shell/aws-shell-final.gif
Installation
============
The aws-shell requires python and `pip`_ to install.
You can install the aws-shell using `pip`_::
$ pip install aws-shell
If you are not installing into a virtualenv you can run::
$ sudo pip install aws-shell
**Mac OS X (10.11 El Capitan) users**: There is a known issue with Apple and
its included python package dependencies (more info at
https://github.com/pypa/pip/issues/3165).
We are investigating ways to fix this issue but in the meantime,
to install the aws-shell, you can run:
``sudo pip install aws-shell --upgrade --ignore-installed six``
Once you've installed the aws-shell, you can now run::
$ aws-shell
To exit the shell, press ``Ctrl-D``.
Upgrading the aws-shell
-----------------------
If you want to upgrade to the latest version of the aws-shell,
you can run::
$ pip install --upgrade aws-shell
You can also use this upgrade command whenever a new version of the AWS CLI is
released that includes new services and API updates. You will then be
able to use these new services and API updates in the aws-shell.
Supported Python Versions
-------------------------
The aws-shell works on the same python versions supported by the AWS CLI:
* 2.6.5 and greater
* 2.7.x and greater
* 3.3.x and greater
* 3.4.x and greater
Configuration
=============
The aws-shell uses the same configuration settings as the AWS CLI.
If you've never used the AWS CLI before, the easiest way to get
started is to run the ``configure`` command::
$ aws-shell
aws> configure
AWS Access Key ID [None]: your-access-key-id
AWS Secret Access Key [None]: your-secret-access-key
Default region name [None]: region-to-use (e.g us-west-2, us-west-1, etc).
Default output format [None]:
aws>
For more information about configure settings, see the
`AWS CLI Getting Started Guide`_.
Basic Usage
===========
The aws-shell accepts the same commands as the AWS CLI, except you don't
need to provide the ``aws`` prefix. For example, here are a few commands
you can try::
$ aws-shell
aws> ec2 describe-regions
{
"Regions": [
{
"Endpoint": "ec2.eu-west-1.amazonaws.com",
"RegionName": "eu-west-1"
},
...
aws> s3 ls
2015-12-07 15:03:34 bucket1
2015-12-07 15:03:34 bucket2
aws> dynamodb list-tables --output text
TABLENAMES First
TABLENAMES Second
TABLENAMES Third
Profiles
--------
The aws-shell supports AWS CLI profiles. You have two options to use
profiles. First, you can provide a profile when you start the aws-shell::
$ aws-shell --profile prod
aws>
When you do this all the server side completion as well as CLI commands
you run will automatically use the ``prod`` profile.
You can also change the current profile while you're in the aws-shell::
$ aws-shell
aws> .profile demo
Current shell profile changed to: demo
You can also check what profile you've configured in the aws-shell using::
aws> .profile
Current shell profile: demo
After changing your profile using the ``.profile`` dot command, all
server side completion as well as CLI commands will automatically use
the new profile you've configured.
Features
========
Auto Completion of Commands and Options
---------------------------------------
The aws-shell provides auto completion of commands and
options as you type.
.. image:: https://cloud.githubusercontent.com/assets/368057/11824078/784a613e-a32c-11e5-8ac5-f1d1873cc643.png
Shorthand Auto Completion
-------------------------
The aws-shell can also fill in an example of the
shorthand syntax used for various AWS CLI options:
.. image:: https://cloud.githubusercontent.com/assets/368057/11823453/e95d85da-a328-11e5-8b8d-67566eccf9e3.png
Server Side Auto Completion
---------------------------
The aws-shell also leverages `boto3`_, the AWS SDK for Python, to auto complete
server side resources such as Amazon EC2 instance Ids, Amazon Dynamodb table
names, AWS IAM user names, Amazon S3 bucket names, etc.
This feature is under active development. The list of supported resources
continues to grow.
.. image:: https://cloud.githubusercontent.com/assets/368057/11824022/3648b4fc-a32c-11e5-8e18-92f028eb1cee.png
Fuzzy Searching
---------------
Every auto completion value supports fuzzy searching. This enables you to
specify the commands, options, and values you want to run with even less
typing. You can try typing:
* The first letter of each sub word: ``ec2 describe-reserved-instances-offerings``
-> ``ec2 drio``
* A little bit of each word: ``ec2 describe-instances`` -> ``ec2 descinst``
* Any part of the command: ``dynamodb table`` -> Offers all commands that
contain the subsequence ``table``.
.. image:: https://cloud.githubusercontent.com/assets/368057/11823996/18e69d16-a32c-11e5-80a2-defbaa6a8a80.png
Inline Documentation
--------------------
The aws-shell will automatically pull up documentation as you type commands.
It will show inline documentation for CLI options. There is also a separate
documentation panel that will show documentation for the current command or
option you are typing. Pressing F9 will toggle focus to the documentation panel
allowing you to navigate it using your selected keybindings.
.. image:: https://cloud.githubusercontent.com/assets/368057/11823320/36ae9b04-a328-11e5-9661-81abfc0afe5a.png
Fish-Style Auto Suggestions
---------------------------
The aws-shell supports Fish-style auto-suggestions. Use the right arrow key to
complete a suggestion.
.. image:: https://cloud.githubusercontent.com/assets/368057/11822961/4bceff94-a326-11e5-87fa-c664e1e82be4.png
Command History
---------------
The aws-shell records the commands you run and writes them to
``~/.aws/shell/history``. You can use the up and down arrow keys to scroll
through your history.
.. image:: https://cloud.githubusercontent.com/assets/368057/11823211/b5851e9a-a327-11e5-877f-687dc1f90e27.png
Toolbar Options
---------------
The aws-shell has a bottom toolbar that provides several options:
* ``F2`` toggles between fuzzy and substring matching
* ``F3`` toggles between VI and Emacs key bindings
* ``F4`` toggles between single and multi column auto completions
* ``F5`` shows and hides the help documentation pane
* ``F9`` toggles focus between the cli and documentation pane
* ``F10`` or ``Ctrl-D`` exits the aws-shell
As you toggle options in the toolbar, your preferences are persisted
to the ``~/.aws/shell/awsshellrc`` file so that the next time you run
the aws-shell, your preferences will be restored.
.. image:: https://cloud.githubusercontent.com/assets/368057/11823907/8c3f1e60-a32b-11e5-9f99-fe504ea0a5dc.png
Dot Commands
------------
The aws-shell provides additional commands specific to the aws-shell.
The commands are available by adding the ``.`` prefix before a command.
Exiting the Shell
~~~~~~~~~~~~~~~~~
You can run the ``.exit`` or ``.quit`` commands to exit the shell.
Creating Shell Scripts with .edit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are times when you may want to take a sequence of commands
you've run in the aws-shell and combine them into a shell script.
In addition to the command history that's persisted to the
history file, the aws-shell also keeps track of all the commands
you've run since you first started your aws-shell session.
You can run the ``.edit`` command to open all these commands in
an editor. The aws-shell will use the ``EDITOR`` environment
variable before defaulting to ``notepad`` on Windows and
``vi`` on other platforms.
::
aws> ec2 describe-instances
aws> dynamodb list-tables
aws> .edit
Changing Profiles with .profile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can change the current AWS CLI profile used by the aws-shell
by using the ``.profile`` dot command. If you run the ``.profile``
command with no arguments, the currently configured shell profile
will be printed.
::
aws> .profile demo
Current shell profile changed to: demo
aws> .profile
Current shell profile: demo
.cd
~~~
You can change the current working directory of the aws-shell by using
the ``.cd`` command::
aws> !pwd
/usr
aws> .cd /tmp
aws> !pwd
/tmp
Executing Shell Commands
------------------------
The aws-shell integrates with other commands in several ways.
First, you can pipe AWS CLI commands to other processes as well
as redirect output to a file::
aws> dynamodb list-tables --output text | head -n 1
TABLENAMES First
aws> dynamodb list-tables --output text > /tmp/foo.txt
Second, if you want to run a shell command rather than an AWS CLI
command, you can add the ``!`` prefix to your command::
aws> !ls /tmp/
foo.txt bar.txt
Developer Preview Status
========================
The aws-shell is currently in developer preview.
We welcome feedback, feature requests, and bug reports.
There may be backwards incompatible changes made in order
to respond to customer feedback as we continue to iterate
on the aws-shell.
More Information
================
Below are miscellaneous links for more information:
* `AWS CLI Reference Docs`_
* `AWS CLI User Guide`_
* `AWS CLI Blog`_
* `AWS CLI Github Repo`_
.. _pip: http://www.pip-installer.org/en/latest/
.. _AWS CLI Getting Started Guide: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
.. _boto3: https://github.com/boto/boto3
.. _AWS CLI Reference Docs: http://docs.aws.amazon.com/cli/latest/reference/
.. _AWS CLI User Guide: http://docs.aws.amazon.com/cli/latest/userguide/
.. _AWS CLI Blog: https://blogs.aws.amazon.com/cli/
.. _AWS CLI Github Repo: https://github.com/aws/aws-cli
================================================
FILE: awsshell/__init__.py
================================================
from __future__ import unicode_literals, print_function
import json
import argparse
import threading
from awsshell import shellcomplete
from awsshell import autocomplete
from awsshell import app
from awsshell import docs
from awsshell import loaders
from awsshell.index import completion
from awsshell import utils
__version__ = '0.2.2'
def determine_doc_index_filename():
import awscli
base = loaders.JSONIndexLoader.index_filename(
awscli.__version__)
return base + '.docs'
def load_index(filename):
load = loaders.JSONIndexLoader()
return load.load_index(filename)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--profile', help='The profile name to use '
'when starting the AWS Shell.')
args = parser.parse_args()
indexer = completion.CompletionIndex()
try:
index_str = indexer.load_index(utils.AWSCLI_VERSION)
index_data = json.loads(index_str)
except completion.IndexLoadError:
print("First run, creating autocomplete index...")
from awsshell.makeindex import write_index
# TODO: Using internal method, but this will eventually
# be moved into the CompletionIndex class anyways.
index_file = indexer._filename_for_version(utils.AWSCLI_VERSION)
write_index(index_file)
index_str = indexer.load_index(utils.AWSCLI_VERSION)
index_data = json.loads(index_str)
doc_index_file = determine_doc_index_filename()
from awsshell.makeindex import write_doc_index
doc_data = docs.load_lazy_doc_index(doc_index_file)
# There's room for improvement here. If the docs didn't finish
# generating, we regen the whole doc index. Ideally we pick up
# from where we left off.
try:
docs.load_doc_db(doc_index_file)['__complete__']
except KeyError:
print("Creating doc index in the background. "
"It will be a few minutes before all documentation is "
"available.")
t = threading.Thread(target=write_doc_index, args=(doc_index_file,))
t.daemon = True
t.start()
model_completer = autocomplete.AWSCLIModelCompleter(index_data)
completer = shellcomplete.AWSShellCompleter(model_completer)
shell = app.create_aws_shell(completer, model_completer, doc_data)
if args.profile:
shell.profile = args.profile
shell.run()
if __name__ == '__main__':
main()
================================================
FILE: awsshell/app.py
================================================
"""AWS Shell application.
Main entry point to the AWS Shell.
"""
from __future__ import unicode_literals
import os
import subprocess
import logging
import sys
from prompt_toolkit.document import Document
from prompt_toolkit.shortcuts import create_eventloop
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.filters import Always
from prompt_toolkit.interface import CommandLineInterface, Application
from prompt_toolkit.interface import AbortAction, AcceptAction
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.history import InMemoryHistory, FileHistory
from prompt_toolkit.enums import EditingMode
from awsshell.ui import create_default_layout
from awsshell.config import Config
from awsshell.keys import KeyManager
from awsshell.style import StyleFactory
from awsshell.toolbar import Toolbar
from awsshell.utils import build_config_file_path, temporary_file
from awsshell import compat
LOG = logging.getLogger(__name__)
EXIT_REQUESTED = object()
def create_aws_shell(completer, model_completer, docs):
return AWSShell(completer, model_completer, docs)
class InputInterrupt(Exception):
"""Stops the input of commands.
Raising `InputInterrupt` is useful to force a cli rebuild, which is
sometimes necessary in order for config changes to take effect.
"""
pass
class ChangeDirHandler(object):
def __init__(self, output=sys.stdout, err=sys.stderr, chdir=os.chdir):
self._output = output
self._err = err
self._chdir = chdir
def run(self, command, application):
# command is a list of parsed commands
if len(command) != 2:
self._err.write("invalid syntax, must be: .cd dirname\n")
return
dirname = os.path.expandvars(os.path.expanduser(command[1]))
try:
self._chdir(dirname)
except OSError as e:
self._err.write("cd: %s\n" % e)
class EditHandler(object):
def __init__(self, popen_cls=None, env=None, err=sys.stderr):
if popen_cls is None:
popen_cls = subprocess.Popen
self._popen_cls = popen_cls
if env is None:
env = os.environ
self._env = env
self._err = err
def _get_editor_command(self):
if 'EDITOR' in self._env:
return self._env['EDITOR']
else:
return compat.default_editor()
def _generate_edit_history(self, application):
history = list(application.history)
commands = [h for h in history if not h.startswith(('.', '!'))]
return '\n'.join(commands)
def run(self, command, application):
"""Open application's history buffer in an editor.
:type command: list
:param command: The dot command as a list split
on whitespace, e.g ``['.foo', 'arg1', 'arg2']``
:type application: AWSShell
:param application: The application object.
"""
with temporary_file('w') as f:
all_commands = self._generate_edit_history(application)
f.write(all_commands)
f.flush()
editor = self._get_editor_command()
try:
p = self._popen_cls([editor, f.name])
p.communicate()
except OSError:
self._err.write("Unable to launch editor: %s\n"
"You can configure which editor to use by "
"exporting the EDITOR environment variable.\n"
% editor)
class ProfileHandler(object):
USAGE = (
'.profile # Print the current profile\n'
'.profile <name> # Change the current profile\n'
)
def __init__(self, output=sys.stdout, err=sys.stderr):
self._output = output
self._err = err
def run(self, command, application):
"""Get or set the profile.
If .profile is called with no args, the current profile
is displayed. If the .profile command is called with a
single arg, then the current profile for the application
will be set to the new value.
"""
if len(command) == 1:
profile = application.profile
if profile is None:
self._output.write(
"Current shell profile: no profile configured\n"
"You can change profiles using: .profile profile-name\n")
else:
self._output.write("Current shell profile: %s\n" % profile)
elif len(command) == 2:
new_profile_name = command[1]
application.profile = new_profile_name
self._output.write("Current shell profile changed to: %s\n" %
new_profile_name)
else:
self._err.write("Usage:\n%s\n" % self.USAGE)
class ExitHandler(object):
def run(self, command, application):
return EXIT_REQUESTED
class DotCommandHandler(object):
HANDLER_CLASSES = {
'edit': EditHandler,
'profile': ProfileHandler,
'cd': ChangeDirHandler,
'exit': ExitHandler,
'quit': ExitHandler,
}
def __init__(self, output=sys.stdout, err=sys.stderr):
self._output = output
self._err = err
def handle_cmd(self, command, application):
"""Handle running a given dot command from a user.
:type command: str
:param command: The full dot command string, e.g. ``.edit``,
of ``.profile prod``.
:type application: AWSShell
:param application: The application object.
"""
parts = command.split()
cmd_name = parts[0][1:]
if cmd_name not in self.HANDLER_CLASSES:
self._unknown_cmd(parts, application)
else:
# Note we expect the class to support no-arg
# instantiation.
return self.HANDLER_CLASSES[cmd_name]().run(parts, application)
def _unknown_cmd(self, cmd_parts, application):
self._err.write("Unknown dot command: %s\n" % cmd_parts[0])
class AWSShell(object):
"""Encapsulate the ui, completer, command history, docs, and config.
Runs the input event loop and delegates the command execution to either
the `awscli` or the underlying shell.
:type refresh_cli: bool
:param refresh_cli: Flag to refresh the cli.
:type config_obj: :class:`configobj.ConfigObj`
:param config_obj: Contains the config information for reading and writing.
:type config_section: :class:`configobj.Section`
:param config_section: Convenience attribute to access the main section
of the config.
:type model_completer: :class:`AWSCLIModelCompleter`
:param model_completer: Matches input with completions. `AWSShell` sets
and gets the attribute `AWSCLIModelCompleter.match_fuzzy`.
:type enable_vi_bindings: bool
:param enable_vi_bindings: If True, enables Vi key bindings. Else, Emacs
key bindings are enabled.
:type show_completion_columns: bool
param show_completion_columns: If True, completions are shown in multiple
columns. Else, completions are shown in a single scrollable column.
:type show_help: bool
:param show_help: If True, shows the help pane. Else, hides the help pane.
:type theme: str
:param theme: The pygments theme.
"""
def __init__(self, completer, model_completer, docs,
input=None, output=None, popen_cls=None):
self.completer = completer
self.model_completer = model_completer
self.history = InMemoryHistory()
self.file_history = FileHistory(build_config_file_path('history'))
self._cli = None
self._docs = docs
self.current_docs = u''
self.refresh_cli = False
self.key_manager = None
self._dot_cmd = DotCommandHandler()
self._env = os.environ.copy()
self._profile = None
self._input = input
self._output = output
if popen_cls is None:
popen_cls = subprocess.Popen
self._popen_cls = popen_cls
# These attrs come from the config file.
self.config_obj = None
self.config_section = None
self.enable_vi_bindings = None
self.show_completion_columns = None
self.show_help = None
self.theme = None
self.load_config()
def load_config(self):
"""Load the config from the config file or template."""
config = Config()
self.config_obj = config.load('awsshellrc')
self.config_section = self.config_obj['aws-shell']
self.model_completer.match_fuzzy = self.config_section.as_bool(
'match_fuzzy')
self.enable_vi_bindings = self.config_section.as_bool(
'enable_vi_bindings')
self.show_completion_columns = self.config_section.as_bool(
'show_completion_columns')
self.show_help = self.config_section.as_bool('show_help')
self.theme = self.config_section['theme']
def save_config(self):
"""Save the config to the config file."""
self.config_section['match_fuzzy'] = self.model_completer.match_fuzzy
self.config_section['enable_vi_bindings'] = self.enable_vi_bindings
self.config_section['show_completion_columns'] = \
self.show_completion_columns
self.config_section['show_help'] = self.show_help
self.config_section['theme'] = self.theme
self.config_obj.write()
@property
def cli(self):
if self._cli is None or self.refresh_cli:
self._cli = self.create_cli_interface(self.show_completion_columns)
self.refresh_cli = False
return self._cli
def run(self):
while True:
try:
document = self.cli.run(reset_current_buffer=True)
text = document.text
except InputInterrupt:
pass
except (KeyboardInterrupt, EOFError):
self.save_config()
break
else:
if text.startswith('.'):
# These are special commands (dot commands) that are
# interpreted by the aws-shell directly and typically used
# to modify some type of behavior in the aws-shell.
result = self._dot_cmd.handle_cmd(text, application=self)
if result is EXIT_REQUESTED:
break
else:
if text.startswith('!'):
# Then run the rest as a normally shell command.
full_cmd = text[1:]
else:
full_cmd = 'aws ' + text
self.history.append(full_cmd)
self.current_docs = u''
self.cli.buffers['clidocs'].reset(
initial_document=Document(self.current_docs,
cursor_position=0))
self.cli.request_redraw()
p = self._popen_cls(full_cmd, shell=True, env=self._env)
p.communicate()
def stop_input_and_refresh_cli(self):
"""Stop input by raising an `InputInterrupt`, forces a cli refresh.
The cli refresh is necessary because changing options such as key
bindings, single vs multi column menu completions, and the help pane
all require a rebuild.
:raises: :class:`InputInterrupt <exceptions.InputInterrupt>`.
"""
self.refresh_cli = True
self.cli.request_redraw()
raise InputInterrupt
def create_layout(self, display_completions_in_columns, toolbar):
from awsshell.lexer import ShellLexer
lexer = ShellLexer
if self.config_section['theme'] == 'none':
lexer = None
return create_default_layout(
self, u'aws> ', lexer=lexer, reserve_space_for_menu=True,
display_completions_in_columns=display_completions_in_columns,
get_bottom_toolbar_tokens=toolbar.handler)
def create_buffer(self, completer, history):
return Buffer(
history=history,
auto_suggest=AutoSuggestFromHistory(),
enable_history_search=True,
completer=completer,
complete_while_typing=Always(),
accept_action=AcceptAction.RETURN_DOCUMENT)
def create_key_manager(self):
"""Create the :class:`KeyManager`.
The inputs to KeyManager are expected to be callable, so we can't
use the standard @property and @attrib.setter for these attributes.
Lambdas cannot contain assignments so we're forced to define setters.
:rtype: :class:`KeyManager`
:return: A KeyManager with callables to set the toolbar options. Also
includes the method stop_input_and_refresh_cli to ensure certain
options take effect within the current session.
"""
def set_match_fuzzy(match_fuzzy):
"""Setter for fuzzy matching mode.
:type match_fuzzy: bool
:param match_fuzzy: The match fuzzy flag.
"""
self.model_completer.match_fuzzy = match_fuzzy
def set_enable_vi_bindings(enable_vi_bindings):
"""Setter for vi mode keybindings.
If vi mode is off, emacs mode is enabled by default by
`prompt_toolkit`.
:type enable_vi_bindings: bool
:param enable_vi_bindings: The enable Vi bindings flag.
"""
self.enable_vi_bindings = enable_vi_bindings
def set_show_completion_columns(show_completion_columns):
"""Setter for showing the completions in columns flag.
:type show_completion_columns: bool
:param show_completion_columns: The show completions in
multiple columns flag.
"""
self.show_completion_columns = show_completion_columns
def set_show_help(show_help):
"""Setter for showing the help container flag.
:type show_help: bool
:param show_help: The show help flag.
"""
self.show_help = show_help
return KeyManager(
lambda: self.model_completer.match_fuzzy, set_match_fuzzy,
lambda: self.enable_vi_bindings, set_enable_vi_bindings,
lambda: self.show_completion_columns, set_show_completion_columns,
lambda: self.show_help, set_show_help,
self.stop_input_and_refresh_cli)
def create_application(self, completer, history,
display_completions_in_columns):
self.key_manager = self.create_key_manager()
toolbar = Toolbar(
lambda: self.model_completer.match_fuzzy,
lambda: self.enable_vi_bindings,
lambda: self.show_completion_columns,
lambda: self.show_help)
style_factory = StyleFactory(self.theme)
buffers = {
'clidocs': Buffer(read_only=True)
}
if self.enable_vi_bindings:
editing_mode = EditingMode.VI
else:
editing_mode = EditingMode.EMACS
return Application(
editing_mode=editing_mode,
layout=self.create_layout(display_completions_in_columns, toolbar),
mouse_support=False,
style=style_factory.style,
buffers=buffers,
buffer=self.create_buffer(completer, history),
on_abort=AbortAction.RETRY,
on_exit=AbortAction.RAISE_EXCEPTION,
on_input_timeout=self.on_input_timeout,
key_bindings_registry=self.key_manager.manager.registry,
)
def on_input_timeout(self, cli):
if not self.show_help:
return
document = cli.current_buffer.document
text = document.text
LOG.debug("document.text = %s", text)
LOG.debug("current_command = %s", self.completer.current_command)
if text.strip():
command = self.completer.current_command
key_name = '.'.join(command.split()).encode('utf-8')
last_option = self.completer.last_option
if last_option:
self.current_docs = self._docs.extract_param(
key_name, last_option)
else:
self.current_docs = self._docs.extract_description(key_name)
else:
self.current_docs = u''
position = cli.buffers['clidocs'].document.cursor_position
# if the docs to be displayed have changed, reset position to 0
if cli.buffers['clidocs'].text != self.current_docs:
position = 0
cli.buffers['clidocs'].reset(
initial_document=Document(
self.current_docs,
cursor_position=position
)
)
cli.request_redraw()
def create_cli_interface(self, display_completions_in_columns):
# A CommandLineInterface from prompt_toolkit
# accepts two things: an application and an
# event loop.
loop = create_eventloop()
app = self.create_application(self.completer,
self.file_history,
display_completions_in_columns)
cli = CommandLineInterface(application=app, eventloop=loop,
input=self._input, output=self._output)
return cli
@property
def profile(self):
return self._profile
@profile.setter
def profile(self, new_profile_name):
# There's only two things that need to know about new profile
# changes.
#
# First, the actual command runner. If we want
# to use a different profile, it should ensure that the CLI
# commands that get run use the new profile (via the
# AWS_DEFAULT_PROFILE env var).
#
# Second, we also need to let the server side autocompleter know.
#
# Given this is easy to manage by hand, I don't think
# it's worth adding an event system or observers just yet.
# If this gets hard to manage, the complexity of those systems
# would be worth it.
self._env['AWS_DEFAULT_PROFILE'] = new_profile_name
self.completer.change_profile(new_profile_name)
self._profile = new_profile_name
================================================
FILE: awsshell/autocomplete.py
================================================
from __future__ import print_function
from awsshell.fuzzy import fuzzy_search
from awsshell.substring import substring_search
class AWSCLIModelCompleter(object):
"""Autocompletion based on the JSON models for AWS services.
This class consumes indexed data based on the JSON models from
AWS service (which we pull through botocore's data loaders).
"""
def __init__(self, index_data, match_fuzzy=True):
self._index = index_data
self._root_name = 'aws'
self._global_options = index_data[self._root_name]['arguments']
# These values mutate as autocompletions occur.
# They track state to improve the autocompletion speed.
self._current_name = 'aws'
self._current = index_data[self._root_name]
self._last_position = 0
self._current_line = ''
self.last_option = ''
# This will get populated as a command is completed.
self.cmd_path = [self._current_name]
self.match_fuzzy = match_fuzzy
@property
def global_arg_metadata(self):
return self._index[self._root_name]['argument_metadata']
@property
def arg_metadata(self):
# Returns the required arguments for the current level.
return self._current.get('argument_metadata', {})
def reset(self):
# Resets all the state. Called after a user runs
# a command.
self._current_name = self._root_name
self._current = self._index[self._root_name]
self._last_position = 0
self.last_option = ''
self.cmd_path = [self._current_name]
def autocomplete(self, line):
"""Given a line, return a list of suggestions."""
current_length = len(line)
self._current_line = line
if current_length == 1 and self._last_position > 1:
# Reset state. This is likely from a user completing
# a previous command.
self.reset()
elif current_length < self._last_position:
# The user has hit backspace. We'll need to check
# the current words.
return self._handle_backspace()
elif not line:
return []
elif current_length != self._last_position + 1:
return self._complete_from_full_parse()
# This position is important. We only update the _last_position
# after we've checked the special cases above where that value
# matters.
self._last_position = len(line)
if line and not line.strip():
# Special case, the user hits a space on a new line so
# we autocomplete all the top level commands.
return self._current['commands']
last_word = line.split()[-1]
if last_word in self.arg_metadata or last_word in self._global_options:
# The last thing we completed was an argument, record
# this as self.last_arg
self.last_option = last_word
if line[-1] == ' ':
# At this point the user has autocompleted a command
# or an argument and has hit space. If they've
# just completed a command, we need to change the
# current context and traverse into the subcommand.
# "ec2 "
# ^--here, need to traverse into "ec2"
#
# Otherwise:
# "ec2 --no-validate-ssl "
# ^-- here, stay on "ec2" context.
if not last_word.startswith('-'):
next_command = self._current['children'].get(last_word)
if next_command is not None:
self._current = next_command
self._current_name = last_word
self.cmd_path.append(self._current_name)
elif last_word in self.arg_metadata and \
self.arg_metadata[last_word]['example']:
# Then this is an arg with a shorthand example so we'll
# suggest that example.
return [self.arg_metadata[last_word]['example']]
# Even if we don't change context, we still want to
# autocomplete all the commands for the current context
# in either of the above two cases.
return self._current['commands'][:]
elif last_word.startswith('-'):
# TODO: cache this for the duration of the current context.
# We don't need to recompute this until the args are
# different.
all_args = self._get_all_args()
if self.match_fuzzy:
return fuzzy_search(last_word, all_args)
else:
return substring_search(last_word, all_args)
if self.match_fuzzy:
return fuzzy_search(last_word, self._current['commands'])
else:
return substring_search(last_word, self._current['commands'])
def _get_all_args(self):
if self._current['arguments'] != self._global_options:
all_args = self._current['arguments'] + self._global_options
else:
all_args = self._current['arguments']
return all_args
def _handle_backspace(self):
return self._complete_from_full_parse()
def _complete_from_full_parse(self):
# We try to avoid calling this, but this is necessary
# sometimes. In this scenario, we're resetting everything
# and starting from the very beginning and reparsing
# everything.
# This is a naive implementation for now. This
# can be optimized.
self.reset()
line = self._current_line
for i in range(1, len(self._current_line)):
self.autocomplete(line[:i])
return self.autocomplete(line)
def _autocomplete_options(self, last_word):
global_args = []
# Autocomplete argument names.
current_arg_completions = [
cmd for cmd in self._current['arguments']
if cmd.startswith(last_word)]
if self._current_name != self._root_name:
# Also autocomplete global arguments.
global_args = [
cmd for cmd in self._global_options if
cmd.startswith(last_word)]
return current_arg_completions + global_args
================================================
FILE: awsshell/awsshellrc
================================================
[aws-shell]
# fuzzy or substring match.
match_fuzzy = True
# vi or emacs key bindings.
enable_vi_bindings = False
# multi or single column completion menu.
show_completion_columns = False
# show or hide the help pane.
show_help = True
# visual theme. possible values: manni, igor, xcode, vim,
# autumn,vs, rrt, native, perldoc, borland, tango, emacs,
# friendly, monokai, paraiso-dark, colorful, murphy, bw,
# pastie, paraiso-light, trac, default, fruity.
# to disable themes, set theme = none
theme = vim
================================================
FILE: awsshell/compat.py
================================================
from __future__ import print_function
import sys
import platform
PY3 = sys.version_info[0] == 3
ON_WINDOWS = platform.system() == 'Windows'
if PY3:
from html.parser import HTMLParser
text_type = str
from io import StringIO
import dbm
else:
from HTMLParser import HTMLParser
text_type = unicode
from cStringIO import StringIO
import anydbm as dbm
if ON_WINDOWS:
def default_editor():
return 'notepad.exe'
else:
def default_editor():
return 'vi'
================================================
FILE: awsshell/config.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import os
import shutil
from configobj import ConfigObj
from awsshell.utils import build_config_file_path
class Config(object):
"""Reads and writes the config template and user config file."""
def load(self, config_template, config_file=None):
"""Read the config file if it exists, else read the default config.
Creates the user config file if it doesn't exist using the template.
:type config_template: str
:param config_template: The config template file name.
:type config_file: str
:param config_file: (Optional) The config file name.
If None, the config_file name will be set to the config_template.
:rtype: :class:`configobj.ConfigObj`
:return: The config information for reading and writing.
"""
if config_file is None:
config_file = config_template
config_path = build_config_file_path(config_file)
template_path = os.path.join(os.path.dirname(__file__),
config_template)
self._copy_template_to_config(template_path, config_path)
return self._load_template_or_config(template_path, config_path)
def _load_template_or_config(self, template_path, config_path):
"""Load the config file if it exists, else read the default config.
:type template_path: str
:param template_path: The template config file path.
:type config_path: str
:param config_path: The user's config file path.
:rtype: :class:`configobj.ConfigObj`
:return: The config information for reading and writing.
"""
expanded_config_path = os.path.expanduser(config_path)
cfg = ConfigObj()
cfg.filename = expanded_config_path
cfg.merge(ConfigObj(template_path, interpolation=False))
cfg.merge(ConfigObj(expanded_config_path, interpolation=False))
return cfg
def _copy_template_to_config(self, template_path,
config_path, overwrite=False):
"""Write the default config from a template.
:type template_path: str
:param template_path: The template config file path.
:type config_path: str
:param config_path: The user's config file path.
:type overwrite: bool
:param overwrite: (Optional) Determines whether to overwrite the
existing config file, if it exists.
:raises: :class:`OSError <exceptions.OSError>`
"""
config_path = os.path.expanduser(config_path)
if not overwrite and os.path.isfile(config_path):
return
else:
try:
config_path_dir_name = os.path.dirname(config_path)
os.makedirs(config_path_dir_name)
except OSError:
if not os.path.isdir(config_path_dir_name):
raise
shutil.copyfile(template_path, config_path)
================================================
FILE: awsshell/data/cloudformation/2010-05-15/completions-1.json
================================================
{
"operations": {
"UpdateStack": {
"StackName": {
"resourceName": "Stack",
"resourceIdentifier": "Name"
}
},
"DeleteStack": {
"StackName": {
"resourceName": "Stack",
"resourceIdentifier": "Name"
}
},
"CancelUpdateStack": {
"StackName": {
"resourceName": "Stack",
"resourceIdentifier": "Name"
}
}
},
"resources": {
"Stack": {
"operation": "DescribeStacks",
"resourceIdentifier": {
"Name": "Stacks[].StackName"
}
}
}
}
================================================
FILE: awsshell/data/dynamodb/2012-08-10/completions-1.json
================================================
{
"operations": {
"UpdateTable": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
},
"DeleteTable": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
},
"Scan": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
},
"GetItem": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
},
"Query": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
},
"PutItem": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
},
"UpdateItem": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
},
"DeleteItem": {
"TableName": {
"resourceName": "Table",
"resourceIdentifier": "Name"
}
}
},
"resources": {
"Table": {
"operation": "ListTables",
"resourceIdentifier": {
"Name": "TableNames[]"
}
}
}
}
================================================
FILE: awsshell/data/ec2/2015-04-15/completions-1.json
================================================
{
"operations": {
"ResetNetworkInterfaceAttribute": {
"NetworkInterfaceId": {
"resourceName": "NetworkInterface",
"resourceIdentifier": "Id"
}
},
"CreateSubnet": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"CopySnapshot": {
"SourceSnapshotId": {
"resourceName": "Snapshot",
"resourceIdentifier": "Id"
}
},
"AttachNetworkInterface": {
"NetworkInterfaceId": {
"resourceName": "NetworkInterface",
"resourceIdentifier": "Id"
}
},
"UnassignPrivateIpAddresses": {
"NetworkInterfaceId": {
"resourceName": "NetworkInterface",
"resourceIdentifier": "Id"
}
},
"DescribeImageAttribute": {
"ImageId": {
"resourceName": "Image",
"resourceIdentifier": "Id"
}
},
"DescribeInstances": {
"InstanceIds": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"ResetSnapshotAttribute": {
"SnapshotId": {
"resourceName": "Snapshot",
"resourceIdentifier": "Id"
}
},
"StartInstances": {
"InstanceIds": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"DeleteDhcpOptions": {
"DhcpOptionsId": {
"resourceName": "DhcpOptions",
"resourceIdentifier": "Id"
}
},
"RejectVpcPeeringConnection": {
"VpcPeeringConnectionId": {
"resourceName": "VpcPeeringConnection",
"resourceIdentifier": "Id"
}
},
"DeleteInternetGateway": {
"InternetGatewayId": {
"resourceName": "InternetGateway",
"resourceIdentifier": "Id"
}
},
"ResetImageAttribute": {
"ImageId": {
"resourceName": "Image",
"resourceIdentifier": "Id"
}
},
"ModifyNetworkInterfaceAttribute": {
"NetworkInterfaceId": {
"resourceName": "NetworkInterface",
"resourceIdentifier": "Id"
}
},
"MonitorInstances": {
"InstanceIds": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"DeleteNetworkAcl": {
"NetworkAclId": {
"resourceName": "NetworkAcl",
"resourceIdentifier": "Id"
}
},
"DisableVpcClassicLink": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"ModifyInstanceAttribute": {
"InstanceId": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"EnableVpcClassicLink": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"AssociateDhcpOptions": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"AssociateRouteTable": {
"RouteTableId": {
"resourceName": "RouteTable",
"resourceIdentifier": "Id"
}
},
"DescribeNetworkInterfaceAttribute": {
"NetworkInterfaceId": {
"resourceName": "NetworkInterface",
"resourceIdentifier": "Id"
}
},
"DeleteVpc": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"GetPasswordData": {
"InstanceId": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"UnmonitorInstances": {
"InstanceIds": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"AcceptVpcPeeringConnection": {
"VpcPeeringConnectionId": {
"resourceName": "VpcPeeringConnection",
"resourceIdentifier": "Id"
}
},
"DeleteSnapshot": {
"SnapshotId": {
"resourceName": "Snapshot",
"resourceIdentifier": "Id"
}
},
"CreateRoute": {
"RouteTableId": {
"resourceName": "RouteTable",
"resourceIdentifier": "Id"
}
},
"CreateNetworkAclEntry": {
"NetworkAclId": {
"resourceName": "NetworkAcl",
"resourceIdentifier": "Id"
}
},
"RevokeSecurityGroupEgress": {
"GroupId": {
"resourceName": "SecurityGroup",
"resourceIdentifier": "Id"
}
},
"DeregisterImage": {
"ImageId": {
"resourceName": "Image",
"resourceIdentifier": "Id"
}
},
"AssignPrivateIpAddresses": {
"NetworkInterfaceId": {
"resourceName": "NetworkInterface",
"resourceIdentifier": "Id"
}
},
"ReportInstanceStatus": {
"Instances": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"DescribeSnapshotAttribute": {
"SnapshotId": {
"resourceName": "Snapshot",
"resourceIdentifier": "Id"
}
},
"DeleteSubnet": {
"SubnetId": {
"resourceName": "Subnet",
"resourceIdentifier": "Id"
}
},
"DeleteVolume": {
"VolumeId": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"CreateNetworkAcl": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"AttachClassicLinkVpc": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"DeleteSecurityGroup": {
"GroupId": {
"resourceName": "SecurityGroup",
"resourceIdentifier": "Id"
}
},
"CreateImage": {
"InstanceId": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"DeleteVpcPeeringConnection": {
"VpcPeeringConnectionId": {
"resourceName": "VpcPeeringConnection",
"resourceIdentifier": "Id"
}
},
"ModifyVolumeAttribute": {
"VolumeId": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"ModifyImageAttribute": {
"ImageId": {
"resourceName": "Image",
"resourceIdentifier": "Id"
}
},
"DeleteNetworkAclEntry": {
"NetworkAclId": {
"resourceName": "NetworkAcl",
"resourceIdentifier": "Id"
}
},
"RunInstances": {
"SubnetId": {
"resourceName": "Subnet",
"resourceIdentifier": "Id"
}
},
"GetConsoleOutput": {
"InstanceId": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"StopInstances": {
"InstanceIds": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"ModifySnapshotAttribute": {
"SnapshotId": {
"resourceName": "Snapshot",
"resourceIdentifier": "Id"
}
},
"DescribeVpcAttribute": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"AttachInternetGateway": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"DescribeVolumeAttribute": {
"VolumeId": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"DetachVolume": {
"VolumeId": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"ReplaceNetworkAclEntry": {
"NetworkAclId": {
"resourceName": "NetworkAcl",
"resourceIdentifier": "Id"
}
},
"AttachVolume": {
"VolumeId": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"DetachNetworkInterface": {},
"TerminateInstances": {
"InstanceIds": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"DetachInternetGateway": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"ResetInstanceAttribute": {
"InstanceId": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"DeletePlacementGroup": {
"GroupName": {
"resourceName": "PlacementGroup",
"resourceIdentifier": "Name"
}
},
"DeleteRouteTable": {
"RouteTableId": {
"resourceName": "RouteTable",
"resourceIdentifier": "Id"
}
},
"CreateTags": {
"Resources": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"EnableVolumeIO": {
"VolumeId": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"CreateRouteTable": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"CreateNetworkInterface": {
"SubnetId": {
"resourceName": "Subnet",
"resourceIdentifier": "Id"
}
},
"ReplaceNetworkAclAssociation": {
"NetworkAclId": {
"resourceName": "NetworkAcl",
"resourceIdentifier": "Id"
}
},
"ModifyVpcAttribute": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"DeleteNetworkInterface": {
"NetworkInterfaceId": {
"resourceName": "NetworkInterface",
"resourceIdentifier": "Id"
}
},
"RebootInstances": {
"InstanceIds": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
},
"DescribeVolumeStatus": {
"VolumeIds": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"DetachClassicLinkVpc": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"AuthorizeSecurityGroupEgress": {
"GroupId": {
"resourceName": "SecurityGroup",
"resourceIdentifier": "Id"
}
},
"CreateVpcPeeringConnection": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"RevokeSecurityGroupIngress": {
"GroupId": {
"resourceName": "SecurityGroup",
"resourceIdentifier": "Id"
}
},
"CreateSecurityGroup": {
"VpcId": {
"resourceName": "Vpc",
"resourceIdentifier": "Id"
}
},
"DeleteKeyPair": {
"KeyName": {
"resourceName": "KeyPair",
"resourceIdentifier": "Name"
}
},
"AuthorizeSecurityGroupIngress": {
"GroupId": {
"resourceName": "SecurityGroup",
"resourceIdentifier": "Id"
}
},
"CreateSnapshot": {
"VolumeId": {
"resourceName": "Volume",
"resourceIdentifier": "Id"
}
},
"DescribeInstanceAttribute": {
"InstanceId": {
"resourceName": "Instance",
"resourceIdentifier": "Id"
}
}
},
"resources": {
"Subnet": {
"operation": "DescribeSubnets",
"resourceIdentifier": {
"Id": "Subnets[].SubnetId"
}
},
"VpcPeeringConnection": {
"operation": "DescribeVpcPeeringConnections",
"resourceIdentifier": {
"Id": "VpcPeeringConnections[].VpcPeeringConnectionId"
}
},
"NetworkAcl": {
"operation": "DescribeNetworkAcls",
"resourceIdentifier": {
"Id": "NetworkAcls[].NetworkAclId"
}
},
"RouteTable": {
"operation": "DescribeRouteTables",
"resourceIdentifier": {
"Id": "RouteTables[].RouteTableId"
}
},
"Snapshot": {
"operation": "DescribeSnapshots",
"resourceIdentifier": {
"Id": "Snapshots[].SnapshotId"
}
},
"DhcpOptions": {
"operation": "DescribeDhcpOptions",
"resourceIdentifier": {
"Id": "DhcpOptions[].DhcpOptionsId"
}
},
"SecurityGroup": {
"operation": "DescribeSecurityGroups",
"resourceIdentifier": {
"Id": "SecurityGroups[].GroupId"
}
},
"NetworkInterface": {
"operation": "DescribeNetworkInterfaces",
"resourceIdentifier": {
"Id": "NetworkInterfaces[].NetworkInterfaceId"
}
},
"Volume": {
"operation": "DescribeVolumes",
"resourceIdentifier": {
"Id": "Volumes[].VolumeId"
}
},
"Instance": {
"operation": "DescribeInstances",
"resourceIdentifier": {
"Id": "Reservations[].Instances[].InstanceId"
}
},
"KeyPair": {
"operation": "DescribeKeyPairs",
"resourceIdentifier": {
"Name": "KeyPairs[].KeyName"
}
},
"InternetGateway": {
"operation": "DescribeInternetGateways",
"resourceIdentifier": {
"Id": "InternetGateways[].InternetGatewayId"
}
},
"Vpc": {
"operation": "DescribeVpcs",
"resourceIdentifier": {
"Id": "Vpcs[].VpcId"
}
},
"PlacementGroup": {
"operation": "DescribePlacementGroups",
"resourceIdentifier": {
"Name": "PlacementGroups[].GroupName"
}
},
"Image": {
"operation": "DescribeImages",
"resourceIdentifier": {
"Id": "Images[].ImageId"
}
}
}
}
================================================
FILE: awsshell/data/elb/2012-06-01/completions-1.json
================================================
{
"operations": {
"AddTags": {
"LoadBalancerNames": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"ApplySecurityGroupsToLoadBalancer": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"AttachLoadBalancerToSubnets": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"ConfigureHealthCheck": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"CreateAppCookieStickinessPolicy": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"CreateLBCookieStickinessPolicy": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"CreateLoadBalancerPolicy": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DetachLoadBalancerFromSubnets": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DescribeInstanceHealth": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DeleteLoadBalancer": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DeleteLoadBalancerListeners": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DeleteLoadBalancerPolicy": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DescribeTags": {
"LoadBalancerNames": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DeregisterInstancesFromLoadBalancer": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DescribeLoadBalancerAttributes": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DescribeLoadBalancerPolicies": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DescribeLoadBalancers": {
"LoadBalancerNames": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"DisableAvailabilityZonesForLoadBalancer": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"EnableAvailabilityZonesForLoadBalancer": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"ModifyLoadBalancerAttributes": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"RegisterInstancesWithLoadBalancer": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"RemoveTags": {
"LoadBalancerNames": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"SetLoadBalancerListenerSSLCertificate": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"SetLoadBalancerPoliciesForBackendServer": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
},
"SetLoadBalancerPoliciesOfListener": {
"LoadBalancerName": {
"resourceName": "LoadBalancer",
"resourceIdentifier": "Name"
}
}
},
"resources": {
"LoadBalancer": {
"operation": "DescribeLoadBalancers",
"resourceIdentifier": {
"Name": "LoadBalancerDescriptions[].LoadBalancerName"
}
}
}
}
================================================
FILE: awsshell/data/glacier/2012-06-01/completions-1.json
================================================
{
"operations": {
"CreateVault": {
"vaultName": {
"resourceName": "Vault",
"resourceIdentifier": "Name"
},
"accountId": {
"resourceName": "Vault",
"resourceIdentifier": "AccountId"
}
},
"DeleteVault": {
"vaultName": {
"resourceName": "Vault",
"resourceIdentifier": "Name"
},
"accountId": {
"resourceName": "Vault",
"resourceIdentifier": "AccountId"
}
},
"InitiateJob": {
"vaultName": {
"resourceName": "Vault",
"resourceIdentifier": "Name"
},
"accountId": {
"resourceName": "Vault",
"resourceIdentifier": "AccountId"
}
},
"UploadArchive": {
"vaultName": {
"resourceName": "Vault",
"resourceIdentifier": "Name"
},
"accountId": {
"resourceName": "Vault",
"resourceIdentifier": "AccountId"
}
},
"InitiateMultipartUpload": {
"vaultName": {
"resourceName": "Vault",
"resourceIdentifier": "Name"
},
"accountId": {
"resourceName": "Vault",
"resourceIdentifier": "AccountId"
}
}
},
"resources": {
"Vault": {
"operation": "ListVaults",
"resourceIdentifier": {
"AccountId": "accountId",
"Name": "VaultList[].VaultName"
}
}
}
}
================================================
FILE: awsshell/data/iam/2010-05-08/completions-1.json
================================================
{
"operations": {
"DeleteInstanceProfile": {
"InstanceProfileName": {
"resourceName": "InstanceProfile",
"resourceIdentifier": "Name"
}
},
"CreateLoginProfile": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"DeletePolicy": {
"PolicyArn": {
"resourceName": "Policy",
"resourceIdentifier": "Arn"
}
},
"CreateGroup": {
"GroupName": {
"resourceName": "Group",
"resourceIdentifier": "Name"
}
},
"DetachRolePolicy": {
"RoleName": {
"resourceName": "Role",
"resourceIdentifier": "Name"
}
},
"EnableMFADevice": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"DeleteSAMLProvider": {
"SAMLProviderArn": {
"resourceName": "SamlProvider",
"resourceIdentifier": "Arn"
}
},
"UpdateUser": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"PutGroupPolicy": {
"GroupName": {
"resourceName": "Group",
"resourceIdentifier": "Name"
}
},
"DeleteVirtualMFADevice": {
"SerialNumber": {
"resourceName": "VirtualMfaDevice",
"resourceIdentifier": "SerialNumber"
}
},
"DeleteGroup": {
"GroupName": {
"resourceName": "Group",
"resourceIdentifier": "Name"
}
},
"DeleteRole": {
"RoleName": {
"resourceName": "Role",
"resourceIdentifier": "Name"
}
},
"AttachGroupPolicy": {
"PolicyArn": {
"resourceName": "Policy",
"resourceIdentifier": "Arn"
}
},
"CreateAccessKey": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"UpdateServerCertificate": {
"ServerCertificateName": {
"resourceName": "ServerCertificate",
"resourceIdentifier": "Name"
}
},
"AddUserToGroup": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"UpdateSAMLProvider": {
"SAMLProviderArn": {
"resourceName": "SamlProvider",
"resourceIdentifier": "Arn"
}
},
"RemoveRoleFromInstanceProfile": {
"InstanceProfileName": {
"resourceName": "InstanceProfile",
"resourceIdentifier": "Name"
}
},
"DetachUserPolicy": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"DeleteUser": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"CreatePolicyVersion": {
"PolicyArn": {
"resourceName": "Policy",
"resourceIdentifier": "Arn"
}
},
"AttachRolePolicy": {
"RoleName": {
"resourceName": "Role",
"resourceIdentifier": "Name"
}
},
"RemoveUserFromGroup": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"AttachUserPolicy": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"PutUserPolicy": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"DeleteServerCertificate": {
"ServerCertificateName": {
"resourceName": "ServerCertificate",
"resourceIdentifier": "Name"
}
},
"UpdateGroup": {
"GroupName": {
"resourceName": "Group",
"resourceIdentifier": "Name"
}
},
"CreateUser": {
"UserName": {
"resourceName": "User",
"resourceIdentifier": "Name"
}
},
"DetachGroupPolicy": {
"PolicyArn": {
"resourceName": "Policy",
"resourceIdentifier": "Arn"
}
},
"AddRoleToInstanceProfile": {
"InstanceProfileName": {
"resourceName": "InstanceProfile",
"resourceIdentifier": "Name"
}
}
},
"resources": {
"Group": {
"operation": "ListGroups",
"resourceIdentifier": {
"Name": "Groups[].GroupName"
}
},
"VirtualMfaDevice": {
"operation": "ListVirtualMFADevices",
"resourceIdentifier": {
"SerialNumber": "VirtualMFADevices[].SerialNumber"
}
},
"InstanceProfile": {
"operation": "ListInstanceProfiles",
"resourceIdentifier": {
"Name": "InstanceProfiles[].InstanceProfileName"
}
},
"Role": {
"operation": "ListRoles",
"resourceIdentifier": {
"Name": "Roles[].RoleName"
}
},
"User": {
"operation": "ListUsers",
"resourceIdentifier": {
"Name": "Users[].UserName"
}
},
"Policy": {
"operation": "ListPolicies",
"resourceIdentifier": {
"Arn": "Policies[].Arn"
}
},
"SamlProvider": {
"operation": "ListSAMLProviders",
"resourceIdentifier": {
"Arn": "SAMLProviderList[].Arn"
}
},
"ServerCertificate": {
"operation": "ListServerCertificates",
"resourceIdentifier": {
"Name": "ServerCertificateMetadataList[].ServerCertificateName"
}
}
}
}
================================================
FILE: awsshell/data/kinesis/2013-12-02/completions-1.json
================================================
{
"operations": {
"AddTagsToStream": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"DeleteStream": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"DecreaseStreamRetentionPeriod": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"DescribeStream": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"GetShardIterator": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"IncreaseStreamRetentionPeriod": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"ListTagsForStream": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"MergeShards": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"PutRecord": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"PutRecords": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"RemoveTagsFromStream": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
},
"SplitShard": {
"StreamName": {
"resourceName": "Stream",
"resourceIdentifier": "Name"
}
}
},
"resources": {
"Stream": {
"operation": "ListStreams",
"resourceIdentifier": {
"Name": "StreamNames[]"
}
}
}
}
================================================
FILE: awsshell/data/opsworks/2013-02-18/completions-1.json
================================================
{
"operations": {
"CreateLayer": {
"StackId": {
"resourceName": "Stack",
"resourceIdentifier": "Id"
}
},
"DeleteStack": {
"StackId": {
"resourceName": "Stack",
"resourceIdentifier": "Id"
}
}
},
"resources": {
"Stack": {
"operation": "DescribeStacks",
"resourceIdentifier": {
"Id": "Stacks[].StackId"
}
}
}
}
================================================
FILE: awsshell/data/s3/2006-03-01/completions-1.json
================================================
{
"operations": {
"CreateBucket": {
"Bucket": {
"resourceName": "Bucket",
"resourceIdentifier": "Name"
}
},
"PutObject": {
"Bucket": {
"resourceName": "Bucket",
"resourceIdentifier": "Name"
}
},
"DeleteObjects": {
"Bucket": {
"resourceName": "Bucket",
"resourceIdentifier": "Name"
}
},
"DeleteBucket": {
"Bucket": {
"resourceName": "Bucket",
"resourceIdentifier": "Name"
}
}
},
"resources": {
"Bucket": {
"operation": "ListBuckets",
"resourceIdentifier": {
"Name": "Buckets[].Name"
}
}
}
}
================================================
FILE: awsshell/data/sns/2010-03-31/completions-1.json
================================================
{
"operations": {
"ConfirmSubscription": {
"TopicArn": {
"resourceName": "Topic",
"resourceIdentifier": "Arn"
}
},
"SetPlatformApplicationAttributes": {
"PlatformApplicationArn": {
"resourceName": "PlatformApplication",
"resourceIdentifier": "Arn"
}
},
"Subscribe": {
"TopicArn": {
"resourceName": "Topic",
"resourceIdentifier": "Arn"
}
},
"SetTopicAttributes": {
"TopicArn": {
"resourceName": "Topic",
"resourceIdentifier": "Arn"
}
},
"DeletePlatformApplication": {
"PlatformApplicationArn": {
"resourceName": "PlatformApplication",
"resourceIdentifier": "Arn"
}
},
"SetSubscriptionAttributes": {
"SubscriptionArn": {
"resourceName": "Subscription",
"resourceIdentifier": "Arn"
}
},
"Publish": {
"TopicArn": {
"resourceName": "Topic",
"resourceIdentifier": "Arn"
}
},
"AddPermission": {
"TopicArn": {
"resourceName": "Topic",
"resourceIdentifier": "Arn"
}
},
"Unsubscribe": {
"SubscriptionArn": {
"resourceName": "Subscription",
"resourceIdentifier": "Arn"
}
},
"RemovePermission": {
"TopicArn": {
"resourceName": "Topic",
"resourceIdentifier": "Arn"
}
},
"CreatePlatformEndpoint": {
"PlatformApplicationArn": {
"resourceName": "PlatformApplication",
"resourceIdentifier": "Arn"
}
},
"DeleteTopic": {
"TopicArn": {
"resourceName": "Topic",
"resourceIdentifier": "Arn"
}
}
},
"resources": {
"Topic": {
"operation": "ListTopics",
"resourceIdentifier": {
"Arn": "Topics[].TopicArn"
}
},
"PlatformApplication": {
"operation": "ListPlatformApplications",
"resourceIdentifier": {
"Arn": "PlatformApplications[].PlatformApplicationArn"
}
},
"Subscription": {
"operation": "ListSubscriptions",
"resourceIdentifier": {
"Arn": "Subscriptions[].SubscriptionArn"
}
}
}
}
================================================
FILE: awsshell/data/sqs/2012-11-05/completions-1.json
================================================
{
"operations": {
"SetQueueAttributes": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"SendMessageBatch": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"DeleteMessageBatch": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"AddPermission": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"ChangeMessageVisibilityBatch": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"SendMessage": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"DeleteQueue": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"PurgeQueue": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"ReceiveMessage": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
},
"RemovePermission": {
"QueueUrl": {
"resourceName": "Queue",
"resourceIdentifier": "Url"
}
}
},
"resources": {
"Queue": {
"operation": "ListQueues",
"resourceIdentifier": {
"Url": "QueueUrls[]"
}
}
}
}
================================================
FILE: awsshell/db.py
================================================
from __future__ import unicode_literals
import os
import sqlite3
class ConcurrentDBM(object):
@classmethod
def open(cls, filename, create=False):
if create and not os.path.isfile(filename):
return cls.create(filename)
else:
db = sqlite3.connect(filename)
return cls(db)
@classmethod
def create(cls, filename):
db = sqlite3.connect(filename)
with db:
db.execute(
'CREATE TABLE docindex (key TEXT PRIMARY KEY, value TEXT)')
return cls(db)
def __init__(self, db):
self._db = db
def __getitem__(self, key):
if isinstance(key, bytes):
key = key.decode('utf-8')
cursor = self._db.cursor()
cursor.execute(
'SELECT value FROM docindex WHERE key = :key', {'key': key})
result = cursor.fetchone()
if result is not None:
return result[0]
raise KeyError(key)
def __setitem__(self, key, value):
with self._db:
self._db.execute(
'INSERT OR REPLACE INTO docindex (key, value) '
'VALUES (:key, :value)',
{'key': key, 'value': value})
def close(self):
self._db.close()
================================================
FILE: awsshell/docs.py
================================================
from __future__ import unicode_literals
from awsshell import db
def load_lazy_doc_index(filename):
d = load_doc_db(filename)
return DocRetriever(d)
def load_doc_db(filename):
d = db.ConcurrentDBM.open(filename, create=True)
return d
class DocRetriever(object):
"""Retrieve documentation for the AWS CLI."""
def __init__(self, doc_index):
# Internally, most of the speedup comes from
# the fact that this data is pre-rendered and
# indexed.
self._doc_index = doc_index
self._cache = {}
def extract_description(self, dot_cmd):
try:
docs = self._doc_index[dot_cmd]
except KeyError:
return u''
index = docs.find('SYNOPSIS')
if index > 0:
docs = docs[:index]
return docs
def extract_param(self, dot_cmd, param_name):
try:
docs = self._doc_index[dot_cmd]
except KeyError:
return u''
index = docs.find('OPTIONS')
param_start_index = docs.find(param_name, index)
param_end_index = docs.find('--', param_start_index + 1)
return docs[param_start_index:param_end_index]
================================================
FILE: awsshell/fuzzy.py
================================================
"""Fuzzy finder for AWS Shell.
This is a fuzzy finder used for the autocompleter
that I've tried to optimize for the AWS CLI set
of commands.
There doesn't seem to be a generic algorithm that
does exactly what I want. Changing any of these
heuristics so far means that you have to give up
some other component of the idealized matching.
The main things I care about:
* Special weight is given to subsequences on a
word boundary. So "drio" scores higher for
"describe-reserved-instances_offering" than
"create-spot-datafeed-subscription".
* The more of the word you complete, the higher
score it has, so given "describe-instance", then
"describe-instances" should match higher than
"describe-instance-attribute" because the former
matches every single character except for one.
* Similar to one, "rinstance" should rank
"run-instances" higher than "describe-instances"
because the "r" falls on a word boundary.
High Level Idea
===============
The basic idea is to try to calculate a numeric
score between 0 and 1 given the users's search
string and a possible word in the corpus of words.
You calculate the score for each word and then sort
them appropriately and return the results in order
back to the user. A score of 1 is the highest
possible score, it would represent an exact match,
and 0 is the lowest meaning these is no possible chance
for the word to be a match.
"""
from __future__ import print_function
def fuzzy_search(user_input, corpus):
candidates = []
for word in corpus:
current_score = calculate_score(user_input, word)
if current_score > 0:
candidates.append((word, current_score))
return [c[0] for c in sorted(candidates, key=lambda x: x[1], reverse=True)]
def calculate_score(search_string, word):
"""Calculate how well the search string matches the word."""
# See the module docstring for a high level description
# of what we're trying to do.
# * If the search string is larger than the word, we know
# immediately that this can't be a match.
if len(search_string) > len(word):
return 0
original_word = word
score = 1
search_index = 0
while True:
scale = 1.0
search_char = search_string[search_index]
i = word.find(search_char)
if i < 0:
return 0
if i > 0 and word[i - 1] == '-':
scale = 0.95
else:
scale = 1 - (i / float(len(word)))
score *= scale
word = word[i + 1:]
search_index += 1
if search_index >= len(search_string):
break
# The more characters that matched the word, the better
# so prefer more complete matches.
completion_scale = 1 - (len(word) / float(len(original_word)))
score *= completion_scale
return score
================================================
FILE: awsshell/index/__init__.py
================================================
"""Subpackage for indexing data.
Note that, while there is documentation for all the code in this
package, these modules/classes are considered internal and not
intended for use outside of the awsshell. It's very likely that
these interfaces will change over time and may have backwards incompatible
changes as improvements are made.
This package contains modules for working with the index data that the AWS
Shell uses. It manages the creation and loading of the AWS Shell index files.
In this context, an index file is specifically used to speed up various
operations performed by the AWS CLI: autocompletion, pulling up docs,
server side resource completion, etc. In typically consists of data constructed
in such a way to make it quick and easy to answer questions such as
"what are the available subcommands? what are the docs for this operation?
what arguments does this command take?".
The AWS Shell supports has several types of index files:
* The local command completion index.
* The documentation index.
* The server side resource completion index.
They're split into separate files because they're used in different scenarios,
and generated by different means (command completion is driven by botocore
JSON models, and server side completion is driven by boto3 resource models).
Couple of things to know about the index files:
* The command completion and doc index are indexed by CLI version. Each version
of the CLI can potentially add new commands/services, so we want to ensure that
we have an index per version so we can offer the correct autocompletion and
documentation based on what CLI version you have installed.
* Command completion and documentation are automatically generated based
on demand. If we notice you don't have an index available for the particular
CLI version, we automatically generate the data.
* The total amount of documentation for every command for every service is
large. We want to avoid loading the entire docset into memory, so we're not
using JSON. Instead we're using a DBM interface (via ``shelve``). May
also consider sqlite.
* Server side resource completion is *not* generated on demand. This package
contains code for generating the a "reverse index" derived from boto3 docs.
This is primarily done while we explore to what extent the boto3 resource
models have the information we need. To be fair, the resource models were
never intended to be used in the mechanism in which we're using them, so it's
very possible we may need to enhance the models by hand. This is why we
pre-generate the index for server side completion up front so we can add hand
modifications if needed. It's very possible that we may be able to come up
with something sufficiently sophisticated enough where we can change this index
to by autogenerated on demand like the other two.
* While the indexes are grouped in a single subpackage for discoverability,
they (purposefully) do not share the same interface for usage. Each
index needs to answer different questions, so it does not make sense
for them to share the same interface.
Usage
=====
Loading the completion index
.. code-block:: python
# Default usage:
loader = CompletionIndex()
# Load based on the current CLI version you have installed
# (i.e awscli.__version__)
index = loader.load_index()
# Load from a specific directory.
loader = CompletionIndex(cache_dir='/tmp/mycache')
index = loader.load_index()
# Load a specific version.
loader = CompletionIndex()
index = loader.load_index(version='1.9.1')
# Generating an index.
loader = CompletionIndex()
# You don't give it a version because we can only
# generate an index based on the version we can import.
completion_index = loader.generate_index()
# You don't need to give a filename, it will write it out
# based on the current version.
loader.write_index_to_file(completion_index)
# Generate and write the index in one step. It will also
# return the generated index in case you need it.
loader.generate_and_write_index()
# This is some autocompleter could use the index loader:
loader = CompletionIndex()
if not loader.index_exists():
index = loader.generate_and_write_index()
else:
index = loader.load_index()
autocompleter = MyAutoCompleter(index)
"""
================================================
FILE: awsshell/index/completion.py
================================================
"""Module for completion index.
Generates, loads, and writes out completion index.
Also provides an interface for working with the
indexed data.
The the subpackage docstring of awsshell.index for
a higher level overview.
"""
import os
import json
from awsshell.utils import FSLayer, FileReadError, build_config_file_path
from awsshell import utils
class IndexLoadError(Exception):
"""Raised when an index could not be loaded."""
class CompletionIndex(object):
"""Handles working with the local commmand completion index.
:type commands: list
:param commands: ec2, s3, elb...
:type subcommands: list
:param subcommands: start-instances, stop-instances, terminate-instances...
:type global_opts: list
:param global_opts: --profile, --region, --output...
:type args_opts: set, to filter out duplicates
:param args_opts: ec2 start-instances: --instance-ids, --dry-run...
"""
# The completion index can read/write to a cache dir
# so that it doesn't have to recompute the completion cache
# every time the CLI starts up.
DEFAULT_CACHE_DIR = build_config_file_path('cache')
def __init__(self, cache_dir=DEFAULT_CACHE_DIR, fslayer=None):
self._cache_dir = cache_dir
if fslayer is None:
fslayer = FSLayer()
self._fslayer = fslayer
self.commands = []
self.subcommands = []
self.global_opts = []
self.args_opts = set()
def load_index(self, version_string):
"""Load the completion index for a given CLI version.
:type version_string: str
:param version_string: The AWS CLI version, e.g "1.9.2".
:raises: :class:`IndexLoadError <exceptions.IndexLoadError>`
"""
filename = self._filename_for_version(version_string)
try:
contents = self._fslayer.file_contents(filename)
except FileReadError as e:
raise IndexLoadError(str(e))
return contents
def _filename_for_version(self, version_string):
return os.path.join(
self._cache_dir, 'completions-%s.json' % version_string)
def load_completions(self):
"""Load completions from the completion index.
Updates the following attributes:
* commands
* subcommands
* global_opts
* args_opts
"""
try:
index_str = self.load_index(utils.AWSCLI_VERSION)
except IndexLoadError:
return
index_str = self.load_index(utils.AWSCLI_VERSION)
index_data = json.loads(index_str)
index_root = index_data['aws']
# ec2, s3, elb...
self.commands = index_root['commands']
# --profile, --region, --output...
self.global_opts = index_root['arguments']
for command in self.commands:
# ec2: start-instances, stop-instances, terminate-instances...
subcommands_current = index_root['children'] \
.get(command)['commands']
self.subcommands.extend(subcommands_current)
for subcommand_current in subcommands_current:
# start-instances: --instance-ids, --dry-run...
args_opts_current = index_root['children'] \
.get(command)['children'] \
.get(subcommand_current)['arguments']
self.args_opts.update(args_opts_current)
================================================
FILE: awsshell/keys.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from prompt_toolkit.key_binding.manager import KeyBindingManager
from prompt_toolkit.keys import Keys
class KeyManager(object):
"""A custom :class:`prompt_toolkit.KeyBindingManager`.
Handles togging of:
* Fuzzy or substring matching.
* Vi or Emacs key bindings.
* Multi or single columns in the autocompletion menu.
* Showing or hiding the help pane.
:type manager: :class:`prompt_toolkit.KeyBindingManager`
:param manager: A custom `KeyBindingManager`.
"""
def __init__(self, get_match_fuzzy, set_match_fuzzy,
get_enable_vi_bindings, set_enable_vi_bindings,
get_show_completion_columns, set_show_completion_columns,
get_show_help, set_show_help, stop_input_and_refresh_cli):
self.manager = None
self._create_key_manager(
get_match_fuzzy, set_match_fuzzy,
get_enable_vi_bindings, set_enable_vi_bindings,
get_show_completion_columns, set_show_completion_columns,
get_show_help, set_show_help, stop_input_and_refresh_cli)
def _create_key_manager(self, get_match_fuzzy, set_match_fuzzy,
get_enable_vi_bindings, set_enable_vi_bindings,
get_show_completion_columns,
set_show_completion_columns,
get_show_help, set_show_help,
stop_input_and_refresh_cli):
"""Create and initialize the keybinding manager.
:type get_fuzzy_match: callable
:param get_fuzzy_match: Gets the fuzzy matching config.
:type set_fuzzy_match: callable
:param set_fuzzy_match: Sets the fuzzy matching config.
:type get_enable_vi_bindings: callable
:param get_enable_vi_bindings: Gets the vi (or emacs) key bindings
config.
:type set_enable_vi_bindings: callable
:param set_enable_vi_bindings: Sets the vi (or emacs) key bindings
config.
:type get_show_completion_columns: callable
:param get_show_completion_columns: Gets the show completions in
multiple or single columns config.
type set_show_completion_columns: callable
:param set_show_completion_columns: Sets the show completions in
multiple or single columns config.
:type get_show_help: callable
:param get_show_help: Gets the show help pane config.
:type set_show_help: callable
:param set_show_help: Sets the show help pane config.
:type stop_input_and_refresh_cli: callable
param stop_input_and_refresh_cli: Stops input by raising an
`InputInterrupt`, forces a cli refresh to ensure certain
options take effect within the current session.
:rtype: :class:`prompt_toolkit.KeyBindingManager`
:return: A custom `KeyBindingManager`.
"""
assert callable(get_match_fuzzy)
assert callable(set_match_fuzzy)
assert callable(get_enable_vi_bindings)
assert callable(set_enable_vi_bindings)
assert callable(get_show_completion_columns)
assert callable(set_show_completion_columns)
assert callable(get_show_help)
assert callable(set_show_help)
assert callable(stop_input_and_refresh_cli)
self.manager = KeyBindingManager(
enable_search=True,
enable_abort_and_exit_bindings=True,
enable_system_bindings=True,
enable_auto_suggest_bindings=True,
enable_open_in_editor=False)
@self.manager.registry.add_binding(Keys.F2)
def handle_f2(_):
"""Toggle fuzzy matching.
:type _: :class:`prompt_toolkit.Event`
:param _: (Unused)
"""
set_match_fuzzy(not get_match_fuzzy())
@self.manager.registry.add_binding(Keys.F3)
def handle_f3(_):
"""Toggle Vi mode keybindings matching.
Disabling Vi keybindings will enable Emacs keybindings.
:type _: :class:`prompt_toolkit.Event`
:param _: (Unused)
"""
set_enable_vi_bindings(not get_enable_vi_bindings())
stop_input_and_refresh_cli()
@self.manager.registry.add_binding(Keys.F4)
def handle_f4(_):
"""Toggle multiple column completions.
:type _: :class:`prompt_toolkit.Event`
:param _: (Unused)
"""
set_show_completion_columns(not get_show_completion_columns())
stop_input_and_refresh_cli()
@self.manager.registry.add_binding(Keys.F5)
def handle_f5(_):
"""Toggle the help container.
:type _: :class:`prompt_toolkit.Event`
:param _: (Unused)
"""
set_show_help(not get_show_help())
stop_input_and_refresh_cli()
@self.manager.registry.add_binding(Keys.F9)
def handle_f9(event):
"""Switch between the default and docs buffers.
:type event: :class:`prompt_toolkit.Event`
:param event: Contains info about the event, namely the cli
which is used to changing which buffer is focused.
"""
if event.cli.current_buffer_name == u'clidocs':
event.cli.focus(u'DEFAULT_BUFFER')
else:
event.cli.focus(u'clidocs')
@self.manager.registry.add_binding(Keys.F10)
def handle_f10(event):
"""Quit when the `F10` key is pressed.
:type event: :class:`prompt_toolkit.Event`
:param event: Contains info about the event, namely the cli
which is used for exiting the app.
"""
event.cli.set_exit()
================================================
FILE: awsshell/lexer.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from pygments.lexer import RegexLexer
from pygments.lexer import words
from pygments.token import Keyword, Literal, Name, Operator, Text
from awsshell.index.completion import CompletionIndex
class ShellLexer(RegexLexer):
"""Provides highlighting for commands, subcommands, arguments, and options.
:type completion_index: :class:`CompletionIndex`
:param completion_index: Completion index used to determine commands,
subcommands, arguments, and options for highlighting.
:type tokens: dict
:param tokens: A dict of (`pygments.lexer`, `pygments.token`) used for
pygments highlighting.
"""
completion_index = CompletionIndex()
completion_index.load_completions()
tokens = {
'root': [
# ec2, s3, elb...
(words(
tuple(completion_index.commands),
prefix=r'\b',
suffix=r'\b'),
Literal.String),
# describe-instances
(words(
tuple(completion_index.subcommands),
prefix=r'\b',
suffix=r'\b'),
Name.Class),
# --instance-ids
(words(
tuple(list(completion_index.args_opts)),
prefix=r'',
suffix=r'\b'),
Keyword.Declaration),
# --profile
(words(
tuple(completion_index.global_opts),
prefix=r'',
suffix=r'\b'),
Operator.Word),
# Everything else
(r'.*\n', Text),
]
}
================================================
FILE: awsshell/loaders.py
================================================
import json
from awsshell.utils import build_config_file_path
class JSONIndexLoader(object):
def __init__(self):
pass
@staticmethod
def index_filename(version_string, type_name='completions'):
return build_config_file_path(
'%s-%s.json' % (version_string, type_name))
def load_index(self, filename):
with open(filename, 'r') as f:
return json.load(f)
================================================
FILE: awsshell/makeindex.py
================================================
"""Module for building the autocompletion indices."""
from __future__ import print_function
import os
import json
from six import BytesIO
from docutils.core import publish_string
import awscli.clidriver
from awscli.argprocess import ParamShorthandDocGen
try:
from botocore.docs.bcdoc import textwriter
except ImportError:
from awscli.bcdoc import textwriter
from awsshell import determine_doc_index_filename
from awsshell.utils import remove_html
from awsshell import docs
SHORTHAND_DOC = ParamShorthandDocGen()
def new_index():
return {'arguments': [], 'argument_metadata': {},
'commands': [], 'children': {}}
def index_command(index_dict, help_command):
arg_table = help_command.arg_table
for arg in arg_table:
arg_obj = arg_table[arg]
metadata = {
'required': arg_obj.required,
'type_name': arg_obj.cli_type_name,
'minidoc': '',
'example': '',
# The name used in the API call/botocore,
# typically CamelCased.
'api_name': getattr(arg_obj, '_serialized_name', '')
}
if arg_obj.documentation:
metadata['minidoc'] = remove_html(
arg_obj.documentation.split('\n')[0])
if SHORTHAND_DOC.supports_shorthand(arg_obj.argument_model):
service_name, op_name = help_command.event_class.rsplit('.', 1)
example = SHORTHAND_DOC.generate_shorthand_example(
cli_argument=arg_obj,
service_id=service_name,
operation_name=op_name,
)
metadata['example'] = example
index_dict['arguments'].append('--%s' % arg)
index_dict['argument_metadata']['--%s' % arg] = metadata
for cmd in help_command.command_table:
index_dict['commands'].append(cmd)
# Each sub command will trigger a recurse.
child = new_index()
index_dict['children'][cmd] = child
sub_command = help_command.command_table[cmd]
sub_help_command = sub_command.create_help_command()
index_command(child, sub_help_command)
def write_index(output_filename=None):
driver = awscli.clidriver.create_clidriver()
help_command = driver.create_help_command()
index = {'aws': new_index()}
current = index['aws']
index_command(current, help_command)
result = json.dumps(index)
if not os.path.isdir(os.path.dirname(output_filename)):
os.makedirs(os.path.dirname(output_filename))
with open(output_filename, 'w') as f:
f.write(result)
def write_doc_index(output_filename=None, db=None, help_command=None):
if output_filename is None:
output_filename = determine_doc_index_filename()
user_provided_db = True
if db is None:
user_provided_db = False
db = docs.load_doc_db(output_filename)
if help_command is None:
driver = awscli.clidriver.create_clidriver()
help_command = driver.create_help_command()
should_close = not user_provided_db
do_write_doc_index(db, help_command, close_db_on_finish=should_close)
def do_write_doc_index(db, help_command, close_db_on_finish):
try:
_index_docs(db, help_command)
db['__complete__'] = 'true'
finally:
if close_db_on_finish:
# If the user provided their own db object,
# they are responsible for closing it.
# If we created our own db object, we own
# closing the db.
db.close()
def _index_docs(db, help_command):
for command_name in help_command.command_table:
command = help_command.command_table[command_name]
sub_help_command = command.create_help_command()
text_docs = render_docs_for_cmd(sub_help_command)
dotted_name = '.'.join(['aws'] + command.lineage_names)
db[dotted_name] = text_docs
_index_docs(db, sub_help_command)
def render_docs_for_cmd(help_command):
renderer = FileRenderer()
help_command.renderer = renderer
help_command(None, None)
# The report_level override is so that we don't print anything
# to stdout/stderr on rendering issues.
original_cli_help = renderer.contents.decode('utf-8')
text_content = convert_rst_to_basic_text(original_cli_help)
index = text_content.find('DESCRIPTION')
if index > 0:
text_content = text_content[index + len('DESCRIPTION'):]
return text_content
def convert_rst_to_basic_text(contents):
"""Convert restructured text to basic text output.
This function removes most of the decorations added
in restructured text.
This function is used to generate documentation we
can show to users in a cross platform manner.
Basic indentation and list formatting are kept,
but many RST features are removed (such as
section underlines).
"""
# The report_level override is so that we don't print anything
# to stdout/stderr on rendering issues.
converted = publish_string(
contents, writer=BasicTextWriter(),
settings_overrides={'report_level': 5})
return converted.decode('utf-8')
class FileRenderer(object):
def __init__(self):
self._io = BytesIO()
def render(self, contents):
self._io.write(contents)
@property
def contents(self):
return self._io.getvalue()
class BasicTextWriter(textwriter.TextWriter):
def translate(self):
visitor = BasicTextTranslator(self.document)
self.document.walkabout(visitor)
self.output = visitor.body
class BasicTextTranslator(textwriter.TextTranslator):
def depart_title(self, node):
# Make the section titles upper cased, similar to
# the man page output.
text = ''.join(x[1] for x in self.states.pop() if x[0] == -1)
self.stateindent.pop()
self.states[-1].append((0, ['', text.upper(), '']))
# The botocore TextWriter has additional formatting
# for literals, for the aws-shell docs we don't want any
# special processing so these nodes are noops.
def visit_literal(self, node):
pass
def depart_literal(self, node):
pass
================================================
FILE: awsshell/resource/__init__.py
================================================
================================================
FILE: awsshell/resource/index.py
================================================
"""Index and retrive information from the resource JSON.
The classes are organized as follows:
* ResourceIndexBuilder - Takes a boto3 resource and converts into the
index format we need to do server side completions.
* CompleterDescriber - Takes the index from ResourceIndexBuilder and looks
up how to perform the autocompletion. Note that this class does
*not* actually do the autocompletion. It merely tells you how
you _would_ do the autocompletion if you made the appropriate
service calls.
* ServerSideCompleter - The thing that does the actual autocompletion.
You tell it the command/operation/param you're on, and it will
return a list of completions for you.
"""
import os
import logging
from collections import namedtuple
import jmespath
from botocore import xform_name
from botocore.exceptions import BotoCoreError
LOG = logging.getLogger(__name__)
# service - The name of the AWS service
# operation - The name of the AWS operation
# params - A dict of params to send in the request (not implemented yet)
# path - A JMESPath expression to select the expected elements.
ServerCompletion = namedtuple('ServerCompletion',
['service', 'operation', 'params', 'path'])
def extract_field_from_jmespath(expression):
result = jmespath.compile(expression)
current = result.parsed
while current['children']:
current = current['children'][0]
if current['type'] == 'field':
return current['value']
class ResourceIndexBuilder(object):
def __init__(self):
pass
def build_index(self, resource_data):
# First we need to go through the 'resources'
# key and map all of its actions back to the
# resource name.
index = {
'operations': {},
'resources': {},
}
service = resource_data['service']
if 'hasMany' in service:
for model in service['hasMany'].values():
resource_name = model['resource']['type']
for identifier in model['resource']['identifiers']:
first_identifier = model['resource']['identifiers'][0]
index['resources'][resource_name] = {
'operation': model['request']['operation'],
# TODO: map all the identifiers.
# We're only taking the first one for now.
'resourceIdentifier': {
first_identifier['target']: first_identifier['path']
}
}
for resource_name, model in resource_data['resources'].items():
if resource_name not in index['resources']:
continue
if 'actions' in model:
resource_actions = model['actions']
for action_model in resource_actions.values():
op_name = action_model['request']['operation']
current = {}
index['operations'][op_name] = current
for param in action_model['request']['params']:
if param['source'] == 'identifier':
field_name = extract_field_from_jmespath(
param['target'])
current[field_name] = {
'resourceName': resource_name,
'resourceIdentifier': param['name'],
}
return index
class CompleterDescriber(object):
"""Describes how to autocomplete a resource.
You give this class a service/operation/param and it will
describe to you how you can autocomplete values for the
provided parameter.
It's up to the caller to actually take that description
and make the appropriate service calls + filtering to
extract out the server side values.
"""
def __init__(self, resource_index):
self._index = resource_index
def describe_autocomplete(self, service, operation, param):
"""Describe operation and args needed for server side completion.
:type service: str
:param service: The AWS service name.
:type operation: str
:param operation: The AWS operation name.
:type param: str
:param param: The name of the parameter being completed. This must
match the casing in the service model (e.g. InstanceIds, not
--instance-ids).
:rtype: ServerCompletion
:return: A ServerCompletion object that describes what API call to make
in order to complete the response.
"""
service_index = self._index[service]
LOG.debug(service_index)
if param not in service_index.get('operations', {}).get(operation, {}):
LOG.debug("param not in index: %s", param)
return None
p = service_index['operations'][operation][param]
resource_name = p['resourceName']
resource_identifier = p['resourceIdentifier']
resource_index = service_index['resources'][resource_name]
completion_operation = resource_index['operation']
path = resource_index['resourceIdentifier'][resource_identifier]
return ServerCompletion(service=service, operation=completion_operation,
params={}, path=path)
class CachedClientCreator(object):
def __init__(self, session):
#: A botocore.session.Session object. Only the
#: create_client() method is used.
self._session = session
self._client_cache = {}
def create_client(self, service_name):
if service_name not in self._client_cache:
client = self._session.create_client(service_name)
self._client_cache[service_name] = client
return self._client_cache[service_name]
class CompleterDescriberCreator(object):
"""Create and cache CompleterDescriber objects."""
def __init__(self, loader):
#: A botocore.loader.Loader
self._loader = loader
self._describer_cache = {}
self._services_with_completions = None
def create_completer_query(self, service_name):
"""Create a CompleterDescriber for a service.
:type service_name: str
:param service_name: The name of the service, e.g. 'ec2'
:return: A CompleterDescriber object.
"""
if service_name not in self._describer_cache:
query = self._create_completer_query(service_name)
self._describer_cache[service_name] = query
return self._describer_cache[service_name]
def _create_completer_query(self, service_name):
completions_model = self._loader.load_service_model(
service_name, 'completions-1')
cq = CompleterDescriber({service_name: completions_model})
return cq
def services_with_completions(self):
if self._services_with_completions is not None:
return self._services_with_completions
self._services_with_completions = set(
self._loader.list_available_services(type_name='completions-1'))
return self._services_with_completions
class ServerSideCompleter(object):
def __init__(self, client_creator, describer_creator):
self._client_creator = client_creator
self._describer_creator = describer_creator
def retrieve_candidate_values(self, service, operation, param):
"""Retrieve server side completions.
:type service: str
:param service: The service name, e.g. 'ec2', 'iam'.
:type operation: str
:param operation: The operation name, in the casing
used by the CLI (words separated by hyphens), e.g.
'describe-instances', 'delete-user'.
:type param: str
:param param: The param name, as specified in the service
model, e.g. 'InstanceIds', 'UserName'.
:rtype: list
:return: A list of possible completions for the
service/operation/param combination. If no
completions were found an empty list is returned.
"""
# Example call:
# service='ec2',
# operation='terminate-instances',
# param='InstanceIds'.
if service not in self._describer_creator.services_with_completions():
return []
try:
client = self._client_creator.create_client(service)
except BotoCoreError as e:
# create_client() could raise an exception if the session
# isn't fully configured (say it's missing a region).
# However, we don't want to turn off all server side
# completions because it's still possible to create
# clients for some services without a region, e.g. IAM.
LOG.debug("Error when trying to create a client for %s",
service, exc_info=True)
return []
api_operation_name = client.meta.method_to_api_mapping.get(
operation.replace('-', '_'))
if api_operation_name is None:
return []
# Now we need to convert the param name to the
# casing used by the API.
completer = self._describer_creator.create_completer_query(service)
result = completer.describe_autocomplete(
service, api_operation_name, param)
if result is None:
return
try:
response = getattr(client, xform_name(result.operation, '_'))()
except Exception as e:
LOG.debug("Error when calling %s.%s: %s", service,
result.operation, e, exc_info=True)
return
results = jmespath.search(result.path, response)
return results
def main():
# Generate the latest autocompletion indices from
# boto3. You'll need to do this if you pull in
# a new boto3 version that has updated resource models.
import sys
import json
import os
import boto3.session
data_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'data')
if not os.path.isdir(data_dir):
os.makedirs(data_dir)
session = boto3.session.Session()
loader = session._loader
builder = ResourceIndexBuilder()
for resource_name in session.get_available_resources():
api_version = loader.determine_latest_version(
resource_name, 'resources-1')
model = loader.load_service_model(resource_name, 'resources-1',
api_version)
index = builder.build_index(model)
output_file = os.path.join(data_dir, resource_name, api_version,
'completions-1.json')
if not os.path.isdir(os.path.dirname(output_file)):
os.makedirs(os.path.dirname(output_file))
with open(output_file, 'w') as f:
f.write(json.dumps(index, indent=2))
if __name__ == '__main__':
main()
================================================
FILE: awsshell/shellcomplete.py
================================================
"""Autocompletion integration with python prompt toolkit.
This module integrates the low level autocomplete functionality
provided in awsshell.autocomplete and integrates it with the
interface required for autocompletion in the python prompt
toolkit.
If you're interested in the heavy lifting of the autocompletion
logic, see awsshell.autocomplete.
"""
import os
import logging
import botocore.session
from prompt_toolkit.completion import Completer, Completion
from awsshell import fuzzy
LOG = logging.getLogger(__name__)
class AWSShellCompleter(Completer):
"""Completer class for the aws-shell.
This is the completer used specifically for the aws shell.
Not to be confused with the AWSCLIModelCompleter, which is more
low level, and can be reused in contexts other than the
aws shell.
"""
def __init__(self, completer, server_side_completer=None):
self._completer = completer
if server_side_completer is None:
server_side_completer = self._create_server_side_completer()
self._server_side_completer = server_side_completer
def _create_server_side_completer(self, session=None):
from awsshell.resource import index
if session is None:
session = botocore.session.Session()
loader = session.get_component('data_loader')
completions_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'data')
loader.search_paths.insert(0, completions_path)
client_creator = index.CachedClientCreator(session)
describer = index.CompleterDescriberCreator(loader)
completer = index.ServerSideCompleter(client_creator, describer)
return completer
def change_profile(self, profile_name):
"""Change the profile used for server side completions."""
self._server_side_completer = self._create_server_side_completer(
session=botocore.session.Session(profile=profile_name))
@property
def completer(self):
return self._completer
@completer.setter
def completer(self, value):
self._completer = value
@property
def last_option(self):
return self._completer.last_option
@property
def current_command(self):
return u' '.join(self._completer.cmd_path)
def _convert_to_prompt_completions(self, low_level_completions,
text_before_cursor):
# Converts the low level completions from the model autocompleter
# and converts them to Completion() objects used by
# prompt_toolkit. We also try to enhance the metadata of the
# completion by including docs and marking required fields.
arg_meta = self._completer.arg_metadata
arg_meta.update(self._completer.global_arg_metadata)
word_before_cursor = ''
if text_before_cursor.strip():
word_before_cursor = text_before_cursor.strip().split()[-1]
for completion in low_level_completions:
# Go through the completions and add inline docs and
# mark which options are required.
if completion.startswith('--') and completion in arg_meta:
# TODO: Need to handle merging in global options as well.
meta = arg_meta[completion]
if meta['required']:
display_text = '%s (required)' % completion
else:
display_text = completion
type_name = arg_meta[completion]['type_name']
display_meta = '[%s] %s' % (type_name,
arg_meta[completion]['minidoc'])
else:
display_text = completion
display_meta = ''
if text_before_cursor and text_before_cursor[-1] == ' ':
location = 0
else:
location = -len(word_before_cursor)
yield Completion(completion, location,
display=display_text, display_meta=display_meta)
def get_completions(self, document, complete_event):
text_before_cursor = document.text_before_cursor
completions = self._completer.autocomplete(text_before_cursor)
prompt_completions = list(self._convert_to_prompt_completions(
completions, text_before_cursor))
if (not prompt_completions and self._completer.last_option and
len(self._completer.cmd_path) == 3):
# If we couldn't complete anything from the JSON model
# completer and we're on a cli option (e.g --foo), we
# can ask the server side completer if it knows anything
# about this resource.
LOG.debug("No local autocompletions found, trying "
"server side completion.")
command = self._completer.cmd_path
service = command[1]
if service == 's3api':
# TODO: we need a more generic way to capture renames
# of commands. This currently lives in the CLI
# customization code.
service = 's3'
operation = command[2]
param = self._completer.arg_metadata.get(
self._completer.last_option, {}).get('api_name')
if param is not None:
LOG.debug("Trying to retrieve autcompletion for: "
"%s, %s, %s", service, operation, param)
results = self._server_side_completer\
.retrieve_candidate_values(service, operation, param)
LOG.debug("Results for %s, %s, %s: %s",
service, operation, param, results)
word_before_cursor = text_before_cursor.strip().split()[-1]
location = 0
if text_before_cursor[-1] != ' ' and \
word_before_cursor and results:
# Filter the results down by fuzzy searching what
# the user has provided.
results = fuzzy.fuzzy_search(word_before_cursor, results)
location = -len(word_before_cursor)
if results is not None:
for result in results:
# Insert at the end
yield Completion(result, location,
display=result,
display_meta='')
else:
for c in prompt_completions:
yield c
================================================
FILE: awsshell/style.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from pygments.token import Token
from pygments.util import ClassNotFound
from pygments.styles import get_style_by_name
from prompt_toolkit.styles import default_style_extensions, style_from_dict
class StyleFactory(object):
"""Provide styles for the autocomplete menu and the toolbar.
:type style: :class:`pygments.style.StyleMeta`
:param style: Contains pygments style info.
"""
def __init__(self, style_name):
self.style = self.style_factory(style_name)
def style_factory(self, style_name):
"""Retrieve the specified pygments style.
If the specified style is not found, the vim style is returned.
:type style_name: str
:param style_name: The pygments style name.
:rtype: :class:`pygments.style.StyleMeta`
:return: Pygments style info.
"""
try:
style = get_style_by_name(style_name)
except ClassNotFound:
style = get_style_by_name('vim')
# Create a style dictionary.
styles = {}
styles.update(style.styles)
styles.update(default_style_extensions)
t = Token
styles.update({
t.Menu.Completions.Completion.Current: 'bg:#00aaaa #000000',
t.Menu.Completions.Completion: 'bg:#008888 #ffffff',
t.Menu.Completions.Meta.Current: 'bg:#00aaaa #000000',
t.Menu.Completions.Meta: 'bg:#00aaaa #ffffff',
t.Scrollbar.Button: 'bg:#003333',
t.Scrollbar: 'bg:#00aaaa',
t.Toolbar: 'bg:#222222 #cccccc',
t.Toolbar.Off: 'bg:#222222 #696969',
t.Toolbar.On: 'bg:#222222 #ffffff',
t.Toolbar.Search: 'noinherit bold',
t.Toolbar.Search.Text: 'nobold',
t.Toolbar.System: 'noinherit bold',
t.Toolbar.Arg: 'noinherit bold',
t.Toolbar.Arg.Text: 'nobold'
})
return style_from_dict(styles)
================================================
FILE: awsshell/substring.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
def substring_search(word, collection):
"""Find all matches in the `collection` for the specified `word`.
If `word` is empty, returns all items in `collection`.
:type word: str
:param word: The substring to search for.
:type collection: collection, usually a list
:param collection: A collection of words to match.
:rtype: list of strings
:return: A sorted list of matching words from collection.
"""
return [item for item in sorted(collection) if item.startswith(word)]
================================================
FILE: awsshell/toolbar.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from pygments.token import Token
class Toolbar(object):
"""Show information about the aws-shell in a tool bar.
:type handler: callable
:param handler: Wraps the callable `get_toolbar_items`.
"""
def __init__(self, get_match_fuzzy, get_enable_vi_bindings,
get_show_completion_columns, get_show_help):
self.handler = self._create_toolbar_handler(
get_match_fuzzy, get_enable_vi_bindings,
get_show_completion_columns, get_show_help)
def _create_toolbar_handler(self, get_match_fuzzy, get_enable_vi_bindings,
get_show_completion_columns, get_show_help):
"""Create the toolbar handler.
:type get_fuzzy_match: callable
:param fuzzy_match: Gets the fuzzy matching config.
:type get_enable_vi_bindings: callable
:param get_enable_vi_bindings: Gets the vi (or emacs) key bindings
config.
:type get_show_completion_columns: callable
:param get_show_completion_columns: Gets the show completions in
multiple or single columns config.
:type get_show_help: callable
:param get_show_help: Gets the show help pane config.
:rtype: callable
:returns: get_toolbar_items.
"""
assert callable(get_match_fuzzy)
assert callable(get_enable_vi_bindings)
assert callable(get_show_completion_columns)
assert callable(get_show_help)
def get_toolbar_items(cli):
"""Return the toolbar items.
:type cli: :class:`prompt_toolkit.Cli`
:param cli: The command line interface from prompt_toolkit
:rtype: list
:return: A list of (pygments.Token.Toolbar, str).
"""
if get_match_fuzzy():
match_fuzzy_token = Token.Toolbar.On
match_fuzzy_cfg = 'ON'
else:
match_fuzzy_token = Token.Toolbar.Off
match_fuzzy_cfg = 'OFF'
if get_enable_vi_bindings():
enable_vi_bindings_token = Token.Toolbar.On
enable_vi_bindings_cfg = 'Vi'
else:
enable_vi_bindings_token = Token.Toolbar.On
enable_vi_bindings_cfg = 'Emacs'
if get_show_completion_columns():
show_columns_token = Token.Toolbar.On
show_columns_cfg = 'Multi'
else:
show_columns_token = Token.Toolbar.On
show_columns_cfg = 'Single'
if get_show_help():
show_help_token = Token.Toolbar.On
show_help_cfg = 'ON'
else:
show_help_token = Token.Toolbar.Off
show_help_cfg = 'OFF'
if cli.current_buffer_name == 'DEFAULT_BUFFER':
show_buffer_name = 'cli'
else:
show_buffer_name = 'doc'
return [
(match_fuzzy_token,
' [F2] Fuzzy: {0} '.format(match_fuzzy_cfg)),
(enable_vi_bindings_token,
' [F3] Keys: {0} '.format(enable_vi_bindings_cfg)),
(show_columns_token,
' [F4] {0} Column '.format(show_columns_cfg)),
(show_help_token,
' [F5] Help: {0} '.format(show_help_cfg)),
(Token.Toolbar,
' [F9] Focus: {0} '.format(show_buffer_name)),
(Token.Toolbar,
' [F10] Exit ')
]
return get_toolbar_items
================================================
FILE: awsshell/ui.py
================================================
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
from prompt_toolkit.filters import IsDone, HasFocus, Always, \
RendererHeightIsKnown, to_cli_filter, Filter
from prompt_toolkit.layout import Window, HSplit, VSplit, FloatContainer, Float
from prompt_toolkit.layout.containers import ConditionalContainer
from prompt_toolkit.layout.controls import BufferControl, \
TokenListControl, FillControl
from prompt_toolkit.layout.dimension import LayoutDimension
from prompt_toolkit.layout.menus import CompletionsMenu, \
MultiColumnCompletionsMenu
from prompt_toolkit.layout.processors import PasswordProcessor, \
HighlightSearchProcessor, HighlightSelectionProcessor, \
ConditionalProcessor, AppendAutoSuggestion
from prompt_toolkit.layout.prompt import DefaultPrompt
from prompt_toolkit.layout.screen import Char
from prompt_toolkit.layout.toolbars import ValidationToolbar, \
SystemToolbar, ArgToolbar, SearchToolbar
from prompt_toolkit.layout.utils import explode_tokens
from prompt_toolkit.layout.lexers import PygmentsLexer
from pygments.token import Token
from pygments.lexer import Lexer
from awsshell.compat import text_type
# This is borrowed from prompt_toolkit because we actually
# need to mess with the layouts to get documentation pulled up.
def create_default_layout(app, message='',
lexer=None, is_password=False,
reserve_space_for_menu=False,
get_prompt_tokens=None,
get_bottom_toolbar_tokens=None,
display_completions_in_columns=False,
extra_input_processors=None, multiline=False):
"""
Generate default layout.
Returns a ``Layout`` instance.
:param message: Text to be used as prompt.
:param lexer: Lexer to be used for the highlighting.
:param is_password: `bool` or `CLIFilter`. When True, display input as '*'.
:param reserve_space_for_menu: When True, make sure that a minimal height
is allocated in the terminal, in order to display the completion menu.
:param get_prompt_tokens: An optional callable that returns the tokens to
be shown in the menu. (To be used instead of a `message`.)
:param get_bottom_toolbar_tokens: An optional callable that returns the
tokens for a toolbar at the bottom.
:param display_completions_in_columns: `bool` or `CLIFilter`. Display the
completions in multiple columns.
:param multiline: `bool` or `CLIFilter`. When True, prefer a layout that is
more adapted for multiline input. Text after newlines is automatically
indented, and search/arg input is shown below the input, instead of
replacing the prompt.
"""
assert isinstance(message, text_type)
assert (get_bottom_toolbar_tokens is None or
callable(get_bottom_toolbar_tokens))
assert get_prompt_tokens is None or callable(get_prompt_tokens)
assert not (message and get_prompt_tokens)
display_completions_in_columns = to_cli_filter(
display_completions_in_columns)
multiline = to_cli_filter(multiline)
if get_prompt_tokens is None:
get_prompt_tokens = lambda _: [(Token.Prompt, message)]
get_prompt_tokens_1, get_prompt_tokens_2 = _split_multiline_prompt(
get_prompt_tokens)
# `lexer` is supposed to be a `Lexer` instance. But if a Pygments lexer
# class is given, turn it into a PygmentsLexer. (Important for
# backwards-compatibility.)
try:
if issubclass(lexer, Lexer):
lexer = PygmentsLexer(lexer)
except TypeError:
# Happens when lexer is `None` or an instance of something else.
pass
# Create processors list.
# (DefaultPrompt should always be at the end.)
input_processors = [
ConditionalProcessor(
# By default, only highlight search when the search
# input has the focus. (Note that this doesn't mean
# there is no search: the Vi 'n' binding for instance
# still allows to jump to the next match in
# navigation mode.)
HighlightSearchProcessor(preview_search=Always()),
HasFocus(SEARCH_BUFFER)),
HighlightSelectionProcessor(),
ConditionalProcessor(
AppendAutoSuggestion(), HasFocus(DEFAULT_BUFFER) & ~IsDone()),
ConditionalProcessor(PasswordProcessor(), is_password)
]
if extra_input_processors:
input_processors.extend(extra_input_processors)
# Show the prompt before the input (using the DefaultPrompt processor.
# This also replaces it with reverse-i-search and 'arg' when required.
# (Only for single line mode.)
input_processors.append(ConditionalProcessor(
DefaultPrompt(get_prompt_tokens), ~multiline))
# Create bottom toolbar.
if get_bottom_toolbar_tokens:
toolbars = [ConditionalContainer(
Window(TokenListControl(get_bottom_toolbar_tokens,
default_char=Char(' ', Token.Toolbar)),
height=LayoutDimension.exact(1)),
filter=~IsDone() & RendererHeightIsKnown())]
else:
toolbars = []
def get_height(cli):
# If there is an autocompletion menu to be shown, make sure that our
# layout has at least a minimal height in order to display it.
if reserve_space_for_menu and not cli.is_done:
return LayoutDimension(min=8)
else:
return LayoutDimension()
def separator():
return ConditionalContainer(
content=Window(height=LayoutDimension.exact(1),
content=FillControl(u'\u2500',
token=Token.Separator)),
filter=HasDocumentation(app) & ~IsDone())
# Create and return Layout instance.
return HSplit([
ConditionalContainer(
Window(
TokenListControl(get_prompt_tokens_1),
dont_extend_height=True),
filter=multiline,
),
VSplit([
# In multiline mode, the prompt is displayed in a left pane.
ConditionalContainer(
Window(
TokenListControl(get_prompt_tokens_2),
dont_extend_width=True,
),
filter=multiline,
),
# The main input, with completion menus floating on top of it.
FloatContainer(
Window(
BufferControl(
input_processors=input_processors,
lexer=lexer,
# Enable preview_search, we want to have immediate
# feedback in reverse-i-search mode.
preview_search=Always(),
focus_on_click=True,
),
get_height=get_height,
),
[
Float(xcursor=True,
ycursor=True,
content=CompletionsMenu(
max_height=16,
scroll_offset=1,
extra_filter=(HasFocus(DEFAULT_BUFFER) &
~display_completions_in_columns))),
Float(xcursor=True,
ycursor=True,
content=MultiColumnCompletionsMenu(
extra_filter=(HasFocus(DEFAULT_BUFFER) &
display_completions_in_columns),
show_meta=Always()))
]
),
]),
separator(),
ConditionalContainer(
content=Window(
BufferControl(
focus_on_click=True,
buffer_name=u'clidocs',
),
height=LayoutDimension(max=15)),
filter=HasDocumentation(app) & ~IsDone(),
),
separator(),
ValidationToolbar(),
SystemToolbar(),
# In multiline mode, we use two toolbars for 'arg' and 'search'.
ConditionalContainer(ArgToolbar(), multiline),
ConditionalContainer(SearchToolbar(), multiline),
] + toolbars)
def _split_multiline_prompt(get_prompt_tokens):
"""Split prompt tokens into two multiline prompt token functions.
Take a `get_prompt_tokens` function. and return two new functions instead.
One that returns the tokens to be shown on the lines above the input, and
another one with the tokens to be shown at the first line of the input.
"""
def before(cli):
result = []
found_nl = False
for token, char in reversed(explode_tokens(get_prompt_tokens(cli))):
if char == '\n':
found_nl = True
elif found_nl:
result.insert(0, (token, char))
return result
def first_input_line(cli):
result = []
for token, char in reversed(explode_tokens(get_prompt_tokens(cli))):
if char == '\n':
break
else:
result.insert(0, (token, char))
return result
return before, first_input_line
class HasDocumentation(Filter):
def __init__(self, app):
self._app = app
def __call__(self, cli):
return bool(self._app.current_docs)
================================================
FILE: awsshell/utils.py
================================================
"""Utility module for misc aws shell functions."""
from __future__ import print_function
import os
import contextlib
import tempfile
import uuid
import awscli
from awsshell.compat import HTMLParser
AWSCLI_VERSION = awscli.__version__
class FileReadError(Exception):
pass
def remove_html(html):
s = DataOnly()
s.feed(html)
return s.get_data()
def build_config_file_path(file_name):
return os.path.join(os.path.expanduser('~'), '.aws', 'shell', file_name)
@contextlib.contextmanager
def temporary_file(mode):
"""Cross platform temporary file creation.
This is an alternative to ``tempfile.NamedTemporaryFile`` that
also works on windows and avoids the "file being used by
another process" error.
"""
tempdir = tempfile.gettempdir()
basename = 'tmpfile-%s' % (uuid.uuid4())
full_filename = os.path.join(tempdir, basename)
if 'w' not in mode:
# We need to create the file before we can open
# it in 'r' mode.
open(full_filename, 'w').close()
try:
with open(full_filename, mode) as f:
yield f
finally:
os.remove(f.name)
class DataOnly(HTMLParser):
def __init__(self):
# HTMLParser is an old-style class, which can't be used with super()
HTMLParser.__init__(self)
self.reset()
self.lines = []
def handle_data(self, data):
self.lines.append(data)
def get_data(self):
return ''.join(self.lines)
class FSLayer(object):
"""Abstraction over common OS commands.
Provides a simpler interface given the operations needed
by the AWS Shell.
"""
def file_contents(self, filename, binary=False):
"""Return the file for a given filename.
If you want binary content use ``mode='rb'``.
"""
if binary:
mode = 'rb'
else:
mode = 'r'
try:
with open(filename, mode) as f:
return f.read()
except (OSError, IOError) as e:
raise FileReadError(str(e))
def file_exists(self, filename):
"""Check if a file exists.
This method returns true if:
* The file exists.
* The filename is a file (not a directory).
"""
return os.path.isfile(filename)
class InMemoryFSLayer(object):
"""Same interface as FSLayer with an in memory implementation."""
def __init__(self, file_mapping):
# path -> file_contents
# file_contents are expected to be text, not
# binary.
self._file_mapping = file_mapping
def file_contents(self, filename, binary=False):
try:
contents = self._file_mapping[filename]
except KeyError:
raise FileReadError(filename)
if binary:
contents = contents.encode('utf-8')
return contents
def file_exists(self, filename):
return filename in self._file_mapping
================================================
FILE: requirements-dev.txt
================================================
doc8==0.6.0
flake8==2.5.0
pep257==0.7.0
pylint==1.7.2
-rrequirements-test.txt
================================================
FILE: requirements-test.txt
================================================
pytest==3.0.2
pytest-cov==2.3.1
mock==1.3.0
tox==2.2.1
configobj==5.0.6
# Note you need at least pip --version of 6.0 or
# higher to be able to pick on these version specifiers.
unittest2==1.1.0; python_version == '2.6'
================================================
FILE: scripts/ci/install
================================================
#!/usr/bin/env python
import os
import sys
from subprocess import check_call
import shutil
_dname = os.path.dirname
REPO_ROOT = _dname(_dname(_dname(os.path.abspath(__file__))))
os.chdir(REPO_ROOT)
def run(command):
return check_call(command, shell=True)
try:
# Has the form "major.minor"
python_version = os.environ['PYTHON_VERSION']
except KeyError:
python_version = '.'.join([str(i) for i in sys.version_info[:2]])
run('pip install -r requirements-test.txt')
if os.path.isdir('dist') and os.listdir('dist'):
shutil.rmtree('dist')
run('python setup.py bdist_wheel')
wheel_dist = os.listdir('dist')[0]
run('pip install %s' % (os.path.join('dist', wheel_dist)))
================================================
FILE: scripts/ci/run-integ-tests
================================================
#!/usr/bin/env python
# Don't run tests from the root repo dir.
# We want to ensure we're importing from the installed
# binary package not from the CWD.
import os
from subprocess import check_call
_dname = os.path.dirname
REPO_ROOT = _dname(_dname(_dname(os.path.abspath(__file__))))
os.chdir(os.path.join(REPO_ROOT, 'tests'))
def run(command):
return check_call(command, shell=True)
run('py.test --cov awsshell --junitxml=./pytests.xml --cov-report term-missing'
' integration/')
================================================
FILE: scripts/ci/run-tests
================================================
#!/usr/bin/env python
# Don't run tests from the root repo dir.
# We want to ensure we're importing from the installed
# binary package not from the CWD.
import os
from subprocess import check_call
_dname = os.path.dirname
REPO_ROOT = _dname(_dname(_dname(os.path.abspath(__file__))))
os.chdir(os.path.join(REPO_ROOT, 'tests'))
def run(command):
return check_call(command, shell=True)
run('py.test --cov awsshell --junitxml=./pytests.xml --cov-report term-missing'
' unit/')
================================================
FILE: scripts/new-change
================================================
#!/usr/bin/env python
"""Generate a new changelog entry.
Usage
=====
To generate a new changelog entry::
scripts/new-change
This will open up a file in your editor (via the ``EDITOR`` env var).
You'll see this template::
# Type should be one of: feature, bugfix
type:
# Category is the high level feature area.
# This can be a service identifier (e.g ``s3``),
# or something like: Paginator.
category:
# A brief description of the change. You can
# use github style references to issues such as
# "fixes #489", "boto/boto3#100", etc. These
# will get automatically replaced with the correct
# link.
description:
Fill in the appropriate values, save and exit the editor.
Make sure to commit these changes as part of your pull request.
If, when your editor is open, you decide don't don't want to add a changelog
entry, save an empty file and no entry will be generated.
You can then use the ``scripts/render-change`` to generate the
CHANGELOG.rst file.
"""
import os
import re
import sys
import json
import string
import random
import tempfile
import subprocess
import argparse
VALID_CHARS = set(string.ascii_letters + string.digits)
CHANGES_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'.changes'
)
TEMPLATE = """\
# Type should be one of: feature, bugfix, enhancement, api-change
# feature: A larger feature or change in behavior, usually resulting in a
# minor version bump.
# bugfix: Fixing a bug in an existing code path.
# enhancment: Small change to an underlying implementation detail.
# api-change: Changes to a modeled API.
type: {change_type}
# Category is the high level feature area.
# This can be a service identifier (e.g ``s3``),
# or something like: Paginator.
category: {category}
# A brief description of the change. You can
# use github style references to issues such as
# "fixes #489", "boto/boto3#100", etc. These
# will get automatically replaced with the correct
# link.
description: {description}
"""
def new_changelog_entry(args):
# Changelog values come from one of two places.
# Either all values are provided on the command line,
# or we open a text editor and let the user provide
# enter their values.
if all_values_provided(args):
parsed_values = {
'type': args.change_type,
'category': args.category,
'description': args.description,
}
else:
parsed_values = get_values_from_editor(args)
if has_empty_values(parsed_values):
sys.stderr.write(
"Empty changelog values received, skipping entry creation.\n")
return 1
replace_issue_references(parsed_values, args.repo)
write_new_change(parsed_values)
return 0
def has_empty_values(parsed_values):
return not (parsed_values.get('type') and
parsed_values.get('category') and
parsed_values.get('description'))
def all_values_provided(args):
return args.change_type and args.category and args.description
def get_values_from_editor(args):
with tempfile.NamedTemporaryFile('w') as f:
contents = TEMPLATE.format(
change_type=args.change_type,
category=args.category,
description=args.description,
)
f.write(contents)
f.flush()
env = os.environ
editor = env.get('VISUAL', env.get('EDITOR', 'vim'))
p = subprocess.Popen('%s %s' % (editor, f.name), shell=True)
p.communicate()
with open(f.name) as f:
filled_in_contents = f.read()
parsed_values = parse_filled_in_contents(filled_in_contents)
return parsed_values
def replace_issue_references(parsed, repo_name):
description = parsed['description']
def linkify(match):
number = match.group()[1:]
return (
'`%s <https://github.com/%s/issues/%s>`__' % (
match.group(), repo_name, number))
new_description = re.sub('#\d+', linkify, description)
parsed['description'] = new_description
def write_new_change(parsed_values):
if not os.path.isdir(CHANGES_DIR):
os.makedirs(CHANGES_DIR)
# Assume that new changes go into the next release.
dirname = os.path.join(CHANGES_DIR, 'next-release')
if not os.path.isdir(dirname):
os.makedirs(dirname)
# Need to generate a unique filename for this change.
# We'll try a couple things until we get a unique match.
category = parsed_values['category']
short_summary = ''.join(filter(lambda x: x in VALID_CHARS, category))
filename = '{type_name}-{summary}'.format(
type_name=parsed_values['type'],
summary=short_summary)
possible_filename = os.path.join(
dirname, '%s-%s.json' % (filename, str(random.randint(1, 100000))))
while os.path.isfile(possible_filename):
possible_filename = os.path.join(
dirname, '%s-%s.json' % (filename, str(random.randint(1, 100000))))
with open(possible_filename, 'w') as f:
f.write(json.dumps(parsed_values, indent=2) + "\n")
def parse_filled_in_contents(contents):
"""Parse filled in file contents and returns parsed dict.
Return value will be::
{
"type": "bugfix",
"category": "category",
"description": "This is a description"
}
"""
if not contents.strip():
return {}
parsed = {}
lines = iter(contents.splitlines())
for line in lines:
line = line.strip()
if line.startswith('#'):
continue
if 'type' not in parsed and line.startswith('type:'):
parsed['type'] = line.split(':')[1].strip()
elif 'category' not in parsed and line.startswith('category:'):
parsed['category'] = line.split(':')[1].strip()
elif 'description' not in parsed and line.startswith('description:'):
# Assume that everything until the end of the file is part
# of the description, so we can break once we pull in the
# remaining lines.
first_line = line.split(':')[1].strip()
full_description = '\n'.join([first_line] + list(lines))
parsed['description'] = full_description.strip()
break
return parsed
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--type', dest='change_type',
default='', choices=('bugfix', 'feature',
'enhancement', 'api-change'))
parser.add_argument('-c', '--category', dest='category',
default='')
parser.add_argument('-d', '--description', dest='description',
default='')
parser.add_argument('-r', '--repo', default='awslabs/aws-shell',
help='Optional repo name, e.g: awslabs/aws-shell')
args = parser.parse_args()
sys.exit(new_changelog_entry(args))
if __name__ == '__main__':
main()
================================================
FILE: setup.cfg
================================================
[bdist_wheel]
universal = 1
================================================
FILE: setup.py
================================================
#!/usr/bin/env python
import re
import ast
from setuptools import setup, find_packages
requires = [
'awscli>=1.16.10,<2.0.0',
'prompt-toolkit>=1.0.0,<1.1.0',
'boto3>=1.9.0,<2.0.0',
'configobj>=5.0.6,<6.0.0',
'Pygments>=2.1.3,<3.0.0',
]
with open('awsshell/__init__.py', 'r') as f:
version = str(
ast.literal_eval(
re.search(
r'__version__\s+=\s+(.*)',
f.read()).group(1)))
setup(
name='aws-shell',
version=version,
description='AWS Shell',
long_description=open('README.rst').read(),
author='James Saryerwinnie',
url='https://github.com/awslabs/aws-shell',
packages=find_packages(exclude=['tests*']),
include_package_data=True,
package_data={'awsshell': ['data/*/*.json',
'awsshellrc']},
install_requires=requires,
entry_points={
'console_scripts': [
'aws-shell = awsshell:main',
'aws-shell-mkindex = awsshell.makeindex:main',
]
},
license="Apache License 2.0",
classifiers=(
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'Natural Language :: English',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
),
)
================================================
FILE: tests/__init__.py
================================================
try:
import unittest2 as unittest
except ImportError:
import unittest
================================================
FILE: tests/compat.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import sys
if sys.version_info < (2, 7):
import unittest2 as unittest
else:
import unittest
================================================
FILE: tests/integration/__init__.py
================================================
================================================
FILE: tests/integration/test_config.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import mock
import os
import unittest
from awsshell.app import AWSShell
from awsshell.config import Config
from awsshell.utils import build_config_file_path
class ConfigTest(unittest.TestCase):
def test_config_off(self):
try:
os.remove(build_config_file_path('test-awsshellrc'))
except OSError:
pass
self.aws_shell = AWSShell(None, mock.Mock(), mock.Mock())
self.aws_shell.model_completer.match_fuzzy = False
self.aws_shell.enable_vi_bindings = False
self.aws_shell.show_completion_columns = False
self.aws_shell.show_help = False
self.aws_shell.theme = 'none'
self.aws_shell.save_config()
self.aws_shell.load_config()
assert self.aws_shell.model_completer.match_fuzzy == False
assert self.aws_shell.enable_vi_bindings == False
assert self.aws_shell.show_completion_columns == False
assert self.aws_shell.show_help == False
assert self.aws_shell.theme == 'none'
def test_config_on(self):
self.aws_shell = AWSShell(None, mock.Mock(), mock.Mock())
self.aws_shell.model_completer.match_fuzzy = True
self.aws_shell.enable_vi_bindings = True
self.aws_shell.show_completion_columns = True
self.aws_shell.show_help = True
self.aws_shell.theme = 'vim'
self.aws_shell.save_config()
self.aws_shell.load_config()
assert self.aws_shell.config_section.as_bool('match_fuzzy') == True
assert self.aws_shell.config_section.as_bool(
'enable_vi_bindings') == True
assert self.aws_shell.config_section.as_bool(
'show_completion_columns') == True
assert self.aws_shell.config_section.as_bool('show_help') == True
assert self.aws_shell.config_section['theme'] == 'vim'
================================================
FILE: tests/integration/test_db.py
================================================
from awsshell import db
import pytest
@pytest.fixture
def shell_db(tmpdir):
filename = tmpdir.join('docs.db').strpath
d = db.ConcurrentDBM.create(filename)
return d
def test_can_get_and_set_value(shell_db):
shell_db['foo'] = 'bar'
assert shell_db['foo'] == 'bar'
def test_raise_key_error_when_no_key_exists(shell_db):
with pytest.raises(KeyError) as e:
shell_db['foo']
assert 'foo' in str(e.value)
def test_can_set_multiple_values(shell_db):
shell_db['foo'] = 'a'
shell_db['bar'] = 'b'
assert shell_db['foo'] == 'a'
assert shell_db['bar'] == 'b'
def test_can_change_existing_value(shell_db):
shell_db['foo'] = 'first'
shell_db['foo'] = 'second'
assert shell_db['foo'] == 'second'
def test_can_update_multiple_times(shell_db):
for i in range(100):
shell_db['foo'] = str(i)
assert shell_db['foo'] == '99'
def test_can_handle_unicode(shell_db):
shell_db['foo'] = u'\u2713'
assert shell_db['foo'] == u'\u2713'
def test_can_create_and_open_db(tmpdir):
filename = tmpdir.join('foo.db').strpath
d = db.ConcurrentDBM.create(filename)
d['foo'] = 'bar'
d.close()
# Should be able to reopen the database and look up 'foo'.
d = db.ConcurrentDBM.open(filename)
assert d['foo'] == 'bar'
================================================
FILE: tests/integration/test_keys.py
================================================
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import mock
from prompt_toolkit.input import PipeInput
from prompt_toolkit.output import DummyOutput
from prompt_toolkit.key_binding.input_processor import KeyPress
from prompt_toolkit.keys import Keys
from tests.compat import unittest
from awsshell.app import AWSShell, InputInterrupt
class KeysTest(unittest.TestCase):
def setUp(self):
self.input = PipeInput()
output = DummyOutput()
self.aws_shell = AWSShell(None, mock.Mock(), mock.Mock(),
input=self.input, output=output)
self.processor = self.aws_shell.cli.input_processor
def tearDown(self):
self.input.close()
def feed_key(self, key):
self.processor.feed(KeyPress(key, u''))
self.processor.process_keys()
def test_F2(self):
match_fuzzy = self.aws_shell.model_completer.match_fuzzy
self.feed_key(Keys.F2)
assert match_fuzzy != self.aws_shell.model_completer.match_fuzzy
def test_F3(self):
enable_vi_bindings = self.aws_shell.enable_vi_bindings
with self.assertRaises(InputInterrupt):
self.feed_key(Keys.F3)
assert enable_vi_bindings != self.aws_shell.enable_vi_bindings
def test_F4(self):
show_completion_columns = self.aws_shell.show_completion_columns
with self.assertRaises(InputInterrupt):
self.feed_key(Keys.F4)
assert show_completion_columns != \
self.aws_shell.show_completion_columns
def test_F5(self):
show_help = self.aws_shell.show_help
with self.assertRaises(InputInterrupt):
self.feed_key(Keys.F5)
assert show_help != self.aws_shell.show_help
def test_F9(self):
assert self.aws_shell.cli.current_buffer_name == u'DEFAULT_BUFFER'
self.feed_key(Keys.F9)
assert self.aws_shell.cli.current_buffer_name == u'clidocs'
def test_F10(self):
self.feed_key(Keys.F10)
assert self.aws_shell.cli.is_exiting
================================================
FILE: tests/integration/test_makeindex.py
================================================
import awscli.clidriver
from awsshell import makeindex
import pytest
@pytest.fixture
def cloudformation_command():
driver = awscli.clidriver.create_clidriver()
cmd = driver.create_help_command()
cfn = cmd.command_table['cloudformation']
return cfn
def test_can_write_doc_index_for_single_operation(cloudformation_command):
# We don't want to try to generate the entire doc index
# for all commands. We're just trying to ensure we're
# integrating with the AWS CLi's help commands properly
# so we're going to pick a single operation to document.
create_stack = cloudformation_command.create_help_command()\
.command_table['create-stack']
help_command = create_stack.create_help_command()
rendered = makeindex.render_docs_for_cmd(help_command=help_command)
# We *really* don't want these to fail when the wording
# changes so I'm purposefully not picking long phrases.
assert 'Creates a stack' in rendered
# Should also see sections in the rendered content.
assert 'SYNOPSIS' in rendered
assert 'EXAMPLES' in rendered
assert 'OUTPUT' in rendered
# Should also see a parameter.
assert '--stack-name' in rendered
def test_can_document_all_service_commands(cloudformation_command):
db = {}
help_command = cloudformation_command.create_help_command()
makeindex.write_doc_index(db=db, help_command=help_command)
# Again, we don't want these to fail when cloudformation has
# API updates so I don't have very strict checking.
assert 'aws.cloudformation.create-stack' in db
assert 'aws.cloudformation.delete-stack' in db
assert 'SYNOPSIS' in db['aws.cloudformation.create-stack']
def test_can_index_a_command(cloudformation_command):
help_command = cloudformation_command.create_help_command()
index = makeindex.new_index()
makeindex.index_command(index, help_command)
================================================
FILE: tests/unit/__init__.py
================================================
================================================
FILE: tests/unit/index/__init__.py
================================================
================================================
FILE: tests/unit/index/test_completions.py
================================================
from tests import unittest
from awsshell.index import completion
from awsshell.utils import InMemoryFSLayer
class TestCompletionIndex(unittest.TestCase):
def setUp(self):
# filename -> file content
self.files = {}
self.fslayer = InMemoryFSLayer(self.files)
def test_can_load_index(self):
c = completion.CompletionIndex(cache_dir='/tmp/cache',
fslayer=self.fslayer)
self.files['/tmp/cache/completions-1.9.1.json'] = '{}'
try:
c.load_index('1.9.1')
except completion.IndexLoadError as e:
self.fail("Expected to load index for '1.9.1', "
"but was unable.")
def test_index_does_not_exist_raises_error(self):
c = completion.CompletionIndex(cache_dir='/tmp/cache',
fslayer=self.fslayer)
with self.assertRaises(completion.IndexLoadError):
c.load_index('1.9.1')
================================================
FILE: tests/unit/test_app.py
================================================
import pytest
import mock
from awsshell import app
from awsshell import shellcomplete
from awsshell import compat
@pytest.fixture
def errstream():
return compat.StringIO()
def test_can_dispatch_dot_commands():
call_args = []
class CustomHandler(object):
def run(self, command, context):
call_args.append((command, context))
handler = app.DotCommandHandler()
handler.HANDLER_CLASSES['foo'] = CustomHandler
context = object()
handler.handle_cmd('.foo a b c', context)
assert call_args == [(['.foo', 'a', 'b', 'c'], context)]
class PopenLogger(object):
def __call__(self, cmd):
self.cmd = cmd
filename = cmd[1]
with open(filename, 'r') as f:
self.contents = f.read()
return mock.Mock()
def test_edit_handler():
env = {'EDITOR': 'my-editor'}
popen_cls = mock.Mock()
application = mock.Mock()
application.history = [
'!ls',
'.edit',
'aws ec2 describe-instances',
'aws ec2 allocate-hosts',
]
popen = PopenLogger()
handler = app.EditHandler(popen, env)
handler.run(['.edit'], application)
# Ensure our editor was called with some arbitrary temp filename.
command_run = popen.cmd
assert len(command_run) == 2
assert command_run[0] == 'my-editor'
# Ensure the contents of the temp file are correct
expected_contents = 'aws ec2 describe-instances\naws ec2 allocate-hosts'
assert popen.contents == expected_contents
def test_error_msg_printed_on_error_handler(errstream):
env = {'EDITOR': 'my-editor'}
popen_cls = mock.Mock()
popen_cls.side_effect = OSError()
context = mock.Mock()
context.history = []
handler = app.EditHandler(popen_cls, env, errstream)
handler.run(['.edit'], context)
# Then we should not propagate an exception, and we
# should print a helpful error message.
assert 'Unable to launch editor: my-editor' in errstream.getvalue()
def
gitextract_8vvun9tj/ ├── .changes/ │ ├── 0.2.0.json │ ├── 0.2.1.json │ └── 0.2.2.json ├── .gitignore ├── .pylintrc ├── .travis.yml ├── CHANGELOG.rst ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── NOTICE.txt ├── README.rst ├── awsshell/ │ ├── __init__.py │ ├── app.py │ ├── autocomplete.py │ ├── awsshellrc │ ├── compat.py │ ├── config.py │ ├── data/ │ │ ├── cloudformation/ │ │ │ └── 2010-05-15/ │ │ │ └── completions-1.json │ │ ├── dynamodb/ │ │ │ └── 2012-08-10/ │ │ │ └── completions-1.json │ │ ├── ec2/ │ │ │ └── 2015-04-15/ │ │ │ └── completions-1.json │ │ ├── elb/ │ │ │ └── 2012-06-01/ │ │ │ └── completions-1.json │ │ ├── glacier/ │ │ │ └── 2012-06-01/ │ │ │ └── completions-1.json │ │ ├── iam/ │ │ │ └── 2010-05-08/ │ │ │ └── completions-1.json │ │ ├── kinesis/ │ │ │ └── 2013-12-02/ │ │ │ └── completions-1.json │ │ ├── opsworks/ │ │ │ └── 2013-02-18/ │ │ │ └── completions-1.json │ │ ├── s3/ │ │ │ └── 2006-03-01/ │ │ │ └── completions-1.json │ │ ├── sns/ │ │ │ └── 2010-03-31/ │ │ │ └── completions-1.json │ │ └── sqs/ │ │ └── 2012-11-05/ │ │ └── completions-1.json │ ├── db.py │ ├── docs.py │ ├── fuzzy.py │ ├── index/ │ │ ├── __init__.py │ │ └── completion.py │ ├── keys.py │ ├── lexer.py │ ├── loaders.py │ ├── makeindex.py │ ├── resource/ │ │ ├── __init__.py │ │ └── index.py │ ├── shellcomplete.py │ ├── style.py │ ├── substring.py │ ├── toolbar.py │ ├── ui.py │ └── utils.py ├── requirements-dev.txt ├── requirements-test.txt ├── scripts/ │ ├── ci/ │ │ ├── install │ │ ├── run-integ-tests │ │ └── run-tests │ └── new-change ├── setup.cfg ├── setup.py ├── tests/ │ ├── __init__.py │ ├── compat.py │ ├── integration/ │ │ ├── __init__.py │ │ ├── test_config.py │ │ ├── test_db.py │ │ ├── test_keys.py │ │ └── test_makeindex.py │ └── unit/ │ ├── __init__.py │ ├── index/ │ │ ├── __init__.py │ │ └── test_completions.py │ ├── test_app.py │ ├── test_autocomplete.py │ ├── test_docs.py │ ├── test_fuzzy.py │ ├── test_load_completions.py │ ├── test_makeindex.py │ ├── test_resources.py │ ├── test_substring.py │ ├── test_toolbar.py │ └── test_utils.py └── tox.ini
SYMBOL INDEX (264 symbols across 35 files)
FILE: awsshell/__init__.py
function determine_doc_index_filename (line 19) | def determine_doc_index_filename():
function load_index (line 26) | def load_index(filename):
function main (line 31) | def main():
FILE: awsshell/app.py
function create_aws_shell (line 35) | def create_aws_shell(completer, model_completer, docs):
class InputInterrupt (line 39) | class InputInterrupt(Exception):
class ChangeDirHandler (line 49) | class ChangeDirHandler(object):
method __init__ (line 50) | def __init__(self, output=sys.stdout, err=sys.stderr, chdir=os.chdir):
method run (line 55) | def run(self, command, application):
class EditHandler (line 67) | class EditHandler(object):
method __init__ (line 68) | def __init__(self, popen_cls=None, env=None, err=sys.stderr):
method _get_editor_command (line 77) | def _get_editor_command(self):
method _generate_edit_history (line 83) | def _generate_edit_history(self, application):
method run (line 88) | def run(self, command, application):
class ProfileHandler (line 114) | class ProfileHandler(object):
method __init__ (line 120) | def __init__(self, output=sys.stdout, err=sys.stderr):
method run (line 124) | def run(self, command, application):
class ExitHandler (line 149) | class ExitHandler(object):
method run (line 150) | def run(self, command, application):
class DotCommandHandler (line 154) | class DotCommandHandler(object):
method __init__ (line 163) | def __init__(self, output=sys.stdout, err=sys.stderr):
method handle_cmd (line 167) | def handle_cmd(self, command, application):
method _unknown_cmd (line 187) | def _unknown_cmd(self, cmd_parts, application):
class AWSShell (line 191) | class AWSShell(object):
method __init__ (line 227) | def __init__(self, completer, model_completer, docs,
method load_config (line 258) | def load_config(self):
method save_config (line 272) | def save_config(self):
method cli (line 283) | def cli(self):
method run (line 289) | def run(self):
method stop_input_and_refresh_cli (line 322) | def stop_input_and_refresh_cli(self):
method create_layout (line 336) | def create_layout(self, display_completions_in_columns, toolbar):
method create_buffer (line 346) | def create_buffer(self, completer, history):
method create_key_manager (line 355) | def create_key_manager(self):
method create_application (line 415) | def create_application(self, completer, history,
method on_input_timeout (line 446) | def on_input_timeout(self, cli):
method create_cli_interface (line 478) | def create_cli_interface(self, display_completions_in_columns):
method profile (line 491) | def profile(self):
method profile (line 495) | def profile(self, new_profile_name):
FILE: awsshell/autocomplete.py
class AWSCLIModelCompleter (line 6) | class AWSCLIModelCompleter(object):
method __init__ (line 13) | def __init__(self, index_data, match_fuzzy=True):
method global_arg_metadata (line 29) | def global_arg_metadata(self):
method arg_metadata (line 33) | def arg_metadata(self):
method reset (line 37) | def reset(self):
method autocomplete (line 46) | def autocomplete(self, line):
method _get_all_args (line 117) | def _get_all_args(self):
method _handle_backspace (line 124) | def _handle_backspace(self):
method _complete_from_full_parse (line 127) | def _complete_from_full_parse(self):
method _autocomplete_options (line 140) | def _autocomplete_options(self, last_word):
FILE: awsshell/compat.py
function default_editor (line 23) | def default_editor():
function default_editor (line 26) | def default_editor():
FILE: awsshell/config.py
class Config (line 21) | class Config(object):
method load (line 24) | def load(self, config_template, config_file=None):
method _load_template_or_config (line 47) | def _load_template_or_config(self, template_path, config_path):
method _copy_template_to_config (line 66) | def _copy_template_to_config(self, template_path,
FILE: awsshell/db.py
class ConcurrentDBM (line 6) | class ConcurrentDBM(object):
method open (line 9) | def open(cls, filename, create=False):
method create (line 17) | def create(cls, filename):
method __init__ (line 24) | def __init__(self, db):
method __getitem__ (line 27) | def __getitem__(self, key):
method __setitem__ (line 38) | def __setitem__(self, key, value):
method close (line 45) | def close(self):
FILE: awsshell/docs.py
function load_lazy_doc_index (line 5) | def load_lazy_doc_index(filename):
function load_doc_db (line 10) | def load_doc_db(filename):
class DocRetriever (line 15) | class DocRetriever(object):
method __init__ (line 17) | def __init__(self, doc_index):
method extract_description (line 24) | def extract_description(self, dot_cmd):
method extract_param (line 34) | def extract_param(self, dot_cmd, param_name):
FILE: awsshell/fuzzy.py
function fuzzy_search (line 45) | def fuzzy_search(user_input, corpus):
function calculate_score (line 54) | def calculate_score(search_string, word):
FILE: awsshell/index/completion.py
class IndexLoadError (line 18) | class IndexLoadError(Exception):
class CompletionIndex (line 22) | class CompletionIndex(object):
method __init__ (line 43) | def __init__(self, cache_dir=DEFAULT_CACHE_DIR, fslayer=None):
method load_index (line 53) | def load_index(self, version_string):
method _filename_for_version (line 68) | def _filename_for_version(self, version_string):
method load_completions (line 72) | def load_completions(self):
FILE: awsshell/keys.py
class KeyManager (line 17) | class KeyManager(object):
method __init__ (line 30) | def __init__(self, get_match_fuzzy, set_match_fuzzy,
method _create_key_manager (line 41) | def _create_key_manager(self, get_match_fuzzy, set_match_fuzzy,
FILE: awsshell/lexer.py
class ShellLexer (line 20) | class ShellLexer(RegexLexer):
FILE: awsshell/loaders.py
class JSONIndexLoader (line 6) | class JSONIndexLoader(object):
method __init__ (line 7) | def __init__(self):
method index_filename (line 11) | def index_filename(version_string, type_name='completions'):
method load_index (line 15) | def load_index(self, filename):
FILE: awsshell/makeindex.py
function new_index (line 23) | def new_index():
function index_command (line 28) | def index_command(index_dict, help_command):
function write_index (line 65) | def write_index(output_filename=None):
function write_doc_index (line 79) | def write_doc_index(output_filename=None, db=None, help_command=None):
function do_write_doc_index (line 94) | def do_write_doc_index(db, help_command, close_db_on_finish):
function _index_docs (line 107) | def _index_docs(db, help_command):
function render_docs_for_cmd (line 117) | def render_docs_for_cmd(help_command):
function convert_rst_to_basic_text (line 131) | def convert_rst_to_basic_text(contents):
class FileRenderer (line 153) | class FileRenderer(object):
method __init__ (line 155) | def __init__(self):
method render (line 158) | def render(self, contents):
method contents (line 162) | def contents(self):
class BasicTextWriter (line 166) | class BasicTextWriter(textwriter.TextWriter):
method translate (line 167) | def translate(self):
class BasicTextTranslator (line 173) | class BasicTextTranslator(textwriter.TextTranslator):
method depart_title (line 174) | def depart_title(self, node):
method visit_literal (line 185) | def visit_literal(self, node):
method depart_literal (line 188) | def depart_literal(self, node):
FILE: awsshell/resource/index.py
function extract_field_from_jmespath (line 35) | def extract_field_from_jmespath(expression):
class ResourceIndexBuilder (line 44) | class ResourceIndexBuilder(object):
method __init__ (line 45) | def __init__(self):
method build_index (line 48) | def build_index(self, resource_data):
class CompleterDescriber (line 90) | class CompleterDescriber(object):
method __init__ (line 102) | def __init__(self, resource_index):
method describe_autocomplete (line 105) | def describe_autocomplete(self, service, operation, param):
class CachedClientCreator (line 140) | class CachedClientCreator(object):
method __init__ (line 141) | def __init__(self, session):
method create_client (line 147) | def create_client(self, service_name):
class CompleterDescriberCreator (line 154) | class CompleterDescriberCreator(object):
method __init__ (line 156) | def __init__(self, loader):
method create_completer_query (line 162) | def create_completer_query(self, service_name):
method _create_completer_query (line 176) | def _create_completer_query(self, service_name):
method services_with_completions (line 182) | def services_with_completions(self):
class ServerSideCompleter (line 190) | class ServerSideCompleter(object):
method __init__ (line 191) | def __init__(self, client_creator, describer_creator):
method retrieve_candidate_values (line 195) | def retrieve_candidate_values(self, service, operation, param):
function main (line 254) | def main():
FILE: awsshell/shellcomplete.py
class AWSShellCompleter (line 24) | class AWSShellCompleter(Completer):
method __init__ (line 32) | def __init__(self, completer, server_side_completer=None):
method _create_server_side_completer (line 38) | def _create_server_side_completer(self, session=None):
method change_profile (line 53) | def change_profile(self, profile_name):
method completer (line 59) | def completer(self):
method completer (line 63) | def completer(self, value):
method last_option (line 67) | def last_option(self):
method current_command (line 71) | def current_command(self):
method _convert_to_prompt_completions (line 74) | def _convert_to_prompt_completions(self, low_level_completions,
method get_completions (line 108) | def get_completions(self, document, complete_event):
FILE: awsshell/style.py
class StyleFactory (line 19) | class StyleFactory(object):
method __init__ (line 26) | def __init__(self, style_name):
method style_factory (line 29) | def style_factory(self, style_name):
FILE: awsshell/substring.py
function substring_search (line 15) | def substring_search(word, collection):
FILE: awsshell/toolbar.py
class Toolbar (line 16) | class Toolbar(object):
method __init__ (line 24) | def __init__(self, get_match_fuzzy, get_enable_vi_bindings,
method _create_toolbar_handler (line 30) | def _create_toolbar_handler(self, get_match_fuzzy, get_enable_vi_bindi...
FILE: awsshell/ui.py
function create_default_layout (line 28) | def create_default_layout(app, message='',
function _split_multiline_prompt (line 200) | def _split_multiline_prompt(get_prompt_tokens):
class HasDocumentation (line 230) | class HasDocumentation(Filter):
method __init__ (line 231) | def __init__(self, app):
method __call__ (line 234) | def __call__(self, cli):
FILE: awsshell/utils.py
class FileReadError (line 16) | class FileReadError(Exception):
function remove_html (line 20) | def remove_html(html):
function build_config_file_path (line 26) | def build_config_file_path(file_name):
function temporary_file (line 31) | def temporary_file(mode):
class DataOnly (line 52) | class DataOnly(HTMLParser):
method __init__ (line 53) | def __init__(self):
method handle_data (line 59) | def handle_data(self, data):
method get_data (line 62) | def get_data(self):
class FSLayer (line 66) | class FSLayer(object):
method file_contents (line 73) | def file_contents(self, filename, binary=False):
method file_exists (line 89) | def file_exists(self, filename):
class InMemoryFSLayer (line 101) | class InMemoryFSLayer(object):
method __init__ (line 104) | def __init__(self, file_mapping):
method file_contents (line 110) | def file_contents(self, filename, binary=False):
method file_exists (line 119) | def file_exists(self, filename):
FILE: tests/integration/test_config.py
class ConfigTest (line 22) | class ConfigTest(unittest.TestCase):
method test_config_off (line 24) | def test_config_off(self):
method test_config_on (line 43) | def test_config_on(self):
FILE: tests/integration/test_db.py
function shell_db (line 6) | def shell_db(tmpdir):
function test_can_get_and_set_value (line 12) | def test_can_get_and_set_value(shell_db):
function test_raise_key_error_when_no_key_exists (line 17) | def test_raise_key_error_when_no_key_exists(shell_db):
function test_can_set_multiple_values (line 23) | def test_can_set_multiple_values(shell_db):
function test_can_change_existing_value (line 30) | def test_can_change_existing_value(shell_db):
function test_can_update_multiple_times (line 36) | def test_can_update_multiple_times(shell_db):
function test_can_handle_unicode (line 42) | def test_can_handle_unicode(shell_db):
function test_can_create_and_open_db (line 47) | def test_can_create_and_open_db(tmpdir):
FILE: tests/integration/test_keys.py
class KeysTest (line 24) | class KeysTest(unittest.TestCase):
method setUp (line 26) | def setUp(self):
method tearDown (line 33) | def tearDown(self):
method feed_key (line 36) | def feed_key(self, key):
method test_F2 (line 40) | def test_F2(self):
method test_F3 (line 45) | def test_F3(self):
method test_F4 (line 51) | def test_F4(self):
method test_F5 (line 58) | def test_F5(self):
method test_F9 (line 64) | def test_F9(self):
method test_F10 (line 69) | def test_F10(self):
FILE: tests/integration/test_makeindex.py
function cloudformation_command (line 8) | def cloudformation_command():
function test_can_write_doc_index_for_single_operation (line 15) | def test_can_write_doc_index_for_single_operation(cloudformation_command):
function test_can_document_all_service_commands (line 35) | def test_can_document_all_service_commands(cloudformation_command):
function test_can_index_a_command (line 46) | def test_can_index_a_command(cloudformation_command):
FILE: tests/unit/index/test_completions.py
class TestCompletionIndex (line 7) | class TestCompletionIndex(unittest.TestCase):
method setUp (line 8) | def setUp(self):
method test_can_load_index (line 13) | def test_can_load_index(self):
method test_index_does_not_exist_raises_error (line 23) | def test_index_does_not_exist_raises_error(self):
FILE: tests/unit/test_app.py
function errstream (line 11) | def errstream():
function test_can_dispatch_dot_commands (line 15) | def test_can_dispatch_dot_commands():
class PopenLogger (line 29) | class PopenLogger(object):
method __call__ (line 30) | def __call__(self, cmd):
function test_edit_handler (line 38) | def test_edit_handler():
function test_error_msg_printed_on_error_handler (line 60) | def test_error_msg_printed_on_error_handler(errstream):
function test_profile_handler_prints_profile (line 74) | def test_profile_handler_prints_profile():
function test_profile_handler_when_no_profile_configured (line 83) | def test_profile_handler_when_no_profile_configured():
function test_profile_command_changes_profile (line 95) | def test_profile_command_changes_profile():
function test_profile_prints_error_on_bad_syntax (line 106) | def test_profile_prints_error_on_bad_syntax():
function test_prints_error_message_on_unknown_dot_command (line 116) | def test_prints_error_message_on_unknown_dot_command(errstream):
function test_delegates_to_complete_changing_profile (line 122) | def test_delegates_to_complete_changing_profile():
function test_cd_handler_can_chdir (line 130) | def test_cd_handler_can_chdir():
function test_chdir_syntax_error_prints_err_msg (line 137) | def test_chdir_syntax_error_prints_err_msg(errstream):
function test_error_displayed_when_chdir_fails (line 145) | def test_error_displayed_when_chdir_fails(errstream):
function test_history_stored_correctly (line 153) | def test_history_stored_correctly():
function test_exit_dot_command_exits_shell (line 172) | def test_exit_dot_command_exits_shell():
FILE: tests/unit/test_autocomplete.py
function index_data (line 5) | def index_data():
function test_completes_service_names (line 16) | def test_completes_service_names(index_data):
function test_completes_service_names_substring (line 22) | def test_completes_service_names_substring(index_data):
function test_completes_multiple_service_names (line 29) | def test_completes_multiple_service_names(index_data):
function test_no_completion (line 35) | def test_no_completion(index_data):
function test_can_complete_subcommands (line 41) | def test_can_complete_subcommands(index_data):
function test_everything_completed_on_space (line 61) | def test_everything_completed_on_space(index_data):
function test_autocomplete_top_leve_services_on_space (line 80) | def test_autocomplete_top_leve_services_on_space(index_data):
function test_reset_auto_complete (line 86) | def test_reset_auto_complete(index_data):
function test_reset_after_subcommand_completion (line 97) | def test_reset_after_subcommand_completion(index_data):
function test_backspace_should_complete_previous_command (line 121) | def test_backspace_should_complete_previous_command(index_data):
function test_can_handle_entire_word_deleted (line 125) | def test_can_handle_entire_word_deleted(index_data):
function test_can_handle_entire_line_deleted (line 129) | def test_can_handle_entire_line_deleted(index_data):
function test_autocompletes_argument_names (line 154) | def test_autocompletes_argument_names(index_data):
function test_autocompletes_argument_names_substring (line 163) | def test_autocompletes_argument_names_substring(index_data):
function test_autocompletes_global_and_service_args (line 172) | def test_autocompletes_global_and_service_args(index_data):
function test_can_mix_options_and_commands (line 193) | def test_can_mix_options_and_commands(index_data):
function test_only_change_context_when_in_index (line 218) | def test_only_change_context_when_in_index(index_data):
function test_can_handle_skips_in_completion (line 240) | def test_can_handle_skips_in_completion(index_data):
function test_cmd_path_updated_on_completions (line 274) | def test_cmd_path_updated_on_completions(index_data):
function test_last_option_updated_up_releated_api_params (line 304) | def test_last_option_updated_up_releated_api_params(index_data):
function test_last_option_is_updated_on_global_options (line 332) | def test_last_option_is_updated_on_global_options(index_data):
function test_can_handle_autocompleting_same_string_twice (line 359) | def test_can_handle_autocompleting_same_string_twice(index_data):
function test_can_handle_autocomplete_empty_string_twice (line 366) | def test_can_handle_autocomplete_empty_string_twice(index_data):
function test_global_arg_metadata_property (line 376) | def test_global_arg_metadata_property(index_data):
FILE: tests/unit/test_docs.py
function test_lazy_doc_factory (line 5) | def test_lazy_doc_factory(tmpdir):
function test_load_doc_db (line 11) | def test_load_doc_db(tmpdir):
FILE: tests/unit/test_fuzzy.py
function test_subsequences (line 14) | def test_subsequences(search, corpus, expected):
FILE: tests/unit/test_load_completions.py
class LoadCompletionsTest (line 18) | class LoadCompletionsTest(unittest.TestCase):
method setUp (line 20) | def setUp(self):
method test_load_completions (line 41) | def test_load_completions(self):
FILE: tests/unit/test_makeindex.py
function test_can_convert_rst_text (line 4) | def test_can_convert_rst_text():
FILE: tests/unit/test_resources.py
function describer_creator (line 11) | def describer_creator():
function test_build_from_has_many (line 21) | def test_build_from_has_many():
function test_removes_jmespath_expressions_from_targets (line 78) | def test_removes_jmespath_expressions_from_targets():
function test_resource_not_included_if_no_has_many (line 135) | def test_resource_not_included_if_no_has_many():
function test_can_complete_query (line 169) | def test_can_complete_query():
function test_cached_client_creator_returns_same_instance (line 199) | def test_cached_client_creator_returns_same_instance():
function test_can_create_service_completers_from_cache (line 213) | def test_can_create_service_completers_from_cache():
function test_empty_results_returned_when_no_completion_data_exists (line 229) | def test_empty_results_returned_when_no_completion_data_exists(describer...
function test_no_completions_when_cant_create_client (line 240) | def test_no_completions_when_cant_create_client(describer_creator):
function test_no_completions_returned_on_unknown_operation (line 253) | def test_no_completions_returned_on_unknown_operation(describer_creator):
FILE: tests/unit/test_substring.py
function test_subsequences (line 23) | def test_subsequences(search, corpus, expected):
FILE: tests/unit/test_toolbar.py
class ToolbarTest (line 21) | class ToolbarTest(unittest.TestCase):
method setUp (line 23) | def setUp(self):
method test_toolbar_on (line 32) | def test_toolbar_on(self):
method test_toolbar_off (line 46) | def test_toolbar_off(self):
FILE: tests/unit/test_utils.py
class TestFSLayer (line 12) | class TestFSLayer(unittest.TestCase):
method setUp (line 17) | def setUp(self):
method tearDown (line 23) | def tearDown(self):
method test_can_read_file_contents (line 26) | def test_can_read_file_contents(self):
method test_file_exists (line 37) | def test_file_exists(self):
method test_file_does_not_exist_error (line 45) | def test_file_does_not_exist_error(self):
class TestInMemoryFSLayer (line 50) | class TestInMemoryFSLayer(unittest.TestCase):
method setUp (line 51) | def setUp(self):
method test_file_exists (line 55) | def test_file_exists(self):
method test_can_read_file_contents (line 59) | def test_can_read_file_contents(self):
method test_file_does_not_exist_error (line 65) | def test_file_does_not_exist_error(self):
class TestTemporaryFile (line 70) | class TestTemporaryFile(unittest.TestCase):
method test_can_use_as_context_manager (line 71) | def test_can_use_as_context_manager(self):
method test_is_removed_after_exiting_context (line 78) | def test_is_removed_after_exiting_context(self):
method test_can_open_in_read (line 85) | def test_can_open_in_read(self):
Condensed preview — 75 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (242K chars).
[
{
"path": ".changes/0.2.0.json",
"chars": 706,
"preview": "{\n \"schema-version\": \"1.0\",\n \"changes\": [\n {\n \"category\": \"Command History\",\n \"description\": \"Ensure aws "
},
{
"path": ".changes/0.2.1.json",
"chars": 271,
"preview": "{\n \"schema-version\": \"1.0\",\n \"changes\": [\n {\n \"category\": \"AWS CLI\",\n \"description\": \"Fixes `#208 <https:"
},
{
"path": ".changes/0.2.2.json",
"chars": 232,
"preview": "{\n \"schema-version\": \"1.0\",\n \"changes\": [\n {\n \"type\": \"bugfix\",\n \"category\": \"Dependency\",\n \"descrip"
},
{
"path": ".gitignore",
"chars": 372,
"preview": "*.py[co]\n*.DS_Store\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nvar\nsdist\ndevelop-eggs\n.installed.cfg\n\n# Installe"
},
{
"path": ".pylintrc",
"chars": 11521,
"preview": "[MASTER]\n\n# Specify a configuration file.\n#rcfile=\n\n# Python code to execute, usually for sys.path manipulation such as\n"
},
{
"path": ".travis.yml",
"chars": 461,
"preview": "language: python\n\nmatrix:\n include:\n - python: 2.7\n env: TEST_TYPE=test\n - python: 2.7\n "
},
{
"path": "CHANGELOG.rst",
"chars": 2403,
"preview": "=========\nCHANGELOG\n=========\n\n0.2.2\n=====\n\n* bugfix:Dependency: Fix bcdoc import errors. Fixes `#247 <https://github.co"
},
{
"path": "LICENSE.txt",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MANIFEST.in",
"chars": 137,
"preview": "include README.rst\ninclude LICENSE.txt\ninclude NOTICE.txt\ninclude awsshell/awsshellrc\nrecursive-include awsshell/data *."
},
{
"path": "Makefile",
"chars": 1002,
"preview": "# Eventually I'll add:\n# py.test --cov awsshell --cov-report term-missing --cov-fail-under 95 tests/\n# which will fail i"
},
{
"path": "NOTICE.txt",
"chars": 82,
"preview": "AWS Shell\nCopyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n"
},
{
"path": "README.rst",
"chars": 10040,
"preview": "aws-shell - The interactive productivity booster for the AWS CLI\n======================================================="
},
{
"path": "awsshell/__init__.py",
"chars": 2458,
"preview": "from __future__ import unicode_literals, print_function\n\nimport json\nimport argparse\nimport threading\n\nfrom awsshell imp"
},
{
"path": "awsshell/app.py",
"chars": 18544,
"preview": "\"\"\"AWS Shell application.\n\nMain entry point to the AWS Shell.\n\n\"\"\"\nfrom __future__ import unicode_literals\nimport os\nimp"
},
{
"path": "awsshell/autocomplete.py",
"chars": 6292,
"preview": "from __future__ import print_function\nfrom awsshell.fuzzy import fuzzy_search\nfrom awsshell.substring import substring_s"
},
{
"path": "awsshell/awsshellrc",
"chars": 511,
"preview": "[aws-shell]\n\n# fuzzy or substring match.\nmatch_fuzzy = True\n\n# vi or emacs key bindings.\nenable_vi_bindings = False\n\n# m"
},
{
"path": "awsshell/compat.py",
"chars": 506,
"preview": "from __future__ import print_function\nimport sys\nimport platform\n\n\nPY3 = sys.version_info[0] == 3\nON_WINDOWS = platform."
},
{
"path": "awsshell/config.py",
"chars": 3535,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "awsshell/data/cloudformation/2010-05-15/completions-1.json",
"chars": 573,
"preview": "{\n \"operations\": {\n \"UpdateStack\": {\n \"StackName\": {\n \"resourceName\": \"Stack\", \n \"resourceIdentif"
},
{
"path": "awsshell/data/dynamodb/2012-08-10/completions-1.json",
"chars": 1179,
"preview": "{\n \"operations\": {\n \"UpdateTable\": {\n \"TableName\": {\n \"resourceName\": \"Table\", \n \"resourceIdentif"
},
{
"path": "awsshell/data/ec2/2015-04-15/completions-1.json",
"chars": 13144,
"preview": "{\n \"operations\": {\n \"ResetNetworkInterfaceAttribute\": {\n \"NetworkInterfaceId\": {\n \"resourceName\": \"Netwo"
},
{
"path": "awsshell/data/elb/2012-06-01/completions-1.json",
"chars": 4148,
"preview": "{\n \"operations\": {\n \"AddTags\": {\n \"LoadBalancerNames\": {\n \"resourceName\": \"LoadBalancer\",\n \"resou"
},
{
"path": "awsshell/data/glacier/2012-06-01/completions-1.json",
"chars": 1408,
"preview": "{\n \"operations\": {\n \"CreateVault\": {\n \"vaultName\": {\n \"resourceName\": \"Vault\", \n \"resourceIdentif"
},
{
"path": "awsshell/data/iam/2010-05-08/completions-1.json",
"chars": 5404,
"preview": "{\n \"operations\": {\n \"DeleteInstanceProfile\": {\n \"InstanceProfileName\": {\n \"resourceName\": \"InstanceProfi"
},
{
"path": "awsshell/data/kinesis/2013-12-02/completions-1.json",
"chars": 1781,
"preview": "{\n \"operations\": {\n \"AddTagsToStream\": {\n \"StreamName\": {\n \"resourceName\": \"Stream\",\n \"resourceId"
},
{
"path": "awsshell/data/opsworks/2013-02-18/completions-1.json",
"chars": 426,
"preview": "{\n \"operations\": {\n \"CreateLayer\": {\n \"StackId\": {\n \"resourceName\": \"Stack\", \n \"resourceIdentifie"
},
{
"path": "awsshell/data/s3/2006-03-01/completions-1.json",
"chars": 684,
"preview": "{\n \"operations\": {\n \"CreateBucket\": {\n \"Bucket\": {\n \"resourceName\": \"Bucket\", \n \"resourceIdentifi"
},
{
"path": "awsshell/data/sns/2010-03-31/completions-1.json",
"chars": 2230,
"preview": "{\n \"operations\": {\n \"ConfirmSubscription\": {\n \"TopicArn\": {\n \"resourceName\": \"Topic\", \n \"resource"
},
{
"path": "awsshell/data/sqs/2012-11-05/completions-1.json",
"chars": 1483,
"preview": "{\n \"operations\": {\n \"SetQueueAttributes\": {\n \"QueueUrl\": {\n \"resourceName\": \"Queue\", \n \"resourceI"
},
{
"path": "awsshell/db.py",
"chars": 1264,
"preview": "from __future__ import unicode_literals\nimport os\nimport sqlite3\n\n\nclass ConcurrentDBM(object):\n\n @classmethod\n de"
},
{
"path": "awsshell/docs.py",
"chars": 1187,
"preview": "from __future__ import unicode_literals\nfrom awsshell import db\n\n\ndef load_lazy_doc_index(filename):\n d = load_doc_db"
},
{
"path": "awsshell/fuzzy.py",
"chars": 2819,
"preview": "\"\"\"Fuzzy finder for AWS Shell.\n\nThis is a fuzzy finder used for the autocompleter\nthat I've tried to optimize for the AW"
},
{
"path": "awsshell/index/__init__.py",
"chars": 4305,
"preview": "\"\"\"Subpackage for indexing data.\n\nNote that, while there is documentation for all the code in this\npackage, these module"
},
{
"path": "awsshell/index/completion.py",
"chars": 3422,
"preview": "\"\"\"Module for completion index.\n\nGenerates, loads, and writes out completion index.\nAlso provides an interface for worki"
},
{
"path": "awsshell/keys.py",
"chars": 6391,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "awsshell/lexer.py",
"chars": 2155,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "awsshell/loaders.py",
"chars": 420,
"preview": "import json\n\nfrom awsshell.utils import build_config_file_path\n\n\nclass JSONIndexLoader(object):\n def __init__(self):\n"
},
{
"path": "awsshell/makeindex.py",
"chars": 6173,
"preview": "\"\"\"Module for building the autocompletion indices.\"\"\"\nfrom __future__ import print_function\nimport os\nimport json\n\nfrom "
},
{
"path": "awsshell/resource/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "awsshell/resource/index.py",
"chars": 11031,
"preview": "\"\"\"Index and retrive information from the resource JSON.\n\nThe classes are organized as follows:\n\n* ResourceIndexBuilder "
},
{
"path": "awsshell/shellcomplete.py",
"chars": 6586,
"preview": "\"\"\"Autocompletion integration with python prompt toolkit.\n\nThis module integrates the low level autocomplete functionali"
},
{
"path": "awsshell/style.py",
"chars": 2492,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "awsshell/substring.py",
"chars": 1082,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "awsshell/toolbar.py",
"chars": 4140,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "awsshell/ui.py",
"chars": 9503,
"preview": "from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER\nfrom prompt_toolkit.filters import IsDone, HasFocus, Alwa"
},
{
"path": "awsshell/utils.py",
"chars": 2961,
"preview": "\"\"\"Utility module for misc aws shell functions.\"\"\"\nfrom __future__ import print_function\nimport os\nimport contextlib\nimp"
},
{
"path": "requirements-dev.txt",
"chars": 78,
"preview": "doc8==0.6.0\nflake8==2.5.0\npep257==0.7.0\npylint==1.7.2\n-rrequirements-test.txt\n"
},
{
"path": "requirements-test.txt",
"chars": 220,
"preview": "pytest==3.0.2\npytest-cov==2.3.1\nmock==1.3.0\ntox==2.2.1\nconfigobj==5.0.6\n# Note you need at least pip --version of 6.0 or"
},
{
"path": "scripts/ci/install",
"chars": 689,
"preview": "#!/usr/bin/env python\nimport os\nimport sys\nfrom subprocess import check_call\nimport shutil\n\n_dname = os.path.dirname\n\nRE"
},
{
"path": "scripts/ci/run-integ-tests",
"chars": 497,
"preview": "#!/usr/bin/env python\n# Don't run tests from the root repo dir.\n# We want to ensure we're importing from the installed\n#"
},
{
"path": "scripts/ci/run-tests",
"chars": 490,
"preview": "#!/usr/bin/env python\n# Don't run tests from the root repo dir.\n# We want to ensure we're importing from the installed\n#"
},
{
"path": "scripts/new-change",
"chars": 7045,
"preview": "#!/usr/bin/env python\n\"\"\"Generate a new changelog entry.\n\nUsage\n=====\n\nTo generate a new changelog entry::\n\n scripts/"
},
{
"path": "setup.cfg",
"chars": 28,
"preview": "[bdist_wheel]\nuniversal = 1\n"
},
{
"path": "setup.py",
"chars": 1719,
"preview": "#!/usr/bin/env python\nimport re\nimport ast\n\nfrom setuptools import setup, find_packages\n\n\nrequires = [\n 'awscli>=1.16"
},
{
"path": "tests/__init__.py",
"chars": 78,
"preview": "try:\n import unittest2 as unittest\nexcept ImportError:\n import unittest\n"
},
{
"path": "tests/compat.py",
"chars": 666,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "tests/integration/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/integration/test_config.py",
"chars": 2398,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "tests/integration/test_db.py",
"chars": 1306,
"preview": "from awsshell import db\nimport pytest\n\n\n@pytest.fixture\ndef shell_db(tmpdir):\n filename = tmpdir.join('docs.db').strp"
},
{
"path": "tests/integration/test_keys.py",
"chars": 2551,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "tests/integration/test_makeindex.py",
"chars": 1907,
"preview": "import awscli.clidriver\nfrom awsshell import makeindex\n\nimport pytest\n\n\n@pytest.fixture\ndef cloudformation_command():\n "
},
{
"path": "tests/unit/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/unit/index/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/unit/index/test_completions.py",
"chars": 979,
"preview": "from tests import unittest\n\nfrom awsshell.index import completion\nfrom awsshell.utils import InMemoryFSLayer\n\n\nclass Tes"
},
{
"path": "tests/unit/test_app.py",
"chars": 5943,
"preview": "import pytest\nimport mock\n\n\nfrom awsshell import app\nfrom awsshell import shellcomplete\nfrom awsshell import compat\n\n\n@p"
},
{
"path": "tests/unit/test_autocomplete.py",
"chars": 13044,
"preview": "import pytest\nfrom awsshell.autocomplete import AWSCLIModelCompleter\n\n@pytest.fixture\ndef index_data():\n return {\n "
},
{
"path": "tests/unit/test_docs.py",
"chars": 390,
"preview": "from awsshell import docs\nfrom awsshell import db\n\n\ndef test_lazy_doc_factory(tmpdir):\n filename = tmpdir.join('foo.d"
},
{
"path": "tests/unit/test_fuzzy.py",
"chars": 629,
"preview": "import pytest\nfrom awsshell.fuzzy import fuzzy_search\n\n\n@pytest.mark.parametrize(\"search,corpus,expected\", [\n ('foo',"
},
{
"path": "tests/unit/test_load_completions.py",
"chars": 1936,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "tests/unit/test_makeindex.py",
"chars": 518,
"preview": "import textwrap\nfrom awsshell import makeindex\n\ndef test_can_convert_rst_text():\n content = textwrap.dedent(\"\"\"\\\n "
},
{
"path": "tests/unit/test_resources.py",
"chars": 8210,
"preview": "\"\"\"Index and retrive information from the resource JSON.\"\"\"\nimport pytest\nimport mock\n\nfrom botocore.exceptions import N"
},
{
"path": "tests/unit/test_substring.py",
"chars": 965,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "tests/unit/test_toolbar.py",
"chars": 2422,
"preview": "# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version"
},
{
"path": "tests/unit/test_utils.py",
"chars": 3227,
"preview": "from tests import unittest\nimport os\nimport tempfile\nimport shutil\n\nfrom awsshell.utils import FSLayer\nfrom awsshell.uti"
},
{
"path": "tox.ini",
"chars": 107,
"preview": "[tox]\nenvlist = py26,py27,py33,py34,py35,py36\n\n[testenv]\ncommands = py.test\ndeps = -rrequirements-test.txt\n"
}
]
About this extraction
This page contains the full source code of the awslabs/aws-shell GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 75 files (221.5 KB), approximately 53.7k tokens, and a symbol index with 264 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.