mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-19 21:03:48 -07:00
Added readline autocomplete to proxmark3 client commands. Still works with shell filenames tab complete
This commit is contained in:
parent
287a99d186
commit
a348f48602
5 changed files with 1006 additions and 0 deletions
249
client/pyscripts/pm3_help2list.py
Executable file
249
client/pyscripts/pm3_help2list.py
Executable file
|
@ -0,0 +1,249 @@
|
|||
#!/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 = 1;
|
||||
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) {
|
||||
return strdup(command + (rlen - len));
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
Loading…
Add table
Add a link
Reference in a new issue