#!/usr/bin/env python # -*- coding: utf-8 -*- from NSCP import Settings, Registry, Core, log, log_debug, log_error, status import plugin_pb2 from optparse import OptionParser from sets import Set import os import traceback #import string from jinja2 import Template, Environment import hashlib helper = None module_template = u"""# {{module.key}} {{module.info.description}} {%if module.ext_desc -%} {{ module.ext_desc}} {%- endif %} {% if module.queries -%} ## List of commands A list of all available queries (check commands) {% set table = [] -%} {% for key,query in module.queries|dictsort -%} {% if query.info.description.startswith('Alternative name for:') -%} {% set command = query.info.description[22:] -%} {% do table.append([query.key|md_self_link, command|rst_link('query')]) -%} {%- elif query.info.description.startswith('Alias for:') -%} {% set command = query.info.description[11:] -%} {% do table.append([query.key|md_self_link, command|rst_link('query')]) -%} {%- else -%} {% do table.append([query.key|md_self_link, query.info.description|firstline]) -%} {%- endif %} {%- endfor %} {{table|rst_table('Command', 'Description')}} {%- endif %} {% if module.aliases -%} ## List of command aliases A list of all short hand aliases for queries (check commands) {% set table = [] -%} {% for key,query in module.aliases|dictsort -%} {% if query.info.description.startswith('Alternative name for:') -%} {% set command = query.info.description[22:] -%} {% do table.append([query.key, "Alias for: " + command|rst_link('query')]) -%} {%- elif query.info.description.startswith('Alias for:') -%} {% set command = query.info.description[11:] -%} {% do table.append([query.key, "Alias for: " + command|rst_link('query')]) -%} {%- else -%} {% do table.append([query.key, query.info.description|firstline]) -%} {%- endif %} {%- endfor %} {{table|rst_table('Command', 'Description')}} {%- endif %} {% if module.paths -%} ## List of Configuration {% set table = [] -%} {% set table_adv = [] -%} {% set table_sam = [] -%} {% for k,path in module.paths|dictsort -%} {% set pkey = path.key|md_self_link -%} {% for k,key in path.keys|dictsort -%} {% set kkey = k|md_prefix_lnk(path.key)|md_self_link(k) -%} {% if key.info.sample -%} {% do table_sam.append([pkey, kkey, key.info.title|firstline]) -%} {%- elif key.info.advanced -%} {% do table_adv.append([pkey, kkey, key.info.title|firstline]) -%} {%- else -%} {% do table.append([pkey, kkey, key.info.title|firstline]) -%} {%- endif %} {%- endfor %} {%- endfor %} {% if table -%} ### Common Keys {{table|rst_table('Path / Section', 'Key', 'Description')}} {%- endif %} {% if table_adv -%} ### Advanced keys {{table_adv|rst_table('Path / Section', 'Key', 'Description')}} {%- endif %} {% if table_sam -%} ### Sample keys {{table_sam|rst_table('Path / Section', 'Key', 'Description')}} {%- endif %} {%- endif %} {% if module.sample %} # Usage _To edit the usage section please edit [this page](https://github.com/mickem/nscp-docs/blob/master/{{module.sample_source}})_ {{module.sample}} {%- endif %} {% if module.queries -%} # Queries A quick reference for all available queries (check commands) in the {{module.key}} module. {% for k,query in module.queries|dictsort -%} ## {{query.key}} {{query.info.description}} {% if query.sample -%} ### Usage _To edit these sample please edit [this page](https://github.com/mickem/nscp-docs/blob/master/{{query.sample_source}})_ {{query.sample}} {%- endif %} ### Usage {% set table = [] -%} {% for help in query.params -%}{% if help.content_type == 4 -%} {% do table.append([help.name|md_prefix_lnk(query.key)|md_self_link(help.name),'N/A', help.long_description|firstline]) %}{% else -%} {% do table.append([help.name|md_prefix_lnk(query.key)|md_self_link(help.name),help.default_value, help.long_description|firstline]) %}{%- endif %} {%- endfor %} {{table|rst_table('Option', 'Default Value', 'Description')}} {% for help in query.params -%} ### {{help.name}} {% if help.default_value %} **Deafult Value:** {{help.default_value}} {%- endif %} **Description:** {%if help.ext -%} {{help.ext.head}} {{help.ext.data|rst_table}} {{help.ext.tail}} {% else -%} {{help.long_description}} {%- endif %} {{'\n'}} {%- endfor %} {%- endfor %} {%- endif %} {% if module.paths -%} # Configuration {% for pkey,path in module.paths|dictsort -%} ## {{path.info.title}} {{path.info.description}} ```ini # {{path.info.description|firstline}} [{{path.key}}] {% for kkey,key in path.keys|dictsort -%} {% if key.info.default_value|extract_value -%} {{kkey}}={{key.info.default_value|extract_value}} {% endif %} {%- endfor %} ``` {% set tbl = [] -%} {% set pkey = path.key|md_self_link -%} {% for k,key in path.keys|dictsort -%} {% set kkey = k|md_prefix_lnk(path.key)|md_self_link(k) -%} {% do tbl.append([kkey, key.info.default_value|extract_value, key.info.title|firstline]) -%} {%- endfor %} {{tbl|rst_table('Key', 'Default Value', 'Description')}} {% for kkey,key in path.keys|dictsort %} ### {{kkey}} **{{key.info.title|as_text}}** {%if key.ext -%} {{key.ext.head}} {{key.ext.data|rst_table}} {{key.ext.tail}} {% else -%} {{key.info.description|as_text}} {%- endif %} {% set table = [] -%} {% do table.append(['Path:', path.key|md_self_link(path.key)]) -%} {% do table.append(['Key:', kkey]) -%} {% if key.info.advanced -%} {% do table.append(['Advanced:', 'Yes (means it is not commonly used)']) -%} {%- endif %} {% if key.info.default_value|extract_value -%} {% do table.append(['Default value:', '`' + key.info.default_value|extract_value + '`']) -%} {% else %} {% do table.append(['Default value:', '_N/A_']) -%} {%- endif %} {% if key.info.sample -%} {% do table.append(['Sample key:', 'Yes (This section is only to show how this key is used)']) -%} {%- endif %} {% do table.append(['Used by:', ', '.join(path.info.plugin|sort)]) -%} {{table|rst_table('Key', 'Description')}} #### Sample ``` [{{path.key}}] # {{key.info.title}} {{kkey}}={{key.info.default_value|extract_value}} ``` {% endfor %} {% endfor %} {%- endif %} """ index_template = u""" # Modules {% set table = [] -%} {% for key,module in plugins|dictsort -%} {% do table.append([module.namespace, ('reference/' + module.namespace + '/' + module.key)|md_link(module.key), module.info.description|firstline]) -%} {%- endfor %} {{table|rst_table('Type', 'Module', 'Description')}} # Queries {% set table = [] -%} {% for mk,module in plugins|dictsort -%} {% for key,query in module.queries|dictsort -%} {% set desc = query.info.description|firstline %} {% if desc.startswith('Alternative name for:') -%} {% set desc = query.info.description[22:]|rst_link('query') -%} {%- elif desc.startswith('Legacy version of ') -%} {% set desc = '**Deprecated** please use: ' + query.info.description[18:]|rst_link('query') -%} {%- elif query.info.description.startswith('Alias for:') -%} {% set desc = query.info.description[11:]|rst_link('query') -%} {%- endif %} {% do table.append([mk, ('reference/' + module.namespace + '/' + module.key + '#' + query.key)|md_link(query.key), desc]) -%} {%- endfor %} {%- endfor %} {{table|rst_table('Module', 'Command', 'Description')}} """ samples_template = u""".. default-domain:: nscp .. default-domain:: nscp =========== All samples =========== A collection of all sample commands {% for mk,module in plugins|dictsort -%} {% set vars = {'found': False} -%} {% for qk,query in module.queries|dictsort -%} {% if query.sample -%} {% if vars.update({'found': True}) -%}{%- endif %} {%- endif %} {%- endfor %} {% if vars.found -%} {{mk|rst_heading('=')}} {% for qk,query in module.queries|dictsort -%} {% if query.sample -%} {{qk|rst_heading}} {{query.info.description|firstline}} .. include:: ../samples/{{query.sample}} {% endif %} {%- endfor %} {%- endif %} {%- endfor %} """ def split_argllist(name, desc): extdata = {} spos = desc.find('\n\n') if spos != -1: epos = desc.find('\n\n', spos+2) if epos != -1: pos = desc.find('\t', spos+2, epos) if pos != -1: extdata['head'] = desc[:spos] extdata['tail'] = desc[epos+2:] data = desc[spos+2:epos] rows = data.split('\n') tbl = [] for r in rows: tbl.append(r.split('\t')) extdata['data'] = tbl return extdata class root_container(object): paths = {} commands = {} aliases = {} plugins = {} windows_modules = ['CheckSystem', 'CheckDisk', 'NSClientServer', 'DotnetPlugins', 'CheckEventLog', 'CheckTaskSched', 'CheckWMI'] unix_modules = ['CheckSystemUnix'] check_modules = ['CheckExternalScripts', 'CheckHelpers', 'CheckLogFile', 'CheckMKClient', 'CheckMKServer', 'CheckNSCP'] client_modules = ['GraphiteClient', 'NRDPClient', 'NRPEClient', 'NRPEServer', 'NSCAClient', 'NSCAServer', 'NSClientServer', 'SMTPClient', 'SyslogClient'] generic_modules = ['CommandClient', 'DotnetPlugins', 'LUAScript', 'PythonScript', 'Scheduler', 'SimpleCache', 'SimpleFileWriter', 'WEBServer'] def __init__(self): self.paths = {} self.commands = {} self.aliases = {} self.plugins = {} def append_key(self, info): path = info.node.path if path in self.paths: self.paths[path].append_key(info) else: p = path_container() p.append_key(info) self.paths[path] = p def append_path(self, info): path = info.node.path if not path in self.paths: self.paths[path] = path_container(info) def append_command(self, info): name = info.name if not name in self.commands: self.commands[name] = command_container(info) def append_alias(self, info): name = info.name if not name in self.commands: self.aliases[name] = command_container(info) def append_plugin(self, info, folder): name = info.name namespace = '' if name in self.windows_modules: namespace = 'windows' elif name in self.unix_modules: namespace = 'unix' elif name in self.check_modules: namespace = 'check' elif name in self.client_modules: namespace = 'client' elif name in self.generic_modules: namespace = 'generic' else: namespace = 'misc' if not name in self.plugins: self.plugins[name] = plugin_container(info, namespace) spath = '%s/samples/%s_samples.md'%(folder, name) self.plugins[name].sample = '' if os.path.exists(spath): with open(spath) as f: self.plugins[name].sample = unicode(f.read(), 'utf8') self.plugins[name].sample_source = 'samples/%s_samples.md'%(name) spath = '%s/samples/%s_desc.md'%(folder, name) self.plugins[name].ext_desc = '' if os.path.exists(spath): with open(spath) as f: self.plugins[name].ext_desc = unicode(f.read(), 'utf8') self.plugins[name].ext_desc_source = 'samples/%s_desc.md'%(name) def get_hash(self): ret = {} ret['paths'] = self.paths ret['commands'] = self.commands ret['aliases'] = self.aliases ret['plugins'] = self.plugins return ret class path_container(object): keys = {} info = None def __init__(self, info = None): self.keys = {} self.info = info.info def append_key(self, info): extdata = split_argllist(info.node.key, info.info.description) if extdata: ninfo = {} ninfo['info'] = info.info ninfo['node'] = info.node ninfo['ext'] = extdata self.keys[info.node.key] = ninfo else: self.keys[info.node.key] = info class command_container(object): info = None parameters = None def __init__(self, info = None): self.info = info.info self.parameters = info.parameters class plugin_container(object): info = None namespace = '' def __init__(self, info = None, namespace = ''): self.info = info.info self.namespace = namespace def first_line(string): return string.strip().split('\n')[0] def make_rst_link(name, type, title = None): if title: return ':%s:`%s<%s>`'%(type, title, name) return ':%s:`%s`'%(type, name) def make_md_link(name, title = None): if title: return '[%s](%s)'%(title, name) return '[%s](%s)'%(name, name) def make_md_self_link(name, title = None): if title: return '[%s](#%s)'%(title, name) return '[%s](#%s)'%(name, name) def make_md_code(name): return '`%s`'%name def make_md_prefix_lnk(value, prefix): return '%s_%s'%(prefix, value) def largest_value(a,b): return map(lambda n: n[0] if len(n[0])>len(n[1]) else n[1], zip(a, b)) def extract_value(value): if value.HasField("string_data"): return value.string_data if value.HasField("int_data"): return '%d'%value.int_data if value.HasField("bool_data"): return "true" if value.bool_data else "false" return '' def as_text(value): value = value.replace('\\', '\\\\') value = value.replace('`', '\\`') value = value.replace('|', '\\|') return value def block_pad(string, pad, prefix = ''): string = string.strip() if not string: return '' ret = '' for line in string.split('\n'): if line != '\n': ret += (pad * ' ') + prefix + line + '\n' else: ret += line + '\n' return ret.rstrip() def render_rst_table(table, *args): if not table: return '' if args: table.insert(0, args) ret = '' maxcol = map(lambda a:len(a), reduce(lambda a,b: largest_value(a,b), table)) for idx, line in enumerate(table): ret += '|' + ''.join(map(lambda a:' ' + a[1].ljust(a[0],' ') + ' |', zip(maxcol, line))) + '\n' if idx == 0: ret += '|' + ''.join(map(lambda a:'-' + ''.ljust(a[0],'-') + '-|', zip(maxcol, line))) + '\n' return ret def render_rst_heading(string, char='-', top=False): if top: return "\n".rjust(len(string)+1, char) + string + "\n".ljust(len(string)+1, char) return string + "\n".ljust(len(string)+1, char) def getcommonletters(strlist): return ''.join([x[0] for x in zip(*strlist) \ if reduce(lambda a,b:(a == b) and a or None,x)]) def calculate_common_head(strlist): strlist = strlist[:] prev = None while True: common = getcommonletters(strlist) if common == prev: break strlist.append(common) prev = common return getcommonletters(strlist) def render_template(hash, template, filename): data = template.render(hash).encode('utf8') path = os.path.dirname(filename) if not os.path.exists(path): os.makedirs(path) if os.path.exists(filename): m1 = hashlib.sha256() m1.update(data) sha1 = m1.digest() with open(filename) as f: m2 = hashlib.sha256() m2.update(f.read()) sha2 = m2.digest() if sha1 == sha2: log_debug("no changes detected in: %s"%filename) return log_debug('Writing file: %s'%filename) f = open(filename,"wb") f.write(data) f.close() class DocumentationHelper(object): plugin_id = None plugin_alias = None script_alias = None conf = None registry = None core = None dir = None folder = None command_cache = {} def __init__(self, plugin_id, plugin_alias, script_alias): self.plugin_id = plugin_id self.plugin_alias = plugin_alias self.script_alias = script_alias self.conf = Settings.get(self.plugin_id) self.registry = Registry.get(self.plugin_id) self.core = Core.get(self.plugin_id) self.command_cache = {} self.folder = None def build_inventory_request(self, path = '/', recursive = True, keys = False): message = plugin_pb2.SettingsRequestMessage() payload = message.payload.add() payload.plugin_id = self.plugin_id payload.inventory.node.path = path payload.inventory.recursive_fetch = recursive payload.inventory.fetch_keys = keys payload.inventory.fetch_paths = not keys payload.inventory.fetch_samples = True payload.inventory.descriptions = True return message.SerializeToString() def build_command_request(self, type = 1): message = plugin_pb2.RegistryRequestMessage() payload = message.payload.add() payload.inventory.fetch_all = True payload.inventory.type.append(type) return message.SerializeToString() def get_paths(self): (code, data) = self.conf.query(self.build_inventory_request()) if code == 1: message = plugin_pb2.SettingsResponseMessage() message.ParseFromString(data) for payload in message.payload: if payload.inventory: log_debug('Found %d paths'%len(payload.inventory)) return payload.inventory return [] def get_keys(self, path): (code, data) = self.conf.query(self.build_inventory_request(path, False, True)) if code == 1: message = plugin_pb2.SettingsResponseMessage() message.ParseFromString(data) for payload in message.payload: if payload.inventory: log_debug('Found %d keys for %s'%(len(payload.inventory), path)) return payload.inventory return [] def get_queries(self): log_debug('Fetching queries...') (code, data) = self.registry.query(self.build_command_request(1)) if code == 1: message = plugin_pb2.RegistryResponseMessage() message.ParseFromString(data) for payload in message.payload: if payload.inventory: log_debug('Found %d queries'%len(payload.inventory)) return payload.inventory log_error('No queries found') return [] def get_query_aliases(self): log_debug('Fetching aliases...') (code, data) = self.registry.query(self.build_command_request(5)) if code == 1: message = plugin_pb2.RegistryResponseMessage() message.ParseFromString(data) for payload in message.payload: if payload.inventory: log_debug('Found %d aliases'%len(payload.inventory)) return payload.inventory log_error('No aliases found') return [] def get_plugins(self): log_debug('Fetching plugins...') (code, data) = self.registry.query(self.build_command_request(7)) if code == 1: message = plugin_pb2.RegistryResponseMessage() message.ParseFromString(data) for payload in message.payload: if payload.inventory: log_debug('Found %d plugins'%len(payload.inventory)) return payload.inventory log_error('No plugins') return [] def get_info(self): root = root_container() for p in self.get_plugins(): root.append_plugin(p, self.folder) for p in self.get_paths(): root.append_path(p) for k in self.get_keys(p.node.path): root.append_key(k) for p in self.get_queries(): root.append_command(p) for p in self.get_query_aliases(): root.append_alias(p) return root def fetch_command(self, module, minfo, command, cinfo): if command in self.command_cache: return self.command_cache[command] params = [] for p in cinfo.parameters.parameter: extdata = split_argllist(p.name, p.long_description) if extdata: np = {} np['long_description'] = p.long_description np['default_value'] = p.default_value np['name'] = p.name np['ext'] = extdata np['content_type'] = p.content_type params.append(np) else: params.append(p) cinfo.params = params spath = '%s/samples/%s_%s_samples.md'%(self.folder, module, command) cinfo.sample = '' if os.path.exists(spath): with open(spath) as f: cinfo.sample = unicode(f.read(), 'utf8') cinfo.sample_source = 'samples/%s_%s_samples.md'%(module, command) self.command_cache[command] = cinfo return cinfo def generate_rst(self, input_dir, output_dir): root = self.get_info() i = 0 env = Environment(extensions=["jinja2.ext.do",]) env.filters['firstline'] = first_line env.filters['rst_link'] = make_rst_link env.filters['md_link'] = make_md_link env.filters['md_prefix_lnk'] = make_md_prefix_lnk env.filters['md_self_link'] = make_md_self_link env.filters['md_code'] = make_md_code env.filters['rst_table'] = render_rst_table env.filters['rst_heading'] = render_rst_heading env.filters['extract_value'] = extract_value env.filters['block_pad'] = block_pad env.filters['common_head'] = calculate_common_head env.filters['as_text'] = as_text for (module,minfo) in root.plugins.iteritems(): out_base_path = '%s/docs/'%output_dir sample_base_path = '%s/docs/samples/'%output_dir if minfo.namespace: out_base_path = '%s/docs/reference/%s/'%(output_dir, minfo.namespace) hash = root.get_hash() minfo.key = module minfo.queries = {} sfile = '%s%s_samples.inc'%(sample_base_path, module) if os.path.exists(sfile): minfo.sample = os.path.basename(sfile) sfile = '%s%s_samples.rst'%(sample_base_path, module) if os.path.exists(sfile): minfo.sample = os.path.basename(sfile) for (c,cinfo) in sorted(root.commands.iteritems()): if module in cinfo.info.plugin: more_info = self.fetch_command(module, minfo, c,cinfo) if more_info: cinfo = more_info sfile = '%s%s_%s_samples.inc'%(sample_base_path, module, c) if os.path.exists(sfile): cinfo.sample = os.path.basename(sfile) #all_samples.append((module, command, sfile)) cinfo.key = c minfo.queries[c] = cinfo minfo.aliases = {} for (c,cinfo) in sorted(root.aliases.iteritems()): if module in cinfo.info.plugin: cinfo.key = c minfo.aliases[c] = cinfo minfo.paths = {} for (c,cinfo) in sorted(root.paths.iteritems()): if module in cinfo.info.plugin: cinfo.key = c minfo.paths[c] = cinfo hash['module'] = minfo i=i+1 log_debug('Processing module: %d of %d [%s]'%(i, len(root.plugins), module)) template = env.from_string(module_template) render_template(hash, template, '%s/%s.md'%(out_base_path, module)) hash = root.get_hash() template = env.from_string(index_template) render_template(hash, template, '%s/docs/reference/index.md'%output_dir) log_debug('%s/samples/index.rst'%output_dir) template = env.from_string(samples_template) render_template(hash, template, '%s/samples/index.rst'%output_dir) def main(self, args): parser = OptionParser(prog="") parser.add_option("-o", "--output", help="write report to FILE(s)") parser.add_option("-i", "--input", help="Reference folder") (options, args) = parser.parse_args(args=args) self.folder = options.output self.generate_rst(options.input, options.output) def __main__(args): global helper helper.main(args); return 0 def init(plugin_id, plugin_alias, script_alias): global helper helper = DocumentationHelper(plugin_id, plugin_alias, script_alias) def shutdown(): global helper helper = None