mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2024-11-14 13:44:49 +08:00
212 lines
6.7 KiB
Python
Executable file
212 lines
6.7 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 script is used as a helper script to generate the pm3line_vocabulary.h file.
|
|
It need 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) Proxmark3 contributors. See AUTHORS.md for details.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// See LICENSE.txt for the text of the license.
|
|
//-----------------------------------------------------------------------------
|
|
// readline auto complete utilities
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef PM3LINE_VOCABULARY_H__
|
|
#define PM3LINE_VOCABULARY_H__
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#include <stdbool.h>
|
|
|
|
typedef struct vocabulary_s {
|
|
bool offline;
|
|
const char *name;
|
|
} vocabulary_t;
|
|
|
|
const static vocabulary_t vocabulary[] = {\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};
|
|
|
|
#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()
|