├── README.md ├── WebIDE.py ├── main.tpl └── static └── style.css /README.md: -------------------------------------------------------------------------------- 1 | The other day I was going to work on a program on my iPad, while sitting next to my laptop. It seemed silly to be writing on an on-screen keyboard when I had a nice keyboard right next to me. Codea has a feature called AirCode, which starts up a web server on the iPad which lets you connect using a computer and write code in a browser and save it to the iPad when you're done. I made something like this using bottle for [Pythonista](http://omz-software.com/pythonista/). It doesn't work on my iPad because I have an old version of Pythonista (my iPad can only run iOS 5.1.1), but I have tested it on an iPod with the latest version of Pythonista. If anyone has any suggestions or bug reports, I'd be glad to hear them. Also, if anyone has any idea why it doesn't work on old Pythonista, I'd really appreciate help. 2 | 3 | GitHub project here: https://github.com/Ivoah/WebIDE 4 | 5 | See: https://forum.omz-software.com/topic/2714/webide 6 | -------------------------------------------------------------------------------- /WebIDE.py: -------------------------------------------------------------------------------- 1 | from bottle import get, post, route, run, debug, template, request, static_file, error, redirect 2 | import contextlib, json, os, socket, urllib 3 | 4 | try: 5 | import editor 6 | from objc_util import * 7 | PYTHONISTA = True 8 | except ImportError: 9 | PYTHONISTA = False 10 | 11 | ROOT = os.path.expanduser('~/Documents') 12 | 13 | def make_file_tree(dir_path=ROOT): 14 | file_dict = {} 15 | def recur(path, list): 16 | for l in os.listdir(path): 17 | f = os.path.join(path, l) 18 | if l[0] == '.': 19 | continue 20 | elif os.path.isdir(f): 21 | list[l] = {} 22 | recur(f, list[l]) 23 | elif l.split('.')[-1] in ['py', 'txt', 'pyui', 'json']: 24 | list[l] = urllib.pathname2url(f[len(dir_path)+1:]) 25 | recur(dir_path.rstrip('/'), file_dict) 26 | return file_dict 27 | 28 | @get('/') 29 | def edit(): 30 | #file_list = { 31 | # 'filename1': 'path1', 32 | # 'filename2': 'path2', 33 | # 'dirname1': { 34 | # 'filename3': 'path3', 35 | # 'dirname2': { 36 | # 'filename4': 'path4', 37 | # 'filename5': 'path5' 38 | # } 39 | # } 40 | #} 41 | file_list = make_file_tree(ROOT) 42 | file = request.GET.get('file') 43 | if file: 44 | with open(os.path.join(ROOT, file), 'r') as in_file: 45 | code = in_file.read() 46 | if file.split('.')[-1] in ['pyui', 'json']: 47 | code = json.dumps(json.loads(code), indent=4, separators=(',', ': ')) 48 | output = template('main.tpl', files = file_list, save_as = file, code = code) 49 | else: 50 | output = template('main.tpl', files = file_list) 51 | return output 52 | 53 | @post('/') 54 | def submit(): 55 | filename = os.path.join(ROOT, request.forms.get('filename')) 56 | with open(filename, 'w') as f: 57 | f.write(request.forms.get('code').replace('\r', '')) 58 | #if PYTHONISTA: 59 | #editor.reload_files() 60 | 61 | @route('/static/') 62 | def server_static(filepath): 63 | return static_file(filepath, root='./static') 64 | 65 | @error(403) 66 | def mistake403(code): 67 | return 'There is a mistake in your url!' 68 | 69 | @error(404) 70 | def mistake404(code): 71 | return "This is not the page you're looking for *waves hand*" 72 | 73 | def get_local_ip_addr(): 74 | with contextlib.closing( 75 | socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s: 76 | s.connect(('8.8.8.8', 80)) 77 | return s.getsockname()[0] 78 | 79 | print('''\nTo remotely edit Pythonista files: 80 | On your computer open a web browser to http://{}:8080'''.format(get_local_ip_addr())) 81 | 82 | if PYTHONISTA: 83 | print('''\nIf you're using Safari to connect, you can simply select "Pythonista WebIDE" from the Bonjour menu (you may need to enable Bonjour in Safari's advanced preferences).\n''') 84 | NSNetService = ObjCClass('NSNetService') 85 | service = NSNetService.alloc().initWithDomain_type_name_port_('', '_http._tcp', 'Pythonista WebIDE 2', 8080) 86 | try: 87 | service.publish() 88 | debug(True) 89 | run(reloader=not PYTHONISTA, host='0.0.0.0') 90 | finally: 91 | service.stop() 92 | service.release() 93 | else: 94 | debug(True) 95 | run(reloader=True, host='0.0.0.0') 96 | -------------------------------------------------------------------------------- /main.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pythonista WebIDE 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 26 | 27 | 28 | 29 | 70 | 71 |
72 |

Edit File

73 |

74 | 75 |

76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | height: auto; 3 | border: 1px solid #ddd; 4 | border-top-left-radius: 10px; 5 | border-top-right-radius: 10px; 6 | } 7 | 8 | .dropdown-submenu { 9 | position: relative; 10 | } 11 | 12 | .dropdown-submenu>.dropdown-menu { 13 | top: 0; 14 | left: 100%; 15 | margin-top: -6px; 16 | margin-left: -1px; 17 | -webkit-border-radius: 0 6px 6px 6px; 18 | -moz-border-radius: 0 6px 6px; 19 | border-radius: 0 6px 6px 6px; 20 | } 21 | 22 | .dropdown-submenu:hover>.dropdown-menu { 23 | display: block; 24 | } 25 | 26 | .dropdown-submenu>a:after { 27 | display: block; 28 | content: " "; 29 | float: right; 30 | width: 0; 31 | height: 0; 32 | border-color: transparent; 33 | border-style: solid; 34 | border-width: 5px 0 5px 5px; 35 | border-left-color: #ccc; 36 | margin-top: 5px; 37 | margin-right: -10px; 38 | } 39 | 40 | .dropdown-submenu:hover>a:after { 41 | border-left-color: #fff; 42 | } 43 | 44 | .dropdown-submenu.pull-left { 45 | float: none; 46 | } 47 | 48 | .dropdown-submenu.pull-left>.dropdown-menu { 49 | left: -100%; 50 | margin-left: 10px; 51 | -webkit-border-radius: 6px 0 6px 6px; 52 | -moz-border-radius: 6px 0 6px 6px; 53 | border-radius: 6px 0 6px 6px; 54 | } 55 | --------------------------------------------------------------------------------