#!/usr/bin/env python3 # -*- coding: utf-8 -*- from __future__ import print_function import os import re def compile(service, version, alias, scheme, ports, unimplemented, dokku_version): return "\n\n".join([ header(service), description(service, version), requirements_section(dokku_version), installation_section(service, dokku_version), commands_section(service, alias, scheme, ports, unimplemented), usage_section(service, alias, scheme, ports, unimplemented), ]).replace("\n\n\n\n\n", "\n").replace("\n\n\n\n", "\n").replace("\n\n\n", "\n\n") def header(service): return " ".join([ f"# dokku {service}", f"[![Build Status](https://img.shields.io/travis/dokku/dokku-{service}.svg?branch=master \"Build Status\")](https://travis-ci.org/dokku/dokku-{service})", f"[![IRC Network](https://img.shields.io/badge/irc-freenode-blue.svg \"IRC Freenode\")](https://webchat.freenode.net/?channels=dokku)", ]) def description(service, version): return f"Official {service} plugin for dokku. Currently defaults to installing [{service} {version}](https://hub.docker.com/_/{service}/)." def requirements_section(dokku_version): return "\n".join([ "## Requirements", "", f"- dokku {dokku_version}", "- docker 1.8.x", ]) def installation_section(service, dokku_version): return "\n".join([ "## Installation", "", "```shell", f"# on {dokku_version}", f"sudo dokku plugin:install https://github.com/dokku/dokku-{service}.git {service}", "```", ]) def commands_section(service, alias, scheme, ports, unimplemented): content = [ "## Commands", "", "```", ] subcommands = os.listdir("subcommands") subcommands.sort() command_list = [] descriptions = [] for filename in subcommands: if filename in unimplemented: continue data = command_data(filename, service, alias, scheme, ports) description = data["description"] arguments = data["arguments_string"] command_list.append(f"{service}:{filename} {arguments}") descriptions.append(description) maxlen = max(map(len, command_list)) if maxlen > 50: maxlen = 50 for command, description in zip(command_list, descriptions): space_count = maxlen - len(command) content.append("{0}{1} # {2}".format(command, " " * space_count, description)) content.append("```") return "\n".join(content) def usage_section(service, alias, scheme, ports, unimplemented): return "\n\n".join([ "## Usage", f"Help for any commands can be displayed by specifying the command as an argument to {service}:help. Please consult the `{service}:help` command for any undocumented commands.", usage_intro(service, alias, scheme, ports, unimplemented), usage_lifecycle(service, alias, scheme, ports, unimplemented), usage_automation(service, alias, scheme, ports, unimplemented), usage_data_management(service, alias, scheme, ports, unimplemented), usage_backup(service, alias, scheme, ports, unimplemented), usage_docker_pull(service, alias, scheme, ports, unimplemented), ]) def usage_intro(service, alias, scheme, ports, unimplemented): commands = ["create", "info", "list", "logs", "link", "unlink"] content = ["### Basic Usage"] return fetch_commands_content(service, alias, scheme, ports, unimplemented, commands, content) def usage_lifecycle(service, alias, scheme, ports, unimplemented): commands = ["connect", "enter", "expose", "unexpose", "promote", "start", "stop", "restart", "upgrade"] content = [ "### Service Lifecycle", "", "The lifecycle of each service can be managed through the following commands:", "", ] return fetch_commands_content(service, alias, scheme, ports, unimplemented, commands, content) def usage_automation(service, alias, scheme, ports, unimplemented): commands = ["app-links", "clone", "exists", "linked", "links"] content = [ "### Service Automation", "", "Service scripting can be executed using the following commands:", "", ] return fetch_commands_content(service, alias, scheme, ports, unimplemented, commands, content) def usage_data_management(service, alias, scheme, ports, unimplemented): commands = ["import", "export"] content = [ "### Data Management", "", "The underlying service data can be imported and exported with the following commands:", "", ] return fetch_commands_content(service, alias, scheme, ports, unimplemented, commands, content) def usage_backup(service, alias, scheme, ports, unimplemented): commands = ["backup-auth", "backup-deauth", "backup", "backup-set-encryption", "backup-unset-encryption", "backup-schedule", "backup-schedule-cat", "backup-unschedule",] content = [ "### Backups", "", "Datastore backups are supported via AWS S3 and S3 compatible services like [minio](https://github.com/minio/minio).", "", "You may skip the `backup-auth` step if your dokku install is running within EC2 and has access to the bucket via an IAM profile. In that case, use the `--use-iam` option with the `backup` command.", "", "Backups can be performed using the backup commands:", "", ] return fetch_commands_content(service, alias, scheme, ports, unimplemented, commands, content) def usage_docker_pull(service, alias, scheme, ports, unimplemented): service_prefix = service.upper() return "\n".join([ "### Disabling `docker pull` calls", "", f"If you wish to disable the `docker pull` calls that the plugin triggers, you may set the `{service_prefix}_DISABLE_PULL` environment variable to `true`. Once disabled, you will need to pull the service image you wish to deploy as shown in the `stderr` output.", "", "Please ensure the proper images are in place when `docker pull` is disabled.", ]) def fetch_commands_content(service, alias, scheme, ports, unimplemented, commands, content): i = 0 for command in commands: output = command_help(command, service, alias, scheme, ports, unimplemented) if output == "": continue content.append(output) i += 1 if i == 0: return "" return "\n".join(content) def parse_args(line): line = line.strip() arguments = [] for arg in re.findall("([A-Z_]+)", line): arg = arg.replace("_", "-").lower() if arg.endswith("optional-flag"): arg = arg.replace("-optional-flag", "") arguments.append(f"[--{arg}]") elif arg.endswith("-flag"): if arg == "info-flag": arguments.append(f"[--single-info-flag]") else: arg = arg.replace("-flag", "") first_letter = arg[0] arguments.append(f"[-{first_letter}|--{arg}]") elif arg.endswith("-flags-list"): arg = arg.replace("-list", "") arguments.append(f"[--{arg}...]") elif arg.endswith("list"): arg = arg.replace("-list", "") arguments.append(f"<{arg}...>") else: arguments.append(f"<{arg}>") return " ".join(arguments) def command_help(command, service, alias, scheme, ports, unimplemented): if command in unimplemented: return "" data = command_data(command, service, alias, scheme, ports) content = [ f"### {data['description']}", "", "```shell", "# usage", f"dokku {service}:{command} {data['arguments_string']}", "```", ] # if len(data["arguments"]) > 0: # content.append("") # content.append("arguments:") # content.append("") # for argument in data["arguments"]: # content.append(f"- {argument}") # if len(data["flags"]) > 0: # content.append("") # content.append("flags:") # content.append("") # for flag in data["flags"]: # content.append(f"- {flag}") if len(data["examples"]) > 0: content.append("") content.append(data["examples"]) return "\n" + "\n".join(content) def command_data(command, service, alias, scheme, ports): description = None arguments = [] arguments_string = "" example_lines = [] flags = [] with open(os.path.join("subcommands", command)) as f: for line in f.readlines(): line = line.strip() line = line.replace("$PLUGIN_SERVICE", service) line = line.replace("$PLUGIN_COMMAND_PREFIX", service) line = line.replace("${PLUGIN_COMMAND_PREFIX}", service) line = line.replace("${PLUGIN_DEFAULT_ALIAS}", alias) line = line.replace("${PLUGIN_SCHEME}", scheme) line = line.replace("${PLUGIN_DATASTORE_PORTS[0]}", ports[0]) if "declare desc" in line: description = re.search('"(.+)"', line).group(1) elif "$1" in line: arguments_string = parse_args(line) elif line.startswith("#A "): argument = line.replace("#A ", "") parts = [a.strip() for a in argument.split(",", 1)] arguments.append(f"`{parts[0]}`: {parts[1]}") elif line.startswith("#F "): flag = line.replace("#F ", "") parts = [a.strip() for a in flag.split(",", 1)] flags.append(f"`{parts[0]}`: {parts[1]}") elif line.startswith("#E "): example_lines.append(line.replace("#E ", "")) examples = [] sentence_lines = [] command_lines = [] codeblock_lines = [] blockquote_lines = [] for line in example_lines: if line.startswith("export") or line.startswith("dokku"): if len(blockquote_lines) > 0: examples.append("\n" + process_blockquote(blockquote_lines)) blockquote_lines = [] if len(codeblock_lines) > 0: examples.append("\n" + process_codeblock(codeblock_lines)) codeblock_lines = [] if len(sentence_lines) > 0: examples.append("\n" + process_sentence(sentence_lines)) sentence_lines = [] command_lines.append(line) elif line.startswith(" "): if len(blockquote_lines) > 0: examples.append("\n" + process_blockquote(blockquote_lines)) blockquote_lines = [] if len(command_lines) > 0: examples.append("\n" + process_command(command_lines)) command_lines = [] if len(sentence_lines) > 0: examples.append("\n" + process_sentence(sentence_lines)) sentence_lines = [] codeblock_lines.append(line.strip()) elif line.startswith(">"): if len(codeblock_lines) > 0: examples.append("\n" + process_codeblock(codeblock_lines)) codeblock_lines = [] if len(command_lines) > 0: examples.append("\n" + process_command(command_lines)) command_lines = [] if len(sentence_lines) > 0: examples.append("\n" + process_sentence(sentence_lines)) sentence_lines = [] blockquote_lines.append(line) else: if len(blockquote_lines) > 0: examples.append("\n" + process_blockquote(blockquote_lines)) blockquote_lines = [] if len(codeblock_lines) > 0: examples.append("\n" + process_codeblock(codeblock_lines)) codeblock_lines = [] if len(command_lines) > 0: examples.append("\n" + process_command(command_lines)) command_lines = [] sentence_lines.append(line) if len(blockquote_lines) > 0: examples.append("\n" + process_blockquote(blockquote_lines)) blockquote_lines = [] if len(codeblock_lines) > 0: examples.append("\n" + process_codeblock(codeblock_lines)) codeblock_lines = [] if len(command_lines) > 0: examples.append("\n" + process_command(command_lines)) command_lines = [] if len(sentence_lines) > 0: examples.append("\n" + process_sentence(sentence_lines)) sentence_lines = [] return { "description": description, "arguments_string": arguments_string, "arguments": arguments, "flags": flags, "examples": "\n".join(examples).strip(), } def process_sentence(sentence_lines): sentence_lines = " ".join(sentence_lines) sentences = ". ".join(i.strip().capitalize() for i in sentence_lines.split(".")).strip() if not sentences.endswith(".") and not sentences.endswith(":"): sentences += ":" return sentences def process_blockquote(blockquote_lines): return "\n".join(blockquote_lines) def process_command(command_lines): command_lines = "\n".join(command_lines) return f"```shell\n{command_lines}\n```" def process_codeblock(codeblock_lines): codeblock_lines = "\n".join(codeblock_lines) return f"```\n{codeblock_lines}\n```" def main(): service = None version = None alias = None unimplemented = [] with open("config") as f: for line in f.readlines(): if "IMAGE_VERSION=${" in line: version = re.search('"(.+)"', line).group(1) if "PLUGIN_COMMAND_PREFIX=" in line: service = re.search('"(.+)"', line).group(1) if "PLUGIN_DEFAULT_ALIAS=" in line: alias = re.search('"(.+)"', line).group(1) if "PLUGIN_SCHEME=" in line: scheme = re.search('"(.+)"', line).group(1) if "PLUGIN_DATASTORE_PORTS=" in line: ports = re.search('\((.+)\)', line).group(1).split(" ") if "PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=" in line: match = re.search('\((.+)\)', line) if match is not None: unimplemented = [s.strip('"') for s in match.group(1).split(" ")] text = compile(service, version, alias, scheme, ports, unimplemented, "0.12.x+") base_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) readme_file = os.path.join(base_path, 'README.md') with open(readme_file, 'w') as f: f.write(text) if __name__ == "__main__": main()