#!/usr/bin/env python3 ############################################################################ # # MODULE: Builds manual pages # AUTHOR(S): Markus Neteler # Glynn Clements # Martin Landa # PURPOSE: Create HTML manual page snippets # COPYRIGHT: (C) 2007-2017 by Glynn Clements # and the GRASS Development Team # # This program is free software under the GNU General # Public License (>=v2). Read the file COPYING that # comes with GRASS for details. # ############################################################################# import sys import os import string import re from datetime import datetime import locale import json try: # Python 2 import from HTMLParser import HTMLParser except: # Python 3 import from html.parser import HTMLParser try: import urlparse except: import urllib.parse as urlparse if sys.version_info[0] == 2: PY2 = True else: PY2 = False if not PY2: unicode = str def _get_encoding(): encoding = locale.getdefaultlocale()[1] if not encoding: encoding = 'UTF-8' return encoding def decode(bytes_): """Decode bytes with default locale and return (unicode) string No-op if parameter is not bytes (assumed unicode string). :param bytes bytes_: the bytes to decode """ if isinstance(bytes_, unicode): return bytes_ if isinstance(bytes_, bytes): enc = _get_encoding() return bytes_.decode(enc) return unicode(bytes_) html_page_footer_pages_path = os.getenv('HTML_PAGE_FOOTER_PAGES_PATH') if \ os.getenv('HTML_PAGE_FOOTER_PAGES_PATH') else '' pgm = sys.argv[1] src_file = "%s.html" % pgm tmp_file = "%s.tmp.html" % pgm trunk_url = "https://github.com/OSGeo/grass/tree/master/" addons_url = "https://github.com/OSGeo/grass-addons/tree/master/" header_base = """ GRASS GIS Manual: ${PGM}
GRASS logo
""" header_nopgm = """

${PGM}

""" header_pgm = """

NAME

${PGM} """ header_pgm_desc = """

NAME

${PGM} - ${PGM_DESC} """ sourcecode = string.Template( """

SOURCE CODE

Available at: ${PGM} source code (history)

""" ) footer_index = string.Template( """

Main index | ${INDEXNAMECAP} index | Topics index | Keywords index | Graphical index | Full index

© 2003-${YEAR} GRASS Development Team, GRASS GIS ${GRASS_VERSION} Reference Manual

""") footer_noindex = string.Template( """

Main index | Topics index | Keywords index | Graphical index | Full index

© 2003-${YEAR} GRASS Development Team, GRASS GIS ${GRASS_VERSION} Reference Manual

