├── LICENSE ├── README.md ├── kernel.json └── swiftkernel.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tim Nugent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift kernel for Project Jupyter 2 | 3 | A simple kernel that adds Apple's [Swift programming language](https://swift.org) into [Project Jupyter](https://jupyter.org). 4 | 5 | Thanks to [O'Reilly Media](http://www.oreilly.com) for sponsoring this development, I would never have gotten around to this without them. 6 | 7 | This is implemented as a Python wrapper kernel and supports the basic functionality of Jupyter in both the console and in notebooks. 8 | It works by creating a temporary file, dumping the user text into the file and running that through swift. 9 | The output is captured, massaged a little bit, and then sent back to Jupyter. 10 | 11 | ## Installation 12 | 13 | ### Requirements 14 | 15 | - Swift 3 16 | - Python (I used 2.7 but 3 should also be fine) 17 | - Jupyter 18 | 19 | ### Steps 20 | 21 | 1. [Install Swift](https://swift.org/download/) for your platform 22 | 2. [Install Jupyter](http://jupyter.org/install.html) 23 | 3. Download the kernel and save it somewhere memorable. The important files are `kernel.json` and `swiftkernel.py` 24 | 4. Install the kernel into Jupyter: `jupyter kernelspec install /path/to/swiftkernel --user` 25 | - You can verify the kernel installed correctly: `jupyter kernelspec list` 26 | - It will appear in the list of kernels installed under the name of the project folder 27 | 5. Run Jupyter and start using Swift 28 | - To use the kernel in the Jupyter console: `jupyter console --kernel kernelname` 29 | - to use the kernel in a notebook: `jupyter notebook` and create a new notebook through the browser 30 | 31 | ## Caveats 32 | 33 | - If you crash the kernel or Jupyter without exiting it will leave temporary files called `canonical.swift` and `scratch.swift` on your machine. 34 | - Unlike many other Jupiter kernels if you type something like `1 + 1` it will display no result because of how I implemented the kernel. So wrap any code like that inside a `print()` call to see it. The code will still run so you can do things like `let thing = 5` and then later `print(thing)` and this will work fine. 35 | - errors just appear underneath the code as valid output. 36 | 37 | ## Future features 38 | 39 | - Add prints to non-printing statements 40 | - Proper error handling 41 | - Implement Jupyter's nice to have features 42 | - Work out a better way of interfacing with Swift that doesn't need temporary files 43 | - Support warnings instead of just ignoring them 44 | - Add in proper tests 45 | 46 | ## FAQ 47 | 48 | **Tim, did anyone actually ask you any of these?** 49 | 50 | No, but I've always wanted to answer an FAQ so I made one up. 51 | 52 | **Tim, why do you write in this manner?** 53 | 54 | I'm gonna blame being Australian, sure, yeah, let's go with that. 55 | 56 | **Does this work on Linux?** 57 | 58 | I did all my development on MacOS but I did test it briefly on Ubuntu and it seemed ok, so yeah..? 59 | 60 | **Why a wrapper kernel?** 61 | 62 | It is the fastest and easiest way to get a Jupyter kernel up and running. The initial idea was to wrap the Swift REPL using replwrap but that fell apart quickly when I realised that the Swift REPL does a few weird things that make it tricky to wrap. At that point I'd already created a file called `swiftkernel.py` and thought I might as well keep going with Python anyway. 63 | 64 | **Why Python 2.7 and not 3.x?** 65 | 66 | It is what was installed on my Macbook. Nothing I've done is 2.7 specific and everything will work fine in Python 3.x. As I already had 2.7 installed on my machine I didn't see the benefit in installing another version. Had I realised how weird the default macOS install of Python was I probably would have. 67 | 68 | **I typed a statement in, ran it and nothing appeared, what gives?** 69 | 70 | Because of how it is currently implemented statements that are non-printing, such as `1 + 1` or `let thing = 4`, do not display any result back to Jupyter. I plan on updating the code to fix this but for now just add a print statement in yourself on anything you want to see. Your code will still run fine, it just won't display anything until you make it. 71 | 72 | **I keep getting an error saying something like "No module named SwiftKernel" when running the kernel what's up?** 73 | 74 | I had this appear almost randomly during development as I have a very broken installation of Python on my Macbook from years of doing horrible things to it. I fixed it by manually setting PYTHONPATH in Bash to where the kernel was installed by Jupyter and this fixed it. According to the Jupyter docs this shouldn't be necessary but hey, maybe you've also done terrible things to Python. 75 | 76 | **Tim, this Python code looks like it was written by someone who doesn't know how to Python** 77 | 78 | That'd be me, I am a Swift, mobile, and game developer. I normally only use Python when I need to make something quick and dirty for scripting, this is my first Python program longer than about 30 lines of code. 79 | Please fix it up and send in a pull request. 80 | 81 | **Tim, you've used Library X incorrectly** 82 | 83 | See above answer. 84 | 85 | **Can I help with this?** 86 | 87 | Please do! Improve my documentation, submit a pull request, open an issue, [tweet at me](https://twitter.com/the_mcjones) saying you like the kernel, buy me a drink or socks, etc etc. I would love for some other people to help make this better than what it currently is. 88 | -------------------------------------------------------------------------------- /kernel.json: -------------------------------------------------------------------------------- 1 | {"argv":["python","-m","swiftkernel", "-f", "{connection_file}"], 2 | "display_name":"Swift" 3 | } -------------------------------------------------------------------------------- /swiftkernel.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # a swift kernel for Jupyter 4 | # copyright Tim Nugent, made available under the MIT License 5 | # see the repository https://github.com/McJones/jupyter-swift-kernel/ for full details 6 | 7 | import subprocess, os, shutil, tempfile, re 8 | from ipykernel.kernelbase import Kernel 9 | 10 | class SwiftKernel(Kernel): 11 | # Jupiter stuff 12 | implementation = 'Swift' 13 | implementation_version = '1.1.1' 14 | language = 'swift' 15 | language_version = '3.0.2' 16 | language_info = {'mimetype': 'text/plain', 'file_extension': 'swift', 'name': 'swift'} 17 | banner = "Swift kernel" 18 | # my stuff 19 | output = "" 20 | swiftDirectory = tempfile.mkdtemp() 21 | 22 | def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): 23 | errorCode, dump = self.runCode(code) 24 | 25 | if errorCode == 0: 26 | 27 | if not silent: 28 | stream = {'name':'stdout', 'text':dump} 29 | self.send_response(self.iopub_socket, 'stream', stream) 30 | 31 | return { 32 | 'status':'ok', 33 | 'execution_count':self.execution_count, 34 | 'payload':[], 35 | 'user_expressions':{} 36 | } 37 | else: 38 | # every example does it like this but this just feels weird 39 | # why does the execution_count increment?! 40 | if not silent: 41 | stream = { 42 | 'status' : 'error', 43 | 'ename': 'ERROR', 44 | 'evalue': 'error', 45 | 'traceback': dump 46 | } 47 | self.send_response(self.iopub_socket, 'error', stream) 48 | 49 | return { 50 | 'status':'error', 51 | 'execution_count':self.execution_count, 52 | 'ename': 'ERROR', 53 | 'evalue': 'error', 54 | 'traceback': dump 55 | } 56 | 57 | def do_shutdown(self, restart): 58 | # delete the temporary swift file(s) and directory 59 | shutil.rmtree(self.swiftDirectory) 60 | 61 | # appends the new text to the swift file 62 | # runs the swift file 63 | # capture all output 64 | # returns the result 65 | def runCode(self, command): 66 | swiftFileLocation = os.path.join(self.swiftDirectory, 'scratch.swift') 67 | canonicalFile = os.path.join(self.swiftDirectory, 'canonical.swift') 68 | 69 | # now copy everything from canonical into the scratch 70 | if os.path.isfile(canonicalFile): 71 | shutil.copyfile(canonicalFile, swiftFileLocation) 72 | 73 | with open(swiftFileLocation, 'a') as swiftFile: 74 | unicodeCommand = (command + "\n").encode("UTF-8") 75 | swiftFile.write(unicodeCommand) 76 | 77 | errorOutput = [] 78 | 79 | # because who needs warnings, right?! 80 | # queue up mental picture of Holtzman while reading the above comment please 81 | cmd = 'swift -suppress-warnings {0}'.format(swiftFileLocation) 82 | swift = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 83 | 84 | # handle all valid output 85 | newOutput = swift.stdout.read() 86 | 87 | # handle any errors 88 | for line in swift.stderr.readlines(): 89 | # to clean up the default error message swift returns 90 | line = re.sub('^.*error: ', '', line) 91 | errorOutput.append(line.rstrip("\n\r")) 92 | 93 | retval = swift.wait() 94 | 95 | # ran without error 96 | if retval == 0: 97 | # putting the valid code back into the canonical file 98 | shutil.copyfile(swiftFileLocation, canonicalFile) 99 | # returning the result 100 | diff = newOutput[len(self.output):] 101 | self.output = newOutput 102 | return 0, diff 103 | else: 104 | # dumping the dodgy file 105 | os.remove(swiftFileLocation) 106 | # returning the error(s) 107 | return 1, errorOutput 108 | 109 | if __name__ == '__main__': 110 | from ipykernel.kernelapp import IPKernelApp 111 | IPKernelApp.launch_instance(kernel_class=SwiftKernel) 112 | --------------------------------------------------------------------------------