New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

devshell

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

devshell - pypi Package Compare versions

Comparing version
0.0.2
to
0.0.3
+78
docs/devshell/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<title>devshell API documentation</title>
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Package <code>devshell</code></h1>
</header>
<section id="section-intro">
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">__version__ = &#39;0.0.3&#39;
from .injector import doctestify, set_end_interactive
from .shell import DevshellCmd</code></pre>
</details>
</section>
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="devshell.injector" href="injector.html">devshell.injector</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="devshell.ptcmd" href="ptcmd.html">devshell.ptcmd</a></code></dt>
<dd>
<div class="desc"><p>This script takes the Cmd object from the built-in standard library, adds an input_method attribute, and replaces all calls to the standard input() …</p></div>
</dd>
<dt><code class="name"><a title="devshell.shell" href="shell.html">devshell.shell</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3><a href="#header-submodules">Sub-modules</a></h3>
<ul>
<li><code><a title="devshell.injector" href="injector.html">devshell.injector</a></code></li>
<li><code><a title="devshell.ptcmd" href="ptcmd.html">devshell.ptcmd</a></code></li>
<li><code><a title="devshell.shell" href="shell.html">devshell.shell</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<title>devshell.injector API documentation</title>
<meta name="description" content="" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>devshell.injector</code></h1>
</header>
<section id="section-intro">
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">import inspect, ast, re, sys, code, readline, importlib, os, doctest, os.path
from io import StringIO
try:
from importlib import reload
except:
pass
try:
input = raw_input
except NameError:
pass
def get_target(target_fqn):
&#34;&#34;&#34;
This function returns the target object, module object, and the module&#39;s fully qualified name based on the provided fully qualified target name.
&#34;&#34;&#34;
module_fqn = target_fqn.split(&#39;.&#39;)
while True:
try:
module = __import__(&#39;.&#39;.join(module_fqn))
break
except ImportError:
module_fqn.pop()
if len(module_fqn) == 0:
raise Exception(&#39;Could not resolve target: %s&#39; % repr(target_fqn))
pieces = target_fqn.split(&#39;.&#39;)
obj = module
for item in pieces[1:]:
obj = getattr(obj,item)
return obj,module,&#39;.&#39;.join(module_fqn)
get_target.__annotations__ = {&#39;target_fqn&#39;:&#39;fully qualified name of target&#39;,&#39;return&#39;:(&#39;target object&#39;,&#39;top-level target module&#39;,&#39;fully qualified name of module&#39;)}
class _ModStdout(object):
def __init__(self,iobuf):
self.iobuf = iobuf
def flush(self):
sys.__stdout__.flush()
def writelines(self,lines):
self.iobuf.extend(data)
sys.__stdout__.writelines(lines)
def write(self,data):
self.iobuf.append(data)
sys.__stdout__.write(data)
class _ModStderr(object):
def __init__(self,iobuf):
self.iobuf = iobuf
def flush(self):
sys.__stderr__.flush()
def writelines(self,lines):
self.iobuf.extend(data)
sys.__stderr__.writelines(lines)
def write(self,data):
self.iobuf.append(data)
sys.__stderr__.write(data)
def get_ast_obj(target_fqn,obj=None,module=None,module_fqn=None):
&#34;&#34;&#34;
This function returns the ast object of the targeted python object
&#34;&#34;&#34;
if obj is None or module is None or module_fqn is None:
obj,module,module_fqn = get_target(target_fqn)
importlib.reload(module)
if inspect.ismodule(obj):
importlib.reload(sys.modules[obj.__name__])
else:
importlib.reload(sys.modules[obj.__module__])
filepath = os.path.abspath(inspect.getsourcefile(obj))
if not filepath.startswith(os.getcwd()):
raise Exception(&#39;Referenced file is not in the current working directory or any subfolders - this is to protect you from modifying system or site-package code: %s&#39; % repr(filepath))
filepath
pieces = target_fqn.split(&#39;.&#39;)
with open(filepath,&#39;r&#39;) as f:
src_lines = f.readlines()
source = &#39;&#39;.join(src_lines)
tree = ast.parse(source)
if inspect.ismodule(obj):
ast_obj = tree
elif inspect.isclass(obj):
ast_obj = [node for node in ast.walk(tree) if isinstance(node,ast.ClassDef) and node.name == pieces[-1]][0]
elif inspect.isfunction(obj):
ast_obj = [node for node in ast.walk(tree) if isinstance(node,ast.FunctionDef) and node.name == pieces[-1]][0]
return ast_obj, filepath,source, src_lines
class DoctestInjector(object):
&#34;&#34;&#34;
This class loads a target object by its fully qualified name and parses its source code to determine how to insert docstring lines for that object.
&#34;&#34;&#34;
def __init__(self,target_fqn):
self.target_fqn = target_fqn
obj,module,module_fqn = get_target(target_fqn)
self.obj = obj
self.module=module
self.module_fqn = module_fqn
ast_obj,self.filepath,self.original_source,self.src_lines = get_ast_obj(target_fqn,obj,module,module_fqn)
if isinstance(ast_obj.body[0],ast.Expr) and isinstance(ast_obj.body[0].value,ast.Str):
#docstring already exists
ast_doc = ast_obj.body[0]
if hasattr(ast_doc,&#39;end_lineno&#39;):
#python 3.8+
line_index = ast_doc.end_lineno-1 #last line of docstring (line containing the ending quotes)
else:
#python 3.7-
line_index = ast_doc.lineno-1 #last line of docstring (line containing the ending quotes)
byte_index = ast_doc.col_offset
indentation = re.search(&#39;^\\s*&#39;,self.src_lines[line_index]).group(0) #use docstring end line to determine indentation
newline = re.search(&#39;[\r\n]+$&#39;,self.src_lines[line_index]).group(0) #use docstring end line to determine newline
top = self.src_lines[:line_index+1] #include entire docstring including end line
bottom = self.src_lines[line_index+1:] #everything else
ending = &#39;&#34;&#34;&#34;&#39;+top[-1].split(&#39;&#34;&#34;&#34;&#39;)[-1] #get the trailing quotes and characters after doctsring
top[-1] = top[-1][len(indentation):-len(ending)]+newline #remove trailing from top
bottom.insert(0,indentation+ending) #add trailing to bottom
else:
if len(ast_obj.body) == 1 and ast_obj.lineno == ast_obj.body[0].lineno:
#docstring does not exist for a single-line function
line_index = ast_obj.lineno-1 #line of function
indentation = re.search(&#39;^\\s*&#39;,self.src_lines[line_index]).group(0).strip(&#39;\r\n&#39;)+&#39; &#39; #use indentation of function plus four spaces
newline = re.search(&#39;[\r\n]+$&#39;,self.src_lines[line_index]).group(0) #use newline of function line
top = self.src_lines[:line_index+1] #include funtion
bottom = src_lines[ast_obj.lineno:] #everything after function
ast_first = ast_obj.body[0] #first (and only) element in body
byte_index = ast_first.col_offset #starting position of first element
first_element = top[-1][byte_index:] #first element text content
top[-1] = top[-1][:byte_index]+newline #remove first element text content from top
bottom.insert(0,indentation+first_element) #add first element text content to bottom
top.append(indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add docstring starting quotes
bottom.insert(0,indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add docstring ending quotes
else:
#docstring does not exist for a multi-line function
ast_first = ast_obj.body[0]
line_index = ast_first.lineno-1 #line number of first element in body of definition
byte_index = ast_first.col_offset
indentation = re.search(&#39;^\\s*&#39;,self.src_lines[line_index]).group(0) #use first element line to determine indentation
newline = re.search(&#39;[\r\n]+$&#39;,self.src_lines[line_index]).group(0) #use first element line to determine newline
top = self.src_lines[:line_index] #everything up to but not including first element
top.append(indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add new docstring starting quotes
bottom = self.src_lines[ast_obj.body[0].lineno-1:] #first element and everything after
bottom.insert(0,indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add docstring ending quotes
self.top = top
self.bottom = bottom
self.indentation = indentation
self.newline = newline
self.middle = []
__init__.__annotations__ = {&#39;target_fqn&#39;:&#39;fully qualified name of target&#39;,&#39;return&#39;:(&#39;target object&#39;,&#39;target module&#39;,&#39;fully qualified name of module&#39;)}
def source(self):
&#34;&#34;&#34;
This returns the updated source code with new inserted docstrings lines for the target object.
&#34;&#34;&#34;
indented_middle = []
last_line = None
for line in self.middle:
line = re.sub(self.newline,self.newline+self.indentation,line)
if last_line is None:
indented_middle.append(self.indentation+line)
else:
indented_middle.append(line)
last_line = line
indented_middle[-1] = indented_middle[-1].rstrip() + self.newline #don&#39;t indent the ending triple quotes
return &#39;&#39;.join(self.top+ indented_middle + self.bottom)
def doctest_console(self,resume=False):
&#34;&#34;&#34;
This function runs doctests on the target file, loads the file, and enters a special interactive mode with inputs/outputs being recorded.
When the console is done being used (via Ctrl+D), the recorded inputs/outputs will be inserted into the docstring of the target object.
Doctests are then run for the udpated code and if there are no problems, the updated code is written to the file location.
If there are problems, the updated code is saved to a file in the same folder as the target file but with the suffix &#34;.failed_doctest_insert&#34;.
&#34;&#34;&#34;
print(&#39;=&#39;*80+&#39;\nDoctestify:\n Testing doctest execution of original file&#39;)
oldfailcount,oldtestcount = self.testmod()
print(&#39; ...done: Fail count = %d, Total count = %d&#39; % (oldfailcount,oldtestcount))
print(&#39;=&#39;*80+&#39;&#39;&#39;\nEntering interactive console:
Target: %s
File: %s
(*) Press Ctrl+D to exit and incorporate session into the targeted docstring
(*) To abort without incorporating anything, call the exit() function
&#39;&#39;&#39; % (self.target_fqn,self.filepath) + &#39;-&#39;*80)
console = code.InteractiveConsole()
print(&#39;&gt;&gt;&gt; from %s import * # automatic import by devshell&#39;&#39;&#39; % (self.module_fqn))
console.push(&#39;from %s import *&#39; % (self.module_fqn))
if resume:
doc = inspect.getdoc(self.obj)
for line in doc.splitlines():
line = line.lstrip()
if line.startswith(&#39;&gt;&gt;&gt; &#39;) or line.startswith(&#39;... &#39;):
print(line)
console.push(line[4:])
iobuf = self.middle
_modstdout = _ModStdout(iobuf)
_modstderr = _ModStderr(iobuf)
def mod_input(prompt,iobuf=iobuf,_modstdout=_modstdout,_modstderr=_modstderr,newline=self.newline):
sys.stdout = sys.__stdout__ #when input() is happening - need normal stdout for readline (up/down arrowkeys) to work properly
sys.stderr = sys.__stderr__
s = input(prompt)
iobuf.append(prompt+s+newline)
sys.stdout = _modstdout
sys.stderr = _modstderr
return s
console.raw_input = mod_input
#sys.stdout = _modstdout
#sys.stderr = _modstderr
console.interact(banner=&#39;&#39;)
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
if len(iobuf) == 0:
print(&#39;No lines were written - exiting&#39;)
else:
print(&#39;Writing doctest lines to file&#39;)
updated_source = self.source()
with open(self.filepath,&#39;w&#39;) as f:
f.write(updated_source)
print(&#39;Testing doctest execution of new file&#39;)
revert = False
try:
newfailcount,newtestcount = self.testmod()
print(&#39;...done: Fail count = %d (old=%d), Total count = %d (old=%d)&#39; % (newfailcount,oldfailcount,newtestcount,oldtestcount))
except:
revert = True
print(&#39;Failed to load new file - reverting back to original file&#39;)
if revert is False and oldfailcount != newfailcount:
revert = True
print(&#39;Failcounts from before did not match after - reverting back to original file&#39;)
if revert:
with open(self.filepath,&#39;w&#39;) as f:
f.write(self.original_source)
print(&#39;Updated source code with problems located at: %s&#39; % (self.filepath+&#39;.failed_doctest_insert&#39;))
with open(self.filepath+&#39;.failed_doctest_insert&#39;,&#39;w&#39;) as f:
f.write(updated_source)
else:
print(&#39;File successfully updated&#39;)
def testmod(self):
&#34;&#34;&#34;
This runs doctests on the target module and returns the failcount and testcount
&#34;&#34;&#34;
self.module = module = reload(sys.modules[self.module_fqn])
failcount,testcount = doctest.testmod(module)
return failcount,testcount
def set_end_interactive(value=True):
&#34;&#34;&#34;
Setting value=True will make python go into interactive mode when the script terminates.
&#34;&#34;&#34;
if value:
os.environ[&#39;PYTHONINSPECT&#39;] = &#39;1&#39;
else:
if &#39;PYTHONINSPECT&#39; in os.environ:
del os.environ[&#39;PYTHONINSPECT&#39;]
def doctestify(target_fqn,resume=False):
&#34;&#34;&#34;
Start an interactive recording session for the item identified by the given fully qualified name.
Write the recorded results to the target object&#39;s docstring and test that the doctest passes.
&#34;&#34;&#34;
di = DoctestInjector(target_fqn)
di.doctest_console(resume)</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="devshell.injector.doctestify"><code class="name flex">
<span>def <span class="ident">doctestify</span></span>(<span>target_fqn, resume=False)</span>
</code></dt>
<dd>
<div class="desc"><p>Start an interactive recording session for the item identified by the given fully qualified name.
Write the recorded results to the target object's docstring and test that the doctest passes.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def doctestify(target_fqn,resume=False):
&#34;&#34;&#34;
Start an interactive recording session for the item identified by the given fully qualified name.
Write the recorded results to the target object&#39;s docstring and test that the doctest passes.
&#34;&#34;&#34;
di = DoctestInjector(target_fqn)
di.doctest_console(resume)</code></pre>
</details>
</dd>
<dt id="devshell.injector.get_ast_obj"><code class="name flex">
<span>def <span class="ident">get_ast_obj</span></span>(<span>target_fqn, obj=None, module=None, module_fqn=None)</span>
</code></dt>
<dd>
<div class="desc"><p>This function returns the ast object of the targeted python object</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_ast_obj(target_fqn,obj=None,module=None,module_fqn=None):
&#34;&#34;&#34;
This function returns the ast object of the targeted python object
&#34;&#34;&#34;
if obj is None or module is None or module_fqn is None:
obj,module,module_fqn = get_target(target_fqn)
importlib.reload(module)
if inspect.ismodule(obj):
importlib.reload(sys.modules[obj.__name__])
else:
importlib.reload(sys.modules[obj.__module__])
filepath = os.path.abspath(inspect.getsourcefile(obj))
if not filepath.startswith(os.getcwd()):
raise Exception(&#39;Referenced file is not in the current working directory or any subfolders - this is to protect you from modifying system or site-package code: %s&#39; % repr(filepath))
filepath
pieces = target_fqn.split(&#39;.&#39;)
with open(filepath,&#39;r&#39;) as f:
src_lines = f.readlines()
source = &#39;&#39;.join(src_lines)
tree = ast.parse(source)
if inspect.ismodule(obj):
ast_obj = tree
elif inspect.isclass(obj):
ast_obj = [node for node in ast.walk(tree) if isinstance(node,ast.ClassDef) and node.name == pieces[-1]][0]
elif inspect.isfunction(obj):
ast_obj = [node for node in ast.walk(tree) if isinstance(node,ast.FunctionDef) and node.name == pieces[-1]][0]
return ast_obj, filepath,source, src_lines</code></pre>
</details>
</dd>
<dt id="devshell.injector.get_target"><code class="name flex">
<span>def <span class="ident">get_target</span></span>(<span>target_fqn: fully qualified name of target) ‑> ('target object', 'top-level target module', 'fully qualified name of module')</span>
</code></dt>
<dd>
<div class="desc"><p>This function returns the target object, module object, and the module's fully qualified name based on the provided fully qualified target name.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_target(target_fqn):
&#34;&#34;&#34;
This function returns the target object, module object, and the module&#39;s fully qualified name based on the provided fully qualified target name.
&#34;&#34;&#34;
module_fqn = target_fqn.split(&#39;.&#39;)
while True:
try:
module = __import__(&#39;.&#39;.join(module_fqn))
break
except ImportError:
module_fqn.pop()
if len(module_fqn) == 0:
raise Exception(&#39;Could not resolve target: %s&#39; % repr(target_fqn))
pieces = target_fqn.split(&#39;.&#39;)
obj = module
for item in pieces[1:]:
obj = getattr(obj,item)
return obj,module,&#39;.&#39;.join(module_fqn)</code></pre>
</details>
</dd>
<dt id="devshell.injector.set_end_interactive"><code class="name flex">
<span>def <span class="ident">set_end_interactive</span></span>(<span>value=True)</span>
</code></dt>
<dd>
<div class="desc"><p>Setting value=True will make python go into interactive mode when the script terminates.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def set_end_interactive(value=True):
&#34;&#34;&#34;
Setting value=True will make python go into interactive mode when the script terminates.
&#34;&#34;&#34;
if value:
os.environ[&#39;PYTHONINSPECT&#39;] = &#39;1&#39;
else:
if &#39;PYTHONINSPECT&#39; in os.environ:
del os.environ[&#39;PYTHONINSPECT&#39;]</code></pre>
</details>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="devshell.injector.DoctestInjector"><code class="flex name class">
<span>class <span class="ident">DoctestInjector</span></span>
<span>(</span><span>target_fqn: fully qualified name of target)</span>
</code></dt>
<dd>
<div class="desc"><p>This class loads a target object by its fully qualified name and parses its source code to determine how to insert docstring lines for that object.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class DoctestInjector(object):
&#34;&#34;&#34;
This class loads a target object by its fully qualified name and parses its source code to determine how to insert docstring lines for that object.
&#34;&#34;&#34;
def __init__(self,target_fqn):
self.target_fqn = target_fqn
obj,module,module_fqn = get_target(target_fqn)
self.obj = obj
self.module=module
self.module_fqn = module_fqn
ast_obj,self.filepath,self.original_source,self.src_lines = get_ast_obj(target_fqn,obj,module,module_fqn)
if isinstance(ast_obj.body[0],ast.Expr) and isinstance(ast_obj.body[0].value,ast.Str):
#docstring already exists
ast_doc = ast_obj.body[0]
if hasattr(ast_doc,&#39;end_lineno&#39;):
#python 3.8+
line_index = ast_doc.end_lineno-1 #last line of docstring (line containing the ending quotes)
else:
#python 3.7-
line_index = ast_doc.lineno-1 #last line of docstring (line containing the ending quotes)
byte_index = ast_doc.col_offset
indentation = re.search(&#39;^\\s*&#39;,self.src_lines[line_index]).group(0) #use docstring end line to determine indentation
newline = re.search(&#39;[\r\n]+$&#39;,self.src_lines[line_index]).group(0) #use docstring end line to determine newline
top = self.src_lines[:line_index+1] #include entire docstring including end line
bottom = self.src_lines[line_index+1:] #everything else
ending = &#39;&#34;&#34;&#34;&#39;+top[-1].split(&#39;&#34;&#34;&#34;&#39;)[-1] #get the trailing quotes and characters after doctsring
top[-1] = top[-1][len(indentation):-len(ending)]+newline #remove trailing from top
bottom.insert(0,indentation+ending) #add trailing to bottom
else:
if len(ast_obj.body) == 1 and ast_obj.lineno == ast_obj.body[0].lineno:
#docstring does not exist for a single-line function
line_index = ast_obj.lineno-1 #line of function
indentation = re.search(&#39;^\\s*&#39;,self.src_lines[line_index]).group(0).strip(&#39;\r\n&#39;)+&#39; &#39; #use indentation of function plus four spaces
newline = re.search(&#39;[\r\n]+$&#39;,self.src_lines[line_index]).group(0) #use newline of function line
top = self.src_lines[:line_index+1] #include funtion
bottom = src_lines[ast_obj.lineno:] #everything after function
ast_first = ast_obj.body[0] #first (and only) element in body
byte_index = ast_first.col_offset #starting position of first element
first_element = top[-1][byte_index:] #first element text content
top[-1] = top[-1][:byte_index]+newline #remove first element text content from top
bottom.insert(0,indentation+first_element) #add first element text content to bottom
top.append(indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add docstring starting quotes
bottom.insert(0,indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add docstring ending quotes
else:
#docstring does not exist for a multi-line function
ast_first = ast_obj.body[0]
line_index = ast_first.lineno-1 #line number of first element in body of definition
byte_index = ast_first.col_offset
indentation = re.search(&#39;^\\s*&#39;,self.src_lines[line_index]).group(0) #use first element line to determine indentation
newline = re.search(&#39;[\r\n]+$&#39;,self.src_lines[line_index]).group(0) #use first element line to determine newline
top = self.src_lines[:line_index] #everything up to but not including first element
top.append(indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add new docstring starting quotes
bottom = self.src_lines[ast_obj.body[0].lineno-1:] #first element and everything after
bottom.insert(0,indentation+&#39;&#34;&#34;&#34;&#39;+newline) #add docstring ending quotes
self.top = top
self.bottom = bottom
self.indentation = indentation
self.newline = newline
self.middle = []
__init__.__annotations__ = {&#39;target_fqn&#39;:&#39;fully qualified name of target&#39;,&#39;return&#39;:(&#39;target object&#39;,&#39;target module&#39;,&#39;fully qualified name of module&#39;)}
def source(self):
&#34;&#34;&#34;
This returns the updated source code with new inserted docstrings lines for the target object.
&#34;&#34;&#34;
indented_middle = []
last_line = None
for line in self.middle:
line = re.sub(self.newline,self.newline+self.indentation,line)
if last_line is None:
indented_middle.append(self.indentation+line)
else:
indented_middle.append(line)
last_line = line
indented_middle[-1] = indented_middle[-1].rstrip() + self.newline #don&#39;t indent the ending triple quotes
return &#39;&#39;.join(self.top+ indented_middle + self.bottom)
def doctest_console(self,resume=False):
&#34;&#34;&#34;
This function runs doctests on the target file, loads the file, and enters a special interactive mode with inputs/outputs being recorded.
When the console is done being used (via Ctrl+D), the recorded inputs/outputs will be inserted into the docstring of the target object.
Doctests are then run for the udpated code and if there are no problems, the updated code is written to the file location.
If there are problems, the updated code is saved to a file in the same folder as the target file but with the suffix &#34;.failed_doctest_insert&#34;.
&#34;&#34;&#34;
print(&#39;=&#39;*80+&#39;\nDoctestify:\n Testing doctest execution of original file&#39;)
oldfailcount,oldtestcount = self.testmod()
print(&#39; ...done: Fail count = %d, Total count = %d&#39; % (oldfailcount,oldtestcount))
print(&#39;=&#39;*80+&#39;&#39;&#39;\nEntering interactive console:
Target: %s
File: %s
(*) Press Ctrl+D to exit and incorporate session into the targeted docstring
(*) To abort without incorporating anything, call the exit() function
&#39;&#39;&#39; % (self.target_fqn,self.filepath) + &#39;-&#39;*80)
console = code.InteractiveConsole()
print(&#39;&gt;&gt;&gt; from %s import * # automatic import by devshell&#39;&#39;&#39; % (self.module_fqn))
console.push(&#39;from %s import *&#39; % (self.module_fqn))
if resume:
doc = inspect.getdoc(self.obj)
for line in doc.splitlines():
line = line.lstrip()
if line.startswith(&#39;&gt;&gt;&gt; &#39;) or line.startswith(&#39;... &#39;):
print(line)
console.push(line[4:])
iobuf = self.middle
_modstdout = _ModStdout(iobuf)
_modstderr = _ModStderr(iobuf)
def mod_input(prompt,iobuf=iobuf,_modstdout=_modstdout,_modstderr=_modstderr,newline=self.newline):
sys.stdout = sys.__stdout__ #when input() is happening - need normal stdout for readline (up/down arrowkeys) to work properly
sys.stderr = sys.__stderr__
s = input(prompt)
iobuf.append(prompt+s+newline)
sys.stdout = _modstdout
sys.stderr = _modstderr
return s
console.raw_input = mod_input
#sys.stdout = _modstdout
#sys.stderr = _modstderr
console.interact(banner=&#39;&#39;)
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
if len(iobuf) == 0:
print(&#39;No lines were written - exiting&#39;)
else:
print(&#39;Writing doctest lines to file&#39;)
updated_source = self.source()
with open(self.filepath,&#39;w&#39;) as f:
f.write(updated_source)
print(&#39;Testing doctest execution of new file&#39;)
revert = False
try:
newfailcount,newtestcount = self.testmod()
print(&#39;...done: Fail count = %d (old=%d), Total count = %d (old=%d)&#39; % (newfailcount,oldfailcount,newtestcount,oldtestcount))
except:
revert = True
print(&#39;Failed to load new file - reverting back to original file&#39;)
if revert is False and oldfailcount != newfailcount:
revert = True
print(&#39;Failcounts from before did not match after - reverting back to original file&#39;)
if revert:
with open(self.filepath,&#39;w&#39;) as f:
f.write(self.original_source)
print(&#39;Updated source code with problems located at: %s&#39; % (self.filepath+&#39;.failed_doctest_insert&#39;))
with open(self.filepath+&#39;.failed_doctest_insert&#39;,&#39;w&#39;) as f:
f.write(updated_source)
else:
print(&#39;File successfully updated&#39;)
def testmod(self):
&#34;&#34;&#34;
This runs doctests on the target module and returns the failcount and testcount
&#34;&#34;&#34;
self.module = module = reload(sys.modules[self.module_fqn])
failcount,testcount = doctest.testmod(module)
return failcount,testcount</code></pre>
</details>
<h3>Methods</h3>
<dl>
<dt id="devshell.injector.DoctestInjector.doctest_console"><code class="name flex">
<span>def <span class="ident">doctest_console</span></span>(<span>self, resume=False)</span>
</code></dt>
<dd>
<div class="desc"><p>This function runs doctests on the target file, loads the file, and enters a special interactive mode with inputs/outputs being recorded.
When the console is done being used (via Ctrl+D), the recorded inputs/outputs will be inserted into the docstring of the target object.
Doctests are then run for the udpated code and if there are no problems, the updated code is written to the file location.
If there are problems, the updated code is saved to a file in the same folder as the target file but with the suffix ".failed_doctest_insert".</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python"> def doctest_console(self,resume=False):
&#34;&#34;&#34;
This function runs doctests on the target file, loads the file, and enters a special interactive mode with inputs/outputs being recorded.
When the console is done being used (via Ctrl+D), the recorded inputs/outputs will be inserted into the docstring of the target object.
Doctests are then run for the udpated code and if there are no problems, the updated code is written to the file location.
If there are problems, the updated code is saved to a file in the same folder as the target file but with the suffix &#34;.failed_doctest_insert&#34;.
&#34;&#34;&#34;
print(&#39;=&#39;*80+&#39;\nDoctestify:\n Testing doctest execution of original file&#39;)
oldfailcount,oldtestcount = self.testmod()
print(&#39; ...done: Fail count = %d, Total count = %d&#39; % (oldfailcount,oldtestcount))
print(&#39;=&#39;*80+&#39;&#39;&#39;\nEntering interactive console:
Target: %s
File: %s
(*) Press Ctrl+D to exit and incorporate session into the targeted docstring
(*) To abort without incorporating anything, call the exit() function
&#39;&#39;&#39; % (self.target_fqn,self.filepath) + &#39;-&#39;*80)
console = code.InteractiveConsole()
print(&#39;&gt;&gt;&gt; from %s import * # automatic import by devshell&#39;&#39;&#39; % (self.module_fqn))
console.push(&#39;from %s import *&#39; % (self.module_fqn))
if resume:
doc = inspect.getdoc(self.obj)
for line in doc.splitlines():
line = line.lstrip()
if line.startswith(&#39;&gt;&gt;&gt; &#39;) or line.startswith(&#39;... &#39;):
print(line)
console.push(line[4:])
iobuf = self.middle
_modstdout = _ModStdout(iobuf)
_modstderr = _ModStderr(iobuf)
def mod_input(prompt,iobuf=iobuf,_modstdout=_modstdout,_modstderr=_modstderr,newline=self.newline):
sys.stdout = sys.__stdout__ #when input() is happening - need normal stdout for readline (up/down arrowkeys) to work properly
sys.stderr = sys.__stderr__
s = input(prompt)
iobuf.append(prompt+s+newline)
sys.stdout = _modstdout
sys.stderr = _modstderr
return s
console.raw_input = mod_input
#sys.stdout = _modstdout
#sys.stderr = _modstderr
console.interact(banner=&#39;&#39;)
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
if len(iobuf) == 0:
print(&#39;No lines were written - exiting&#39;)
else:
print(&#39;Writing doctest lines to file&#39;)
updated_source = self.source()
with open(self.filepath,&#39;w&#39;) as f:
f.write(updated_source)
print(&#39;Testing doctest execution of new file&#39;)
revert = False
try:
newfailcount,newtestcount = self.testmod()
print(&#39;...done: Fail count = %d (old=%d), Total count = %d (old=%d)&#39; % (newfailcount,oldfailcount,newtestcount,oldtestcount))
except:
revert = True
print(&#39;Failed to load new file - reverting back to original file&#39;)
if revert is False and oldfailcount != newfailcount:
revert = True
print(&#39;Failcounts from before did not match after - reverting back to original file&#39;)
if revert:
with open(self.filepath,&#39;w&#39;) as f:
f.write(self.original_source)
print(&#39;Updated source code with problems located at: %s&#39; % (self.filepath+&#39;.failed_doctest_insert&#39;))
with open(self.filepath+&#39;.failed_doctest_insert&#39;,&#39;w&#39;) as f:
f.write(updated_source)
else:
print(&#39;File successfully updated&#39;)</code></pre>
</details>
</dd>
<dt id="devshell.injector.DoctestInjector.source"><code class="name flex">
<span>def <span class="ident">source</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>This returns the updated source code with new inserted docstrings lines for the target object.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def source(self):
&#34;&#34;&#34;
This returns the updated source code with new inserted docstrings lines for the target object.
&#34;&#34;&#34;
indented_middle = []
last_line = None
for line in self.middle:
line = re.sub(self.newline,self.newline+self.indentation,line)
if last_line is None:
indented_middle.append(self.indentation+line)
else:
indented_middle.append(line)
last_line = line
indented_middle[-1] = indented_middle[-1].rstrip() + self.newline #don&#39;t indent the ending triple quotes
return &#39;&#39;.join(self.top+ indented_middle + self.bottom)</code></pre>
</details>
</dd>
<dt id="devshell.injector.DoctestInjector.testmod"><code class="name flex">
<span>def <span class="ident">testmod</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>This runs doctests on the target module and returns the failcount and testcount</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def testmod(self):
&#34;&#34;&#34;
This runs doctests on the target module and returns the failcount and testcount
&#34;&#34;&#34;
self.module = module = reload(sys.modules[self.module_fqn])
failcount,testcount = doctest.testmod(module)
return failcount,testcount</code></pre>
</details>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="devshell" href="index.html">devshell</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="devshell.injector.doctestify" href="#devshell.injector.doctestify">doctestify</a></code></li>
<li><code><a title="devshell.injector.get_ast_obj" href="#devshell.injector.get_ast_obj">get_ast_obj</a></code></li>
<li><code><a title="devshell.injector.get_target" href="#devshell.injector.get_target">get_target</a></code></li>
<li><code><a title="devshell.injector.set_end_interactive" href="#devshell.injector.set_end_interactive">set_end_interactive</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="devshell.injector.DoctestInjector" href="#devshell.injector.DoctestInjector">DoctestInjector</a></code></h4>
<ul class="">
<li><code><a title="devshell.injector.DoctestInjector.doctest_console" href="#devshell.injector.DoctestInjector.doctest_console">doctest_console</a></code></li>
<li><code><a title="devshell.injector.DoctestInjector.source" href="#devshell.injector.DoctestInjector.source">source</a></code></li>
<li><code><a title="devshell.injector.DoctestInjector.testmod" href="#devshell.injector.DoctestInjector.testmod">testmod</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<meta name="generator" content="pdoc 0.9.2" />
<title>devshell.ptcmd API documentation</title>
<meta name="description" content="This script takes the Cmd object from the built-in standard library, adds an input_method attribute, and replaces all calls to the standard input() …" />
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/sanitize.min.css" integrity="sha256-PK9q560IAAa6WVRRh76LtCaI8pjTJ2z11v0miyNNjrs=" crossorigin>
<link rel="preload stylesheet" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/11.0.1/typography.min.css" integrity="sha256-7l/o7C8jubJiy74VsKTidCy1yBkRtiUGbVkYBylBqUg=" crossorigin>
<link rel="stylesheet preload" as="style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/github.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em .5em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js" integrity="sha256-Uv3H6lx7dJmRfRvH8TH6kJD1TSK1aFcwgx+mdg3epi8=" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => hljs.initHighlighting())</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>devshell.ptcmd</code></h1>
</header>
<section id="section-intro">
<p>This script takes the Cmd object from the built-in standard library, adds an input_method attribute, and replaces all calls to the standard input() with calls to the input_method attribute</p>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">&#34;&#34;&#34;
This script takes the Cmd object from the built-in standard library, adds an input_method attribute, and replaces all calls to the standard input() with calls to the input_method attribute
&#34;&#34;&#34;
import ast, inspect, cmd, shlex
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit import PromptSession
class PTCmd_Completer(Completer):
def __init__(self,ptcmd):
self.ptcmd = ptcmd
def get_completions(self,document,complete_event):
for suggestion in self.ptcmd.pt_complete(document,complete_event):
#yield Completion(suggestion,start_position=0)
try:
yield Completion(suggestion,-len(shlex.split(document.current_line_before_cursor)[-1]))
except:
pass
class PTCmd(cmd.Cmd):
def __init__(self, completekey=&#39;tab&#39;, stdin=None, stdout=None,**psession_kwargs):
super().__init__(completekey,stdin,stdout)
psession_kwargs[&#39;completer&#39;] = PTCmd_Completer(self)
psession_kwargs[&#39;complete_while_typing&#39;] = True
self.psession = PromptSession(**psession_kwargs)
self.input_method = self.psession.prompt
def pt_complete(self, document,complete_event):
origline = document.text
line = origline.lstrip()
stripped = len(origline) - len(line)
begidx = document.cursor_position_col - stripped
endidx = len(document.text) - stripped
if begidx&gt;0:
cmd, args, foo = self.parseline(line)
if cmd == &#39;&#39;:
compfunc = self.completedefault
else:
try:
compfunc = getattr(self, &#39;complete_&#39; + cmd)
except AttributeError:
compfunc = self.completedefault
else:
compfunc = self.completenames
self.completion_matches = compfunc(document.text, line, begidx, endidx)
yield from self.completion_matches
class SwitchInput(ast.NodeTransformer):
def visit_Call(self,node):
if isinstance(node.func,ast.Name) and node.func.id == &#39;input&#39;:
load = ast.Load()
return ast.Call(
func = ast.Attribute(
value=ast.Name(
id=&#39;self&#39;,
ctx=load,
),
attr=&#39;input_method&#39;,
ctx=load,
),
args=node.args,
keywords=node.keywords,
)
else:
return node
ptcmd_tree = ast.parse(inspect.getsource(PTCmd))
cmd_tree = ast.fix_missing_locations(SwitchInput().visit(ast.parse(inspect.getsource(cmd.Cmd)))) #get a version that swaps all input(...) calls with self.input_method(...)
#find the cmdloop function
found = False
for node in ast.walk(cmd_tree):
if isinstance(node,ast.FunctionDef) and node.name == &#39;cmdloop&#39;:
cmdloop_node = node
found = True
break
assert(found)
#find the PTCmd class
found = False
for node in ast.walk(ptcmd_tree):
if isinstance(node,ast.ClassDef) and node.name == &#39;PTCmd&#39;:
found = True
ptcmd_node = node
break
assert(found)
#add the cmdloop function to the class definition (overwrite inherited version)
ptcmd_node.body.append(cmdloop_node)
ptcmd_tree = ast.fix_missing_locations(ptcmd_tree)
#Redefine the new class
exec(compile(ptcmd_tree,&#39;&lt;ast&gt;&#39;,&#39;exec&#39;))</code></pre>
</details>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="devshell.ptcmd.PTCmd"><code class="flex name class">
<span>class <span class="ident">PTCmd</span></span>
<span>(</span><span>completekey='tab', stdin=None, stdout=None, **psession_kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>A simple framework for writing line-oriented command interpreters.</p>
<p>These are often useful for test harnesses, administrative tools, and
prototypes that will later be wrapped in a more sophisticated interface.</p>
<p>A Cmd instance or subclass instance is a line-oriented interpreter
framework.
There is no good reason to instantiate Cmd itself; rather,
it's useful as a superclass of an interpreter class you define yourself
in order to inherit Cmd's methods and encapsulate action methods.</p>
<p>Instantiate a line-oriented interpreter framework.</p>
<p>The optional argument 'completekey' is the readline name of a
completion key; it defaults to the Tab key. If completekey is
not None and the readline module is available, command completion
is done automatically. The optional arguments stdin and stdout
specify alternate input and output file objects; if not specified,
sys.stdin and sys.stdout are used.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class PTCmd(cmd.Cmd):
def __init__(self, completekey=&#39;tab&#39;, stdin=None, stdout=None,**psession_kwargs):
super().__init__(completekey,stdin,stdout)
psession_kwargs[&#39;completer&#39;] = PTCmd_Completer(self)
psession_kwargs[&#39;complete_while_typing&#39;] = True
self.psession = PromptSession(**psession_kwargs)
self.input_method = self.psession.prompt
def pt_complete(self, document,complete_event):
origline = document.text
line = origline.lstrip()
stripped = len(origline) - len(line)
begidx = document.cursor_position_col - stripped
endidx = len(document.text) - stripped
if begidx&gt;0:
cmd, args, foo = self.parseline(line)
if cmd == &#39;&#39;:
compfunc = self.completedefault
else:
try:
compfunc = getattr(self, &#39;complete_&#39; + cmd)
except AttributeError:
compfunc = self.completedefault
else:
compfunc = self.completenames
self.completion_matches = compfunc(document.text, line, begidx, endidx)
yield from self.completion_matches</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li>cmd.Cmd</li>
</ul>
<h3>Subclasses</h3>
<ul class="hlist">
<li><a title="devshell.shell.DevshellCmd" href="shell.html#devshell.shell.DevshellCmd">DevshellCmd</a></li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="devshell.ptcmd.PTCmd.cmdloop"><code class="name flex">
<span>def <span class="ident">cmdloop</span></span>(<span>self, intro=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Repeatedly issue a prompt, accept input, parse an initial prefix
off the received input, and dispatch to action methods, passing them
the remainder of the line as argument.</p></div>
</dd>
<dt id="devshell.ptcmd.PTCmd.pt_complete"><code class="name flex">
<span>def <span class="ident">pt_complete</span></span>(<span>self, document, complete_event)</span>
</code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</dd>
<dt id="devshell.ptcmd.PTCmd_Completer"><code class="flex name class">
<span>class <span class="ident">PTCmd_Completer</span></span>
<span>(</span><span>ptcmd)</span>
</code></dt>
<dd>
<div class="desc"><p>Base class for completer implementations.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class PTCmd_Completer(Completer):
def __init__(self,ptcmd):
self.ptcmd = ptcmd
def get_completions(self,document,complete_event):
for suggestion in self.ptcmd.pt_complete(document,complete_event):
#yield Completion(suggestion,start_position=0)
try:
yield Completion(suggestion,-len(shlex.split(document.current_line_before_cursor)[-1]))
except:
pass</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li>prompt_toolkit.completion.base.Completer</li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="devshell.ptcmd.PTCmd_Completer.get_completions"><code class="name flex">
<span>def <span class="ident">get_completions</span></span>(<span>self, document, complete_event)</span>
</code></dt>
<dd>
<div class="desc"><p>This should be a generator that yields :class:<code>.Completion</code> instances.</p>
<p>If the generation of completions is something expensive (that takes a
lot of time), consider wrapping this <code>Completer</code> class in a
<code>ThreadedCompleter</code>. In that case, the completer algorithm runs in a
background thread and completions will be displayed as soon as they
arrive.</p>
<p>:param document: :class:<code>~prompt_toolkit.document.Document</code> instance.
:param complete_event: :class:<code>.CompleteEvent</code> instance.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def get_completions(self,document,complete_event):
for suggestion in self.ptcmd.pt_complete(document,complete_event):
#yield Completion(suggestion,start_position=0)
try:
yield Completion(suggestion,-len(shlex.split(document.current_line_before_cursor)[-1]))
except:
pass</code></pre>
</details>
</dd>
</dl>
</dd>
<dt id="devshell.ptcmd.SwitchInput"><code class="flex name class">
<span>class <span class="ident">SwitchInput</span></span>
</code></dt>
<dd>
<div class="desc"><p>A :class:<code>NodeVisitor</code> subclass that walks the abstract syntax tree and
allows modification of nodes.</p>
<p>The <code>NodeTransformer</code> will walk the AST and use the return value of the
visitor methods to replace or remove the old node.
If the return value of
the visitor method is <code>None</code>, the node will be removed from its location,
otherwise it is replaced with the return value.
The return value may be the
original node in which case no replacement takes place.</p>
<p>Here is an example transformer that rewrites all occurrences of name lookups
(<code>foo</code>) to <code>data['foo']</code>::</p>
<p>class RewriteName(NodeTransformer):</p>
<pre><code> def visit_Name(self, node):
return Subscript(
value=Name(id='data', ctx=Load()),
slice=Index(value=Str(s=node.id)),
ctx=node.ctx
)
</code></pre>
<p>Keep in mind that if the node you're operating on has child nodes you must
either transform the child nodes yourself or call the :meth:<code>generic_visit</code>
method for the node first.</p>
<p>For nodes that were part of a collection of statements (that applies to all
statement nodes), the visitor may also return a list of nodes rather than
just a single node.</p>
<p>Usually you use the transformer like this::</p>
<p>node = YourTransformer().visit(node)</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">class SwitchInput(ast.NodeTransformer):
def visit_Call(self,node):
if isinstance(node.func,ast.Name) and node.func.id == &#39;input&#39;:
load = ast.Load()
return ast.Call(
func = ast.Attribute(
value=ast.Name(
id=&#39;self&#39;,
ctx=load,
),
attr=&#39;input_method&#39;,
ctx=load,
),
args=node.args,
keywords=node.keywords,
)
else:
return node</code></pre>
</details>
<h3>Ancestors</h3>
<ul class="hlist">
<li>ast.NodeTransformer</li>
<li>ast.NodeVisitor</li>
</ul>
<h3>Methods</h3>
<dl>
<dt id="devshell.ptcmd.SwitchInput.visit_Call"><code class="name flex">
<span>def <span class="ident">visit_Call</span></span>(<span>self, node)</span>
</code></dt>
<dd>
<div class="desc"></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def visit_Call(self,node):
if isinstance(node.func,ast.Name) and node.func.id == &#39;input&#39;:
load = ast.Load()
return ast.Call(
func = ast.Attribute(
value=ast.Name(
id=&#39;self&#39;,
ctx=load,
),
attr=&#39;input_method&#39;,
ctx=load,
),
args=node.args,
keywords=node.keywords,
)
else:
return node</code></pre>
</details>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
<h1>Index</h1>
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="devshell" href="index.html">devshell</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="devshell.ptcmd.PTCmd" href="#devshell.ptcmd.PTCmd">PTCmd</a></code></h4>
<ul class="">
<li><code><a title="devshell.ptcmd.PTCmd.cmdloop" href="#devshell.ptcmd.PTCmd.cmdloop">cmdloop</a></code></li>
<li><code><a title="devshell.ptcmd.PTCmd.pt_complete" href="#devshell.ptcmd.PTCmd.pt_complete">pt_complete</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="devshell.ptcmd.PTCmd_Completer" href="#devshell.ptcmd.PTCmd_Completer">PTCmd_Completer</a></code></h4>
<ul class="">
<li><code><a title="devshell.ptcmd.PTCmd_Completer.get_completions" href="#devshell.ptcmd.PTCmd_Completer.get_completions">get_completions</a></code></li>
</ul>
</li>
<li>
<h4><code><a title="devshell.ptcmd.SwitchInput" href="#devshell.ptcmd.SwitchInput">SwitchInput</a></code></h4>
<ul class="">
<li><code><a title="devshell.ptcmd.SwitchInput.visit_Call" href="#devshell.ptcmd.SwitchInput.visit_Call">visit_Call</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.9.2</a>.</p>
</footer>
</body>
</html>

Sorry, the diff of this file is too big to display

Copyright (c) 2019 U.N. Owen
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
files = setup.py src/devshell/*.py
all: do_build do_upload do_install
do_build: $(files)
echo "building"
if [ -d ./build ]; then rm -rf ./build; fi
if [ -d ./dist ]; then rm -rf ./dist; fi
if [ -d ./devshell.egg-info ]; then rm -rf ./devshell.egg-info; fi
pdoc --html --force --output-dir docs src/devshell
python3 setup.py sdist bdist_wheel
do_upload: do_build
echo "uploading"
python3 -m twine upload dist/*
do_install: do_upload
echo "installing"
python3 -m pip install --user --upgrade --force-reinstall devshell

Sorry, the diff of this file is not supported yet

"""
This script takes the Cmd object from the built-in standard library, adds an input_method attribute, and replaces all calls to the standard input() with calls to the input_method attribute
"""
import ast, inspect, cmd, shlex
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit import PromptSession
class PTCmd_Completer(Completer):
def __init__(self,ptcmd):
self.ptcmd = ptcmd
def get_completions(self,document,complete_event):
for suggestion in self.ptcmd.pt_complete(document,complete_event):
#yield Completion(suggestion,start_position=0)
try:
yield Completion(suggestion,-len(shlex.split(document.current_line_before_cursor)[-1]))
except:
pass
class PTCmd(cmd.Cmd):
def __init__(self, completekey='tab', stdin=None, stdout=None,**psession_kwargs):
super().__init__(completekey,stdin,stdout)
psession_kwargs['completer'] = PTCmd_Completer(self)
psession_kwargs['complete_while_typing'] = True
self.psession = PromptSession(**psession_kwargs)
self.input_method = self.psession.prompt
def pt_complete(self, document,complete_event):
origline = document.text
line = origline.lstrip()
stripped = len(origline) - len(line)
begidx = document.cursor_position_col - stripped
endidx = len(document.text) - stripped
if begidx>0:
cmd, args, foo = self.parseline(line)
if cmd == '':
compfunc = self.completedefault
else:
try:
compfunc = getattr(self, 'complete_' + cmd)
except AttributeError:
compfunc = self.completedefault
else:
compfunc = self.completenames
self.completion_matches = compfunc(document.text, line, begidx, endidx)
yield from self.completion_matches
class SwitchInput(ast.NodeTransformer):
def visit_Call(self,node):
if isinstance(node.func,ast.Name) and node.func.id == 'input':
load = ast.Load()
return ast.Call(
func = ast.Attribute(
value=ast.Name(
id='self',
ctx=load,
),
attr='input_method',
ctx=load,
),
args=node.args,
keywords=node.keywords,
)
else:
return node
ptcmd_tree = ast.parse(inspect.getsource(PTCmd))
cmd_tree = ast.fix_missing_locations(SwitchInput().visit(ast.parse(inspect.getsource(cmd.Cmd)))) #get a version that swaps all input(...) calls with self.input_method(...)
#find the cmdloop function
found = False
for node in ast.walk(cmd_tree):
if isinstance(node,ast.FunctionDef) and node.name == 'cmdloop':
cmdloop_node = node
found = True
break
assert(found)
#find the PTCmd class
found = False
for node in ast.walk(ptcmd_tree):
if isinstance(node,ast.ClassDef) and node.name == 'PTCmd':
found = True
ptcmd_node = node
break
assert(found)
#add the cmdloop function to the class definition (overwrite inherited version)
ptcmd_node.body.append(cmdloop_node)
ptcmd_tree = ast.fix_missing_locations(ptcmd_tree)
#Redefine the new class
exec(compile(ptcmd_tree,'<ast>','exec'))
+61
-5
Metadata-Version: 2.1
Name: devshell
Version: 0.0.2
Version: 0.0.3
Summary: Shell-like tool to make it easier to develop python code

@@ -13,8 +13,64 @@ Home-page: https://github.com/mmiguel6288code/devshell

devshell is a tool to make python development easier.
[View API documentation](http://htmlpreview.github.io/?https://github.com/mmiguel6288code/devshell/blob/master/docs/devshell/index.html)
## Key Features
1. Enables entering an interactive session tied to the docstring of a particular python object. Inputs and responses are automatically recorded and inserted into the docstring as doctests.
2. Shell-like tool for navigating, inspecting, testing, and making doctests for python objects defined in a project
devshell provides the power user terminal python developer feeling.
There's normal basic shell navigation with cd, ls, pwd, and then there are python versions of those for navigating through a code tree: pcd, pls, ppwd.
What is a code tree? It is the following types of code blocks:
1. Package
2. Module
3. Class
4. Function/Method/Coroutine
```bash
$ cd ~/projects/statopy
$ ls
LICENSE __pycache__ statopy.py
$ python3 -m devshell
Starting devshell command line interface...
devshell version 0.0.3
Welcome to devshell. Type help or ? to list commands. Start a line with ! to execute a shell command in a sub-shell (does not retain environmental variables).
(devshell)$
```
If a package or module in your current working directory (the normal type affected by cd and reported with pwd), then those will show up when you type pls.
You can enter your "python location" into it via pcd and check your current python location with ppwd.
```bash
(devshell)$ pls
statopy module directory
(devshell)$ pcd statopy
(devshell)$ pls
ScalarProbModel class directory
ScalarRegression class directory
ScalarStats class directory
VectorStats class directory
(devshell)$ pcd ScalarStats
(devshell)$ pls
__add__ function non-directory
__init__ function non-directory
__setattr__ function non-directory
consume function non-directory
update function non-directory
(devshell)$ ppwd
/statopy.ScalarStats (class)
(devshell)$
```
That's nice, but what can you do besides inspecting what code blocks exist?
```bash
(devshell)$ help
Documented commands (type help <topic>):
========================================
EOF create doctestify h mv pwd read source
activate deactivate edit help pcd pytest restart venv
cd debug editvim interactive pip python rm
coverage doc exit ls pls q rmtree
cp doctest grep mkdir ppwd quit run
```
## What are doctests and why should I care?

@@ -21,0 +77,0 @@ Doctests are snippets of text that resemble a Python interactive mode session.

# devshell
devshell is a tool to make python development easier.
[View API documentation](http://htmlpreview.github.io/?https://github.com/mmiguel6288code/devshell/blob/master/docs/devshell/index.html)
## Key Features
1. Enables entering an interactive session tied to the docstring of a particular python object. Inputs and responses are automatically recorded and inserted into the docstring as doctests.
2. Shell-like tool for navigating, inspecting, testing, and making doctests for python objects defined in a project
devshell provides the power user terminal python developer feeling.
There's normal basic shell navigation with cd, ls, pwd, and then there are python versions of those for navigating through a code tree: pcd, pls, ppwd.
What is a code tree? It is the following types of code blocks:
1. Package
2. Module
3. Class
4. Function/Method/Coroutine
```bash
$ cd ~/projects/statopy
$ ls
LICENSE __pycache__ statopy.py
$ python3 -m devshell
Starting devshell command line interface...
devshell version 0.0.3
Welcome to devshell. Type help or ? to list commands. Start a line with ! to execute a shell command in a sub-shell (does not retain environmental variables).
(devshell)$
```
If a package or module in your current working directory (the normal type affected by cd and reported with pwd), then those will show up when you type pls.
You can enter your "python location" into it via pcd and check your current python location with ppwd.
```bash
(devshell)$ pls
statopy module directory
(devshell)$ pcd statopy
(devshell)$ pls
ScalarProbModel class directory
ScalarRegression class directory
ScalarStats class directory
VectorStats class directory
(devshell)$ pcd ScalarStats
(devshell)$ pls
__add__ function non-directory
__init__ function non-directory
__setattr__ function non-directory
consume function non-directory
update function non-directory
(devshell)$ ppwd
/statopy.ScalarStats (class)
(devshell)$
```
That's nice, but what can you do besides inspecting what code blocks exist?
```bash
(devshell)$ help
Documented commands (type help <topic>):
========================================
EOF create doctestify h mv pwd read source
activate deactivate edit help pcd pytest restart venv
cd debug editvim interactive pip python rm
coverage doc exit ls pls q rmtree
cp doctest grep mkdir ppwd quit run
```
## What are doctests and why should I care?

@@ -10,0 +66,0 @@ Doctests are snippets of text that resemble a Python interactive mode session.

+1
-1

@@ -14,3 +14,3 @@ from io import open

REQUIRES = ['pypager','pytest','coverage']
REQUIRES = ['pypager','pytest','coverage','prompt-toolkit','pdoc3']

@@ -17,0 +17,0 @@ setup(

Metadata-Version: 2.1
Name: devshell
Version: 0.0.2
Version: 0.0.3
Summary: Shell-like tool to make it easier to develop python code

@@ -13,8 +13,64 @@ Home-page: https://github.com/mmiguel6288code/devshell

devshell is a tool to make python development easier.
[View API documentation](http://htmlpreview.github.io/?https://github.com/mmiguel6288code/devshell/blob/master/docs/devshell/index.html)
## Key Features
1. Enables entering an interactive session tied to the docstring of a particular python object. Inputs and responses are automatically recorded and inserted into the docstring as doctests.
2. Shell-like tool for navigating, inspecting, testing, and making doctests for python objects defined in a project
devshell provides the power user terminal python developer feeling.
There's normal basic shell navigation with cd, ls, pwd, and then there are python versions of those for navigating through a code tree: pcd, pls, ppwd.
What is a code tree? It is the following types of code blocks:
1. Package
2. Module
3. Class
4. Function/Method/Coroutine
```bash
$ cd ~/projects/statopy
$ ls
LICENSE __pycache__ statopy.py
$ python3 -m devshell
Starting devshell command line interface...
devshell version 0.0.3
Welcome to devshell. Type help or ? to list commands. Start a line with ! to execute a shell command in a sub-shell (does not retain environmental variables).
(devshell)$
```
If a package or module in your current working directory (the normal type affected by cd and reported with pwd), then those will show up when you type pls.
You can enter your "python location" into it via pcd and check your current python location with ppwd.
```bash
(devshell)$ pls
statopy module directory
(devshell)$ pcd statopy
(devshell)$ pls
ScalarProbModel class directory
ScalarRegression class directory
ScalarStats class directory
VectorStats class directory
(devshell)$ pcd ScalarStats
(devshell)$ pls
__add__ function non-directory
__init__ function non-directory
__setattr__ function non-directory
consume function non-directory
update function non-directory
(devshell)$ ppwd
/statopy.ScalarStats (class)
(devshell)$
```
That's nice, but what can you do besides inspecting what code blocks exist?
```bash
(devshell)$ help
Documented commands (type help <topic>):
========================================
EOF create doctestify h mv pwd read source
activate deactivate edit help pcd pytest restart venv
cd debug editvim interactive pip python rm
coverage doc exit ls pls q rmtree
cp doctest grep mkdir ppwd quit run
```
## What are doctests and why should I care?

@@ -21,0 +77,0 @@ Doctests are snippets of text that resemble a Python interactive mode session.

pypager
pytest
coverage
prompt-toolkit
pdoc3

@@ -0,6 +1,14 @@

LICENSE
Makefile
README.md
notes
setup.py
docs/devshell/index.html
docs/devshell/injector.html
docs/devshell/ptcmd.html
docs/devshell/shell.html
src/devshell/__init__.py
src/devshell/__main__.py
src/devshell/injector.py
src/devshell/ptcmd.py
src/devshell/shell.py

@@ -11,2 +19,3 @@ src/devshell.egg-info/PKG-INFO

src/devshell.egg-info/requires.txt
src/devshell.egg-info/top_level.txt
src/devshell.egg-info/top_level.txt
tests/__init__.py

@@ -1,3 +0,4 @@

__version__ = '0.0.2'
__version__ = '0.0.3'
from .injector import doctestify, set_end_interactive
from .shell import DevshellCmd

@@ -32,2 +32,3 @@ import inspect, ast, re, sys, code, readline, importlib, os, doctest, os.path

get_target.__annotations__ = {'target_fqn':'fully qualified name of target','return':('target object','top-level target module','fully qualified name of module')}
class _ModStdout(object):

@@ -83,3 +84,3 @@ def __init__(self,iobuf):

return ast_obj, filepath,source
return ast_obj, filepath,source, src_lines

@@ -96,3 +97,3 @@ class DoctestInjector(object):

self.module_fqn = module_fqn
ast_obj,self.filepath,self.original_source = get_ast_obj(target_fqn,obj,module,module_fqn)
ast_obj,self.filepath,self.original_source,self.src_lines = get_ast_obj(target_fqn,obj,module,module_fqn)

@@ -110,6 +111,6 @@ if isinstance(ast_obj.body[0],ast.Expr) and isinstance(ast_obj.body[0].value,ast.Str):

byte_index = ast_doc.col_offset
indentation = re.search('^\\s*',src_lines[line_index]).group(0) #use docstring end line to determine indentation
newline = re.search('[\r\n]+$',src_lines[line_index]).group(0) #use docstring end line to determine newline
top = src_lines[:line_index+1] #include entire docstring including end line
bottom = src_lines[line_index+1:] #everything else
indentation = re.search('^\\s*',self.src_lines[line_index]).group(0) #use docstring end line to determine indentation
newline = re.search('[\r\n]+$',self.src_lines[line_index]).group(0) #use docstring end line to determine newline
top = self.src_lines[:line_index+1] #include entire docstring including end line
bottom = self.src_lines[line_index+1:] #everything else
ending = '"""'+top[-1].split('"""')[-1] #get the trailing quotes and characters after doctsring

@@ -122,5 +123,5 @@ top[-1] = top[-1][len(indentation):-len(ending)]+newline #remove trailing from top

line_index = ast_obj.lineno-1 #line of function
indentation = re.search('^\\s*',src_lines[line_index]).group(0).strip('\r\n')+' ' #use indentation of function plus four spaces
newline = re.search('[\r\n]+$',src_lines[line_index]).group(0) #use newline of function line
top = src_lines[:line_index+1] #include funtion
indentation = re.search('^\\s*',self.src_lines[line_index]).group(0).strip('\r\n')+' ' #use indentation of function plus four spaces
newline = re.search('[\r\n]+$',self.src_lines[line_index]).group(0) #use newline of function line
top = self.src_lines[:line_index+1] #include funtion
bottom = src_lines[ast_obj.lineno:] #everything after function

@@ -139,7 +140,7 @@ ast_first = ast_obj.body[0] #first (and only) element in body

byte_index = ast_first.col_offset
indentation = re.search('^\\s*',src_lines[line_index]).group(0) #use first element line to determine indentation
newline = re.search('[\r\n]+$',src_lines[line_index]).group(0) #use first element line to determine newline
top = src_lines[:line_index] #everything up to but not including first element
indentation = re.search('^\\s*',self.src_lines[line_index]).group(0) #use first element line to determine indentation
newline = re.search('[\r\n]+$',self.src_lines[line_index]).group(0) #use first element line to determine newline
top = self.src_lines[:line_index] #everything up to but not including first element
top.append(indentation+'"""'+newline) #add new docstring starting quotes
bottom = src_lines[ast_obj.body[0].lineno-1:] #first element and everything after
bottom = self.src_lines[ast_obj.body[0].lineno-1:] #first element and everything after
bottom.insert(0,indentation+'"""'+newline) #add docstring ending quotes

@@ -167,3 +168,3 @@ self.top = top

return ''.join(self.top+ indented_middle + self.bottom)
def doctest_console(self):
def doctest_console(self,resume=False):
"""

@@ -175,12 +176,21 @@ This function runs doctests on the target file, loads the file, and enters a special interactive mode with inputs/outputs being recorded.

"""
print('Testing doctest execution of original file')
print('='*80+'\nDoctestify:\n Testing doctest execution of original file')
oldfailcount,oldtestcount = self.testmod()
print('...done: Fail count = %d, Total count = %d' % (oldfailcount,oldtestcount))
print('Entering interactive console')
banner='''Doctest insertion targeting object %s within %s
Press Ctrl+D to stop writing code and incorporate session into the docstring of the targeted object
To abort this session without writing anything into the targeted file, call the exit() function
>>> from %s import * # automatic import by devshell''' % (self.target_fqn,self.filepath,self.module_fqn)
print(' ...done: Fail count = %d, Total count = %d' % (oldfailcount,oldtestcount))
print('='*80+'''\nEntering interactive console:
Target: %s
File: %s
(*) Press Ctrl+D to exit and incorporate session into the targeted docstring
(*) To abort without incorporating anything, call the exit() function
''' % (self.target_fqn,self.filepath) + '-'*80)
console = code.InteractiveConsole()
print('>>> from %s import * # automatic import by devshell''' % (self.module_fqn))
console.push('from %s import *' % (self.module_fqn))
if resume:
doc = inspect.getdoc(self.obj)
for line in doc.splitlines():
line = line.lstrip()
if line.startswith('>>> ') or line.startswith('... '):
print(line)
console.push(line[4:])
iobuf = self.middle

@@ -200,3 +210,3 @@ _modstdout = _ModStdout(iobuf)

#sys.stderr = _modstderr
console.interact(banner=banner)
console.interact(banner='')
sys.stdout = sys.__stdout__

@@ -246,3 +256,3 @@ sys.stderr = sys.__stderr__

del os.environ['PYTHONINSPECT']
def doctestify(target_fqn):
def doctestify(target_fqn,resume=False):
"""

@@ -253,3 +263,3 @@ Start an interactive recording session for the item identified by the given fully qualified name.

di = DoctestInjector(target_fqn)
di.doctest_console()
di.doctest_console(resume)

@@ -0,4 +1,7 @@

#if you go inside a directory, the python part of things shoud stay up at the top package level
import os, pkgutil, os.path, readline, inspect, doctest, sys, re, importlib, pdb, traceback, code, subprocess, shutil,textwrap, argparse, shlex
from contextlib import contextmanager
from cmd import Cmd
#from cmd import Cmd
from pypager.pager import Pager

@@ -8,5 +11,14 @@ from pypager.source import StringSource

from .injector import doctestify, get_target, get_ast_obj
from .ptcmd import PTCmd
from . import __version__
from prompt_toolkit.shortcuts import PromptSession
from prompt_toolkit.styles import Style
from pygments.lexers.shell import BashLexer, BatchLexer, FishShellLexer, PowerShellLexer, TcshLexer
from pygments.lexers.python import PythonConsoleLexer, PythonLexer
from prompt_toolkit.lexers import PygmentsLexer
@contextmanager

@@ -56,41 +68,57 @@ def capture_stdout():

class DevshellCmd(Cmd,object):
class DevshellCmd(PTCmd):
"""
This implements the command line interface for devshell
"""
prompt = '(devshell)$ '
prompt = [('class:prompt','(devshell)$ ')]
intro = 'devshell version %s\nWelcome to devshell. Type help or ? to list commands. Start a line with ! to execute a shell command in a sub-shell (does not retain environmental variables).\n' % __version__
_cdable = set(['package','module','class','root'])
_callable = set(['function','method','coroutine','class'])
def __init__(self,completekey='tab',stdin=None,stdout=None):
self.cwd = os.getcwd()
self.orig_sys_path = sys.path
self.ppwd = []
self._pls_cache = None
self.style = Style.from_dict({
'prompt':'#ff0066',
})
super(DevshellCmd,self).__init__(completekey,stdin,stdout,lexer=PygmentsLexer(BashLexer),style=self.style)
def _ls(self,args=''):
def _pls(self,args=''):
args = args.strip()
if args != '':
orig_cwd = self.cwd
orig_pwd = list(self.pwd)
self._ls_cache = None
self.do_cd(args)
result = self._ls('')
orig_ppwd = list(self.ppwd)
self._pls_cache = None
self.do_pcd(args)
result = self._pls('')
self.cwd = orig_cwd
os.chdir(orig_cwd)
self.pwd = orig_pwd
self._ls_cache = None
self.ppwd = orig_ppwd
self._pls_cache = None
return result
else:
if self._ls_cache is not None:
return self._ls_cache
if len(self.pwd) == 0:
self._ls_cache = [((mi[1],'package') if mi[2] else (mi[1],'module')) for mi in sorted(pkgutil.iter_modules([self.cwd,os.path.join(self.cwd,'src')]),key=lambda mi: mi[1]) if (mi[1],mi[2]) != ('setup',False)]
return self._ls_cache
if self._pls_cache is not None:
return self._pls_cache
if len(self.ppwd) == 0:
self._pls_cache = [((mi[1],'package') if mi[2] else (mi[1],'module')) for mi in sorted(pkgutil.iter_modules([self.cwd,os.path.join(self.cwd,'src')]),key=lambda mi: mi[1]) if (mi[1],mi[2]) != ('setup',False)]
return self._pls_cache
else:
current_name,current_type = self.pwd[-1]
current_name,current_type = self.ppwd[-1]
if current_type == 'package':
_,bottom_folder = os.path.split(self.cwd)
mod_ppwd = list(self.ppwd)
for i,(package_name,_) in enumerate(self.ppwd):
if bottom_folder == package_name:
mod_ppwd = self.ppwd[i+1:]
break
self._ls_cache = [((mi[1],'package') if mi[2] else (mi[1],'module')) for mi in sorted(pkgutil.iter_modules([os.path.join(self.cwd,*[item[0] for item in self.pwd])]),key=lambda mi: mi[1])]
package_fqn = '.'.join(item[0] for item in self.pwd)
cwd = os.getcwd()
if not cwd in sys.path:
sys.path.insert(0,cwd)
self._pls_cache = [((mi[1],'package') if mi[2] else (mi[1],'module')) for mi in sorted(pkgutil.iter_modules([os.path.join(self.cwd,*[item[0] for item in mod_ppwd])]),key=lambda mi: mi[1])]
package_fqn = '.'.join(item[0] for item in self.ppwd)
try:

@@ -103,3 +131,3 @@ pkg = __import__(package_fqn)

for item in self.pwd[1:]:
for item in self.ppwd[1:]:
pkg = getattr(pkg,item[0])

@@ -110,12 +138,12 @@ for item_name,item in pkg.__dict__.items():

if inspect.isfunction(item):
self._ls_cache.append((item_name,'function'))
self._pls_cache.append((item_name,'function'))
elif hasattr(inspect,'iscoroutinefunction') and inspect.iscoroutinefunction(item):
self._ls_cache.append((item_name,'coroutine'))
self._pls_cache.append((item_name,'coroutine'))
elif inspect.isclass(item):
self._ls_cache.append((item_name,'class'))
self._ls_cache.sort()
return self._ls_cache
self._pls_cache.append((item_name,'class'))
self._pls_cache.sort()
return self._pls_cache
elif current_type == 'module':
module_fqn = '.'.join(item[0] for item in self.pwd)
module_fqn = '.'.join(item[0] for item in self.ppwd)
try:

@@ -127,5 +155,5 @@ mod = __import__(module_fqn)

return
for item in self.pwd[1:]:
for item in self.ppwd[1:]:
mod = getattr(mod,item[0])
self._ls_cache = []
self._pls_cache = []
for item_name,item in mod.__dict__.items():

@@ -135,11 +163,11 @@ if inspect.getmodule(item) != mod:

if inspect.isfunction(item):
self._ls_cache.append((item_name,'function'))
self._pls_cache.append((item_name,'function'))
elif hasattr(inspect,'iscoroutinefunction') and inspect.iscoroutinefunction(item):
self._ls_cache.append((item_name,'coroutine'))
self._pls_cache.append((item_name,'coroutine'))
elif inspect.isclass(item):
self._ls_cache.append((item_name,'class'))
self._ls_cache.sort()
return self._ls_cache
self._pls_cache.append((item_name,'class'))
self._pls_cache.sort()
return self._pls_cache
elif current_type == 'class':
klass_fqn = '.'.join(item[0] for item in self.pwd)
klass_fqn = '.'.join(item[0] for item in self.ppwd)
try:

@@ -151,23 +179,18 @@ klass,_,_ = get_target(klass_fqn)

self._ls_cache = []
self._pls_cache = []
for item_name,item in klass.__dict__.items():
if inspect.ismethod(item):
self._ls_cache.append((item_name,'method'))
self._pls_cache.append((item_name,'method'))
elif inspect.isfunction(item):
self._ls_cache.append((item_name,'function'))
self._pls_cache.append((item_name,'function'))
elif hasattr(inspect,'iscoroutinefunction') and inspect.iscoroutinefunction(item):
self._ls_cache.append((item_name,'coroutine'))
self._pls_cache.append((item_name,'coroutine'))
elif inspect.isclass(item):
self._ls_cache.append((item_name,'class'))
self._ls_cache.sort()
return self._ls_cache
self._pls_cache.append((item_name,'class'))
self._pls_cache.sort()
return self._pls_cache
else:
print('Error - cannot perform ls when targeting a %s - try to run "cd .." first' % current_type)
print('Error - cannot perform pls when targeting a %s - try to run "pcd .." first' % current_type)
return []
def __init__(self,*args,**kwargs):
self.cwd = os.getcwd()
self.pwd = []
self._ls_cache = None
super(DevshellCmd,self).__init__(*args,**kwargs)
def do_h(self,args):

@@ -195,5 +218,52 @@ """ Alias for help """

"""
sys.exit(subprocess.run([sys.executable,'-m','devshell','-d',os.path.abspath(os.getcwd()),'-t','.'.join([item[0] for item in self.pwd])]).returncode)
sys.exit(subprocess.run([sys.executable,'-m','devshell','-d',os.path.abspath(os.getcwd()),'-t','.'.join([item[0] for item in self.ppwd])]).returncode)
def do_venv(self,args):
"""
Help: (devshell)$ venv [env]
Creates a virtual environment at the current location with the given name
If no name is given, the name will be "env"
"""
if args.strip() == '':
args = 'env'
subprocess.run([sys.executable,'-m','venv',args])
def do_activate():
"""
Help: (devshell)$ activate [env]
Activates the virtual environment at the current location with the given name
If no name is given, the name will be "env"
"""
cwd = os.path.abspath(os.getcwd())
ppwd = '.'.join([item[0] for item in self.ppwd])
if sys.platform in ['win32','cygwin']:
subprocess.run(['cmd.exe','/C',r'"{env}\Scripts\activate.bat & {executable} -m devshell -d {cwd} -t {ppwd}"'.format(env=env,executable=os.path.basename(sys.executable),cwd=cwd,ppwd=ppwd)])
else:
default_shell = os.environ['SHELL']
shell_name = os.path.basename(default_shell)
subprocess.run([default_shell,'-c','"source {env}/bin/activate; {executable} -m devshell -d {cwd} -t {ppwd}"'.format(env=env,executable=os.path.basename(sys.executable),cwd=cwd,ppwd=ppwd)])
def do_deactivate():
"""
Help: (devshell)$ activate [env]
Deactivates the current virtual environment
Note: This will result in changing the current working directory and python target to what they were at the time the virtual environment was activated
"""
if 'VIRTUAL_ENV' in os.environ:
print('Exiting %s' % os.environ['VIRTUAL_ENV'])
sys.exit(0)
else:
print('Not currently in a virtual environment')
def do_create(self,args):
"""
Help: (devshell)$ create project_name
Creates a new directory with the provided project name
Creates a src subfolder with an empty python package with the project name
Creates an empty tests python package
Creates a setup.py
Creates a LICENSE file (MIT)
Creates a Makefile
Creates a docs subfolder
Creates a venv env and activates it
"""
def do_read(self,args):

@@ -280,11 +350,11 @@ """

if len(editor) == 0:
print('Specify an editor (e.g. vim, nano, notepad++.exe, etc)')
print('Specify an editor (e.g. edit vim, edit nano, edit notepad++.exe, etc)')
return
else:
editor=[editor]
target_fqn = '.'.join(item[0] for item in self.pwd)
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':
current_name,current_type = self.pwd[-1]
current_name,current_type = self.ppwd[-1]
if current_type == 'package':
filepath = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.')) + '/__init__.py')
filepath = self._get_path() + '__init__.py'
if os.path.getsize(filepath) == 0:

@@ -295,3 +365,3 @@ print('File is empty')

elif current_type == 'module':
filepath = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.'))+'.py')
filepath = self._get_path() + '.py'
if os.path.getsize(filepath) == 0:

@@ -312,2 +382,17 @@ print('File is empty')

print('No target identified')
def _get_path(self):
ppwd = list(self.ppwd)
while len(ppwd) > 0 and ppwd[-1][1] not in ['package','module']:
ppwd.pop(-1)
current_name,current_type = ppwd[-1]
_,bottom_folder = os.path.split(self.cwd)
mod_names = list([name for name,_ in self.ppwd])
for i,(package_name,_) in enumerate(self.ppwd):
if bottom_folder == package_name:
mod_names = [name for name,_ in self.ppwd[i+1:]]
break
return os.path.abspath(os.path.join(self.cwd,*mod_names))
def do_editvim(self,args):

@@ -323,7 +408,7 @@ """

editor=['vim']
target_fqn = '.'.join(item[0] for item in self.pwd)
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':
current_name,current_type = self.pwd[-1]
current_name,current_type = self.ppwd[-1]
if current_type == 'package':
filepath = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.')) + '/__init__.py')
filepath = self._get_path() + '__init__.py'
if os.path.getsize(filepath) == 0:

@@ -335,3 +420,11 @@ print('File is empty')

elif current_type == 'module':
filepath = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.'))+'.py')
_,bottom_folder = os.path.split(self.cwd)
mod_ppwd = list(self.ppwd)
for i,(package_name,_) in enumerate(self.ppwd):
if bottom_folder == package_name:
mod_ppwd = self.ppwd[i+1:]
break
filepath = self._get_path() + '.py'
if os.path.getsize(filepath) == 0:

@@ -345,3 +438,3 @@ print('File is empty')

obj,mod,mod_fqn = get_target(target_fqn)
ast_obj,filepath,source = get_ast_obj(target_fqn,obj,mod,mod_fqn)
ast_obj,filepath,source,src_lines = get_ast_obj(target_fqn,obj,mod,mod_fqn)
lineno = ast_obj.lineno

@@ -365,3 +458,3 @@ #except:

"""
target_fqn = '.'.join(item[0] for item in self.pwd)
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':

@@ -374,4 +467,4 @@ try:

args = args.strip()
obj_type = self.pwd[-1][1]
if len(self.pwd) == 0:
obj_type = self.ppwd[-1][1]
if len(self.ppwd) == 0:
print('No target is selected')

@@ -401,5 +494,5 @@ return

def do_listdir(self,args):
def do_ls(self,args):
"""
Help: (devshell)$ listdir path
Help: (devshell)$ ls path
This lists the files/subfolders within the provided operating system folder path

@@ -428,12 +521,16 @@ If path is not provided, then files/subfolders within the operating system folder path (current working directory) will be listed

def do_chdir(self,args):
def do_cd(self,args):
"""
Help: (devshell)$ chdir path
Help: (devshell)$ cd path
This changes the operating system folder path (current working directory) where devshell will look for packages and modules
"""
if os.path.exists(args) and os.path.isdir(args):
if os.path.exists(os.path.join(args,'__init__.py')):
self.do_pcd(args)
else:
self.ppwd = []
self._pls_cache = None
os.chdir(args)
self.cwd = os.getcwd()
self.pwd = []
self._ls_cache = None
sys.path = self.orig_sys_path + [self.cwd]
else:

@@ -445,9 +542,13 @@ print('Error - path does not exist: %s' % args)

Help: (devshell)$ doctestify
(devshell)$ doctestify resume
Performs doctestify on the currently targeted item.
This will cause an interactive python recording session to begin with all items from the targeted item's module imported in automatically.
All inputs and outputs will be recorded and entered into the targeted item's docstring as a doctest.
If "doctestify resume" is called, then the current doctest commands will be automatically executed into the interpreter
"""
target_fqn = '.'.join(item[0] for item in self.pwd)
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':
doctestify(target_fqn)
resume = args.strip() == 'resume'
doctestify(target_fqn,resume)
else:

@@ -487,7 +588,7 @@ print('No target identified')

"""
if len(self.pwd) == 0:
if len(self.ppwd) == 0:
print('No target identified')
return
current_type = self.pwd[-1][1]
target_fqn = '.'.join(item[0] for item in self.pwd)
current_type = self.ppwd[-1][1]
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':

@@ -543,3 +644,3 @@ try:

If there is no currently targeted item, pytest will be run against the folder indicated by getcwd:
If there is no currently targeted item, pytest will be run against the folder indicated by pwd:
(devshell)$ pytest -ra --doctest-modules

@@ -563,6 +664,6 @@ is equivalent to:

arglist = [arg.strip() for arg in args.split() if arg.strip() != '']
if len(self.pwd) == 0:
if len(self.ppwd) == 0:
run_cmd([sys.executable,'-m','pytest',os.path.abspath(self.cwd),'--doctest-modules']+arglist)
return
current_type = self.pwd[-1][1]
current_type = self.ppwd[-1][1]

@@ -572,3 +673,3 @@ item_names = []

item_names_inside_module = []
for item_name,item_type in self.pwd:
for item_name,item_type in self.ppwd:
item_names.append(item_name)

@@ -609,3 +710,3 @@ if reached_module:

If there is no currently targeted item, coverage and pytest will be run against the folder indicated by getcwd:
If there is no currently targeted item, coverage and pytest will be run against the folder indicated by pwd:
(devshell)$ coverage -ra

@@ -634,10 +735,10 @@ is functionally equivalent to:

arglist = [arg.strip() for arg in args.split() if arg.strip() != '']
if len(self.pwd) == 0:
if len(self.ppwd) == 0:
run_coverage(self.cwd,arglist)
return
current_type = self.pwd[-1][1]
current_type = self.ppwd[-1][1]
item_names = []
reached_module = False
item_names_inside_module = []
for item_name,item_type in self.pwd:
for item_name,item_type in self.ppwd:
item_names.append(item_name)

@@ -668,7 +769,7 @@ if reached_module:

"""
target_fqn = '.'.join(item[0] for item in self.pwd)
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':
current_name,current_type = self.pwd[-1]
current_name,current_type = self.ppwd[-1]
if current_type == 'package':
filepath = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.')) + '/__init__.py')
filepath = self._get_path() + '/__init__.py'
if os.path.getsize(filepath) == 0:

@@ -680,3 +781,3 @@ print('File is empty')

elif current_type == 'module':
filepath = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.'))+'.py')
filepath = self._get_path() + '.py'
if os.path.getsize(filepath) == 0:

@@ -726,12 +827,12 @@ print('File is empty')

"""
target_fqn = '.'.join(item[0] for item in self.pwd)
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':
current_name,current_type = self.pwd[-1]
current_name,current_type = self.ppwd[-1]
if current_type == 'package':
#recurse through package directory
path = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.')))
path = self._get_path()
grep(args,path=path)
elif current_type == 'module':
filepath = os.path.abspath(os.path.join(self.cwd,*target_fqn.split('.'))+'.py')
filepath = self._get_path() + '.py'
grep(args,path=filepath)

@@ -759,3 +860,3 @@ else:

"""
target_fqn = '.'.join(item[0] for item in self.pwd)
target_fqn = '.'.join(item[0] for item in self.ppwd)
if target_fqn != '':

@@ -769,6 +870,6 @@ try:

lines = []
if self.pwd[-1][1] in self._callable:
lines.append(self.pwd[-1][1]+': '+self.pwd[-1][0] + str(inspect.signature(obj)))
if self.ppwd[-1][1] in self._callable:
lines.append(self.ppwd[-1][1]+': '+self.ppwd[-1][0] + str(inspect.signature(obj)))
else:
lines.append(self.pwd[-1][1]+': '+self.pwd[-1][0])
lines.append(self.ppwd[-1][1]+': '+self.ppwd[-1][0])
if doc is not None:

@@ -790,11 +891,11 @@ lines.append('"""')

def do_getcwd(self,args):
def do_pwd(self,args):
"""
Help: (devshell)$ getcwd
Help: (devshell)$ pwd
This displays the operating system folder path (current working directory) where devshell will look for packages and modules
"""
print(self.cwd)
def do_ls(self,args):
def do_pls(self,args):
"""
Help: (devshell)$ ls [python_object]
Help: (devshell)$ pls [python_object]
This will show all items contained within the currently targeted item.

@@ -810,7 +911,5 @@ e.g. for a package, this would list the modules

This is NOT the same as the usual interpretation of ls in other shells.
For the usual interpretation, see listdir.
"""
lines = []
result = self._ls(args)
result = self._pls(args)
if result is None:

@@ -820,3 +919,3 @@ return

if item_type in self._cdable:
if len(self.pwd) == 0 and item_type == 'package' and not os.path.exists(os.path.join(self.cwd,item_name,'__init__.py')) and os.path.exists(os.path.join(self.cwd,'src',item_name,'__init__.py')):
if len(self.ppwd) == 0 and item_type == 'package' and not os.path.exists(os.path.join(self.cwd,item_name,'__init__.py')) and os.path.exists(os.path.join(self.cwd,'src',item_name,'__init__.py')):
lines.append(' %s%sdirectory (./src)' % (item_name.ljust(30), item_type.ljust(30)))

@@ -829,64 +928,62 @@ else:

print('\n'.join(lines))
def _pwd(self):
if len(self.pwd) > 0:
return ('/'+'.'.join(item[0] for item in self.pwd),self.pwd[-1][1])
def _ppwd(self):
if len(self.ppwd) > 0:
return ('/'+'.'.join(item[0] for item in self.ppwd),self.ppwd[-1][1])
else:
return '/','root'
def do_pwd(self,args):
def do_ppwd(self,args):
"""
Help: (devshell)$ pwd
Help: (devshell)$ ppwd
This shows the fully qualified name of the currently targeted item.
This is NOT the same as the usual interpretation of pwd in other shells.
For the usual interpretation, see getcwd.
"""
pwd,current_type = self._pwd()
print('%s (%s)' % (pwd.ljust(30),current_type))
ppwd,current_type = self._ppwd()
print('%s (%s)' % (ppwd.ljust(30),current_type))
def _cd(self,args):
def _pcd(self,args):
resolved = False
clear_ls_cache = False
clear_pls_cache = False
if args == '.':
resolved = True
clear_ls_cache = False
clear_pls_cache = False
elif args == '..':
if len(self.pwd) > 0:
last_item,last_item_type = self.pwd.pop()
if len(self.pwd) == 0 and last_item_type == 'package' and os.path.basename(self.cwd) == 'src' and os.path.exists(os.path.join(self.cwd,last_item,'__init__.py')) and not os.path.exists(os.path.join(self.cwd,'..',last_item,'__init__.py')):
self.do_chdir('..')
if len(self.ppwd) > 0:
last_item,last_item_type = self.ppwd.pop()
if len(self.ppwd) == 0 and last_item_type == 'package' and os.path.basename(self.cwd) == 'src' and os.path.exists(os.path.join(self.cwd,last_item,'__init__.py')) and not os.path.exists(os.path.join(self.cwd,'..',last_item,'__init__.py')):
self.do_cd('..')
resolved = True
clear_ls_cache = True
clear_pls_cache = True
#go up if in src
elif args == '/':
del self.pwd[:]
del self.ppwd[:]
resolved = True
clear_ls_cache = True
clear_pls_cache = True
elif '.' not in args:
for item,item_type in self._ls():
for item,item_type in self._pls():
if item == args:
if len(self.pwd) == 0 and item_type == 'package' and not os.path.exists(os.path.join(self.cwd,item,'__init__.py')) and os.path.exists(os.path.join(self.cwd,'src',item,'__init__.py')):
self.do_chdir('src')
self.pwd.append((item,item_type))
if len(self.ppwd) == 0 and item_type == 'package' and not os.path.exists(os.path.join(self.cwd,item,'__init__.py')) and os.path.exists(os.path.join(self.cwd,'src',item,'__init__.py')):
self.do_cd('src')
self.ppwd.append((item,item_type))
resolved = True
clear_ls_cache = True
clear_pls_cache = True
break
else:
pieces = args.split('.')
orig_pwd = list(self.pwd)
orig_ppwd = list(self.ppwd)
resolved = True
clear_ls_cache = False
orig_ls_cache = self._ls_cache
clear_pls_cache = False
orig_pls_cache = self._pls_cache
for piece in pieces:
self._ls_cache = None
piece_resolved,piece_clear_ls_cache = self._cd(piece)
self._pls_cache = None
piece_resolved,piece_clear_pls_cache = self._pcd(piece)
if not piece_resolved:
resolved = False
self.pwd = orig_pwd
clear_ls_cache = False
self.ppwd = orig_ppwd
clear_pls_cache = False
break
else:
clear_ls_cache = clear_ls_cache or piece_clear_ls_cache
self._ls_cache = orig_ls_cache
return (resolved,clear_ls_cache)
clear_pls_cache = clear_pls_cache or piece_clear_pls_cache
self._pls_cache = orig_pls_cache
return (resolved,clear_pls_cache)
def do_interactive(self,args):

@@ -906,5 +1003,5 @@ """

def do_cd(self,args):
def do_pcd(self,args):
"""
Help: (devshell)$ cd <argument>
Help: (devshell)$ pcd <argument>
This changes the currently targeted item.

@@ -914,3 +1011,3 @@

If there is no current target, then one may cd into a package within the current working directory or within a package in a subfolder of the current working directory named "src".
If there is no current target, then one may pcd into a package within the current working directory or within a package in a subfolder of the current working directory named "src".
Cding into the "src" subfolder only occurs when the src subfolder has the package with the given name and the current working directory does not.

@@ -923,31 +1020,29 @@ Cding into the "src" subfolder will change the current working directory to be the "src" subfolder.

(devshell)$ cd /
(devshell)$ pcd /
This will remove all parts of the current fully qualified name
(devshell)$ cd .
(devshell)$ pcd .
This has no effect
(devshell)$ cd ..
(devshell)$ pcd ..
This removes the last piece of the currently fully qualified name (navigates up to the parent item)
If leaving a package to a subfolder named "src", will also change the current working directory to be the parent directory of "src" if a package with the current target as its name exists only in the "src" directory and not in the parent directory.
This is NOT the same as the usual interpretation of cd in other shells.
For the usual interpretation, see chdir.
"""
resolved,clear_ls_cache = self._cd(args)
resolved,clear_pls_cache = self._pcd(args)
if not resolved is True:
print('Error - "%s" does not exist' % args)
#else:
# self.prompt = '(devshell)%s$ ' % self._pwd()
if clear_ls_cache:
self._ls_cache = None
def complete_cd(self,text,line,begin_idx,end_idx):
# self.prompt = '(devshell)%s$ ' % self._ppwd()
if clear_pls_cache:
self._pls_cache = None
def complete_pcd(self,text,line,begin_idx,end_idx):
return self._complete_python(text,line,begin_idx,end_idx)
def complete_ls(self,text,line,begin_idx,end_idx):
def complete_pls(self,text,line,begin_idx,end_idx):
return self._complete_python(text,line,begin_idx,end_idx)
def _complete_python(self,text,line,begin_idx,end_idx):
orig_cwd = self.cwd
last_piece = shlex.split(text)[-1]
if '.' not in text:
results = [item[0] for item in self._ls() if item[0].startswith(text)]
results = [item[0] for item in self._pls() if item[0].startswith(last_piece)]
if self.cwd != orig_cwd:

@@ -958,17 +1053,21 @@ self.cwd = orig_cwd

elif set(last_piece) == set(['.']):
return []
else:
orig_pwd = list(self.pwd)
orig_ls_cache = self._ls_cache
orig_ppwd = list(self.ppwd)
orig_pls_cache = self._pls_cache
ts = text.split('.')
front = '.'.join(ts[:-1])
#front = '.'.join(ts[:-1])
front = shlex.join(shlex.split('.'.join(ts[:-1]))[1:])
last_piece = ts[-1]
resolved,clear_ls_cache = self._cd(front)
resolved,clear_pls_cache = self._pcd(front)
#resolved,clear_pls_cache = self._pcd(shlex.join(shlex.split(front)[1:]))
if resolved:
self._ls_cache = None
results = [front+'.'+item[0] for item in self._ls() if item[0].startswith(last_piece)]
self._pls_cache = None
results = [front+'.'+item[0] for item in self._pls() if item[0].startswith(last_piece)]
else:
results = []
self.pwd = orig_pwd
self._ls_cache = orig_ls_cache
self.ppwd = orig_ppwd
self._pls_cache = orig_pls_cache
if self.cwd != orig_cwd:

@@ -978,7 +1077,7 @@ self.cwd = orig_cwd

return results
def complete_chdir(self,text,line,begin_idx,end_idx):
return self._complete_dirs('chdir',text,line,begin_idx,end_idx)
def complete_cd(self,text,line,begin_idx,end_idx):
return self._complete_dirs('cd',text,line,begin_idx,end_idx)
def complete_listdir(self,text,line,begin_idx,end_idx):
return self._complete_dirs('listdir',text,line,begin_idx,end_idx)
def complete_ls(self,text,line,begin_idx,end_idx):
return self._complete_dirs('ls',text,line,begin_idx,end_idx)

@@ -1014,3 +1113,3 @@ def complete_rmtree(self,text,line,begin_idx,end_idx):

last_piece = path
return [item for item in os.listdir(front) if item.startswith(last_piece) and os.path.isdir(os.path.join(front,item))]
return [(os.path.join(front,item) if front != '.' else item) for item in os.listdir(front) if item.startswith(last_piece) and os.path.isdir(os.path.join(front,item))]

@@ -1029,3 +1128,3 @@ def _complete_files(self,cmd,text,line,begin_idx,end_idx):

last_piece = path
return [item for item in os.listdir(front) if item.startswith(last_piece) and not os.path.isdir(os.path.join(front,item))]
return [(os.path.join(front,item) if front != '.' else item) for item in os.listdir(front) if item.startswith(last_piece) and not os.path.isdir(os.path.join(front,item))]
def _complete_lastdirfile(self,cmd,text,line,begin_idx,end_idx):

@@ -1032,0 +1131,0 @@ if cmd is not None: