|
| 1 | +import sys |
| 2 | +import os |
| 3 | +import re |
| 4 | + |
| 5 | +__version__ = '1.0.0' |
| 6 | + |
| 7 | +cmd_cls = 'keyword' |
| 8 | +subcmd_cls = 'keywordtype' |
| 9 | +comment_class = 'comment' |
| 10 | +sudo_cmd_class = 'keywordflow' |
| 11 | + |
| 12 | +# Bash commands that are generally followed by a second command |
| 13 | +super_cmds = ['git', 'apt', 'docker'] |
| 14 | +url_pattern = r'(https?|ftp)://[^\s/$.?#].[^\s]*' |
| 15 | + |
| 16 | +def ProcessWord(word: str, is_command = False, is_subcommand = False) -> str: |
| 17 | + """ Process word and return type |
| 18 | +
|
| 19 | + :param word: word |
| 20 | + :param is_command: is command |
| 21 | + :param is_subcommand: is subcommand |
| 22 | + :return: type |
| 23 | + """ |
| 24 | + if word == 'sudo' and is_command: |
| 25 | + print('<span class="{}">{}</span>'.format(sudo_cmd_class, word), end='') |
| 26 | + return "sudo" |
| 27 | + if re.match(url_pattern, word): |
| 28 | + print('<a href="{}">{}</a>'.format(word, word), end='') |
| 29 | + return "url" |
| 30 | + if word in super_cmds and is_command: |
| 31 | + print('<span class="{}">{}</span>'.format(cmd_cls, word), end='') |
| 32 | + return "super" |
| 33 | + if is_command: |
| 34 | + print('<span class="{}">{}</span>'.format(cmd_cls, word), end='') |
| 35 | + return |
| 36 | + if is_subcommand: |
| 37 | + print('<span class="{}">{}</span>'.format(subcmd_cls, word), end='') |
| 38 | + return |
| 39 | + print(word, end='') |
| 40 | + return |
| 41 | + |
| 42 | +def ProcessBashCode(code: str) -> None: |
| 43 | + """ Process bash code and convert to doxygen format |
| 44 | +
|
| 45 | + :param code: bash code |
| 46 | + :return: None |
| 47 | + """ |
| 48 | + print('@htmlonly' + '\n', end='') |
| 49 | + # Start div fragment |
| 50 | + print('<div class="fragment">' + '\n', end='') |
| 51 | + |
| 52 | + # Split code into lines |
| 53 | + lines = code.split('\n') |
| 54 | + |
| 55 | + prev_line_continuation = False |
| 56 | + for line in lines: |
| 57 | + if len(line) == 0: |
| 58 | + continue |
| 59 | + # Start div line |
| 60 | + print('<div class="line">', end='') |
| 61 | + if prev_line_continuation: |
| 62 | + next_is_command = False |
| 63 | + next_is_subcommand = False |
| 64 | + else: |
| 65 | + next_is_command = True |
| 66 | + next_is_subcommand = False |
| 67 | + # if last character is \ then the command is continued in next line |
| 68 | + if line[-1] == '\\': |
| 69 | + prev_line_continuation = True |
| 70 | + else: |
| 71 | + prev_line_continuation = False |
| 72 | + |
| 73 | + word = '' |
| 74 | + char_count = 0 |
| 75 | + for ch in line: |
| 76 | + char_count += 1 |
| 77 | + if ch == '<': |
| 78 | + word += '<' |
| 79 | + continue |
| 80 | + if ch == '>': |
| 81 | + word += '>' |
| 82 | + continue |
| 83 | + if ch == '#': |
| 84 | + print('<span class="{}">{}</span>'.format(comment_class, line[char_count-1:]), end='') |
| 85 | + break |
| 86 | + if ch == ' ' or ch == '\t': |
| 87 | + if len(word) > 0: |
| 88 | + word_type = ProcessWord(word, next_is_command, next_is_subcommand) |
| 89 | + if word_type == "sudo": |
| 90 | + next_is_command = True |
| 91 | + next_is_subcommand = False |
| 92 | + elif word_type == "super": |
| 93 | + next_is_command = False |
| 94 | + next_is_subcommand = True |
| 95 | + else: |
| 96 | + next_is_command = False |
| 97 | + next_is_subcommand = False |
| 98 | + if ch == ' ': |
| 99 | + print(ch, end='') |
| 100 | + elif ch == '\t': |
| 101 | + print(' ', end='') |
| 102 | + word = '' |
| 103 | + else: |
| 104 | + word += ch |
| 105 | + |
| 106 | + if len(word) > 0: |
| 107 | + ProcessWord(word, next_is_command, next_is_subcommand) |
| 108 | + |
| 109 | + print('</div>' + '\n', end='') |
| 110 | + |
| 111 | + print('</div>' + '\n', end='') |
| 112 | + print('@endhtmlonly', end='') |
| 113 | + |
| 114 | + |
| 115 | + |
| 116 | +def FileHandle(filehandle: type(open)) -> None: |
| 117 | + """ Read file handle and convert bash code to a doxygen convenient format. |
| 118 | +
|
| 119 | + :param filehandle: input file handle |
| 120 | + :return: None |
| 121 | + """ |
| 122 | + # Find all code blocks with bash code |
| 123 | + for line in filehandle: |
| 124 | + code_block = "" |
| 125 | + if re.match(r'^```bash', line): |
| 126 | + for line in filehandle: |
| 127 | + if re.match(r'^```', line): |
| 128 | + break |
| 129 | + code_block += line |
| 130 | + ProcessBashCode(code_block) |
| 131 | + else: |
| 132 | + print(line, end='') |
| 133 | + return |
| 134 | + |
| 135 | + |
| 136 | +def main(file: str) -> None: |
| 137 | + """ filter main procedure |
| 138 | +
|
| 139 | + :param file: input file name or '-' for input stream |
| 140 | + :return: |
| 141 | + """ |
| 142 | + if file == '-': |
| 143 | + FileHandle(sys.stdin) |
| 144 | + else: |
| 145 | + try: |
| 146 | + with open(file, 'r') as filehandle: |
| 147 | + FileHandle(filehandle) |
| 148 | + except OSError as err: |
| 149 | + print("ERROR: can't open '{}' for read: {} (errno={})".format(err.filename, err.strerror, err.errno), |
| 150 | + file=sys.stderr) |
| 151 | + |
| 152 | +if __name__ == '__main__': |
| 153 | + if len(sys.argv) >= (1+1): |
| 154 | + main(sys.argv[1]) |
| 155 | + else: |
| 156 | + pname = os.path.basename(__file__) |
| 157 | + print('{} {}\n\nUSAGE: {} <name>.py'.format(pname, __version__, pname), file=sys.stderr) |
| 158 | + |
| 159 | +# EOF # |
0 commit comments