diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..29bfc57 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,14 @@ +name: Deploy to GitHub Pages + +permissions: + contents: write + pages: write + +on: + push: + branches: [ "main", "master" ] + workflow_dispatch: +jobs: + deploy: + runs-on: ubuntu-latest + steps: [uses: fastai/workflows/quarto-ghp@master] diff --git a/.github/workflows/test.yaml.off b/.github/workflows/test.yaml.off new file mode 100644 index 0000000..5608592 --- /dev/null +++ b/.github/workflows/test.yaml.off @@ -0,0 +1,7 @@ +name: CI +on: [workflow_dispatch, pull_request, push] + +jobs: + test: + runs-on: ubuntu-latest + steps: [uses: fastai/workflows/nbdev-ci@master] diff --git a/00_core.ipynb b/00_core.ipynb index 567bc5e..f941b00 100644 --- a/00_core.ipynb +++ b/00_core.ipynb @@ -15,7 +15,7 @@ "id": "a1f9e825", "metadata": {}, "source": [ - "# bashnb IPython magic\n", + "# pshnb IPython magic\n", "> Provides `psh` persistent bash magics in Jupyter and IPython" ] }, @@ -27,12 +27,16 @@ "outputs": [], "source": [ "#| export\n", + "from fastcore.utils import *\n", "import pexpect, re, os\n", + "from pexpect import TIMEOUT\n", "from pathlib import Path\n", - "from IPython.core.magic import register_cell_magic\n", + "from getpass import getpass\n", + "from IPython.core.magic import register_cell_magic, no_var_expand\n", "from IPython.display import display, Javascript\n", "from IPython.paths import get_ipython_dir\n", - "from IPython.core.interactiveshell import InteractiveShell" + "from IPython.core.interactiveshell import InteractiveShell\n", + "from IPython.core.magic_arguments import magic_arguments, argument" ] }, { @@ -48,7 +52,19 @@ { "cell_type": "code", "execution_count": null, - "id": "2ed1a8cd", + "id": "a0054574", + "metadata": {}, + "outputs": [], + "source": [ + "env = dict(os.environ, TERM='dumb', PS1='$', PS2='$')\n", + "eshell = os.environ['SHELL']\n", + "sh = pexpect.spawn(eshell, encoding='utf-8', env=env)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "089660c3", "metadata": {}, "outputs": [ { @@ -64,16 +80,40 @@ ], "source": [ "env = dict(os.environ, TERM='dumb', PS1='', PS2='')\n", - "sh = pexpect.spawn(os.environ['SHELL'], encoding='utf-8', env=env)\n", - "\n", + "eshell = os.environ['SHELL']\n", + "sh = pexpect.spawn(eshell, encoding='utf-8', env=env)\n", + "sh.sendline('stty -echo')\n", + "sh.readline()\n", "echo = os.urandom(8).hex()\n", "echo_re = re.compile(fr'^{echo}\\s*$', flags=re.MULTILINE)\n", - "prompt = f'BASHNB_PROMPT_{echo}>'\n", - "sh.sendline(f'export PS1=\"{prompt}\"')\n", + "sh.sendline(f'export PS1=\"\"')\n", "sh.sendline('set +o vi +o emacs')\n", "sh.sendline('echo '+echo)\n", + "sh.expect(echo_re, timeout=2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d2b3914", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "00_core.ipynb\r\n", + "LICENSE\r\n", + "MANIFEST.in\r\n", + "\n" + ] + } + ], + "source": [ + "sh.sendline('ls | head -3')\n", + "sh.sendline('echo '+echo)\n", "sh.expect(echo_re, timeout=2)\n", - "sh.expect_exact(prompt, timeout=2)" + "print(sh.before)" ] }, { @@ -83,41 +123,38 @@ "metadata": {}, "outputs": [], "source": [ + "#| export\n", "class ShellInterpreter:\n", - " def __init__(self, debug=False, timeout=30, shell_path=None):\n", + " def __init__(self, debug=False, timeout=2, shell_path=None, sudo=False, dumb=False):\n", " self.debug,self.timeout = debug,timeout\n", " if shell_path is None: shell_path = os.environ.get('SHELL', '/bin/bash')\n", - " env = dict(os.environ, TERM='xterm')\n", - " env = dict(os.environ, TERM='dumb')\n", + " if sudo: shell_path = 'sudo -i ' + shell_path\n", + " env = dict(os.environ, TERM='dumb' if dumb else 'xterm')\n", " self.sh = pexpect.spawn(shell_path, encoding='utf-8', env=env)\n", + " self.sh.sendline('stty -echo')\n", + " self.sh.readline()\n", " self.echo = os.urandom(8).hex()\n", " self.echo_re = re.compile(fr'^{self.echo}\\s*$', flags=re.MULTILINE)\n", - " self.prompt = f'BASHNB_PROMPT_{self.echo}>'\n", - " self.sh.sendline(f'export PS1=\"{self.prompt}\"')\n", + " self.sh.sendline(f'export PS1=\"\"')\n", + " self.sh.sendline(f'export PS2=\"\"')\n", " self.sh.sendline('set +o vi +o emacs')\n", " self.wait_echo()\n", - " self.wait_prompt()\n", - " self.debug = debug\n", "\n", - " def wait_prompt(self):\n", - " self.sh.expect_exact(self.prompt, timeout=self.timeout)\n", - " if self.debug: print('$', self.sh.before)\n", + " def wait_echo(self, timeout=None):\n", + " self.sh.sendline('echo')\n", + " self.sh.sendline('echo '+self.echo)\n", + " self.sh.expect(self.echo_re, timeout=timeout)\n", + " return self.sh.before.rstrip()\n", "\n", - " def wait_echo(self):\n", - " echo = 'echo '+self.echo\n", - " self.sh.sendline(echo)\n", - " self.sh.expect(self.echo_re, timeout=self.timeout)\n", - " return self.sh.before.replace(echo, '').replace(self.prompt, '\\n').rstrip()\n", - "\n", - " def _ex(self, s):\n", + " def _ex(self, s, timeout=None):\n", + " if timeout is None: timeout=self.timeout\n", " if self.debug: print('#', s)\n", " self.sh.sendline(s)\n", - " res = self.wait_echo()\n", - " self.wait_prompt()\n", + " res = self.wait_echo(timeout=timeout)\n", " return res\n", " \n", - " def __call__(self, cmd):\n", - " output = self._ex(cmd.rstrip())\n", + " def __call__(self, cmd, timeout=None):\n", + " output = self._ex(cmd.rstrip(), timeout=timeout)\n", " return output.replace(cmd + '\\r\\n', '', 1).rstrip()" ] }, @@ -126,95 +163,158 @@ "execution_count": null, "id": "ecf32f3e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "00_core.ipynb\r\n", + "LICENSE\r\n", + "MANIFEST.in\n" + ] + } + ], "source": [ - "sh = ShellInterpreter(False)" + "sh = ShellInterpreter()\n", + "print(sh('ls | head -3'))" ] }, { "cell_type": "code", "execution_count": null, - "id": "84ba6dab", + "id": "d945c992", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "00_core.ipynb\tMANIFEST.in\t_quarto.yml\tindex.ipynb\tsetup.py\r\n", - "LICENSE\t\tREADME.md\tbashnb\t\tsettings.ini\tstyles.css\n" + "/var/root\n", + "/var\n", + "root\n" ] } ], "source": [ - "print(sh('ls'))" + "sh = ShellInterpreter(sudo=True)\n", + "\n", + "sh('cd')\n", + "print(sh('pwd'))\n", + "sh('cd ..')\n", + "print(sh('pwd'))\n", + "print(sh('whoami'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e6099f1", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def shell_replace(s, shell=None):\n", + " \"Replace `@{var}` refs in `s` with their variable values, if they exist\"\n", + " if not shell: shell = get_ipython()\n", + " def f(m): return str(shell.user_ns.get(m[1], m[0]))\n", + " return re.sub(r'\\@{(\\w+?)}', f, s)" ] }, { "cell_type": "code", "execution_count": null, - "id": "e0ac18e4", + "id": "f89875fb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "cd ..\n", - "ContextKit\n", - "FastHTML-Gallery\n", - "aimagic\n", - "answerdotai\n", - "aplnb\n", - "apswutils\n", - "audio_enhancement\n", - "bashnb\n", - "billing\n", - "blog-fastai \n", - "\n", - "/Users/jhoward/Documents/GitHub\n" + "asdf\n", + "$1 @{aa}\n", + "fdsa\n" ] } ], "source": [ - "shell = ShellInterpreter(debug=False)\n", - "print(shell('cd ..'))\n", - "print(shell('ls | head'), '\\n')\n", - "print(shell('pwd'))" + "b = 1\n", + "\n", + "a = '''asdf\n", + "$@{b} @{aa}\n", + "fdsa'''\n", + "\n", + "print(shell_replace(a))" ] }, { "cell_type": "code", "execution_count": null, - "id": "04f56c87", + "id": "1cea4122", "metadata": {}, "outputs": [], "source": [ "#| export\n", - "class BashMagic:\n", - " def __init__(self):\n", - " self.o = ShellInterpreter()\n", + "class PshMagic:\n", + " def __init__(self, shell, sudo=False, timeout=2, expand=True, o=None): store_attr()\n", + " def reset(self): self.o = ShellInterpreter(sudo=self.sudo, timeout=self.timeout)\n", + " def help (self): self.psh.parser.print_help()\n", + "\n", + " def _xpand(self, expand=False): self.expand = expand\n", + " def _sudo(self, sudo=False):\n", + " self.sudo = sudo\n", + " self.o = None\n", + " def _timeout(self, timeout=2):\n", + " self.timeout = timeout\n", + " self.o = None\n", "\n", - " def bash(self, line, cell=None):\n", - " if line and not cell: cell=line\n", + " @magic_arguments()\n", + " @argument('-h', '--help', action='store_true', help='Show this help')\n", + " @argument('-r', '--reset', action='store_true', help='Reset the shell interpreter')\n", + " @argument('-o', '--obj', action='store_true', help='Return this magic object')\n", + " @argument('-x', '--expand', action='store_true', help='Enable variable expansion')\n", + " @argument('-X', '--no-expand', action='store_true', help='Disable variable expansion')\n", + " @argument('-s', '--sudo', action='store_true', help='Enable sudo')\n", + " @argument('-S', '--no-sudo', action='store_true', help='Disable sudo')\n", + " @argument('-t', '--timeout', type=int, help='Set timeout in seconds')\n", + " @argument('command', nargs='*', help='The command to run')\n", + " @no_var_expand\n", + " def psh(self, line, cell=None):\n", + " \"Run line or cell in persistent shell\"\n", + " if cell: cell = shell_replace(cell, self.shell)\n", + " if line: line = shell_replace(line, self.shell)\n", + " args = self.psh.parser.parse_args(line.split())\n", + " if args.expand: return self._xpand(True)\n", + " if args.no_expand: return self._xpand(False)\n", + " if args.sudo: return self._sudo (True)\n", + " if args.no_sudo: return self._sudo (False)\n", + " if args.timeout: return self._timeout(args.timeout)\n", + " if args.reset: return self.reset()\n", + " if args.help: return self.help()\n", + " if args.obj: return self\n", + " if args.command: cell = ' '.join(args.command)\n", + " if not cell and line: cell=line\n", " disp = True\n", " if cell.endswith(';'): disp,cell = False,cell[:-1]\n", - " res = self.o(cell) or None\n", - " if disp: print(res)" + " if not self.o: self.reset()\n", + " try: res = self.o(cell) or None\n", + " except Exception as e:\n", + " self.o = None\n", + " raise e from None\n", + " if disp and res: print(res)" ] }, { "cell_type": "code", "execution_count": null, - "id": "953c6348", + "id": "955c955b", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def create_magic(shell=None):\n", " if not shell: shell = get_ipython()\n", - " bash_magic = BashMagic()\n", - " shell.register_magic_function(bash_magic.bash, 'line_cell', 'psh')" + " magic = PshMagic(shell)\n", + " shell.register_magic_function(magic.psh, magic_name='psh', magic_kind='line_cell')" ] }, { @@ -231,27 +331,42 @@ { "cell_type": "code", "execution_count": null, - "id": "3e0cb6bc", + "id": "b8e6cfa3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/jhoward/Documents/GitHub/pshnb\n" + ] + } + ], + "source": [ + "%psh pwd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c0f8e60", "metadata": {}, "outputs": [], "source": [ - "#|export\n", - "def load_ipython_extension(ipython):\n", - " \"Required function for creating magic\"\n", - " create_magic(shell=ipython)" + "%psh cd .." ] }, { "cell_type": "code", "execution_count": null, - "id": "dc1836e8", + "id": "9b4392f0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "/Users/jhoward/Documents/GitHub/bashnb\n" + "/Users/jhoward/Documents/GitHub\n" ] } ], @@ -262,25 +377,153 @@ { "cell_type": "code", "execution_count": null, - "id": "2acc4fc1", + "id": "52112f28", + "metadata": {}, + "outputs": [], + "source": [ + "%%psh\n", + "cat > tmp << EOF\n", + "hi\n", + "there\n", + "EOF" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1eb29d47", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "cd ..\n" + "hi\r\n", + "there\n" ] } ], "source": [ - "%psh cd .." + "%psh cat tmp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3974538", + "metadata": {}, + "outputs": [], + "source": [ + "%psh rm tmp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88cae507", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34mContextKit\u001b[39;49m\u001b[0m\r\n", + "\u001b[34mFastHTML-Gallery\u001b[39;49m\u001b[0m\r\n", + "\u001b[34maimagic\u001b[39;49m\u001b[0m\n" + ] + } + ], + "source": [ + "%psh ls | head -3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66765d04", + "metadata": {}, + "outputs": [], + "source": [ + "n = 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13999ec5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + } + ], + "source": [ + "%psh echo @{n}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "972ceb3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34mContextKit\u001b[39;49m\u001b[0m\r\n", + "\u001b[34mFastHTML-Gallery\u001b[39;49m\u001b[0m\n" + ] + } + ], + "source": [ + "%psh ls | head -@{n}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a83f67b4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "::\n", + "\n", + " %psh [-h] [-r] [-o] [-x] [-X] [-s] [-S] [-t TIMEOUT] [command ...]\n", + "\n", + "Run line or cell in persistent shell\n", + "\n", + "positional arguments:\n", + " command The command to run\n", + "\n", + "options:\n", + " -h, --help Show this help\n", + " -r, --reset Reset the shell interpreter\n", + " -o, --obj Return this magic object\n", + " -x, --expand Enable variable expansion\n", + " -X, --no-expand Disable variable expansion\n", + " -s, --sudo Enable sudo\n", + " -S, --no-sudo Disable sudo\n", + " -t TIMEOUT, --timeout TIMEOUT\n", + " Set timeout in seconds\n" + ] + } + ], + "source": [ + "%psh -h" ] }, { "cell_type": "code", "execution_count": null, - "id": "393febe0", + "id": "8ce2d26c", "metadata": {}, "outputs": [ { @@ -298,25 +541,127 @@ { "cell_type": "code", "execution_count": null, - "id": "1c2789ac", + "id": "b934eb1d", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -r" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82ecc695", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "cd -\r\n", - "echo done cd\r\n", - "/Users/jhoward/Documents/GitHub/bashnb\r\n", - "\n", - "done cd\n" + "/Users/jhoward/Documents/GitHub/pshnb\n" ] } ], "source": [ - "%%psh\n", - "cd -\n", - "echo done cd" + "%psh pwd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8673aa0e", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -s" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7125b817", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n" + ] + } + ], + "source": [ + "%psh whoami" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1169a0ab", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -S" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2193f7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "jhoward\n" + ] + } + ], + "source": [ + "%psh whoami" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de589a7f", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -t 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2905556b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "timed out\n" + ] + } + ], + "source": [ + "try: get_ipython().run_line_magic('psh', 'sleep 2')\n", + "except TIMEOUT: print(\"timed out\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1db7450a", + "metadata": {}, + "outputs": [], + "source": [ + "#|export\n", + "def load_ipython_extension(ipython):\n", + " \"Required function for creating magic\"\n", + " create_magic(shell=ipython)" ] }, { @@ -328,12 +673,12 @@ "source": [ "#| export\n", "def create_ipython_config():\n", - " \"Called by `bashnb_install` to install magic\"\n", + " \"Called by `pshnb_install` to install magic\"\n", " ipython_dir = Path(get_ipython_dir())\n", " cf = ipython_dir/'profile_default'/'ipython_config.py'\n", " cf.parent.mkdir(parents=True, exist_ok=True)\n", - " if cf.exists() and 'bashnb' in cf.read_text(): return print('bashnb already installed!')\n", - " with cf.open(mode='a') as f: f.write(\"\\nc.InteractiveShellApp.extensions.append('bashnb.core')\\n\\n\")\n", + " if cf.exists() and 'pshnb' in cf.read_text(): return print('pshnb already installed!')\n", + " with cf.open(mode='a') as f: f.write(\"\\nc.InteractiveShellApp.extensions.append('pshnb.core')\\n\\n\")\n", " print(f\"Jupyter config updated at {cf}\")" ] }, diff --git a/README.md b/README.md index 69bd7ae..5dfe967 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,215 @@ -# bashnb -An IPython magic for persistent bash +# Using `psh` magics + + + + +`pshnb` adds `%psh` and `%%psh` functions to Jupyter and IPython, which +execute expressions in a persistent shell. + +## Installation + +Install pshnb with: + + pip install pshnb + +Once that’s complete, you can install the magics to all IPython and +Jupyter sessions automatically by running in your terminal: + + pshnb_install + +## What’s the point? + +In jupyter and ipython, you can run a shell command using the `!` +prefix: + +``` python +!pwd +``` + + /Users/jhoward/Documents/GitHub/pshnb + +However, each time you run this command, a new shell is created and then +removed, so for instance, `cd` doesn’t actually do anything if you run +another command afterwards: + +``` python +!cd .. +!pwd +``` + + /Users/jhoward/Documents/GitHub/pshnb + +As you see from the `!pwd` output, the directory hasn’t actually +changed! + +`%psh`, on the other hand, creates a *persistent* shell, which solves +this problem: + +``` python +%psh pwd +``` + + /Users/jhoward/Documents/GitHub/pshnb + +``` python +%psh cd .. +%psh pwd +``` + + /Users/jhoward/Documents/GitHub + +With `%psh`, you can implement, and document in notebooks, multi-step +stateful shell interactions, including setting environment variables, +sourcing scripts, and changing directories. + +## Features + +You can use the `%%psh` cell magic to run multi-line shell commands, +such as here-docs. For instance: + +``` python +%%psh +cat > tmp << EOF +hi +there +EOF +``` + +This creates a file called `tmp` containing two lines. Let’s check it +worked, and then remove it – as you see, you can also use the cell magic +to run multiple commands: + +``` python +%%psh +cat tmp +rm tmp +``` + + hi + there + +You can pipe commands together just like in a regular shell, and use +standard unix utilities like `head` to process the output. For instance, +here we show just the first 3 lines of the directory listing: + +``` python +%psh ls | head -3 +``` + +You can use Python variables in your shell commands by prefixing them +with `@{}`. For instance, here we create a variable `n` and then display +it using `echo`: + +``` python +n = 2 +``` + +``` python +%psh echo @{n} +``` + + 2 + +Here we use `n` to show just the first two entries from the directory +listing: + +``` python +%psh ls | head -@{n} +``` + + ContextKit + FastHTML-Gallery + +You can get help on the `%psh` magic’s options using `-h`. + +``` python +%psh -h +``` + + :: + + %psh [-h] [-r] [-o] [-x] [-X] [-s] [-S] [-t TIMEOUT] [command ...] + + Run line or cell in persistent shell + + positional arguments: + command The command to run + + options: + -h, --help Show this help + -r, --reset Reset the shell interpreter + -o, --obj Return this magic object + -x, --expand Enable variable expansion + -X, --no-expand Disable variable expansion + -s, --sudo Enable sudo + -S, --no-sudo Disable sudo + -t TIMEOUT, --timeout TIMEOUT + Set timeout in seconds + +You can reset the shell to its initial state using the `-r` flag. Let’s +first check our current directory: + +``` python +%psh pwd +``` + + /Users/jhoward/Documents/GitHub + +Now let’s reset the shell: + +``` python +%psh -r +``` + +As you can see, after resetting we’re back in our starting directory: + +``` python +%psh pwd +``` + + /Users/jhoward/Documents/GitHub/pshnb + +The `-s` flag enables `sudo` mode, which runs commands as the root user, +and `-S` disables it. For instance, here we first enable `sudo` mode: + +``` python +%psh -s +``` + +Then we can check which user we’re running as: + +``` python +%psh whoami +``` + + root + +As you can see, we’re now running as `root`. We can disable `sudo` mode: + +``` python +%psh -S +``` + +And when we check again, we’re back to our regular user: + +``` python +%psh whoami +``` + + jhoward + +You can set a timeout (in seconds) using the `-t` flag, which will raise +a `TIMEOUT` exception if a command takes too long. For instance, here we +set a 1-second timeout: + +``` python +%psh -t 1 +``` + +Then we try to run a command that sleeps for 2 seconds – since this is +longer than our timeout, we’ll get a timeout error: + +``` python +try: get_ipython().run_line_magic('psh', 'sleep 2') +except TIMEOUT: print("timed out") +``` diff --git a/bashnb/_modidx.py b/bashnb/_modidx.py deleted file mode 100644 index 272b24f..0000000 --- a/bashnb/_modidx.py +++ /dev/null @@ -1,13 +0,0 @@ -# Autogenerated by nbdev - -d = { 'settings': { 'branch': 'main', - 'doc_baseurl': '/bashnb', - 'doc_host': 'https://answerdotai.github.io', - 'git_url': 'https://github.com/answerdotai/bashnb', - 'lib_path': 'bashnb'}, - 'syms': { 'bashnb.core': { 'bashnb.core.BashMagic': ('core.html#bashmagic', 'bashnb/core.py'), - 'bashnb.core.BashMagic.__init__': ('core.html#bashmagic.__init__', 'bashnb/core.py'), - 'bashnb.core.BashMagic.bash': ('core.html#bashmagic.bash', 'bashnb/core.py'), - 'bashnb.core.create_ipython_config': ('core.html#create_ipython_config', 'bashnb/core.py'), - 'bashnb.core.create_magic': ('core.html#create_magic', 'bashnb/core.py'), - 'bashnb.core.load_ipython_extension': ('core.html#load_ipython_extension', 'bashnb/core.py')}}} diff --git a/bashnb/core.py b/bashnb/core.py deleted file mode 100644 index 5a9c094..0000000 --- a/bashnb/core.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Provides `psh` persistent bash magics in Jupyter and IPython""" - -# AUTOGENERATED! DO NOT EDIT! File to edit: ../00_core.ipynb. - -# %% auto 0 -__all__ = ['BashMagic', 'create_magic', 'load_ipython_extension', 'create_ipython_config'] - -# %% ../00_core.ipynb -import pexpect, re, os -from pathlib import Path -from IPython.core.magic import register_cell_magic -from IPython.display import display, Javascript -from IPython.paths import get_ipython_dir -from IPython.core.interactiveshell import InteractiveShell - -# %% ../00_core.ipynb -class BashMagic: - def __init__(self): - self.o = ShellInterpreter() - - def bash(self, line, cell=None): - if line and not cell: cell=line - disp = True - if cell.endswith(';'): disp,cell = False,cell[:-1] - res = self.o(cell) or None - if disp: print(res) - -# %% ../00_core.ipynb -def create_magic(shell=None): - if not shell: shell = get_ipython() - bash_magic = BashMagic() - shell.register_magic_function(bash_magic.bash, 'line_cell', 'psh') - -# %% ../00_core.ipynb -def load_ipython_extension(ipython): - "Required function for creating magic" - create_magic(shell=ipython) - -# %% ../00_core.ipynb -def create_ipython_config(): - "Called by `bashnb_install` to install magic" - ipython_dir = Path(get_ipython_dir()) - cf = ipython_dir/'profile_default'/'ipython_config.py' - cf.parent.mkdir(parents=True, exist_ok=True) - if cf.exists() and 'bashnb' in cf.read_text(): return print('bashnb already installed!') - with cf.open(mode='a') as f: f.write("\nc.InteractiveShellApp.extensions.append('bashnb.core')\n\n") - print(f"Jupyter config updated at {cf}") diff --git a/index.ipynb b/index.ipynb index aca0ff7..624ec10 100644 --- a/index.ipynb +++ b/index.ipynb @@ -5,8 +5,8 @@ "id": "d969a371", "metadata": {}, "source": [ - "# Using `apl` magics\n", - "> An introduction to `aplnb`" + "# Using `psh` magics\n", + "> An introduction to `pshnb`" ] }, { @@ -14,7 +14,7 @@ "id": "ba9ee970", "metadata": {}, "source": [ - "`aplnb` adds `%apl` and `%%apl` functions to Jupyter and IPython, which exectute expressions in Dyalog APL." + "`pshnb` adds `%psh` and `%%psh` functions to Jupyter and IPython, which execute expressions in a persistent shell." ] }, { @@ -30,16 +30,16 @@ "id": "67e0aaac", "metadata": {}, "source": [ - "First, install [Dyalog APL](https://www.dyalog.com/). Dyalog provides a basic license for free. Once Dyalog is installed, install aplnb with:\n", + "Install pshnb with:\n", "\n", "```\n", - "pip install aplnb\n", + "pip install pshnb\n", "```\n", "\n", "Once that's complete, you can install the magics to all IPython and Jupyter sessions automatically by running in your terminal:\n", "\n", "```\n", - "aplnb_install\n", + "pshnb_install\n", "```" ] }, @@ -48,502 +48,494 @@ "id": "cb919ad1", "metadata": {}, "source": [ - "## Usage" + "## What's the point?" ] }, { "cell_type": "markdown", - "id": "d10e0e57", + "id": "77e9194e", "metadata": {}, "source": [ - "After first running an `apl` magic in a notebook, the [APL language bar](https://abrudz.github.io/lb/apl) by Adám Brudzewsky is automatically added to the current page. (There is one change to Adám's original version, which is that you can type a backtick twice in a row to enter triple backticks. To get a `⋄` glyph, type backtick-q.)\n", - "\n", - "You can use either a cell magic (`%%ai`) or a line magic (`%ai`). In either case the expression is evaluated and returned:" + "In jupyter and ipython, you can run a shell command using the `!` prefix:" ] }, { "cell_type": "code", "execution_count": null, - "id": "93495e59", + "id": "6927a9a0", "metadata": {}, "outputs": [ { - "data": { - "application/javascript": [ - "; (_ => {\n", - "\tlet hc = { '<': '<', '&': '&', \"'\": ''', '\"': '"' }, he = x => x.replace(/[<&'\"]/g, c => hc[c]) //html chars and escape fn\n", - "\t\t, tcs = '<-←xx×/\\\\×:-÷*O⍟[-⌹-]⌹OO○77⌈FF⌈ll⌊LL⌊T_⌶II⌶|_⊥TT⊤-|⊣|-⊢=/≠L-≠<=≤<_≤>=≥>_≥==≡=_≡7=≢Z-≢vv∨^^∧^~⍲v~⍱^|↑v|↓((⊂cc⊂(_⊆c_⊆))⊃[|⌷|]⌷A|⍋V|⍒ii⍳i_⍸ee∊e_⍷' +\n", - "\t\t\t'uu∪UU∪nn∩/-⌿\\\\-⍀,-⍪rr⍴pp⍴O|⌽O-⊖O\\\\⍉::¨\"\"¨~:⍨~\"⍨*:⍣*\"⍣oo∘o:⍤o\"⍤O:⍥O\"⍥[\\'⍞\\']⍞[]⎕[:⍠:]⍠[=⌸=]⌸[<⌺>]⌺o_⍎oT⍕o-⍕<>⋄^v⋄on⍝->→aa⍺ww⍵VV∇v-∇--¯0~⍬' +\n", - "\t\t\t'AA∆^-∆A_⍙^=⍙[?⍰?]⍰:V⍢∇\"⍢||∥ox¤)_⊇_)⊇V~⍫\\'\\'`'\n", - "\t\t, lbs = ['←←\\nASSIGN', ' ', '++\\nconjugate\\nplus', '--\\nnegate\\nminus', '××\\ndirection\\ntimes', '÷÷\\nreciprocal\\ndivide', '**\\nexponential\\npower', '⍟⍟\\nnatural logarithm\\nlogarithm',\n", - "\t\t\t'⌹⌹\\nmatrix inverse\\nmatrix divide', '○○\\npi times\\ncircular', '!!\\nfactorial\\nbinomial', '??\\nroll\\ndeal', ' ', '||\\nmagnitude\\nresidue',\n", - "\t\t\t'⌈⌈\\nceiling\\nmaximum', '⌊⌊\\nfloor\\nminimum', '⊥⊥\\ndecode', '⊤⊤\\nencode', '⊣⊣\\nsame\\nleft', '⊢⊢\\nsame\\nright', ' ', '==\\nequal', '≠≠\\nunique mask\\nnot equal',\n", - "\t\t\t'≤≤\\nless than or equal to', '<<\\nless than', '>>\\ngreater than', '≥≥\\ngreater than or equal to', '≡≡\\ndepth\\nmatch', '≢≢\\ntally\\nnot match', ' ', '∨∨\\ngreatest common divisor/or',\n", - "\t\t\t'∧∧\\nlowest common multiple/and', '⍲⍲\\nnand', '⍱⍱\\nnor', ' ', '↑↑\\nmix\\ntake', '↓↓\\nsplit\\ndrop', '⊂⊂\\nenclose\\npartioned enclose', '⊃⊃\\nfirst\\npick', '⊆⊆\\nnest\\npartition', '⌷⌷\\nmaterialise\\nindex', '⍋⍋\\ngrade up\\ngrades up',\n", - "\t\t\t'⍒⍒\\ngrade down\\ngrades down', ' ', '⍳⍳\\nindices\\nindices of', '⍸⍸\\nwhere\\ninterval index', '∊∊\\nenlist\\nmember of', '⍷⍷\\nfind', '∪∪\\nunique\\nunion', '∩∩\\nintersection', '~~\\nnot\\nwithout', ' ',\n", - "\t\t\t'//\\nreplicate\\nReduce', '\\\\\\\\\\n\\expand\\nScan', '⌿⌿\\nreplicate first\\nReduce First', '⍀⍀\\nexpand first\\nScan First', ' ', ',,\\nravel\\ncatenate/laminate',\n", - "\t\t\t'⍪⍪\\ntable\\ncatenate first/laminate', '⍴⍴\\nshape\\nreshape', '⌽⌽\\nreverse\\nrotate', '⊖⊖\\nreverse first\\nrotate first',\n", - "\t\t\t'⍉⍉\\ntranspose\\nreorder axes', ' ', '¨¨\\nEach', '⍨⍨\\nConstant\\nSelf\\nSwap', '⍣⍣\\nRepeat\\nUntil', '..\\nOuter Product (∘.)\\nInner Product',\n", - "\t\t\t'∘∘\\nOUTER PRODUCT (∘.)\\nBind\\nBeside', '⍤⍤\\nRank\\nAtop', '⍥⍥\\nOver', '@@\\nAt', ' ', '⍞⍞\\nSTDIN\\nSTDERR', '⎕⎕\\nEVALUATED STDIN\\nSTDOUT\\nSYSTEM NAME PREFIX', '⍠⍠\\nVariant',\n", - "\t\t\t'⌸⌸\\nIndex Key\\nKey', '⌺⌺\\nStencil', '⌶⌶\\nI-Beam', '⍎⍎\\nexecute', '⍕⍕\\nformat', ' ', '⋄⋄\\nSTATEMENT SEPARATOR', '⍝⍝\\nCOMMENT', '→→\\nABORT\\nBRANCH', '⍵⍵\\nRIGHT ARGUMENT\\nRIGHT OPERAND (⍵⍵)', '⍺⍺\\nLEFT ARGUMENT\\nLEFT OPERAND (⍺⍺)',\n", - "\t\t\t'∇∇\\nrecursion\\nRecursion (∇∇)', '&&\\nSpawn', ' ', '¯¯\\nNEGATIVE', '⍬⍬\\nEMPTY NUMERIC VECTOR', '∆∆\\nIDENTIFIER CHARACTER', '⍙⍙\\nIDENTIFIER CHARACTER']\n", - "\t\t, bqk = ' =1234567890-qwertyuiop\\\\asdfghjk∙l;\\'zxcvbnm,./q[]+!@#$%^&*()_QWERTYUIOP|ASDFGHJKL:\"ZXCVBNM<>?~{}'.replace(/∙/g, '')\n", - "\t\t, bqv = '`÷¨¯<≤=≥>≠∨∧×⋄⍵∊⍴~↑↓⍳○*⊢∙⍺⌈⌊_∇∆∘\\'⎕⍎⍕∙⊂⊃∩∪⊥⊤|⍝⍀⌿⋄←→⌹⌶⍫⍒⍋⌽⍉⊖⍟⍱⍲!⍰W⍷R⍨YU⍸⍥⍣⊣ASDF⍢H⍤⌸⌷≡≢⊆⊇CVB¤∥⍪⍙⍠⌺⍞⍬'.replace(/∙/g, '')\n", - "\t\t, tc = {}, bqc = {} //tab completions and ` completions\n", - "\tfor (let i = 0; i < bqk.length; i++)bqc[bqk[i]] = bqv[i]\n", - "\tfor (let i = 0; i < tcs.length; i += 3)tc[tcs[i] + tcs[i + 1]] = tcs[i + 2]\n", - "\tfor (let i = 0; i < tcs.length; i += 3) { let k = tcs[i + 1] + tcs[i]; tc[k] = tc[k] || tcs[i + 2] }\n", - "\tlet lbh = ''; for (let i = 0; i < lbs.length; i++) {\n", - "\t\tlet ks = []\n", - "\t\tfor (let j = 0; j < tcs.length; j += 3)if (lbs[i][0] === tcs[j + 2]) ks.push('\\n' + tcs[j] + ' ' + tcs[j + 1] + ' ')\n", - "\t\tfor (let j = 0; j < bqk.length; j++)if (lbs[i][0] === bqv[j]) ks.push('\\n` ' + bqk[j])\n", - "\t\tlbh += '' + lbs[i][0] + ''\n", - "\t}\n", - "\tlet d = document, el = d.createElement('div'); el.innerHTML =\n", - "\t\t`
${lbh}
\n", - " \n", - " `\n", - "\td.body.appendChild(el)\n", - "\tlet t, ts = [], lb = el.firstChild, bqm = 0 //t:textarea or input, lb:language bar, bqm:backquote mode\n", - "\tlet pd = x => x.preventDefault()\n", - "\tlet ev = (x, t, f, c) => x.addEventListener(t, f, c)\n", - "\tev(lb, 'mousedown', x => {\n", - "\t\tif (x.target.classList.contains('ngn_x')) { lb.hidden = 1; upd(); pd(x); return }\n", - "\t\tif (x.target.nodeName === 'B' && t) {\n", - "\t\t\tlet i = t.selectionStart, j = t.selectionEnd, v = t.value, s = x.target.textContent\n", - "\t\t\tif (i != null && j != null) { t.value = v.slice(0, i) + s + v.slice(j); t.selectionStart = t.selectionEnd = i + 1 }\n", - "\t\t\tpd(x); return\n", - "\t\t}\n", - "\t})\n", - "\tlet fk = x => {\n", - "\t\tlet t = x.target\n", - "\t\tif (bqm) {\n", - "\t\t\tlet i = t.selectionStart, v = t.value, c = bqc[x.key]\n", - "\t\t\tif (x.key === '`') {\n", - "\t\t\t\tt.value = v.slice(0, i) + '```' + v.slice(i)\n", - "\t\t\t\tt.selectionStart = t.selectionEnd = i + 1\n", - "\t\t\t\tbqm = 0\n", - "\t\t\t\td.body.classList.remove('ngn_bq')\n", - "\t\t\t\tpd(x)\n", - "\t\t\t\treturn !1\n", - "\t\t\t}\n", - "\t\t\tif (x.which > 31) { bqm = 0; d.body.classList.remove('ngn_bq') }\n", - "\t\t\tif (c) { t.value = v.slice(0, i) + c + v.slice(i); t.selectionStart = t.selectionEnd = i + 1; pd(x); return !1 }\n", - "\t\t}\n", - "\t\tif (!x.ctrlKey && !x.shiftKey && !x.altKey && !x.metaKey) {\n", - "\t\t\tif (\"`½²^º§ùµ°\".indexOf(x.key) > -1) {\n", - "\t\t\t\tbqm = 1; d.body.classList.add('ngn_bq'); pd(x); // ` or other trigger symbol pressed, wait for next key\n", - "\t\t\t} else if (x.key == \"Tab\") {\n", - "\t\t\t\tlet i = t.selectionStart, v = t.value, c = tc[v.slice(i - 2, i)]\n", - "\t\t\t\tif (c) { t.value = v.slice(0, i - 2) + c + v.slice(i); t.selectionStart = t.selectionEnd = i - 1; pd(x) }\n", - "\t\t\t}\n", - "\t\t}\n", - "\t}\n", - "\tlet ff = x => {\n", - "\t\tlet t0 = x.target, nn = t0.nodeName.toLowerCase()\n", - "\t\tif (nn !== 'textarea' && (nn !== 'input' || t0.type !== 'text' && t0.type !== 'search')) return\n", - "\t\tt = t0; if (!t.ngn) { t.ngn = 1; ts.push(t); ev(t, 'keydown', fk) }\n", - "\t}\n", - "\tlet upd = _ => { d.body.style.marginTop = lb.clientHeight + 'px' }\n", - "\tupd(); ev(window, 'resize', upd)\n", - "\tev(d, 'focus', ff, !0); let ae = d.activeElement; ae && ff({ type: 'focus', target: ae })\n", - "})();\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/jhoward/Documents/GitHub/pshnb\r\n" + ] + } + ], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "markdown", + "id": "2f413e70", + "metadata": {}, + "source": [ + "However, each time you run this command, a new shell is created and then removed, so for instance, `cd` doesn't actually do anything if you run another command afterwards:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10dfeb9d", + "metadata": {}, + "outputs": [ { - "data": { - "text/plain": [ - "[1, 4, 9]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/jhoward/Documents/GitHub/pshnb\r\n" + ] } ], "source": [ - "%%apl\n", - "y←⍳3\n", - "z←y×y" + "!cd ..\n", + "!pwd" + ] + }, + { + "cell_type": "markdown", + "id": "65a3bf8b", + "metadata": {}, + "source": [ + "As you see from the `!pwd` output, the directory hasn't actually changed!\n", + "\n", + "`%psh`, on the other hand, creates a *persistent* shell, which solves this problem:" ] }, { "cell_type": "code", "execution_count": null, - "id": "5742fe99", + "id": "be0e4948", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "[3, 6, 9, 12]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/jhoward/Documents/GitHub/pshnb\n" + ] } ], "source": [ - "%apl 3×⍳4" + "%psh pwd" ] }, { "cell_type": "code", "execution_count": null, - "id": "69a7582d", + "id": "1cc95899", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/jhoward/Documents/GitHub\n" + ] } ], "source": [ - "%apl ⎕A" + "%psh cd ..\n", + "%psh pwd" ] }, { "cell_type": "markdown", - "id": "593deaea", + "id": "c38c5a50", "metadata": {}, "source": [ - "You can store the value of an expression in a python variable using the line magic. Scalars, lists, and nest lists are used:" + "With `%psh`, you can implement, and document in notebooks, multi-step stateful shell interactions, including setting environment variables, sourcing scripts, and changing directories." + ] + }, + { + "cell_type": "markdown", + "id": "d47412a5", + "metadata": {}, + "source": [ + "## Features" + ] + }, + { + "cell_type": "markdown", + "id": "7458388b", + "metadata": {}, + "source": [ + "You can use the `%%psh` cell magic to run multi-line shell commands, such as here-docs. For instance:" ] }, { "cell_type": "code", "execution_count": null, - "id": "c8c46b7c", + "id": "52112f28", + "metadata": {}, + "outputs": [], + "source": [ + "%%psh\n", + "cat > tmp << EOF\n", + "hi\n", + "there\n", + "EOF" + ] + }, + { + "cell_type": "markdown", + "id": "f2a2c4e1", + "metadata": {}, + "source": [ + "This creates a file called `tmp` containing two lines. Let's check it worked, and then remove it -- as you see, you can also use the cell magic to run multiple commands:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1eb29d47", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[1, 4, 9]\n" + "hi\r\n", + "there\n" ] } ], "source": [ - "z = %apl z\n", - "print(z)" + "%%psh\n", + "cat tmp\n", + "rm tmp" ] }, { "cell_type": "markdown", - "id": "208d07eb", + "id": "aa737da1", "metadata": {}, "source": [ - "To avoid having the expression returned and/or displayed, end the last line with a `;`." + "You can pipe commands together just like in a regular shell, and use standard unix utilities like `head` to process the output. For instance, here we show just the first 3 lines of the directory listing:" ] }, { "cell_type": "code", "execution_count": null, - "id": "9ccd0817", + "id": "88cae507", "metadata": {}, "outputs": [], "source": [ - "%%apl\n", - "a←2 2 ⍴ ⍳4;" + "%psh ls | head -3" ] }, { "cell_type": "markdown", - "id": "bf74dd92", + "id": "11dcdafa", + "metadata": {}, + "source": [ + "You can use Python variables in your shell commands by prefixing them with `@{}`. For instance, here we create a variable `n` and then display it using `echo`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66765d04", "metadata": {}, + "outputs": [], "source": [ - "You can print from cell magics using the standard APL ⎕ glyph:" + "n = 2" ] }, { "cell_type": "code", "execution_count": null, - "id": "084137cd", + "id": "13999ec5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1 2\n", - "3 4\n" + "2\n" ] } ], "source": [ - "%%apl\n", - "⎕←a;" + "%psh echo @{n}" ] }, { "cell_type": "markdown", - "id": "390ce835", + "id": "b468b7db", "metadata": {}, "source": [ - "To use numpy, just pass the result of `%apl` into `np.array`:" + "Here we use `n` to show just the first two entries from the directory listing:" ] }, { "cell_type": "code", "execution_count": null, - "id": "17819843", + "id": "972ceb3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34mContextKit\u001b[39;49m\u001b[0m\r\n", + "\u001b[34mFastHTML-Gallery\u001b[39;49m\u001b[0m\n" + ] + } + ], + "source": [ + "%psh ls | head -@{n}" + ] + }, + { + "cell_type": "markdown", + "id": "c7acc94b", + "metadata": {}, + "source": [ + "You can get help on the `%psh` magic's options using `-h`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a83f67b4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "::\n", + "\n", + " %psh [-h] [-r] [-o] [-x] [-X] [-s] [-S] [-t TIMEOUT] [command ...]\n", + "\n", + "Run line or cell in persistent shell\n", + "\n", + "positional arguments:\n", + " command The command to run\n", + "\n", + "options:\n", + " -h, --help Show this help\n", + " -r, --reset Reset the shell interpreter\n", + " -o, --obj Return this magic object\n", + " -x, --expand Enable variable expansion\n", + " -X, --no-expand Disable variable expansion\n", + " -s, --sudo Enable sudo\n", + " -S, --no-sudo Disable sudo\n", + " -t TIMEOUT, --timeout TIMEOUT\n", + " Set timeout in seconds\n" + ] + } + ], + "source": [ + "%psh -h" + ] + }, + { + "cell_type": "markdown", + "id": "1c25d321", "metadata": {}, - "outputs": [], "source": [ - "import numpy as np" + "You can reset the shell to its initial state using the `-r` flag. Let's first check our current directory:" ] }, { "cell_type": "code", "execution_count": null, - "id": "04407ed2", + "id": "8ce2d26c", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "array([[1, 2],\n", - " [3, 4]])" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/jhoward/Documents/GitHub\n" + ] } ], "source": [ - "a = %apl a\n", - "np.array(a)" + "%psh pwd" ] }, { "cell_type": "markdown", - "id": "90ad8377", + "id": "bd6954d3", "metadata": {}, "source": [ - "### Example algorithms" + "Now let's reset the shell:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b934eb1d", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -r" ] }, { "cell_type": "markdown", - "id": "d0660108", + "id": "c23edbf1", "metadata": {}, "source": [ - "The fibonacci sequence:" + "As you can see, after resetting we're back in our starting directory:" ] }, { "cell_type": "code", "execution_count": null, - "id": "77cc837d", + "id": "82ecc695", "metadata": {}, "outputs": [ { - "data": { - "application/javascript": [ - "; (_ => {\n", - "\tlet hc = { '<': '<', '&': '&', \"'\": ''', '\"': '"' }, he = x => x.replace(/[<&'\"]/g, c => hc[c]) //html chars and escape fn\n", - "\t\t, tcs = '<-←xx×/\\\\×:-÷*O⍟[-⌹-]⌹OO○77⌈FF⌈ll⌊LL⌊T_⌶II⌶|_⊥TT⊤-|⊣|-⊢=/≠L-≠<=≤<_≤>=≥>_≥==≡=_≡7=≢Z-≢vv∨^^∧^~⍲v~⍱^|↑v|↓((⊂cc⊂(_⊆c_⊆))⊃[|⌷|]⌷A|⍋V|⍒ii⍳i_⍸ee∊e_⍷' +\n", - "\t\t\t'uu∪UU∪nn∩/-⌿\\\\-⍀,-⍪rr⍴pp⍴O|⌽O-⊖O\\\\⍉::¨\"\"¨~:⍨~\"⍨*:⍣*\"⍣oo∘o:⍤o\"⍤O:⍥O\"⍥[\\'⍞\\']⍞[]⎕[:⍠:]⍠[=⌸=]⌸[<⌺>]⌺o_⍎oT⍕o-⍕<>⋄^v⋄on⍝->→aa⍺ww⍵VV∇v-∇--¯0~⍬' +\n", - "\t\t\t'AA∆^-∆A_⍙^=⍙[?⍰?]⍰:V⍢∇\"⍢||∥ox¤)_⊇_)⊇V~⍫\\'\\'`'\n", - "\t\t, lbs = ['←←\\nASSIGN', ' ', '++\\nconjugate\\nplus', '--\\nnegate\\nminus', '××\\ndirection\\ntimes', '÷÷\\nreciprocal\\ndivide', '**\\nexponential\\npower', '⍟⍟\\nnatural logarithm\\nlogarithm',\n", - "\t\t\t'⌹⌹\\nmatrix inverse\\nmatrix divide', '○○\\npi times\\ncircular', '!!\\nfactorial\\nbinomial', '??\\nroll\\ndeal', ' ', '||\\nmagnitude\\nresidue',\n", - "\t\t\t'⌈⌈\\nceiling\\nmaximum', '⌊⌊\\nfloor\\nminimum', '⊥⊥\\ndecode', '⊤⊤\\nencode', '⊣⊣\\nsame\\nleft', '⊢⊢\\nsame\\nright', ' ', '==\\nequal', '≠≠\\nunique mask\\nnot equal',\n", - "\t\t\t'≤≤\\nless than or equal to', '<<\\nless than', '>>\\ngreater than', '≥≥\\ngreater than or equal to', '≡≡\\ndepth\\nmatch', '≢≢\\ntally\\nnot match', ' ', '∨∨\\ngreatest common divisor/or',\n", - "\t\t\t'∧∧\\nlowest common multiple/and', '⍲⍲\\nnand', '⍱⍱\\nnor', ' ', '↑↑\\nmix\\ntake', '↓↓\\nsplit\\ndrop', '⊂⊂\\nenclose\\npartioned enclose', '⊃⊃\\nfirst\\npick', '⊆⊆\\nnest\\npartition', '⌷⌷\\nmaterialise\\nindex', '⍋⍋\\ngrade up\\ngrades up',\n", - "\t\t\t'⍒⍒\\ngrade down\\ngrades down', ' ', '⍳⍳\\nindices\\nindices of', '⍸⍸\\nwhere\\ninterval index', '∊∊\\nenlist\\nmember of', '⍷⍷\\nfind', '∪∪\\nunique\\nunion', '∩∩\\nintersection', '~~\\nnot\\nwithout', ' ',\n", - "\t\t\t'//\\nreplicate\\nReduce', '\\\\\\\\\\n\\expand\\nScan', '⌿⌿\\nreplicate first\\nReduce First', '⍀⍀\\nexpand first\\nScan First', ' ', ',,\\nravel\\ncatenate/laminate',\n", - "\t\t\t'⍪⍪\\ntable\\ncatenate first/laminate', '⍴⍴\\nshape\\nreshape', '⌽⌽\\nreverse\\nrotate', '⊖⊖\\nreverse first\\nrotate first',\n", - "\t\t\t'⍉⍉\\ntranspose\\nreorder axes', ' ', '¨¨\\nEach', '⍨⍨\\nConstant\\nSelf\\nSwap', '⍣⍣\\nRepeat\\nUntil', '..\\nOuter Product (∘.)\\nInner Product',\n", - "\t\t\t'∘∘\\nOUTER PRODUCT (∘.)\\nBind\\nBeside', '⍤⍤\\nRank\\nAtop', '⍥⍥\\nOver', '@@\\nAt', ' ', '⍞⍞\\nSTDIN\\nSTDERR', '⎕⎕\\nEVALUATED STDIN\\nSTDOUT\\nSYSTEM NAME PREFIX', '⍠⍠\\nVariant',\n", - "\t\t\t'⌸⌸\\nIndex Key\\nKey', '⌺⌺\\nStencil', '⌶⌶\\nI-Beam', '⍎⍎\\nexecute', '⍕⍕\\nformat', ' ', '⋄⋄\\nSTATEMENT SEPARATOR', '⍝⍝\\nCOMMENT', '→→\\nABORT\\nBRANCH', '⍵⍵\\nRIGHT ARGUMENT\\nRIGHT OPERAND (⍵⍵)', '⍺⍺\\nLEFT ARGUMENT\\nLEFT OPERAND (⍺⍺)',\n", - "\t\t\t'∇∇\\nrecursion\\nRecursion (∇∇)', '&&\\nSpawn', ' ', '¯¯\\nNEGATIVE', '⍬⍬\\nEMPTY NUMERIC VECTOR', '∆∆\\nIDENTIFIER CHARACTER', '⍙⍙\\nIDENTIFIER CHARACTER']\n", - "\t\t, bqk = ' =1234567890-qwertyuiop\\\\asdfghjk∙l;\\'zxcvbnm,./q[]+!@#$%^&*()_QWERTYUIOP|ASDFGHJKL:\"ZXCVBNM<>?~{}'.replace(/∙/g, '')\n", - "\t\t, bqv = '`÷¨¯<≤=≥>≠∨∧×⋄⍵∊⍴~↑↓⍳○*⊢∙⍺⌈⌊_∇∆∘\\'⎕⍎⍕∙⊂⊃∩∪⊥⊤|⍝⍀⌿⋄←→⌹⌶⍫⍒⍋⌽⍉⊖⍟⍱⍲!⍰W⍷R⍨YU⍸⍥⍣⊣ASDF⍢H⍤⌸⌷≡≢⊆⊇CVB¤∥⍪⍙⍠⌺⍞⍬'.replace(/∙/g, '')\n", - "\t\t, tc = {}, bqc = {} //tab completions and ` completions\n", - "\tfor (let i = 0; i < bqk.length; i++)bqc[bqk[i]] = bqv[i]\n", - "\tfor (let i = 0; i < tcs.length; i += 3)tc[tcs[i] + tcs[i + 1]] = tcs[i + 2]\n", - "\tfor (let i = 0; i < tcs.length; i += 3) { let k = tcs[i + 1] + tcs[i]; tc[k] = tc[k] || tcs[i + 2] }\n", - "\tlet lbh = ''; for (let i = 0; i < lbs.length; i++) {\n", - "\t\tlet ks = []\n", - "\t\tfor (let j = 0; j < tcs.length; j += 3)if (lbs[i][0] === tcs[j + 2]) ks.push('\\n' + tcs[j] + ' ' + tcs[j + 1] + ' ')\n", - "\t\tfor (let j = 0; j < bqk.length; j++)if (lbs[i][0] === bqv[j]) ks.push('\\n` ' + bqk[j])\n", - "\t\tlbh += '' + lbs[i][0] + ''\n", - "\t}\n", - "\tlet d = document, el = d.createElement('div'); el.innerHTML =\n", - "\t\t`
${lbh}
\n", - " \n", - " `\n", - "\td.body.appendChild(el)\n", - "\tlet t, ts = [], lb = el.firstChild, bqm = 0 //t:textarea or input, lb:language bar, bqm:backquote mode\n", - "\tlet pd = x => x.preventDefault()\n", - "\tlet ev = (x, t, f, c) => x.addEventListener(t, f, c)\n", - "\tev(lb, 'mousedown', x => {\n", - "\t\tif (x.target.classList.contains('ngn_x')) { lb.hidden = 1; upd(); pd(x); return }\n", - "\t\tif (x.target.nodeName === 'B' && t) {\n", - "\t\t\tlet i = t.selectionStart, j = t.selectionEnd, v = t.value, s = x.target.textContent\n", - "\t\t\tif (i != null && j != null) { t.value = v.slice(0, i) + s + v.slice(j); t.selectionStart = t.selectionEnd = i + 1 }\n", - "\t\t\tpd(x); return\n", - "\t\t}\n", - "\t})\n", - "\tlet fk = x => {\n", - "\t\tlet t = x.target\n", - "\t\tif (bqm) {\n", - "\t\t\tlet i = t.selectionStart, v = t.value, c = bqc[x.key]\n", - "\t\t\tif (x.key === '`') {\n", - "\t\t\t\tt.value = v.slice(0, i) + '```' + v.slice(i)\n", - "\t\t\t\tt.selectionStart = t.selectionEnd = i + 1\n", - "\t\t\t\tbqm = 0\n", - "\t\t\t\td.body.classList.remove('ngn_bq')\n", - "\t\t\t\tpd(x)\n", - "\t\t\t\treturn !1\n", - "\t\t\t}\n", - "\t\t\tif (x.which > 31) { bqm = 0; d.body.classList.remove('ngn_bq') }\n", - "\t\t\tif (c) { t.value = v.slice(0, i) + c + v.slice(i); t.selectionStart = t.selectionEnd = i + 1; pd(x); return !1 }\n", - "\t\t}\n", - "\t\tif (!x.ctrlKey && !x.shiftKey && !x.altKey && !x.metaKey) {\n", - "\t\t\tif (\"`½²^º§ùµ°\".indexOf(x.key) > -1) {\n", - "\t\t\t\tbqm = 1; d.body.classList.add('ngn_bq'); pd(x); // ` or other trigger symbol pressed, wait for next key\n", - "\t\t\t} else if (x.key == \"Tab\") {\n", - "\t\t\t\tlet i = t.selectionStart, v = t.value, c = tc[v.slice(i - 2, i)]\n", - "\t\t\t\tif (c) { t.value = v.slice(0, i - 2) + c + v.slice(i); t.selectionStart = t.selectionEnd = i - 1; pd(x) }\n", - "\t\t\t}\n", - "\t\t}\n", - "\t}\n", - "\tlet ff = x => {\n", - "\t\tlet t0 = x.target, nn = t0.nodeName.toLowerCase()\n", - "\t\tif (nn !== 'textarea' && (nn !== 'input' || t0.type !== 'text' && t0.type !== 'search')) return\n", - "\t\tt = t0; if (!t.ngn) { t.ngn = 1; ts.push(t); ev(t, 'keydown', fk) }\n", - "\t}\n", - "\tlet upd = _ => { d.body.style.marginTop = lb.clientHeight + 'px' }\n", - "\tupd(); ev(window, 'resize', upd)\n", - "\tev(d, 'focus', ff, !0); let ae = d.activeElement; ae && ff({ type: 'focus', target: ae })\n", - "})();\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/jhoward/Documents/GitHub/pshnb\n" + ] } ], "source": [ - "%apl {⍵,+/¯2↑⍵}⍣15⊢1 1" + "%psh pwd" ] }, { "cell_type": "markdown", - "id": "89a171e7", + "id": "5c741bb0", "metadata": {}, "source": [ - "Explanation:\n", - "\n", - "1. `1 1`: Initial seed (first two Fibonacci numbers)\n", - "2. `{⍵,+/¯2↑⍵}`: Function that appends the sum of the last two elements\n", - "3. `⍣15`: Apply the function 15 times\n", - "4. `⊢`: Identity function, passes the initial argument (1 1) to the iteration" + "The `-s` flag enables `sudo` mode, which runs commands as the root user, and `-S` disables it. For instance, here we first enable `sudo` mode:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77575e38", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -s" ] }, { "cell_type": "markdown", - "id": "c74bb1b9", + "id": "f2360621", "metadata": {}, "source": [ - "Prime number sieve:" + "Then we can check which user we're running as:" ] }, { "cell_type": "code", "execution_count": null, - "id": "c89a63d5", + "id": "7125b817", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n" + ] } ], "source": [ - "%%apl\n", - "primes ← {⍵×2=+⌿0=⍵∘.|⍵}⍳\n", - "(primes 50)~0" + "%psh whoami" ] }, { "cell_type": "markdown", - "id": "eec3f4b3", + "id": "fd6b77f9", "metadata": {}, "source": [ - "Explanation:\n", - "\n", - "1. `⍳50` generates integers 1 to 50\n", - "2. `⍵∘.|⍵` creates a 50x50 matrix of divisibility (1 if divisible, 0 if not)\n", - "3. `0=` inverts the matrix (1 for non-divisible)\n", - "4. `+⌿` sums columns, counting non-divisors for each number\n", - "5. `2=` checks if count equals 2 (prime property)\n", - "6. `⍵×` multiplies result with original numbers, keeping primes\n", - "7. `~0` removes zero from the result" + "As you can see, we're now running as `root`. We can disable `sudo` mode:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1169a0ab", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -S" + ] + }, + { + "cell_type": "markdown", + "id": "5528aee8", + "metadata": {}, + "source": [ + "And when we check again, we're back to our regular user:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2193f7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "jhoward\n" + ] + } + ], + "source": [ + "%psh whoami" ] }, { "cell_type": "markdown", - "id": "e61c4ea2", + "id": "a37ad61c", "metadata": {}, "source": [ - "## Learning APL" + "You can set a timeout (in seconds) using the `-t` flag, which will raise a `TIMEOUT` exception if a command takes too long. For instance, here we set a 1-second timeout:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de589a7f", + "metadata": {}, + "outputs": [], + "source": [ + "%psh -t 1" ] }, { "cell_type": "markdown", - "id": "e238320a", + "id": "a0f817d9", "metadata": {}, "source": [ - "To start learning APL, follow the [17 video series](https://forums.fast.ai/t/apl-array-programming/97188) run by Jeremy Howard, and have a look at the [study notes](https://fastai.github.io/apl-study/apl.html). Note however that the comments there about using commands starting with `]`, such as `]Help ≠`, will not work with aplnb, so you should skip them." + "Then we try to run a command that sleeps for 2 seconds -- since this is longer than our timeout, we'll get a timeout error:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2905556b", + "metadata": {}, + "outputs": [], + "source": [ + "try: get_ipython().run_line_magic('psh', 'sleep 2')\n", + "except TIMEOUT: print(\"timed out\")" ] }, { diff --git a/bashnb/__init__.py b/pshnb/__init__.py similarity index 100% rename from bashnb/__init__.py rename to pshnb/__init__.py diff --git a/pshnb/_modidx.py b/pshnb/_modidx.py new file mode 100644 index 0000000..a882d6a --- /dev/null +++ b/pshnb/_modidx.py @@ -0,0 +1,24 @@ +# Autogenerated by nbdev + +d = { 'settings': { 'branch': 'main', + 'doc_baseurl': '/pshnb', + 'doc_host': 'https://answerdotai.github.io', + 'git_url': 'https://github.com/answerdotai/pshnb', + 'lib_path': 'pshnb'}, + 'syms': { 'pshnb.core': { 'pshnb.core.PshMagic': ('core.html#pshmagic', 'pshnb/core.py'), + 'pshnb.core.PshMagic.__init__': ('core.html#pshmagic.__init__', 'pshnb/core.py'), + 'pshnb.core.PshMagic._sudo': ('core.html#pshmagic._sudo', 'pshnb/core.py'), + 'pshnb.core.PshMagic._timeout': ('core.html#pshmagic._timeout', 'pshnb/core.py'), + 'pshnb.core.PshMagic._xpand': ('core.html#pshmagic._xpand', 'pshnb/core.py'), + 'pshnb.core.PshMagic.help': ('core.html#pshmagic.help', 'pshnb/core.py'), + 'pshnb.core.PshMagic.psh': ('core.html#pshmagic.psh', 'pshnb/core.py'), + 'pshnb.core.PshMagic.reset': ('core.html#pshmagic.reset', 'pshnb/core.py'), + 'pshnb.core.ShellInterpreter': ('core.html#shellinterpreter', 'pshnb/core.py'), + 'pshnb.core.ShellInterpreter.__call__': ('core.html#shellinterpreter.__call__', 'pshnb/core.py'), + 'pshnb.core.ShellInterpreter.__init__': ('core.html#shellinterpreter.__init__', 'pshnb/core.py'), + 'pshnb.core.ShellInterpreter._ex': ('core.html#shellinterpreter._ex', 'pshnb/core.py'), + 'pshnb.core.ShellInterpreter.wait_echo': ('core.html#shellinterpreter.wait_echo', 'pshnb/core.py'), + 'pshnb.core.create_ipython_config': ('core.html#create_ipython_config', 'pshnb/core.py'), + 'pshnb.core.create_magic': ('core.html#create_magic', 'pshnb/core.py'), + 'pshnb.core.load_ipython_extension': ('core.html#load_ipython_extension', 'pshnb/core.py'), + 'pshnb.core.shell_replace': ('core.html#shell_replace', 'pshnb/core.py')}}} diff --git a/pshnb/core.py b/pshnb/core.py new file mode 100644 index 0000000..5d31b01 --- /dev/null +++ b/pshnb/core.py @@ -0,0 +1,129 @@ +"""Provides `psh` persistent bash magics in Jupyter and IPython""" + +# AUTOGENERATED! DO NOT EDIT! File to edit: ../00_core.ipynb. + +# %% auto 0 +__all__ = ['ShellInterpreter', 'shell_replace', 'PshMagic', 'create_magic', 'load_ipython_extension', 'create_ipython_config'] + +# %% ../00_core.ipynb +from fastcore.utils import * +import pexpect, re, os +from pexpect import TIMEOUT +from pathlib import Path +from getpass import getpass +from IPython.core.magic import register_cell_magic, no_var_expand +from IPython.display import display, Javascript +from IPython.paths import get_ipython_dir +from IPython.core.interactiveshell import InteractiveShell +from IPython.core.magic_arguments import magic_arguments, argument + +# %% ../00_core.ipynb +class ShellInterpreter: + def __init__(self, debug=False, timeout=2, shell_path=None, sudo=False, dumb=False): + self.debug,self.timeout = debug,timeout + if shell_path is None: shell_path = os.environ.get('SHELL', '/bin/bash') + if sudo: shell_path = 'sudo -i ' + shell_path + env = dict(os.environ, TERM='dumb' if dumb else 'xterm') + self.sh = pexpect.spawn(shell_path, encoding='utf-8', env=env) + self.sh.sendline('stty -echo') + self.sh.readline() + self.echo = os.urandom(8).hex() + self.echo_re = re.compile(fr'^{self.echo}\s*$', flags=re.MULTILINE) + self.sh.sendline(f'export PS1=""') + self.sh.sendline(f'export PS2=""') + self.sh.sendline('set +o vi +o emacs') + self.wait_echo() + + def wait_echo(self, timeout=None): + self.sh.sendline('echo') + self.sh.sendline('echo '+self.echo) + self.sh.expect(self.echo_re, timeout=timeout) + return self.sh.before.rstrip() + + def _ex(self, s, timeout=None): + if timeout is None: timeout=self.timeout + if self.debug: print('#', s) + self.sh.sendline(s) + res = self.wait_echo(timeout=timeout) + return res + + def __call__(self, cmd, timeout=None): + output = self._ex(cmd.rstrip(), timeout=timeout) + return output.replace(cmd + '\r\n', '', 1).rstrip() + +# %% ../00_core.ipynb +def shell_replace(s, shell=None): + "Replace `@{var}` refs in `s` with their variable values, if they exist" + if not shell: shell = get_ipython() + def f(m): return str(shell.user_ns.get(m[1], m[0])) + return re.sub(r'\@{(\w+?)}', f, s) + +# %% ../00_core.ipynb +class PshMagic: + def __init__(self, shell, sudo=False, timeout=2, expand=True, o=None): store_attr() + def reset(self): self.o = ShellInterpreter(sudo=self.sudo, timeout=self.timeout) + def help (self): self.psh.parser.print_help() + + def _xpand(self, expand=False): self.expand = expand + def _sudo(self, sudo=False): + self.sudo = sudo + self.o = None + def _timeout(self, timeout=2): + self.timeout = timeout + self.o = None + + @magic_arguments() + @argument('-h', '--help', action='store_true', help='Show this help') + @argument('-r', '--reset', action='store_true', help='Reset the shell interpreter') + @argument('-o', '--obj', action='store_true', help='Return this magic object') + @argument('-x', '--expand', action='store_true', help='Enable variable expansion') + @argument('-X', '--no-expand', action='store_true', help='Disable variable expansion') + @argument('-s', '--sudo', action='store_true', help='Enable sudo') + @argument('-S', '--no-sudo', action='store_true', help='Disable sudo') + @argument('-t', '--timeout', type=int, help='Set timeout in seconds') + @argument('command', nargs='*', help='The command to run') + @no_var_expand + def psh(self, line, cell=None): + "Run line or cell in persistent shell" + if cell: cell = shell_replace(cell, self.shell) + if line: line = shell_replace(line, self.shell) + args = self.psh.parser.parse_args(line.split()) + if args.expand: return self._xpand(True) + if args.no_expand: return self._xpand(False) + if args.sudo: return self._sudo (True) + if args.no_sudo: return self._sudo (False) + if args.timeout: return self._timeout(args.timeout) + if args.reset: return self.reset() + if args.help: return self.help() + if args.obj: return self + if args.command: cell = ' '.join(args.command) + if not cell and line: cell=line + disp = True + if cell.endswith(';'): disp,cell = False,cell[:-1] + if not self.o: self.reset() + try: res = self.o(cell) or None + except Exception as e: + self.o = None + raise e from None + if disp and res: print(res) + +# %% ../00_core.ipynb +def create_magic(shell=None): + if not shell: shell = get_ipython() + magic = PshMagic(shell) + shell.register_magic_function(magic.psh, magic_name='psh', magic_kind='line_cell') + +# %% ../00_core.ipynb +def load_ipython_extension(ipython): + "Required function for creating magic" + create_magic(shell=ipython) + +# %% ../00_core.ipynb +def create_ipython_config(): + "Called by `pshnb_install` to install magic" + ipython_dir = Path(get_ipython_dir()) + cf = ipython_dir/'profile_default'/'ipython_config.py' + cf.parent.mkdir(parents=True, exist_ok=True) + if cf.exists() and 'pshnb' in cf.read_text(): return print('pshnb already installed!') + with cf.open(mode='a') as f: f.write("\nc.InteractiveShellApp.extensions.append('pshnb.core')\n\n") + print(f"Jupyter config updated at {cf}") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f2c07bf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=64.0"] +build-backend = "setuptools.build_meta" diff --git a/settings.ini b/settings.ini index d561bba..6141783 100644 --- a/settings.ini +++ b/settings.ini @@ -1,12 +1,12 @@ [DEFAULT] -repo = bashnb -lib_name = bashnb +repo = pshnb +lib_name = pshnb version = 0.0.3 min_python = 3.7 license = apache2 black_formatting = False doc_path = _docs -lib_path = bashnb +lib_path = pshnb nbs_path = . recursive = False tst_flags = notest @@ -15,9 +15,9 @@ cell_number = False branch = main custom_sidebar = False doc_host = https://answerdotai.github.io -doc_baseurl = /bashnb -git_url = https://github.com/answerdotai/bashnb -title = bashnb +doc_baseurl = /pshnb +git_url = https://github.com/answerdotai/pshnb +title = pshnb audience = Developers author = Jeremy Howard author_email = info@answer.ai @@ -27,7 +27,7 @@ keywords = nbdev jupyter notebook python bash language = English status = 3 user = answerdotai -console_scripts = bashnb_install=bashnb:create_ipython_config +console_scripts = pshnb_install=pshnb.core:create_ipython_config requirements = fastcore readme_nb = index.ipynb allowed_metadata_keys = diff --git a/setup.py b/setup.py index a603853..638227b 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ long_description = re.sub(r'src=\"(.*)\.'+ext+'\"', 'src=\"' + raw.format(cfg['user'],cfg['lib_name'])+'/'+cfg['branch']+'/\\1.'+ext+'\"', long_description) setuptools.setup( - name = 'bashnb', + name = 'pshnb', license = lic[0], classifiers = [ 'Development Status :: ' + statuses[int(cfg['status'])], @@ -59,7 +59,7 @@ entry_points = { 'console_scripts': cfg.get('console_scripts','').split(), 'nbdev': [f'{cfg.get("lib_path")} = {cfg.get("lib_path")}._modidx:d'], - 'ipython': [ 'bashnb=bashnb.core:load_ipython_extension' ] + 'ipython': [ 'pshnb=pshnb.core:load_ipython_extension' ] }, **setup_cfg)