""") def read_file(name): try: f = open(name, 'rb') s = f.read() f.close() if PY2: return s else: return decode(s) except IOError: return "" def create_toc(src_data): class MyHTMLParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.reset() self.idx = 1 self.tag_curr = '' self.tag_last = '' self.process_text = False self.data = [] self.tags_allowed = ('h1', 'h2', 'h3') self.tags_ignored = ('img') self.text = '' def handle_starttag(self, tag, attrs): if tag in self.tags_allowed: self.process_text = True self.tag_last = self.tag_curr self.tag_curr = tag def handle_endtag(self, tag): if tag in self.tags_allowed: self.data.append((tag, '%s_%d' % (tag, self.idx), self.text)) self.idx += 1 self.process_text = False self.text = '' self.tag_curr = self.tag_last def handle_data(self, data): if not self.process_text: return if self.tag_curr in self.tags_allowed or self.tag_curr in self.tags_ignored: self.text += data else: self.text += '<%s>%s' % (self.tag_curr, data, self.tag_curr) # instantiate the parser and fed it some HTML parser = MyHTMLParser() parser.feed(src_data) return parser.data def escape_href(label): # remove html tags label = re.sub('<[^<]+?>', '', label) # fix   label = label.replace(' ', '') # fix " label = label.replace('"', '') # replace space with underscore + lower return label.replace(' ', '-').lower() def write_toc(data): if not data: return fd = sys.stdout fd.write('
\n') fd.write('

Table of contents

\n') fd.write('\n') fd.write('
\n') def update_toc(data): ret_data = [] pat = re.compile(r'(<(h[2|3])>)(.+)()') idx = 1 for line in data.splitlines(): if pat.search(line): xline = pat.split(line) line = xline[1] + '' % escape_href(xline[3]) + xline[3] + '' + xline[4] idx += 1 ret_data.append(line) return '\n'.join(ret_data) def get_addon_path(pgm): """Check if pgm is in addons list and get addon path :param pgm str: pgm :return tuple: (True, path) if pgm is addon else (None, None) """ addon_base = os.getenv('GRASS_ADDON_BASE') if addon_base: """'addons_paths.json' is file created during install extension check get_addons_paths() function in the g.extension.py file """ addons_paths = os.path.join(addon_base, 'addons_paths.json') if os.path.exists(addons_paths): with open(addons_paths, 'r') as f: addons_paths = json.load(f) for addon in addons_paths['tree']: split_path = addon['path'].split('/') root_dir, module_dir = split_path[0], split_path[-1] if 'grass7' == root_dir and pgm == module_dir: return True, addon['path'] return None, None # process header src_data = read_file(src_file) name = re.search('()', src_data, re.IGNORECASE) pgm_desc = None if name: pgm = name.group(2).strip().split('-', 1)[0].strip() name_desc = re.search('()', src_data, re.IGNORECASE) if name_desc: pgm_desc = name_desc.group(2).strip() desc = re.search('()', src_data, re.IGNORECASE) if desc: pgm = desc.group(2).strip() header_tmpl = string.Template(header_base + header_nopgm) else: if not pgm_desc: header_tmpl = string.Template(header_base + header_pgm) else: header_tmpl = string.Template(header_base + header_pgm_desc) if not re.search('', src_data, re.IGNORECASE): tmp_data = read_file(tmp_file) """ Adjusting keywords html pages paths if add-on html man page stored on the server """ if html_page_footer_pages_path: new_keywords_paths = [] orig_keywords_paths = re.search( r'KEYWORDS(.*?)', tmp_data, re.DOTALL, ).group(1) for i in orig_keywords_paths.split(','): index = i.index('href="') + len('href="') new_keywords_paths.append( i[:index] + html_page_footer_pages_path + i[index:], ) if new_keywords_paths: tmp_data = tmp_data.replace( orig_keywords_paths, ','.join(new_keywords_paths) if len(new_keywords_paths) > 1 else new_keywords_paths[0], ) if not re.search('', tmp_data, re.IGNORECASE): sys.stdout.write(header_tmpl.substitute(PGM=pgm, PGM_DESC=pgm_desc)) if tmp_data: for line in tmp_data.splitlines(True): if not re.search('|', line, re.IGNORECASE): sys.stdout.write(line) # create TOC write_toc(create_toc(src_data)) # process body sys.stdout.write(update_toc(src_data)) # if is found, suppose a complete html is provided. # otherwise, generate module class reference: if re.search('', src_data, re.IGNORECASE): sys.exit() index_names = { 'd' : 'display', 'db': 'database', 'g' : 'general', 'i' : 'imagery', 'm' : 'miscellaneous', 'ps': 'postscript', 'p' : 'paint', 'r' : 'raster', 'r3': 'raster3d', 's' : 'sites', 't' : 'temporal', 'v' : 'vector' } def to_title(name): """Convert name of command class/family to form suitable for title""" if name == 'raster3d': return '3D raster' elif name == 'postscript': return 'PostScript' else: return name.capitalize() index_titles = {} for key, name in index_names.items(): index_titles[key] = to_title(name) # process footer index = re.search('()', src_data, re.IGNORECASE) if index: index_name = index.group(2).strip() if '|' in index_name: index_name, index_name_cap = index_name.split('|', 1) else: index_name_cap = to_title(index_name) else: mod_class = pgm.split('.', 1)[0] index_name = index_names.get(mod_class, '') index_name_cap = index_titles.get(mod_class, '') grass_version = os.getenv("VERSION_NUMBER", "unknown") year = os.getenv("VERSION_DATE") if not year: year = str(datetime.now().year) # check the names of scripts to assign the right folder topdir = os.path.abspath(os.getenv("MODULE_TOPDIR")) curdir = os.path.abspath(os.path.curdir) if curdir.startswith(topdir): source_url = trunk_url pgmdir = curdir.replace(topdir, '').lstrip(os.path.sep) else: # addons source_url = addons_url pgmdir = os.path.sep.join(curdir.split(os.path.sep)[-3:]) url_source = '' if os.getenv('SOURCE_URL', ''): # addons for prefix in index_names.keys(): cwd = os.getcwd() idx = cwd.find('{0}{1}.'.format(os.path.sep, prefix)) if idx > -1: pgmname = cwd[idx+1:] classname = index_names[prefix] url_source = urlparse.urljoin('{0}{1}/'.format( os.environ['SOURCE_URL'], classname), pgmname ) break else: url_source = urlparse.urljoin(source_url, pgmdir) if sys.platform == 'win32': url_source = url_source.replace(os.path.sep, '/') if index_name: tree = 'grass/tree' commits = 'grass/commits' is_addon, addon_path = get_addon_path(pgm=pgm) if is_addon: # Fix gui/wxpython addon url path url_source = urlparse.urljoin( os.environ['SOURCE_URL'], addon_path.split('/', 1)[1], ) tree = 'grass-addons/tree' commits = 'grass-addons/commits' sys.stdout.write(sourcecode.substitute( URL_SOURCE=url_source, PGM=pgm, URL_LOG=url_source.replace( tree, commits))) sys.stdout.write( footer_index.substitute( INDEXNAME=index_name, INDEXNAMECAP=index_name_cap, YEAR=year, GRASS_VERSION=grass_version, HTML_PAGE_FOOTER_PAGES_PATH=html_page_footer_pages_path, ), ) else: sys.stdout.write( footer_noindex.substitute( YEAR=year, GRASS_VERSION=grass_version, HTML_PAGE_FOOTER_PAGES_PATH=html_page_footer_pages_path, ), )