Last active
October 16, 2019 15:04
-
-
Save Aran-Fey/b01c2dd5a8c5e36bf54ad006c8a98b38 to your computer and use it in GitHub Desktop.
A userscript that automatically turns text surrounded by quotation marks like ?foo? or ?foo.bar? into links to the documentation for "foo" or "foo.bar". A short user manual can be found in the code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name StackOverflow auto documentation links | |
// @description Converts text enclosed in quotation marks like ?list.append()? to a link to the relevant documentation | |
// @version 1.3.3 | |
// @author Paul Pinterits | |
// @include *://*.stackexchange.com/questions/* | |
// @include *://meta.serverfault.com/questions/* | |
// @include *://meta.stackoverflow.com/questions/* | |
// @include *://meta.superuser.com/questions/* | |
// @include *://serverfault.com/questions/* | |
// @include *://stackoverflow.com/questions/* | |
// @include *://superuser.com/questions/* | |
// @include *://chat.stackoverflow.com/rooms/* | |
// @exclude *://*/questions/tagged/* | |
// @namespace Aran-Fey | |
// @grant none | |
// @updateURL https://gist.github.com/Aran-Fey/b01c2dd5a8c5e36bf54ad006c8a98b38/raw/SO_documentation_links.user.js | |
// @downloadURL https://gist.github.com/Aran-Fey/b01c2dd5a8c5e36bf54ad006c8a98b38/raw/SO_documentation_links.user.js | |
// ==/UserScript== | |
/* =================== | |
* === USER MANUAL === | |
* =================== | |
* | |
* Text surrounded by quotation marks like ?foo.bar? is converted to a link like [foo.bar](url_to_module_foo#bar). | |
* This also works if the text is surrounded by backticks like `?foo.bar?`, which will automatically wrap the | |
* link around the backticks like [`foo.bar`](url_to_module_foo#bar). A function-call-like syntax is also supported, | |
* so you can write things like `?foo.bar()?` and it'll still insert a link to `foo.bar`. | |
* | |
* The script is active in answers, comments, and questions on StackOverflow. It also works in SO chatrooms. | |
* | |
* If the function/class name is unique in the entire stdlib, the module name can be omitted. | |
* For example, ?ArgumentParser.parse_args? links to argparse.ArgumentParser.parse_args. | |
* | |
* The script is stupid and doesn't care much if the url is valid. | |
* If you enter something that doesn't have an url, you'll get a broken url. | |
* | |
* The substitution takes place 4 seconds after you stop typing or immediately when you post your answer/message. | |
* | |
* === SPECIAL DIRECTIVES === | |
* | |
* °) Modules | |
* ?xyz module? creates a link to the official documentation of module xyz | |
* | |
* °) PEPs | |
* ?pep 123? creates a link of the form [PEP 123](url_to_pep_123) | |
* | |
* °) numpy | |
* ?np.foo? links to the numpy docs | |
* | |
* °) pandas | |
* ?df.foo? links to the DataFrame.foo method | |
* ?series.foo? links to the Series.foo method | |
* | |
* °) dundermethods | |
* ?__foo__? links to object.__foo__ in the Data Model docs | |
* | |
* °) datetimes | |
* ?dt.foo? is a shortcut for datetime.datetime.foo | |
* | |
* °) enums | |
* ?enum? links to the enum module docs | |
* */ | |
var REPL_REGEX = /\?(?! )([-. \w]+)((?:\(.*?\)+)?)\?/g; | |
var CODEBLOCK_REPL_REGEX = new RegExp('`'+REPL_REGEX.source+'`', 'g'); | |
var REPLACEMENTS = new Map([ | |
[/(abs|help|min|setattr|all|dir|hex|next|slice|any|divmod|id|object|sorted|ascii|enumerate|input|oct|staticmethod|bin|eval|int|open|bool|exec|isinstance|ord|sum|bytearray|filter|issubclass|pow|super|float|iter|print|callable|format|len|property|type|chr|vars|classmethod|getattr|locals|repr|zip|compile|globals|map|reversed|__import__|complex|hasattr|max|round|delattr|hash)/, | |
'https://docs.python.org/3/library/functions.html#$1'], | |
[/(set|str|dict|bytes|frozenset|list|memoryview|range|tuple)/, | |
'https://docs.python.org/3/library/functions.html#func-$1'], | |
[/((int|float|str|bytes|bytearray|memoryview|dict)\.\w+)/, | |
'https://docs.python.org/3/library/stdtypes.html#$1'], | |
[/list\.(\w+)/, function($0, $1){ | |
var url; | |
if ($1 == 'sort') | |
url = 'https://docs.python.org/3/library/stdtypes.html#list.sort'; | |
else | |
url = 'https://docs.python.org/3/tutorial/datastructures.html#more-on-lists'; | |
return '[`'+$0+'`]('+url+')'; | |
}], | |
[/(set\.(\w+))/, 'https://docs.python.org/3/library/stdtypes.html#frozenset.$2'], | |
[/((zlib|zipimport|zipfile|zipapp|xmlrpc\.server|xmlrpc\.client|xmlrpc|xml\.sax\.xmlreader|xml\.sax\.saxutils|xml\.sax\.handler|xml\.sax|xml\.parsers\.expat|xml\.etree\.ElementTree|xml\.dom\.pulldom|xml\.dom\.minidom|xml\.dom|xdrlib|wsgiref|winsound|winreg|webbrowser|weakref|wave|warnings|venv|uuid|uu|urllib\.robotparser|urllib\.response|urllib\.request|urllib\.parse|urllib\.error|urllib|unittest\.mock|unittest|unicodedata|typing|types|turtle|tty|tracemalloc|traceback|trace|tokenize|token|tkinter\.ttk|tkinter\.tix|tkinter\.scrolledtext|tkinter|timeit|time|threading|textwrap|test\.support|test|termios|tempfile|telnetlib|tarfile|tabnanny|syslog|sysconfig|sys|symtable|symbol|sunau|subprocess|struct|stringprep|string|statistics|stat|ssl|sqlite3|spwd|socketserver|socket|sndhdr|smtplib|smtpd|site|signal|shutil|shlex|shelve|selectors|select|secrets|sched|runpy|rlcompleter|resource|reprlib|readline|re|random|quopri|queue|pydoc|pyclbr|py_compile|pwd|pty|pprint|posix|poplib|plistlib|platform|pkgutil|pipes|pickletools|pickle|pdb|pathlib|parser|ossaudiodev|os\.path|os|optparse|operator|numbers|nntplib|nis|netrc|multiprocessing|msvcrt|msilib|modulefinder|mmap|mimetypes|math|marshal|mailcap|mailbox|macpath|lzma|logging\.handlers|logging\.config|logging|locale|linecache|keyword|json|itertools|ipaddress|io|inspect|importlib|imp|imghdr|imaplib|http\.server|http\.cookies|http\.cookiejar|http\.client|http|html\.parser|html\.entities|html|hmac|heapq|hashlib|gzip|grp|glob|gettext|getpass|getopt|gc|functools|ftplib|fractions|fpectl|formatter|fnmatch|fileinput|filecmp|fcntl|faulthandler|errno|enum|ensurepip|email|dummy_threading|doctest|distutils|dis|difflib|decimal|dbm|datetime|curses\.textpad|curses\.panel|curses\.ascii|curses|ctypes|csv|crypt|copyreg|copy|contextlib|configparser|concurrent\.futures|compileall|colorsys|collections\.abc|collections|codeop|codecs|code|cmd|cmath|chunk|cgitb|cgi|calendar|bz2|builtins|bisect|binhex|binascii|bdb|base64|audioop|atexit|asyncore|asyncio|asynchat|ast|array|argparse|aifc|abc|__future__)\.([\w.]+))/, | |
'https://docs.python.org/3/library/$2.html#$1'], | |
[/((TestCase|TestResult|TestSuite|TestLoader|mock|FunctionTestCase|main|BaseTestSuite|TextTestRunner)(?:\.\w+)?)/, 'https://docs.python.org/3/library/unittest.html#unittest.$1'], | |
[/((StatisticDiff|Snapshot|Statistic|Frame|DomainFilter)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tracemalloc.html#tracemalloc.$1'], | |
[/((Packer|Unpacker)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xdrlib.html#xdrlib.$1'], | |
[/((ArrayType)(?:\.\w+)?)/, 'https://docs.python.org/3/library/array.html#array.$1'], | |
[/((Wm|Menu|Misc|tix|Listbox|DoubleVar|getdouble|PhotoImage|Spinbox|Image|ttk|scrolledtext|Variable|Place|BooleanVar|Scrollbar|XView|Pack|YView|BaseWidget|IntVar|StringVar)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tkinter.html#tkinter.$1'], | |
[/((Decimal|Context|DivisionImpossible|DecimalTuple|DivisionUndefined|DecimalException|DivisionByZero|ConversionSyntax|InvalidOperation|Overflow|InvalidContext)(?:\.\w+)?)/, 'https://docs.python.org/3/library/decimal.html#decimal.$1'], | |
[/((IMAP4|IMAP4_SSL|IMAP4_stream)(?:\.\w+)?)/, 'https://docs.python.org/3/library/imaplib.html#imaplib.$1'], | |
[/((Chrome|Elinks|UnixBrowser|Netscape|Grail|BaseBrowser|Mozilla|Opera|Galeon|Konqueror|BackgroundBrowser|GenericBrowser)(?:\.\w+)?)/, 'https://docs.python.org/3/library/webbrowser.html#webbrowser.$1'], | |
[/((FileInput)(?:\.\w+)?)/, 'https://docs.python.org/3/library/fileinput.html#fileinput.$1'], | |
[/((Transport|Marshaller|DateTime|Unmarshaller|SafeTransport|ExpatParser)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xmlrpc.client.html#xmlrpc.client.$1'], | |
[/((GNUTranslations|NullTranslations)(?:\.\w+)?)/, 'https://docs.python.org/3/library/gettext.html#gettext.$1'], | |
[/((MH|BabylMessage|Mailbox|Babyl|MaildirMessage|Maildir|MHMessage)(?:\.\w+)?)/, 'https://docs.python.org/3/library/mailbox.html#mailbox.$1'], | |
[/((XMLFilterBase|XMLGenerator)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.sax.saxutils.html#xml.sax.saxutils.$1'], | |
[/((SequenceMatcher|Match|Differ|HtmlDiff)(?:\.\w+)?)/, 'https://docs.python.org/3/library/difflib.html#difflib.$1'], | |
[/((DictConfigurator|ConvertingDict|StreamRequestHandler|ConvertingList|BaseConfigurator|ConvertingMixin)(?:\.\w+)?)/, 'https://docs.python.org/3/library/logging.config.html#logging.config.$1'], | |
[/((StringTemplateStyle|config|StreamHandler|NullHandler|PercentStyle|handlers|Handler|BufferingFormatter|Logger|LoggerAdapter|StrFormatStyle|Filterer|Manager|LogRecord|PlaceHolder)(?:\.\w+)?)/, 'https://docs.python.org/3/library/logging.html#logging.$1'], | |
[/((SocketType)(?:\.\w+)?)/, 'https://docs.python.org/3/library/socket.html#socket.$1'], | |
[/((FieldStorage|MiniFieldStorage|Message|FeedParser)(?:\.\w+)?)/, 'https://docs.python.org/3/library/cgi.html#cgi.$1'], | |
[/((frozenset|BaseException|ImportError|StopIteration|set|property|dict|tuple|bytearray|UnicodeEncodeError|UnicodeTranslateError|range|SyntaxError|complex|slice|list|UnicodeDecodeError|SystemExit|type)(?:\.\w+)?)/, 'https://docs.python.org/3/library/builtins.html#builtins.$1'], | |
[/((statvfs_result|DirEntry|sched_param|path|waitid_result|stat_result|times_result|terminal_size)(?:\.\w+)?)/, 'https://docs.python.org/3/library/os.html#os.$1'], | |
[/((OpcodeInfo|ArgumentDescriptor|StackObject)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pickletools.html#pickletools.$1'], | |
[/((request|response|parse|robotparser)(?:\.\w+)?)/, 'https://docs.python.org/3/library/urllib.html#urllib.$1'], | |
[/((NodeVisitor|NodeTransformer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/ast.html#ast.$1'], | |
[/((Random|SystemRandom)(?:\.\w+)?)/, 'https://docs.python.org/3/library/random.html#random.$1'], | |
[/((PrettyPrinter)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pprint.html#pprint.$1'], | |
[/((HTMLDoc|TextDoc|Helper|Doc|TextRepr|HTMLRepr|ModuleScanner)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pydoc.html#pydoc.$1'], | |
[/((TurtleScreen|RawTurtle|Canvas|ScrolledCanvas|TPen|Tbuffer|Vec2D|TNavigator|TurtleScreenBase|Shape)(?:\.\w+)?)/, 'https://docs.python.org/3/library/turtle.html#turtle.$1'], | |
[/((BytesIO)(?:\.\w+)?)/, 'https://docs.python.org/3/library/shelve.html#shelve.$1'], | |
[/((sha3_256|sha3_384|shake_128|sha3_224|blake2s|blake2b|shake_256|sha3_512)(?:\.\w+)?)/, 'https://docs.python.org/3/library/hashlib.html#hashlib.$1'], | |
[/((TarFile|TarInfo)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tarfile.html#tarfile.$1'], | |
[/((FTP|FTP_TLS)(?:\.\w+)?)/, 'https://docs.python.org/3/library/ftplib.html#ftplib.$1'], | |
[/((SMTP|LMTP|SMTP_SSL)(?:\.\w+)?)/, 'https://docs.python.org/3/library/smtplib.html#smtplib.$1'], | |
[/((SysLogHandler|BufferingHandler|MemoryHandler|SocketHandler|QueueHandler|TimedRotatingFileHandler|RotatingFileHandler|WatchedFileHandler|QueueListener|NTEventLogHandler|DatagramHandler|SMTPHandler|BaseRotatingHandler)(?:\.\w+)?)/, 'https://docs.python.org/3/library/logging.handlers.html#logging.handlers.$1'], | |
[/((tixCommand|ComboBox|TList|TixWidget|Form|Tree|FileEntry|CheckList|ButtonBox|HList|ResizeHandle|NoteBook|TixSubWidget|DialogShell|FileSelectDialog|Balloon|DirTree|DirSelectDialog|Control|StdButtonBox|ListNoteBook|Select|DirList|ExFileSelectBox|FileSelectBox|DisplayStyle|PopupMenu|ExFileSelectDialog)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tkinter.tix.html#tkinter.tix.$1'], | |
[/((CoroutineType|MappingProxyType|DynamicClassAttribute|FrameType|CodeType|GeneratorType|AsyncGeneratorType|TracebackType)(?:\.\w+)?)/, 'https://docs.python.org/3/library/types.html#types.$1'], | |
[/((DocumentType|CDATASection|Document|Entity|DOMImplementation|Attr|CharacterData|EmptyNodeList|NamedNodeMap|DocumentFragment|ReadOnlySequentialNamedNodeMap|Identified|ProcessingInstruction|Notation|DocumentLS|TypeInfo|Comment|Childless|ElementInfo|DOMImplementationLS|NodeList)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.dom.minidom.html#xml.dom.minidom.$1'], | |
[/((BaseRequestHandler|BaseServer|ForkingMixIn|TCPServer|UDPServer|UnixStreamServer|ThreadingMixIn|DatagramRequestHandler|UnixDatagramServer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/socketserver.html#socketserver.$1'], | |
[/((AttributesImpl|AttributesNSImpl|IncrementalParser|XMLReader|Locator)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.sax.xmlreader.html#xml.sax.xmlreader.$1'], | |
[/((Wave_write|Wave_read)(?:\.\w+)?)/, 'https://docs.python.org/3/library/wave.html#wave.$1'], | |
[/((Bdb|Breakpoint|Tdb)(?:\.\w+)?)/, 'https://docs.python.org/3/library/bdb.html#bdb.$1'], | |
[/((DefaultCookiePolicy|CookieJar|CookiePolicy|MozillaCookieJar|Cookie|FileCookieJar|LWPCookieJar)(?:\.\w+)?)/, 'https://docs.python.org/3/library/http.cookiejar.html#http.cookiejar.$1'], | |
[/((Cmd)(?:\.\w+)?)/, 'https://docs.python.org/3/library/cmd.html#cmd.$1'], | |
[/((PullDOM|SAX2DOM|DOMEventStream)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.dom.pulldom.html#xml.dom.pulldom.$1'], | |
[/((xmlreader|saxutils|handler|ContentHandler|InputSource|SAXParseException|SAXException)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.sax.html#xml.sax.$1'], | |
[/((Options|SMTPChannel|SMTPServer|Devnull|PureProxy|MailmanProxy|DebuggingServer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/smtpd.html#smtpd.$1'], | |
[/((Treeview|Notebook|Panedwindow|Widget|Progressbar|Style|LabeledScale|Combobox)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tkinter.ttk.html#tkinter.ttk.$1'], | |
[/((RobotFileParser|RequestRate|RuleLine)(?:\.\w+)?)/, 'https://docs.python.org/3/library/urllib.robotparser.html#urllib.robotparser.$1'], | |
[/((XMLParserType)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.parsers.expat.html#xml.parsers.expat.$1'], | |
[/((MemoryBIO|SSLContext|SSLSocket|SSLObject|SSLSession|DefaultVerifyPaths)(?:\.\w+)?)/, 'https://docs.python.org/3/library/ssl.html#ssl.$1'], | |
[/((HTTPDefaultErrorHandler|HTTPSHandler|HTTPCookieProcessor|ftpwrapper|AbstractDigestAuthHandler|FancyURLopener|ProxyDigestAuthHandler|HTTPPasswordMgr|ProxyHandler|HTTPPasswordMgrWithPriorAuth|URLopener|AbstractBasicAuthHandler|addclosehook|ProxyBasicAuthHandler|OpenerDirector|HTTPRedirectHandler|Request|AbstractHTTPHandler|BaseHandler|HTTPErrorProcessor|HTTPBasicAuthHandler|HTTPDigestAuthHandler|FTPHandler|CacheFTPHandler|DataHandler|HTTPPasswordMgrWithDefaultRealm|UnknownHandler|addinfourl|HTTPError)(?:\.\w+)?)/, 'https://docs.python.org/3/library/urllib.request.html#urllib.request.$1'], | |
[/((bytes_|int_|UUID)(?:\.\w+)?)/, 'https://docs.python.org/3/library/uuid.html#uuid.$1'], | |
[/((ZipFile|ZipInfo|PyZipFile)(?:\.\w+)?)/, 'https://docs.python.org/3/library/zipfile.html#zipfile.$1'], | |
[/((OrderedDict|deque|Counter|defaultdict)(?:\.\w+)?)/, 'https://docs.python.org/3/library/collections.html#collections.$1'], | |
[/((Pdb)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pdb.html#pdb.$1'], | |
[/((ModuleFinder)(?:\.\w+)?)/, 'https://docs.python.org/3/library/modulefinder.html#modulefinder.$1'], | |
[/((Cursor|Connection|Cache|Row)(?:\.\w+)?)/, 'https://docs.python.org/3/library/sqlite3.html#sqlite3.$1'], | |
[/((StreamReaderWriter|Codec|BufferedIncrementalEncoder|IncrementalDecoder|IncrementalEncoder|StreamRecoder|BufferedIncrementalDecoder)(?:\.\w+)?)/, 'https://docs.python.org/3/library/codecs.html#codecs.$1'], | |
[/((Aifc_write|Aifc_read|Chunk)(?:\.\w+)?)/, 'https://docs.python.org/3/library/aifc.html#aifc.$1'], | |
[/((epoll)(?:\.\w+)?)/, 'https://docs.python.org/3/library/select.html#select.$1'], | |
[/((CGIHTTPRequestHandler|BaseHTTPRequestHandler|SimpleHTTPRequestHandler|HTTPServer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/http.server.html#http.server.$1'], | |
[/((NullWriter|NullFormatter|AbstractFormatter|DumbWriter|AbstractWriter)(?:\.\w+)?)/, 'https://docs.python.org/3/library/formatter.html#formatter.$1'], | |
[/((IPv6Interface|IPv6Address|IPv4Network|IPv4Interface|IPv6Network|IPv4Address)(?:\.\w+)?)/, 'https://docs.python.org/3/library/ipaddress.html#ipaddress.$1'], | |
[/((DictWriter|StringIO|Dialect|DictReader|excel_tab|excel|unix_dialect|Sniffer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/csv.html#csv.$1'], | |
[/((struct_time)(?:\.\w+)?)/, 'https://docs.python.org/3/library/time.html#time.$1'], | |
[/((WriteTransport|AbstractEventLoop|SelectorEventLoop|Protocol|BaseEventLoop|BaseProtocol|BaseTransport|AbstractChildWatcher|FastChildWatcher|SubprocessTransport|DefaultEventLoopPolicy|Lock|SafeChildWatcher|StreamReaderProtocol|ReadTransport|SubprocessProtocol|Task|DatagramTransport|AbstractServer|AbstractEventLoopPolicy|Handle|DatagramProtocol|TimerHandle)(?:\.\w+)?)/, 'https://docs.python.org/3/library/asyncio.html#asyncio.$1'], | |
[/((POP3|POP3_SSL)(?:\.\w+)?)/, 'https://docs.python.org/3/library/poplib.html#poplib.$1'], | |
[/((minidom|pulldom|UserDataHandler|InvalidCharacterErr|SyntaxErr|IndexSizeErr|NotFoundErr|InuseAttributeErr|WrongDocumentErr|NoModificationAllowedErr|ValidationErr|InvalidStateErr|NotSupportedErr|InvalidAccessErr|NoDataAllowedErr|InvalidModificationErr|DomstringSizeErr|HierarchyRequestErr|NamespaceErr)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.dom.html#xml.dom.$1'], | |
[/((SpooledTemporaryFile|TemporaryDirectory)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tempfile.html#tempfile.$1'], | |
[/((struct_group)(?:\.\w+)?)/, 'https://docs.python.org/3/library/grp.html#grp.$1'], | |
[/((finalize|WeakSet|KeyedRef)(?:\.\w+)?)/, 'https://docs.python.org/3/library/weakref.html#weakref.$1'], | |
[/((dircmp)(?:\.\w+)?)/, 'https://docs.python.org/3/library/filecmp.html#filecmp.$1'], | |
[/((HTTPConnection|HTTPSConnection|HTTPMessage)(?:\.\w+)?)/, 'https://docs.python.org/3/library/http.client.html#http.client.$1'], | |
[/((FileIO|BufferedRandom|BufferedWriter|TextIOWrapper|IncrementalNewlineDecoder|BufferedReader|BufferedRWPair)(?:\.\w+)?)/, 'https://docs.python.org/3/library/io.html#io.$1'], | |
[/((cookiejar|cookies)(?:\.\w+)?)/, 'https://docs.python.org/3/library/http.html#http.$1'], | |
[/((struct_rusage)(?:\.\w+)?)/, 'https://docs.python.org/3/library/resource.html#resource.$1'], | |
[/((dispatcher|file_wrapper|file_dispatcher|dispatcher_with_send)(?:\.\w+)?)/, 'https://docs.python.org/3/library/asyncore.html#asyncore.$1'], | |
[/((Path|PurePath|WindowsPath)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pathlib.html#pathlib.$1'], | |
[/((UCD)(?:\.\w+)?)/, 'https://docs.python.org/3/library/unicodedata.html#unicodedata.$1'], | |
[/((Struct)(?:\.\w+)?)/, 'https://docs.python.org/3/library/struct.html#struct.$1'], | |
[/((SndHeaders)(?:\.\w+)?)/, 'https://docs.python.org/3/library/sndhdr.html#sndhdr.$1'], | |
[/((IndentedHelpFormatter|OptionParser|Option|Values|TitledHelpFormatter|OptionContainer|OptionGroup)(?:\.\w+)?)/, 'https://docs.python.org/3/library/optparse.html#optparse.$1'], | |
[/((DocTestRunner|DocTestCase|SkipDocTestCase|DebugRunner|TestResults|DocFileCase|DocTestFinder|DocTestParser|OutputChecker)(?:\.\w+)?)/, 'https://docs.python.org/3/library/doctest.html#doctest.$1'], | |
[/((Au_read|Au_write)(?:\.\w+)?)/, 'https://docs.python.org/3/library/sunau.html#sunau.$1'], | |
[/((ArgumentParser)(?:\.\w+)?)/, 'https://docs.python.org/3/library/argparse.html#argparse.$1'], | |
[/((XMLParser|ElementTree|TreeBuilder|XMLPullParser)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.etree.ElementTree.html#xml.etree.ElementTree.$1'], | |
[/((Popen|CompletedProcess|CalledProcessError|TimeoutExpired)(?:\.\w+)?)/, 'https://docs.python.org/3/library/subprocess.html#subprocess.$1'], | |
[/((SimpleXMLRPCRequestHandler|CGIXMLRPCRequestHandler|SimpleXMLRPCDispatcher|XMLRPCDocGenerator|ServerHTMLDoc|MultiPathXMLRPCServer|DocXMLRPCRequestHandler|DocCGIXMLRPCRequestHandler|SimpleXMLRPCServer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xmlrpc.server.html#xmlrpc.server.$1'], | |
[/((Repr)(?:\.\w+)?)/, 'https://docs.python.org/3/library/reprlib.html#reprlib.$1'], | |
[/((ProcessPoolExecutor|Executor|ThreadPoolExecutor)(?:\.\w+)?)/, 'https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.$1'], | |
[/((ArticleInfo|GroupInfo)(?:\.\w+)?)/, 'https://docs.python.org/3/library/nntplib.html#nntplib.$1'], | |
[/((Parameter|Arguments|Attribute|FullArgSpec|Signature|BoundArguments|FrameInfo|Traceback|ArgInfo|ClosureVars|BlockFinder|ArgSpec)(?:\.\w+)?)/, 'https://docs.python.org/3/library/inspect.html#inspect.$1'], | |
[/((Symbol|Function|SymbolTable|Class|SymbolTableFactory)(?:\.\w+)?)/, 'https://docs.python.org/3/library/symtable.html#symtable.$1'], | |
[/((BZ2Compressor|BZ2Decompressor)(?:\.\w+)?)/, 'https://docs.python.org/3/library/bz2.html#bz2.$1'], | |
[/((DTDHandler|EntityResolver)(?:\.\w+)?)/, 'https://docs.python.org/3/library/xml.sax.handler.html#xml.sax.handler.$1'], | |
[/((Morsel|SimpleCookie|BaseCookie)(?:\.\w+)?)/, 'https://docs.python.org/3/library/http.cookies.html#http.cookies.$1'], | |
[/((Unpickler|Pickler)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pickle.html#pickle.$1'], | |
[/((NonCallableMock|MagicMock|MagicProxy|NonCallableMagicMock)(?:\.\w+)?)/, 'https://docs.python.org/3/library/unittest.mock.html#unittest.mock.$1'], | |
[/((struct_passwd)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pwd.html#pwd.$1'], | |
[/((Whitespace|NannyNag)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tabnanny.html#tabnanny.$1'], | |
[/((partial|RLock)(?:\.\w+)?)/, 'https://docs.python.org/3/library/functools.html#functools.$1'], | |
[/((struct_spwd)(?:\.\w+)?)/, 'https://docs.python.org/3/library/spwd.html#spwd.$1'], | |
[/((struct_siginfo)(?:\.\w+)?)/, 'https://docs.python.org/3/library/signal.html#signal.$1'], | |
[/((Interpolation|ParsingError|LegacyInterpolation|ExtendedInterpolation|BasicInterpolation)(?:\.\w+)?)/, 'https://docs.python.org/3/library/configparser.html#configparser.$1'], | |
[/((Calendar|HTMLCalendar|TextCalendar|LocaleTextCalendar|LocaleHTMLCalendar)(?:\.\w+)?)/, 'https://docs.python.org/3/library/calendar.html#calendar.$1'], | |
[/((ascii|panel|textpad)(?:\.\w+)?)/, 'https://docs.python.org/3/library/curses.html#curses.$1'], | |
[/((HTMLParser)(?:\.\w+)?)/, 'https://docs.python.org/3/library/html.parser.html#html.parser.$1'], | |
[/((tzinfo|date|timezone|timedelta)(?:\.\w+)?)/, 'https://docs.python.org/3/library/datetime.html#datetime.$1'], | |
[/((TracebackException|FrameSummary|StackSummary)(?:\.\w+)?)/, 'https://docs.python.org/3/library/traceback.html#traceback.$1'], | |
[/((async_chat|simple_producer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/asynchat.html#asynchat.$1'], | |
[/((TextWrapper)(?:\.\w+)?)/, 'https://docs.python.org/3/library/textwrap.html#textwrap.$1'], | |
[/((zipimporter)(?:\.\w+)?)/, 'https://docs.python.org/3/library/zipimport.html#zipimport.$1'], | |
[/((EnvBuilder)(?:\.\w+)?)/, 'https://docs.python.org/3/library/venv.html#venv.$1'], | |
[/((scheduler)(?:\.\w+)?)/, 'https://docs.python.org/3/library/sched.html#sched.$1'], | |
[/((MimeTypes)(?:\.\w+)?)/, 'https://docs.python.org/3/library/mimetypes.html#mimetypes.$1'], | |
[/((Template)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pipes.html#pipes.$1'], | |
[/((ImpImporter|ImpLoader|ModuleInfo)(?:\.\w+)?)/, 'https://docs.python.org/3/library/pkgutil.html#pkgutil.$1'], | |
[/((Bytecode)(?:\.\w+)?)/, 'https://docs.python.org/3/library/dis.html#dis.$1'], | |
[/((STType)(?:\.\w+)?)/, 'https://docs.python.org/3/library/parser.html#parser.$1'], | |
[/((InteractiveConsole|InteractiveInterpreter)(?:\.\w+)?)/, 'https://docs.python.org/3/library/code.html#code.$1'], | |
[/((Telnet)(?:\.\w+)?)/, 'https://docs.python.org/3/library/telnetlib.html#telnetlib.$1'], | |
[/((Completer)(?:\.\w+)?)/, 'https://docs.python.org/3/library/rlcompleter.html#rlcompleter.$1'], | |
[/((Plist|Data)(?:\.\w+)?)/, 'https://docs.python.org/3/library/plistlib.html#plistlib.$1'], | |
[/((addinfo)(?:\.\w+)?)/, 'https://docs.python.org/3/library/urllib.response.html#urllib.response.$1'], | |
[/((JSONEncoder|JSONDecoder)(?:\.\w+)?)/, 'https://docs.python.org/3/library/json.html#json.$1'], | |
[/((SourcelessFileLoader|NullImporter)(?:\.\w+)?)/, 'https://docs.python.org/3/library/imp.html#imp.$1'], | |
[/((SplitResultBytes|ParseResultBytes|ParseResult|SplitResult|DefragResultBytes|DefragResult)(?:\.\w+)?)/, 'https://docs.python.org/3/library/urllib.parse.html#urllib.parse.$1'], | |
[/((HMAC)(?:\.\w+)?)/, 'https://docs.python.org/3/library/hmac.html#hmac.$1'], | |
[/((GetoptError)(?:\.\w+)?)/, 'https://docs.python.org/3/library/getopt.html#getopt.$1'], | |
[/((Hook)(?:\.\w+)?)/, 'https://docs.python.org/3/library/cgitb.html#cgitb.$1'], | |
[/((Untokenizer|TokenInfo)(?:\.\w+)?)/, 'https://docs.python.org/3/library/tokenize.html#tokenize.$1'], | |
[/((SelectorKey)(?:\.\w+)?)/, 'https://docs.python.org/3/library/selectors.html#selectors.$1'], | |
[/((openrsrc|BinHex|HexBin)(?:\.\w+)?)/, 'https://docs.python.org/3/library/binhex.html#binhex.$1'], | |
[/((Textbox)(?:\.\w+)?)/, 'https://docs.python.org/3/library/curses.textpad.html#curses.textpad.$1'], | |
[/((auto)(?:\.\w+)?)/, 'https://docs.python.org/3/library/enum.html#enum.$1'], | |
[/((ABCMeta)(?:\.\w+)?)/, 'https://docs.python.org/3/library/numbers.html#numbers.$1'], | |
[/((CoverageResults)(?:\.\w+)?)/, 'https://docs.python.org/3/library/trace.html#trace.$1'], | |
[/((chain)(?:\.\w+)?)/, 'https://docs.python.org/3/library/statistics.html#statistics.$1'], | |
[/((entities)(?:\.\w+)?)/, 'https://docs.python.org/3/library/html.html#html.$1'], | |
[/((LibraryLoader)(?:\.\w+)?)/, 'https://docs.python.org/3/library/ctypes.html#ctypes.$1'], | |
[/((Scanner)(?:\.\w+)?)/, 'https://docs.python.org/3/library/re.html#re.$1'], | |
[/(__(init|new|del|repr|str|bytes|format|lt|le|eq|ne|gt|ge|hash|bool|getattr|getattribute|getattribute|setattr|setattr|delattr|dir|get|set|delete|set_name|call|len|length_hint|getitem|missing|setitem|delitem|iter|reversed|contains|add|sub|mul|matmul|truediv|floordiv|mod|divmod|pow|lshift|rshift|and|xor|or|radd|rsub|rmul|rmatmul|rtruediv|rfloordiv|rmod|rdivmod|rpow|rlshift|rrshift|rand|rxor|ror|iadd|isub|imul|imatmul|itruediv|ifloordiv|imod|ipow|ilshift|irshift|iand|ixor|ior|neg|pos|abs|invert|complex|int|float|index|round|trunc|floor|ceil|enter|exit|await|aiter|anext|aenter|aexit)__)/, | |
'https://docs.python.org/3/reference/datamodel.html#object.$1'], | |
// stdlib shortcuts for things with long names or name clashes | |
[/dt\.(\w+)/, function($0, $1){return '[`datetime.'+$1+'`](https://docs.python.org/3/library/datetime.html#datetime.datetime.'+$1+')';}], | |
// python glossary links | |
[/(2to3|abstract base class|annotation|argument|asynchronous context manager|asynchronous generator|asynchronous generator iterator|asynchronous iterable|asynchronous iterator|attribute|awaitable|BDFL|binary file|bytecode|bytes-like object|class|class variable|coercion|complex number|context manager|contiguous|coroutine|coroutine function|CPython|decorator|descriptor|dictionary|dictionary view|docstring|duck-typing|EAFP|expression|extension module|f-string|file object|file-like object|finder|floor division|function|function annotation|garbage collection|generator iterator|generic function|GIL|global interpreter lock|hash-based pyc|hashable|IDLE|immutable|import path|importing|importer|interactive|interpreted|interpreter shutdown|iterable|iterator|key function|keyword argument|lambda|LBYL|list|list comprehension|loader|mapping|meta path finder|metaclass|method|method resolution order|module|module spec|MRO|mutable|named tuple|namespace|namespace package|nested scope|new-style class|object|package|parameter|path entry|path entry finder|path entry hook|path based finder|path-like object|PEP|portion|positional argument|provisional API|provisional package|Python 3000|Pythonic|qualified name|reference count|regular package|sequence|single dispatch|slice|special method|statement|struct sequence|text encoding|text file|triple-quoted string|type|type alias|type hint|universal newlines|variable annotation|virtual environment|virtual machine|Zen of Python)/, function($0, $1){ | |
return '['+$0+'](https://docs.python.org/3/glossary.html#term-' + $1.replace(/ /g, '-').toLowerCase() + ')'; | |
}], | |
[/>>>/, 'https://docs.python.org/3/glossary.html#term'], | |
[/.../, 'https://docs.python.org/3/glossary.html#term-1'], | |
[/__future__/, 'https://docs.python.org/3/glossary.html#term-future'], | |
[/__slots__/, 'https://docs.python.org/3/glossary.html#term-slots'], | |
// random programming lingo | |
[/(XY problem)/, 'https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem'], | |
[/((\w+) module)/, function($0, $1, $2){return '[`'+$2+'`](https://docs.python.org/3/library/'+$2+'.html) module';}], | |
[/((?:current )?working directory)/, 'https://en.wikipedia.org/wiki/Working_directory'], | |
[/(relative(?: file)? path)/, 'https://en.wikipedia.org/wiki/Path_%28computing%29#Absolute_and_relative_paths'], | |
[/(absolute(?: file)? path)/, 'https://en.wikipedia.org/wiki/Path_%28computing%29#Absolute_and_relative_paths'], | |
[/(enum)/, 'https://docs.python.org/3/library/enum.html'], | |
[/(mro|MRO|method resolution order)/, 'https://docs.python.org/3/glossary.html#term-method-resolution-order'], | |
[/(eafp|lbyl)/, function($0, $1){return '['+$1+'](https://docs.python.org/3/glossary.html#term-'+$1.toLowerCase+')';}], | |
[/(iterator)/, 'https://docs.python.org/3/library/stdtypes.html#iterator-types'], | |
[/((?:(?:non-)?data )?descriptor)/, 'https://docs.python.org/3/howto/descriptor.html'], | |
[/(list comprehensions?)/, 'https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions'], | |
[/(dict(?:ionary) comprehensions?)/, 'https://www.python.org/dev/peps/pep-0274/'], // couldn't find a better link | |
[/((?:argument )?unpack(?:ing)?)/, 'https://docs.python.org/3/tutorial/controlflow.html#tut-unpacking-arguments'], | |
[/(metaclass(?:es)?)/, 'https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python'], | |
[/(mixins?)/, 'https://stackoverflow.com/questions/533631/what-is-a-mixin-and-why-are-they-useful'], | |
[/(`?yield`? (?:statement|keyword))/, 'https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do'], | |
// design patterns | |
[/(strategy pattern)/, 'https://en.wikipedia.org/wiki/Strategy_pattern'], | |
// peps | |
[/(?:pep|PEP)[ -](\d+)/, function($0, $1){ | |
var padded = $1; | |
while (padded.length < 4) | |
padded = "0" + padded; | |
return '[PEP '+$1+'](https://www.python.org/dev/peps/pep-'+padded+'/)'; | |
}], | |
// regex stuff | |
[/(captur(?:e|ing) groups?)/, 'https://www.regular-expressions.info/brackets.html'], | |
[/(backreferences?)/, 'https://www.regular-expressions.info/backref.html'], | |
[/((?:negative |positive )?look(?:ahead|behind)(?: assertion)?s?)/, 'https://www.regular-expressions.info/lookaround.html'], | |
[/(character class)/, 'https://www.regular-expressions.info/charclass.html'], | |
[/(anchor)/, 'https://www.regular-expressions.info/anchors.html'], | |
[/((?:word )boundary)/, 'https://www.regular-expressions.info/wordboundaries.html'], | |
[/(catastrophic backtracking)/, 'https://www.regular-expressions.info/catastrophic.html'], | |
// chat stuff | |
[/formatguide/, function(){return '[An Illustrated Guide To Formatting Code In Chat](https://sopython.com/wiki/An_Illustrated_Guide_To_Formatting_Code_In_Chat)';}], | |
[/(sandbox)/, 'https://chat.stackoverflow.com/rooms/1'], | |
[/shrug/, function(){return '¯\\\\_(ツ)_/¯';}], | |
[/tableflip/, function(){return '(ノ°Д°)ノ︵ ┻━┻';}], | |
[/lenny/, function(){return '( ͡° ͜ʖ ͡°)';}], | |
[/disapprove/, function(){return 'ಠ_ಠ';}], | |
[/themoreyouknow/, function(){return 'the more you know ☆ミ';}], | |
[/witchhunt|pitchfork/, function(){return '( ಠ_ಠ)–Ψ';}], | |
[/goaway|exorcis[tm]/, function(){return '✝_(º_º)';}], | |
[/maintenance/, function(){return 'http://i.imgur.com/dYykLSF.jpg';}], | |
// popular 3rd party modules | |
[/(?:np|numpy)\.(\w+)/, 'https://docs.scipy.org/doc/numpy/reference/generated/numpy.$1.html'], | |
[/df\.(\w+)/, function($0, $1){return '[`DataFrame.'+$1+'`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.'+$1+'.html)';}], | |
[/ser(?:ies)?\.(\w+)/, function($0, $1){return '[`Series.'+$1+'`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.'+$1+'.html)';}], | |
]); | |
REPLACEMENTS = (function(repls){ | |
var anchored_repls = new Map(); | |
for (var pair of repls){ | |
var regex = new RegExp('^'+pair[0].source+'$'); | |
anchored_repls.set(regex, pair[1]); | |
} | |
return anchored_repls; | |
})(REPLACEMENTS); | |
var typingTimer; //timer identifier | |
var doneTypingInterval = 4000; //time in ms | |
function replace_and_maintain_cursor(text, regex, repl, selection_start, selection_end){ | |
function recalc_cursor(cursor, start, end, newlength){ | |
// cursor is in front of the replaced text? no change | |
if (cursor < start) | |
return cursor; | |
// cursor is in the middle of the replaced text? move it to the end | |
if (cursor < end) | |
return start + newlength; | |
// cursor is behind the replaced text? move it according to how | |
// many characters were added/deleted | |
return cursor + newlength - (end - start); | |
} | |
function replace(){ | |
var match_start = arguments[arguments.length-2]; | |
var match_end = match_start + arguments[0].length; | |
var text; | |
if (typeof repl == 'string') | |
text = repl; | |
else | |
text = repl(...arguments); | |
var length = text.length; | |
selection_start = recalc_cursor(selection_start, match_start, match_end, length); | |
selection_end = recalc_cursor(selection_end, match_start, match_end, length); | |
return text; | |
} | |
text = text.replace(regex, replace); | |
return {text: text, | |
selection_start: selection_start, | |
selection_end: selection_end | |
}; | |
} | |
function replace($0, text, funccall, offset, string, IS_CODEBLOCK){ | |
for (var pair of REPLACEMENTS){ | |
var regex = pair[0]; | |
var repl = pair[1]; | |
if (!regex.test(text)) | |
continue; | |
if (typeof repl == 'string'){ | |
if (IS_CODEBLOCK == undefined) | |
repl = '[$1'+funccall+']('+repl+')'; | |
else | |
repl = '[`$1'+funccall+'`]('+repl+')'; | |
} | |
return text.replace(regex, repl); | |
} | |
return $0; | |
} | |
function codeblock_replace($0, text, funccall, offset, string){ | |
return replace($0, text, funccall, offset, string, true); | |
} | |
function insert_links(){ | |
var textareas = document.querySelectorAll('.wmd-input,.js-comment-text-input,#input'); | |
for (var textarea of textareas){ | |
var text = textarea.value; | |
var result = replace_and_maintain_cursor(text, CODEBLOCK_REPL_REGEX, codeblock_replace, textarea.selectionStart, textarea.selectionEnd); | |
result = replace_and_maintain_cursor(result.text, REPL_REGEX, replace, result.selection_start, result.selection_end); | |
text = result.text; | |
if (text === textarea.value) | |
continue; | |
textarea.value = text; | |
textarea.selectionStart = result.selection_start; | |
textarea.selectionEnd = result.selection_end; | |
// trigger a keypress event so that SO updates the live preview | |
textarea.dispatchEvent(new KeyboardEvent('keypress', {'key': ' '})); | |
} | |
} | |
// when a key is pressed, reset the timeout | |
window.addEventListener('keyup', function(){ | |
clearTimeout(typingTimer); | |
typingTimer = setTimeout(insert_links, doneTypingInterval); | |
}); | |
// when a "post" or "submit" button is pressed, perform the substitution immediately | |
window.addEventListener('click', function(event){ | |
var target = event.target; | |
if (target.id === 'submit-button' | |
|| target.id === 'sayit-button' | |
|| (target.tagName === 'INPUT' && target.value === 'Add Comment')){ | |
insert_links(); | |
} | |
}, true); | |
// also react to when the Enter key is pressed in chat or in a comment | |
window.addEventListener('keydown', function on_key_press(event){ | |
var key = event.keyCode ? event.keyCode : event.which; | |
if (key == 13 && !event.shiftKey){ // Enter and not Shift | |
var elem = document.activeElement; | |
if (elem.id == 'input' || elem.classList.contains('js-comment-text-input')) | |
insert_links(); | |
} | |
}, true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment