1from docutils import nodes 2import itertools 3import string 4 5################################################## 6### Utility functions. 7################################################## 8 9def print_error(err_string): 10 """ 11 Print the err_string in red font. 12 """ 13 14 FAIL = '\033[91m' 15 ENDC = '\033[0m' 16 print(FAIL + err_string + ENDC) 17 18 19def get_project(inliner): 20 """ 21 Returns the project name associated with the file being processed. 22 This is used when generating _implicit_ links. 23 """ 24 25 return inliner.document.settings.env.app.config.project 26 27 28def convert_special_chars_to_ascii(func_name): 29 """ 30 Convert non-alphanumeric characters to their ascii representation. 31 32 This is how Haddock generates links for operators. 33 34 '!?' => '-33--63-' 35 """ 36 37 if func_name == None: 38 return None 39 40 escaped_func_name = [ c if c not in string.punctuation else '-' + str(ord(c)) + '-' 41 for c in func_name ] 42 43 return ''.join(escaped_func_name) 44 45 46def parse_haddock_ref_text(text): 47 """ 48 Parses text of the form pkg-name/Module.Path#ident into the tuple 49 (package, module, ident). 50 51 The module and function name are optional, if they are omitted then 'None' 52 will be returned in the corresponding tuple element. If this is an implicit 53 reference (/Module.Path#ident) then 'package' will be 'None'. 54 55 Example inputs: 56 Explicit package references: 57 'pkg' => ('package_name', None, None) 58 'pkg/Module.Name' => ('pkg', 'Module.Name', None) 59 'pkg/Module.Name#ident' => ('pkg', 'Module.Name', 'ident') 60 61 Implicit package references: 62 '/Module.Name' => (None, 'Module.Name', None) 63 '/Module.Name#ident' => (None, 'Module.Name', 'ident') 64 """ 65 66 # If there's no '/' then this is a reference to a package. 67 # A module or identifier reference is invalid here. 68 if '/' not in text: 69 if '#' in text: 70 print_error('Invalid haddock reference: ' + text) 71 raise Exception('Invalid haddock reference, see error above.') 72 return (None, None, None) 73 else: 74 return (text, None, None) 75 76 # Leading '/' means local package reference. 77 # Calling code responsible for determining what "local" means. 78 if 0 == text.find('/'): 79 text = text[1:] 80 if '#' in text: 81 module,ident = text.split('#') 82 return (None, module, ident) 83 else: 84 return (None, text, None) 85 86 # Otherwise, explicit reference. 87 pkg_name,rest = text.split('/') 88 89 ident = None 90 module = None 91 if '#' in rest: 92 module,ident = rest.split('#') 93 else: 94 module = rest 95 96 return (pkg_name, module, ident) 97 98 99################################################## 100### Link generation functions. 101################################################## 102 103 104def pkg_root_ref(haddock_host, haddock_root, pkg): 105 """ 106 Returns the URI for the root of pkg's Haddocks. 107 108 Note: Hackage uses a different URI scheme than stackage and local. 109 110 URI enclosed in {} corresponds to 'haddock_root'. 111 112 Hackage: {hackage.haskell.org/package/}<pkg_name> 113 Stackage: {www.stackage.org/haddock/<resolver>/}<pkg_name>/index.html 114 Local: {file:///path/to/html/}<pkg_name>/index.html 115 """ 116 117 if haddock_host == 'hackage': 118 return haddock_root + pkg 119 120 if haddock_host == 'stackage': 121 return haddock_root + pkg + '/index.html' 122 123 if haddock_host == 'local': 124 return haddock_root + pkg + '/index.html' 125 126 127def module_ref(haddock_host, haddock_root, pkg, module, func_name): 128 """ 129 Returns the URI referring to pkg/module#func_name. 130 131 Note: Hackage uses a different URI scheme than stackage and local. 132 133 URI enclosed in {} corresponds to 'haddock_root'. 134 135 Hackage: {hackage.haskell.org/package/}<pkg_name>/docs/<module>.html#v:<func_name> 136 Stackage: {www.stackage.org/haddock/<resolver>/}<pkg_name>/<module>.html#t:<func_name> 137 Local: {file:///path/to/html/}<pkg_name>/<module>.html#t:<func_name> 138 """ 139 140 if module != None: 141 module = module.replace('.', '-') 142 143 if haddock_host == 'hackage': 144 ref = haddock_root + pkg + '/docs/' + module + '.html' 145 146 if haddock_host == 'stackage': 147 ref = haddock_root + pkg + '/' + module + '.html' 148 149 if haddock_host == 'local': 150 ref = haddock_root + pkg + '/' + module + '.html' 151 152 # If a function name was provided, link to it. 153 if func_name != None: 154 # Select the correct anchor, types use #t, functions use #v. 155 # TODO(m-renuad): Determine if there's cases where this is incorrect. 156 if func_name[0].isupper(): 157 anchor_type = '#t:' 158 else: 159 anchor_type = '#v:' 160 ref = ref + anchor_type + func_name 161 162 return ref 163 164 165def haddock_ref(haddock_host, haddock_root, pkg, module, func_name): 166 """ 167 Return a reference link to Haddocks for pkg/module#func_name. 168 """ 169 170 if module == None and func_name == None: 171 return pkg_root_ref(haddock_host, haddock_root, pkg) 172 else: 173 func_name = convert_special_chars_to_ascii(func_name) 174 return module_ref(haddock_host, haddock_root, pkg, module, func_name) 175 176 177 178############################################# 179# -- Custom roles for linking to Hackage docs 180############################################# 181 182# Support building docs with link to hackage, stackage, or locally build haddocks. 183# Valid options: 184# hackage - link to hackage.haskell.org 185# stackage - link to www.stackage.org (must also pass STACKAGE_RESOLVER) 186# local - any path to local docs (must also set HADDOCK_DIR) 187# 188# Note: Defaults to hackage if none specified. 189# 190# Note: We need to do some custom URL rewriting for stackage because it uses a different 191# format from what the haddock tool builds 192# 193# TODO(m-renaud): Improve this and publish as sphinx extension. 194 195### URI scheme examples for Hackage, Stackage, and local docs. 196## Packages 197# Hackage: hackage.haskell.org/package/containers 198# Stackage: www.stackage.org/haddock/lts-10.0/containers/index.html 199# Local: file:///local/path/html/containers/index.html 200 201## Module (and function) references 202# Hackage: hackage.haskell.org/package/containers/docs/Data.Set.html#v:empty 203# Stackage: www.stackage.org/haddock/lts-10.0/containers/Data.Set.html#t:empty 204# Local: file:///path/to/html/containers/Data.Set.html#t:empty 205 206class HaddockAutolinker: 207 208 def __init__(self, haddock_host, haddock_root): 209 self.haddock_host = haddock_host 210 self.haddock_root = haddock_root 211 212 213 def haddock_role(self, display_name_only=False): 214 def haddock_role_impl(name, rawtext, text, lineno, inliner, options={}, content=[]): 215 """ 216 Role handler for :haddock:. 217 """ 218 219 (pkg, module, ident) = parse_haddock_ref_text(text) 220 221 # If the pkg isn't set then this is a local reference to a module 222 # function in the current package. 223 if pkg == None: 224 pkg = get_project(inliner) 225 ref = haddock_ref(self.haddock_host, self.haddock_root, pkg, module, ident) 226 227 if ref == None: 228 print_error('ERROR: invalid argument to :' + name + ':') 229 print_error(' Markup: ' + str(rawtext)) 230 print_error(' Line: ' + str(lineno)) 231 raise Exception('Invalid Haddock link, see ERROR above.') 232 233 if module == None: 234 link_text = pkg 235 else: 236 if ident == None: 237 link_text = module 238 else: 239 if display_name_only: 240 link_text = ident 241 else: 242 link_text = module + '#' + ident 243 244 node = nodes.reference(rawtext, link_text, refuri=ref, **options) 245 return [node], [] 246 return haddock_role_impl 247