You will be redirected to {0} shortly.
481 | 482 | """.format(request.path.split('URL:')[1]) 483 | 484 | menu = [] 485 | if request.path == '': 486 | request.path = '/' 487 | res_path = os.path.abspath( 488 | (pub_dir + request.path).replace('\\', '/').replace('//', '/')) 489 | if not res_path.startswith(os.path.abspath(pub_dir)): 490 | # Reject connections that try to break out of the publish directory 491 | return [errors['403']] 492 | if os.path.isdir(res_path): 493 | # is directory 494 | if os.path.exists(res_path): 495 | if os.path.isfile(res_path + '/gophermap'): 496 | in_file = open(res_path + '/gophermap', "r+") 497 | gmap = in_file.read() 498 | in_file.close() 499 | menu = parse_gophermap(source=gmap, 500 | def_host=request.host, 501 | def_port=request.advertised_port, 502 | gophermap_dir=request.path, 503 | pub_dir=pub_dir) 504 | else: 505 | gmap = '?*\t\r\n' 506 | menu = parse_gophermap(source=gmap, 507 | def_host=request.host, 508 | def_port=request.advertised_port, 509 | gophermap_dir=request.path, 510 | pub_dir=pub_dir) 511 | return menu 512 | elif os.path.isfile(res_path): 513 | in_file = open(res_path, "rb") 514 | data = in_file.read() 515 | in_file.close() 516 | return data 517 | 518 | if request.alt_handler: 519 | alt = request.alt_handler(request) 520 | if alt: 521 | return alt 522 | 523 | e = errors['404'] 524 | e.text = e.text.format(request.path) 525 | return [e] 526 | 527 | 528 | def serve(host="127.0.0.1", port=70, advertised_port=None, 529 | handler=handle, pub_dir='pub/', alt_handler=False, 530 | send_period=False, debug=True): 531 | """ 532 | *Server.* Starts serving Gopher requests. Allows for using a custom handler that will return a Bytes, String, or List 533 | object (which can contain either Strings or Items) to send to the client, or the default handler which can serve 534 | a directory. Along with the default handler, you can set an alternate handler to use if a 404 error is generated for 535 | dynamic applications. 536 | """ 537 | if pub_dir is None or pub_dir == '': 538 | pub_dir = '.' 539 | print('Gopher server is now running on', host + ':' + str(port) + '.') 540 | 541 | class GopherProtocol(asyncio.Protocol): 542 | def connection_made(self, transport): 543 | self.transport = transport 544 | print('Connected by', transport.get_extra_info('peername')) 545 | 546 | def data_received(self, data): 547 | request = data.decode('utf-8').replace('\r\n', '').split('\t') 548 | path = request[0] 549 | query = '' 550 | if len(request) > 1: 551 | query = request[1] 552 | if debug: 553 | print('Client requests: {}'.format(request)) 554 | 555 | resp = handler( 556 | Request(path=path, query=query, host=host, 557 | port=port, advertised_port=advertised_port, 558 | client=self.transport.get_extra_info( 559 | 'peername')[0], pub_dir=pub_dir, 560 | alt_handler=alt_handler)) 561 | 562 | if type(resp) == str: 563 | resp = bytes(resp, 'utf-8') 564 | elif type(resp) == list: 565 | out = "" 566 | for line in resp: 567 | if type(line) == Item: 568 | out += line.source() 569 | elif type(line) == str: 570 | line = line.replace('\r\n', '\n') 571 | line = line.replace('\n', '\r\n') 572 | if not line.endswith('\r\n'): 573 | line += '\r\n' 574 | out += line 575 | resp = bytes(out, 'utf-8') 576 | elif type(resp) == Item: 577 | resp = bytes(resp.source(), 'utf-8') 578 | 579 | self.transport.write(resp) 580 | if send_period: 581 | self.transport.write(b'.') 582 | 583 | self.transport.close() 584 | if debug: 585 | print('Connection closed') 586 | 587 | async def main(h, p): 588 | loop = asyncio.get_running_loop() 589 | server = await loop.create_server(GopherProtocol, h, p) 590 | await server.serve_forever() 591 | 592 | asyncio.run(main('0.0.0.0', port)) 593 | -------------------------------------------------------------------------------- /examples/catenifer.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | 3 | home = 'gopher://gopher.floodgap.com/1/' 4 | typeIcons = { 5 | "i": "ℹ️", 6 | "3": "⚠️", 7 | "1": "🚪", 8 | "0": "📝", 9 | "h": "🌎", 10 | "7": "🔍", 11 | "9": "⚙️" 12 | } 13 | noLinkTypes = {"i", "h", "3"} 14 | compatibleTypes = {'0', '1', '7'} 15 | menuTypes = {'1', '7'} 16 | lastType = '1' 17 | 18 | 19 | def bold(txt): 20 | return "\033[1m" + txt + "\033[0;0m" 21 | 22 | 23 | requests = {} 24 | 25 | 26 | def go(url, itype=''): 27 | req = pituophis.parse_url(url) 28 | # if req.url().endswith('/'): 29 | # req.type = '1' parse_url() now does this in Pituophis 1.0 30 | if itype == '7': 31 | req.type = itype 32 | print(bold('URL: ' + req.url())) 33 | if req.type == '7': 34 | if req.query == '': 35 | req.query = input(bold('Search term: ')) 36 | if req.type in compatibleTypes: 37 | resp = req.get() 38 | if req.type in menuTypes: 39 | menu = resp.menu() 40 | items = 0 41 | for selector in menu: 42 | text = typeIcons['9'] 43 | if selector.type in typeIcons: 44 | text = typeIcons[selector.type] 45 | text = text + ' ' + selector.text 46 | if selector.type not in noLinkTypes: 47 | items += 1 48 | requests[items] = selector.request() 49 | text = text + ' (' + requests[items].url() + ') ' + bold('[#' + str(items) + ']') 50 | if selector.path.startswith('URL:'): 51 | text = text + ' (' + selector.path.split('URL:')[1] + ')' 52 | print(text) 53 | elif req.type == '0': 54 | print(resp.text()) 55 | else: 56 | print("nyi binaries") 57 | 58 | 59 | go(home) 60 | while True: 61 | cmd = input(bold('# or URL: ')) 62 | try: 63 | cmd = int(cmd) 64 | go(requests[cmd].url(), requests[cmd].type) 65 | except Exception: 66 | go(cmd) 67 | -------------------------------------------------------------------------------- /examples/server.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | pituophis.serve("127.0.0.1", 70, pub_dir='pub/') # typical Gopher port is 70 3 | -------------------------------------------------------------------------------- /examples/server_cgi.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | 3 | def alt(request): 4 | if request.path == '/': 5 | return [pituophis.Item(text='root')] 6 | if request.path == '/test': 7 | return [pituophis.Item(text='test!')] 8 | 9 | pituophis.serve("127.0.0.1", 70, pub_dir='pub/', alt_handler=alt) # typical Gopher port is 70 10 | -------------------------------------------------------------------------------- /examples/server_custom.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | from pituophis import Item 3 | 4 | 5 | def handle(request): 6 | if request.path == '/txt': 7 | text = """ 8 | This is plain text. 9 | Nothing fancy. 10 | """ 11 | return text 12 | elif request.path == '/server.png': 13 | in_file = open("server.png", "rb") # you'd need a file with the name server.png in the working directory, naturally 14 | data = in_file.read() 15 | in_file.close() 16 | return data 17 | else: 18 | # Note that clients may send '.' or '' when they want the root of the server; 19 | # the . behavior has been observed in Gophpup (an early Windows client) and may be the case for others. 20 | menu = [ 21 | Item(text="Path: " + request.path), 22 | Item(text="Query: " + request.query), 23 | Item(text="Host: " + request.host), 24 | Item(text="Port: " + str(request.port)), 25 | Item(text="Client: " + request.client), 26 | Item(), 27 | Item(itype="I", text="View server.png", path="/server.png", host=request.host, port=request.port), 28 | Item(itype="0", text="View some text", path="/txt", host=request.host, port=request.port) 29 | ] 30 | return menu 31 | 32 | 33 | # serve with custom handler 34 | pituophis.serve("127.0.0.1", 70, handler=handle) 35 | -------------------------------------------------------------------------------- /examples/server_custom_comments.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | from pituophis import Item 3 | 4 | # TODO: Save comment entries to a file. 5 | 6 | comments = [] 7 | 8 | 9 | def handle(request): 10 | if request.path == '/add': 11 | menu = [Item(text="Comment added."), 12 | Item(itype='1', text="View comments", path="/", host=request.host, port=request.port)] 13 | comments.append(request.query) 14 | return menu 15 | 16 | menu = [Item(text='Welcome!'), 17 | Item(), 18 | Item(itype='7', text="Add a comment.", path="/add", host=request.host, port=request.port), 19 | Item()] 20 | if len(comments) == 0: 21 | menu.append(Item(text="There are no messages yet.. be the first!")) 22 | for entry in comments: 23 | menu.append(Item(text=str(entry))) 24 | return menu 25 | 26 | 27 | # serve with custom handler 28 | pituophis.serve("127.0.0.1", 70, handler=handle) 29 | -------------------------------------------------------------------------------- /examples/server_custom_reroute.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | 3 | # This example directly reroutes the client's request to another server and port. 4 | # It then fetches the request, then sends the received binary data. 5 | 6 | def handle(request): 7 | request.host = 'gopher.floodgap.com' 8 | request.port = 70 9 | resp = request.get() 10 | return resp.binary 11 | 12 | # serve with custom handler 13 | pituophis.serve("127.0.0.1", 70, handler=handle) 14 | -------------------------------------------------------------------------------- /examples/tests_client.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | 3 | # this is an antique already! 4 | 5 | yes = ['y', 'yes', 'yeah', 'yep', 'yup', 'ye', 'sure'] 6 | 7 | print(""" 8 | pituophis testing grounds 9 | would you like to... 10 | 1. view a gopher menu, parsed 11 | 2. view a gopher menu, unparsed 12 | 3. run a search for "test" with veronica 2 13 | 4. download a file 14 | 5. try it yourself 15 | 6. enter a host or URL 16 | """) 17 | 18 | choices = ['1', '2', '3', '4', '5', '6'] 19 | 20 | choice = '' 21 | while not choice in choices: 22 | choice = input('> ') 23 | 24 | host = 'gopher.floodgap.com' 25 | port = 70 26 | path = '/' 27 | query = '' 28 | binary = False 29 | menu = False 30 | 31 | if choice == '1': 32 | menu = True 33 | if choice == '2': 34 | pass 35 | if choice == '3': 36 | path = '/v2/vs' 37 | query = 'test' 38 | if choice == '4': 39 | binary = True 40 | #path = '/archive/info-mac/edu/yng/kid-pix.hqx' 41 | path = '/gopher/clients/win/hgopher2_3.zip' 42 | if choice == '5': 43 | host = input('host: ') 44 | port = int(input('port: ')) 45 | path = input('path: ') 46 | query = input('query: ') 47 | binary = False 48 | if input('binary? (y/n): ') in yes: 49 | binary = True 50 | menu = False 51 | if not binary: 52 | if input('menu? (y/n): ') in yes: 53 | menu = True 54 | if choice == '6': 55 | if input('binary? (y/n): ') in yes: 56 | binary = True 57 | host = input('host/url: ') 58 | 59 | response = pituophis.get(host, port=port, path=path, query=query) 60 | if binary: 61 | print(""" 62 | what to do with this binary? 63 | 1. decode utf-8 64 | 2. save to disk 65 | """) 66 | choices = ['1', '2'] 67 | choice = '' 68 | while not choice in choices: 69 | choice = input('> ') 70 | if choice == '1': 71 | print(response.text()) 72 | else: 73 | if choice == '2': 74 | suggested_filename = path.split('/')[len(path.split('/')) - 1] 75 | filename = input('filename (' + suggested_filename + ')? ') 76 | if filename == '': 77 | filename = suggested_filename 78 | with open(filename, "wb") as f: 79 | f.write(response.binary) 80 | else: 81 | if menu: 82 | print(response.menu()) 83 | else: 84 | print(response.text()) -------------------------------------------------------------------------------- /examples/tests_client_ipv6.py: -------------------------------------------------------------------------------- 1 | import pituophis 2 | 3 | # Run a server on localhost, port 70 4 | 5 | req = pituophis.Request() 6 | req.host = '[::1]' # or [0:0:0:0:0:0:0:1] (make sure you have []) 7 | resp = req.get() 8 | 9 | menu = resp.menu() 10 | 11 | for item in menu: 12 | print('--') 13 | print(item.type) 14 | print(item.text) 15 | print(item.path) 16 | print(item.host) 17 | print(item.port) 18 | 19 | print('--') 20 | -------------------------------------------------------------------------------- /make_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # pdoc 4 | pdoc --html pituophis --html-dir docs/ --overwrite 5 | 6 | # sphinx 7 | cd sphinx 8 | make html 9 | make text -------------------------------------------------------------------------------- /pituophis/__init__.py: -------------------------------------------------------------------------------- 1 | # BSD 2-Clause License 2 | # 3 | # Copyright (c) 2020, dotcomboomYou will be redirected to {0} shortly.
484 | 485 | """.format(request.path.split('URL:')[1]) 486 | 487 | menu = [] 488 | if request.path in ['', '.']: 489 | request.path = '/' 490 | res_path = os.path.abspath( 491 | (pub_dir + request.path) 492 | .replace('\\', '/').replace('//', '/')) 493 | print(res_path) 494 | if not res_path.startswith(os.path.abspath(pub_dir)): 495 | # Reject connections that try to break out of the publish directory 496 | return [errors['403']] 497 | if os.path.isdir(res_path): 498 | # is directory 499 | if os.path.exists(res_path): 500 | if os.path.isfile(res_path + '/gophermap'): 501 | in_file = open(res_path + '/gophermap', "r+") 502 | gmap = in_file.read() 503 | in_file.close() 504 | menu = parse_gophermap(source=gmap, 505 | def_host=request.host, 506 | def_port=request.advertised_port, 507 | gophermap_dir=request.path, 508 | pub_dir=pub_dir) 509 | else: 510 | gmap = '?*\t\r\n' 511 | menu = parse_gophermap(source=gmap, 512 | def_host=request.host, 513 | def_port=request.advertised_port, 514 | gophermap_dir=request.path, 515 | pub_dir=pub_dir) 516 | return menu 517 | elif os.path.isfile(res_path): 518 | in_file = open(res_path, "rb") 519 | data = in_file.read() 520 | in_file.close() 521 | return data 522 | 523 | if request.alt_handler: 524 | alt = request.alt_handler(request) 525 | if alt: 526 | return alt 527 | 528 | e = errors['404'] 529 | e.text = e.text.format(request.path) 530 | return [e] 531 | 532 | 533 | def serve(host="127.0.0.1", port=70, advertised_port=None, 534 | handler=handle, pub_dir='pub/', alt_handler=False, debug=True): 535 | """ 536 | *Server.* Starts serving Gopher requests. Allows for using a custom handler that will return a Bytes, String, or List 537 | object (which can contain either Strings or Items) to send to the client, or the default handler which can serve 538 | a directory. Along with the default handler, you can set an alternate handler to use if a 404 error is generated for 539 | dynamic applications. send_period is good practice to the RFC and required for some clients to work. 540 | """ 541 | if pub_dir is None or pub_dir == '': 542 | pub_dir = '.' 543 | print('Gopher server is now running on', host + ':' + str(port) + '.') 544 | 545 | class GopherProtocol(asyncio.Protocol): 546 | def connection_made(self, transport): 547 | self.transport = transport 548 | print('Connected by', transport.get_extra_info('peername')) 549 | 550 | def data_received(self, data): 551 | request = data.decode('utf-8').replace('\r\n', '').replace('\n', '').split('\t') # \n is used at the end of Gopher Client (iOS)'s requests 552 | path = request[0] 553 | query = '' 554 | if len(request) > 1: 555 | query = request[1] 556 | if debug: 557 | print('Client requests: {}'.format(request)) 558 | 559 | resp = handler( 560 | Request(path=path, query=query, host=host, 561 | port=port, advertised_port=advertised_port, 562 | client=self.transport.get_extra_info( 563 | 'peername')[0], pub_dir=pub_dir, 564 | alt_handler=alt_handler)) 565 | 566 | if type(resp) == str: 567 | resp = bytes(resp, 'utf-8') 568 | elif type(resp) == list: 569 | out = "" 570 | for line in resp: 571 | if type(line) == Item: 572 | out += line.source() 573 | elif type(line) == str: 574 | line = line.replace('\r\n', '\n') 575 | line = line.replace('\n', '\r\n') 576 | if not line.endswith('\r\n'): 577 | line += '\r\n' 578 | out += line 579 | resp = bytes(out + '.\r\n', 'utf-8') # Menus are sent with the Lastline at the end; see below 580 | elif type(resp) == Item: 581 | resp = bytes(resp.source(), 'utf-8') 582 | 583 | self.transport.write(resp) 584 | 585 | # According to RFC 1436, the Lastline is '.'CR-LF ('.\r\n'), and it is preceded by 'a block' of ASCII text. 586 | # Lastline is now put in after menus; this functionality replaces the former send_period option. 587 | 588 | # Lastline is not put in after text file contents at this time, because that's kinda tricky: not all other servers do it, 589 | # most clients seem to show the Lastline when present, and the RFC suggested that clients be prepared for the server to send it without 590 | # for TextFile entities anyways (suggested that the client could then also use fingerd servers). 591 | 592 | self.transport.close() 593 | if debug: 594 | print('Connection closed') 595 | 596 | async def main(h, p): 597 | loop = asyncio.get_running_loop() 598 | server = await loop.create_server(GopherProtocol, h, p) 599 | await server.serve_forever() 600 | 601 | asyncio.run(main('0.0.0.0', port)) 602 | -------------------------------------------------------------------------------- /pituophis/cli.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import sys 3 | import pituophis 4 | 5 | # check if the user is running the script with the correct number of arguments 6 | if len(sys.argv) < 2: 7 | # if not, print the usage 8 | print('usage: pituophis [command] cd [options]') 9 | print('Commands:') 10 | print(' serve [options]') 11 | print(' fetch [url] [options]') 12 | print('Server Options:') 13 | print(' -H, --host=HOST\t\tAdvertised host (default: 127.0.0.1)') 14 | print(' -p, --port=PORT\t\tPort to bind to (default: 70)') 15 | print(' -a, --advertised-port=PORT\tPort to advertise') 16 | print(' -d, --directory=DIR\t\tDirectory to serve (default: pub/)') 17 | print(' -A, --alt-handler=HANDLER\tAlternate handler to use if 404 error is generated (python file with it defined as "def alt(request):")') 18 | print(' -s, --send-period\t\tSend a period at the end of each response (default: False)') 19 | print(' -D, --debug\t\t\tPrint requests as they are received (default: False)') 20 | print(' -v, --version\t\t\tPrint version') 21 | print('Fetch Options:') 22 | print(' -o, --output=FILE\t\tFile to write to (default: stdout)') 23 | else: 24 | # check if the user is serving or fetching 25 | if sys.argv[1] == 'serve': 26 | # check for arguments 27 | # host 28 | host = '127.0.0.1' 29 | if '-H' in sys.argv or '--host' in sys.argv: 30 | host = sys.argv[sys.argv.index('-H') + 1] 31 | # port 32 | port = 70 33 | if '-p' in sys.argv or '--port' in sys.argv: 34 | port = int(sys.argv[sys.argv.index('-p') + 1]) 35 | # advertised port 36 | advertised_port = None 37 | if '-a' in sys.argv or '--advertised-port' in sys.argv: 38 | advertised_port = int(sys.argv[sys.argv.index('-a') + 1]) 39 | # directory 40 | pub_dir = 'pub/' 41 | if '-d' in sys.argv or '--directory' in sys.argv: 42 | pub_dir = sys.argv[sys.argv.index('-d') + 1] 43 | # alternate handler 44 | alt_handler = False 45 | if '-A' in sys.argv or '--alt-handler' in sys.argv: 46 | alt_handler = sys.argv[sys.argv.index('-A') + 1] 47 | # get the function from the file 48 | alt_handler = getattr( 49 | importlib.import_module(alt_handler), 'handler') 50 | 51 | # send period 52 | send_period = False 53 | if '-s' in sys.argv or '--send-period' in sys.argv: 54 | send_period = True 55 | # debug 56 | debug = False 57 | if '-D' in sys.argv or '--debug' in sys.argv: 58 | debug = True 59 | # start the server 60 | pituophis.serve(host=host, port=port, advertised_port=advertised_port, 61 | handler=pituophis.handle, pub_dir=pub_dir, alt_handler=alt_handler, 62 | send_period=send_period, debug=debug) 63 | elif sys.argv[1] == 'fetch': 64 | # check for arguments 65 | # url 66 | url = sys.argv[2] 67 | # output file 68 | output = 'stdout' 69 | if '-o' in sys.argv or '--output' in sys.argv: 70 | output = sys.argv[sys.argv.index('-o') + 1] 71 | # start the fetch 72 | o = pituophis.get(url) 73 | if output == 'stdout': 74 | sys.stdout.buffer.write(o.binary) 75 | else: 76 | with open(output, 'wb') as f: 77 | f.write(o.binary) 78 | f.close() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | natsort -------------------------------------------------------------------------------- /server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcomboom/Pituophis/85464ad281c84dc68043ebe06a467b24dfa1f747/server.png -------------------------------------------------------------------------------- /server_def.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcomboom/Pituophis/85464ad281c84dc68043ebe06a467b24dfa1f747/server_def.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name='Pituophis', 8 | version='1.2', 9 | install_requires=['natsort'], 10 | packages=['pituophis'], 11 | url='https://github.com/dotcomboom/Pituophis', 12 | license='BSD 2-Clause License', 13 | author='dotcomboom', 14 | author_email='dotcomboom@somnolescent.net', 15 | description='Gopher client and server module for Python 3', 16 | long_description=long_description, 17 | long_description_content_type="text/markdown", 18 | classifiers=[ 19 | "Development Status :: 5 - Production/Stable", 20 | "Intended Audience :: Developers", 21 | "Intended Audience :: Information Technology", 22 | "Natural Language :: English", 23 | "Programming Language :: Python :: 3", 24 | "License :: OSI Approved :: BSD License", 25 | "Operating System :: OS Independent" 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /sphinx/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | # BUILDDIR = build 9 | BUILDDIR = ../docs 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /sphinx/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /sphinx/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx_rtd_theme 2 | recommonmark 3 | sphinx == 1.8.4 4 | natsort 5 | -------------------------------------------------------------------------------- /sphinx/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath('../../')) 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'Pituophis' 23 | copyright = '2019, dotcomboom' 24 | author = 'dotcomboom' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '' 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | # 35 | # needs_sphinx = '1.0' 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | 'sphinx.ext.autodoc', 42 | 'sphinx.ext.napoleon', 43 | 'sphinx.ext.todo', 44 | 'sphinx.ext.coverage', 45 | 'sphinx.ext.githubpages', 46 | 'recommonmark' 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = ['.rst', '.md'] 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This pattern also affects html_static_path and html_extra_path. 71 | exclude_patterns = [] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = None 75 | 76 | # -- Options for HTML output ------------------------------------------------- 77 | 78 | # The theme to use for HTML and HTML Help pages. See the documentation for 79 | # a list of builtin themes. 80 | # 81 | html_theme = 'sphinx_rtd_theme' 82 | 83 | # Theme options are theme-specific and customize the look and feel of a theme 84 | # further. For a list of options available for each theme, see the 85 | # documentation. 86 | # 87 | # html_theme_options = {} 88 | 89 | # Add any paths that contain custom static files (such as style sheets) here, 90 | # relative to this directory. They are copied after the builtin static files, 91 | # so a file named "default.css" will overwrite the builtin "default.css". 92 | html_static_path = ['_static'] 93 | 94 | # Custom sidebar templates, must be a dictionary that maps document names 95 | # to template names. 96 | # 97 | # The default sidebars (for documents that don't match any pattern) are 98 | # defined by theme itself. Builtin themes are using these templates by 99 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 100 | # 'searchbox.html']``. 101 | # 102 | # html_sidebars = {} 103 | 104 | 105 | # -- Options for HTMLHelp output --------------------------------------------- 106 | 107 | # Output file base name for HTML help builder. 108 | htmlhelp_basename = 'Pituophisdoc' 109 | 110 | # -- Options for LaTeX output ------------------------------------------------ 111 | 112 | latex_elements = { 113 | # The paper size ('letterpaper' or 'a4paper'). 114 | # 115 | # 'papersize': 'letterpaper', 116 | 117 | # The font size ('10pt', '11pt' or '12pt'). 118 | # 119 | # 'pointsize': '10pt', 120 | 121 | # Additional stuff for the LaTeX preamble. 122 | # 123 | # 'preamble': '', 124 | 125 | # Latex figure (float) alignment 126 | # 127 | # 'figure_align': 'htbp', 128 | } 129 | 130 | # Grouping the document tree into LaTeX files. List of tuples 131 | # (source start file, target name, title, 132 | # author, documentclass [howto, manual, or own class]). 133 | latex_documents = [ 134 | (master_doc, 'Pituophis.tex', 'Pituophis Documentation', 135 | 'dotcomboom', 'manual'), 136 | ] 137 | 138 | # -- Options for manual page output ------------------------------------------ 139 | 140 | # One entry per manual page. List of tuples 141 | # (source start file, name, description, authors, manual section). 142 | man_pages = [ 143 | (master_doc, 'pituophis', 'Pituophis Documentation', 144 | [author], 1) 145 | ] 146 | 147 | # -- Options for Texinfo output ---------------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'Pituophis', 'Pituophis Documentation', 154 | author, 'Pituophis', 'One line description of project.', 155 | 'Miscellaneous'), 156 | ] 157 | 158 | # -- Options for Epub output ------------------------------------------------- 159 | 160 | # Bibliographic Dublin Core info. 161 | epub_title = project 162 | 163 | # The unique identifier of the text. This can be a ISBN number 164 | # or the project homepage. 165 | # 166 | # epub_identifier = '' 167 | 168 | # A unique identification for the text. 169 | # 170 | # epub_uid = '' 171 | 172 | # A list of files that should not be packed into the epub file. 173 | epub_exclude_files = ['search.html'] 174 | 175 | # -- Extension configuration ------------------------------------------------- 176 | 177 | # -- Options for todo extension ---------------------------------------------- 178 | 179 | # If true, `todo` and `todoList` produce output, else they produce nothing. 180 | todo_include_todos = True 181 | -------------------------------------------------------------------------------- /sphinx/source/index.rst: -------------------------------------------------------------------------------- 1 | Pituophis API 2 | ================= 3 | 4 | * :ref:`genindex` 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents: 9 | 10 | .. automodule:: pituophis 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: -------------------------------------------------------------------------------- /sphinx/source/modules.rst: -------------------------------------------------------------------------------- 1 | pituophis 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pituophis 8 | -------------------------------------------------------------------------------- /treegopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotcomboom/Pituophis/85464ad281c84dc68043ebe06a467b24dfa1f747/treegopher.png --------------------------------------------------------------------------------