proxmark3/client/pyscripts/pm3_help2list.py

255 lines
7.6 KiB
Python
Executable file

#!/usr/bin/env python3
"""
PM3 Help 2 List
This script takes the full text help output from the PM3 client and converts it to a list to be used for readline autocomplete.
It is based on pm3_help2JSON.py by
Original Authors / Maintainers:
- Samuel Windall
This version
- Iceman
Note:
This file is used as a helper script to generate the rl_vocabulory.h file need.
It also needs a working proxmark3 client to extract the help text.
Ie: this script can't be used inside the normal build sequence.
"""
import re
import datetime
import argparse
import logging
##############################################################################
# Script version data: (Please increment when making updates)
APP_NAME = 'PM3Help2List'
VERSION_MAJOR = 1
VERSION_MINOR = 0
##############################################################################
# Main Application Code:
def main():
"""The main function for the script"""
args = build_arg_parser().parse_args()
logging_format = '%(message)s'
if args.debug:
logging.basicConfig(level=logging.DEBUG, format=logging_format)
else:
logging.basicConfig(level=logging.WARN, format=logging_format)
logging.info(f'{get_version()} starting...')
help_text = args.input_file.read()
command_data = parse_all_command_data(help_text)
args.output_file.write("""//-----------------------------------------------------------------------------
// Copyright (C) 2021 <iceman>
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// readline auto complete utilities
//-----------------------------------------------------------------------------
#ifndef RL_VOCABULORY_H__
#define RL_VOCABULORY_H__
#ifdef __cplusplus
extern "C" {
#endif
#ifdef HAVE_READLINE
#include <stdlib.h>
#include <string.h>
#include <readline/readline.h>
#include "ui.h" // g_session
char* rl_command_generator(const char *text, int state);
char **rl_command_completion(const char *text, int start, int end);
typedef struct vocabulory_s {
bool offline;
const char *name;
} vocabulory_t;
const static vocabulory_t vocabulory[] = {\n""")
for key, values in command_data.items():
offline = 0
if (values['offline'] == True):
offline = 1
cmd = values['command']
args.output_file.write(' {{ {}, "{}" }}, \n'.format(offline, cmd))
args.output_file.write(""" {0, NULL}\n};
char **rl_command_completion(const char *text, int start, int end) {
rl_attempted_completion_over = 0;
return rl_completion_matches (text, rl_command_generator);
}
char* rl_command_generator(const char *text, int state) {
static int index;
static size_t len;
size_t rlen = strlen(rl_line_buffer);
const char *command;
if (!state) {
index = 0;
len = strlen(text);
}
while ((command = vocabulory[index].name)) {
// When no pm3 device present
// and the command is not available offline,
// we skip it.
if ((g_session.pm3_present == false) && (vocabulory[index].offline == false )) {
index++;
continue;
}
index++;
if (strncmp (command, rl_line_buffer, rlen) == 0) {
const char *next = command + (rlen - len);
const char *space = strstr(next, " ");
if (space != NULL) {
return strndup(next, space - next);
}
return strdup(next);
}
}
return NULL;
}
#endif
#ifdef __cplusplus
}
#endif
#endif""")
logging.info(f'{get_version()} completed!')
def build_arg_parser():
"""Build the argument parser for reading the program arguments"""
parser = argparse.ArgumentParser()
parser.add_argument('input_file', type=argparse.FileType('r'), help='Source of full text help from the PM3 client.')
parser.add_argument('output_file', type=argparse.FileType('w'), help='Destination for list output.')
parser.add_argument('--version', '-v', action='version', version=get_version(), help='Version data about this app.')
parser.add_argument('--debug', '-d', action='store_true', help='Log debug messages.')
return parser
def build_help_regex():
"""The regex uses to parse the full text output of help data from the pm3 client."""
# Reads the divider followed by the command itself
re_command = r'-{87}\n(?P<command>.+)\n'
# Reads if the command is available offline
re_offline = r'available offline: (?P<offline>yes|no)\n+'
return re.compile(re_command+re_offline, re.MULTILINE);
def parse_all_command_data(help_text):
"""Turns the full text output of help data from the pm3 client into a list of dictionaries"""
command_dicts = {}
# Strip out ANSI escape sequences
help_text = remove_ansi_escape_codes(help_text)
# Find all commands in the full text help output
matches = build_help_regex().finditer(help_text)
for match in matches:
# Turn a match into a dictionary with keys for the extracted fields
command_object = parse_command_data(match)
# Store this command against its name for easy lookup
command_dicts[command_object['command']] = command_object
return command_dicts
def parse_command_data(match):
"""Turns a regex match of a command in the help text and converts it into a dictionary"""
logging.info('Parsing new command...')
# Get and clean the command string
command = remove_extra_whitespace(match.group('command'))
logging.info(f' Command: {command}')
# Get the online status as a boolean. Note: the regex only picks up 'yes' or 'no' so this check is safe.
offline = (match.group('offline') == 'yes')
logging.debug(f' Offline: {offline}')
# Construct the command dictionary
command_data = {
'command': command,
'offline': offline,
}
logging.info('Completed parsing command!')
return command_data
##############################################################################
# Helper Functions:
def get_version():
"""Get the version string for this script"""
return f'{APP_NAME} v{VERSION_MAJOR}.{VERSION_MINOR:02}'
def remove_ansi_escape_codes(text):
"""Remove ANSI escape sequences that may be left in the text."""
re_ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
return re_ansi_escape.sub('', str(text)).lower()
def remove_extra_whitespace(text):
"""Removes extra whitespace that may be in the text."""
# Ensure input is a string
text = str(text)
# Remove whitespace from the start and end of the text
text = text.strip()
# Deduplicate spaces in the string
text = re.sub(r' +', ' ', text)
return text
def text_to_oneliner(text):
"""Converts a multi line string into a single line string and removes extra whitespace"""
# Ensure input is a string
text = str(text)
# Replace newlines with spaces
text = re.sub(r'\n+', ' ', text)
# Remove the extra whitespace
text = remove_extra_whitespace(text)
return text
def text_to_list(text):
"""Converts a multi line string into a list of lines and removes extra whitespace"""
# Ensure input is a string
text = str(text)
# Get all the lines
lines = text.strip().split('\n')
# For each line clean up any extra whitespace
return [remove_extra_whitespace(line) for line in lines]
##############################################################################
# Application entrypoint:
if __name__ == '__main__':
main()