From a300526eb61f008a71ea1a3ef94f383b2b48fd83 Mon Sep 17 00:00:00 2001 From: Mark Oborne Date: Sun, 21 Jul 2024 20:30:25 +0100 Subject: [PATCH] feat: optimize incase of large inputs Also changes --summarise flag to --summary. --summarise was recent and unreleased. --- .gitignore | 4 ++- Cargo.lock | 9 +++++- Cargo.toml | 3 +- completion/_prefix | 34 ++++++++++++++-------- completion/_prefix.ps1 | 26 ++++++++++------- completion/prefix.bash | 30 +++++++++++++++---- completion/prefix.fish | 13 +++++---- man/prefix.1 | 38 +++++++++++++++--------- src/command.rs | 2 +- src/prefix/mod.rs | 65 +++++++++++++++++++++--------------------- 10 files changed, 142 insertions(+), 82 deletions(-) diff --git a/.gitignore b/.gitignore index 35fe21a..2f2cb43 100644 --- a/.gitignore +++ b/.gitignore @@ -175,4 +175,6 @@ target/ # These are backup files generated by rustfmt **/*.rs.bk -notes.md \ No newline at end of file +notes.md +test.txt +large_test.txt diff --git a/Cargo.lock b/Cargo.lock index 36bf9b6..5e833fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,13 +124,20 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "prefix" -version = "1.0.4" +version = "1.1.0" dependencies = [ "clap", "clap_complete", "clap_mangen", + "once_cell", "regex", ] diff --git a/Cargo.toml b/Cargo.toml index cc00c27..3c48147 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prefix" -version = "1.0.4" +version = "1.1.0" authors = ["Shivix"] edition = "2021" description = "A customizable pretty printer for FIX messages" @@ -27,6 +27,7 @@ categories = [ [dependencies] regex = "1.10.5" clap = "4.5.9" +once_cell = "1.19.0" [build-dependencies] clap = "4.5.9" diff --git a/completion/_prefix b/completion/_prefix index 518d4e6..8cf55b5 100644 --- a/completion/_prefix +++ b/completion/_prefix @@ -14,17 +14,23 @@ _prefix() { fi local context curcontext="$curcontext" state line - _arguments "${_arguments_options[@]}" \ -'-d+[Set delimiter string to print after each FIX field.]: : ' \ -'--delimiter=[Set delimiter string to print after each FIX field.]: : ' \ -'-h[Print help information]' \ -'--help[Print help information]' \ -'-V[Print version information]' \ -'--version[Print version information]' \ -'-v[Translate the values of some tags. (for Side: 1 -> Buy)]' \ -'--value[Translate the values of some tags. (for Side: 1 -> Buy)]' \ -'-s[Strip the whitespace around the = in each field. Less human readable but closer to real FIX.]' \ -'--strip[Strip the whitespace around the = in each field. Less human readable but closer to real FIX.]' \ + _arguments "${_arguments_options[@]}" : \ +'-c+[Adds colour to the delimiter and = in for FIX fields, auto will colour only when printing directly into a tty]:when:(always auto never)' \ +'--color=[Adds colour to the delimiter and = in for FIX fields, auto will colour only when printing directly into a tty]:when:(always auto never)' \ +'-d+[Set delimiter string to print after each FIX field]:delimiter: ' \ +'--delimiter=[Set delimiter string to print after each FIX field]:delimiter: ' \ +'-z+[Summarise each fix message based on an optional template]' \ +'--summary=[Summarise each fix message based on an optional template]' \ +'-s[Strip the whitespace around the = in each field]' \ +'--strip[Strip the whitespace around the = in each field]' \ +'-t[Translate all numbers to tag names whether part of a message or not]' \ +'--tag[Translate all numbers to tag names whether part of a message or not]' \ +'-v[Translate the values of some tags (for Side\: 1 -> Buy)]' \ +'--value[Translate the values of some tags (for Side\: 1 -> Buy)]' \ +'-h[Print help]' \ +'--help[Print help]' \ +'-V[Print version]' \ +'--version[Print version]' \ '*::message -- FIX message to be parsed, if not provided will look for a message piped through stdin:' \ && ret=0 } @@ -35,4 +41,8 @@ _prefix_commands() { _describe -t commands 'prefix commands' commands "$@" } -_prefix "$@" +if [ "$funcstack[1]" = "_prefix" ]; then + _prefix "$@" +else + compdef _prefix prefix +fi diff --git a/completion/_prefix.ps1 b/completion/_prefix.ps1 index 720ddeb..406258d 100644 --- a/completion/_prefix.ps1 +++ b/completion/_prefix.ps1 @@ -21,16 +21,22 @@ Register-ArgumentCompleter -Native -CommandName 'prefix' -ScriptBlock { $completions = @(switch ($command) { 'prefix' { - [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Set delimiter string to print after each FIX field.') - [CompletionResult]::new('--delimiter', 'delimiter', [CompletionResultType]::ParameterName, 'Set delimiter string to print after each FIX field.') - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') - [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information') - [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Translate the values of some tags. (for Side: 1 -> Buy)') - [CompletionResult]::new('--value', 'value', [CompletionResultType]::ParameterName, 'Translate the values of some tags. (for Side: 1 -> Buy)') - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Strip the whitespace around the = in each field. Less human readable but closer to real FIX.') - [CompletionResult]::new('--strip', 'strip', [CompletionResultType]::ParameterName, 'Strip the whitespace around the = in each field. Less human readable but closer to real FIX.') + [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Adds colour to the delimiter and = in for FIX fields, auto will colour only when printing directly into a tty') + [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Adds colour to the delimiter and = in for FIX fields, auto will colour only when printing directly into a tty') + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Set delimiter string to print after each FIX field') + [CompletionResult]::new('--delimiter', 'delimiter', [CompletionResultType]::ParameterName, 'Set delimiter string to print after each FIX field') + [CompletionResult]::new('-z', 'z', [CompletionResultType]::ParameterName, 'Summarise each fix message based on an optional template') + [CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'Summarise each fix message based on an optional template') + [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Strip the whitespace around the = in each field') + [CompletionResult]::new('--strip', 'strip', [CompletionResultType]::ParameterName, 'Strip the whitespace around the = in each field') + [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Translate all numbers to tag names whether part of a message or not') + [CompletionResult]::new('--tag', 'tag', [CompletionResultType]::ParameterName, 'Translate all numbers to tag names whether part of a message or not') + [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Translate the values of some tags (for Side: 1 -> Buy)') + [CompletionResult]::new('--value', 'value', [CompletionResultType]::ParameterName, 'Translate the values of some tags (for Side: 1 -> Buy)') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version') + [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version') break } }) diff --git a/completion/prefix.bash b/completion/prefix.bash index fd980ca..53ed58c 100644 --- a/completion/prefix.bash +++ b/completion/prefix.bash @@ -1,5 +1,5 @@ _prefix() { - local i cur prev opts cmds + local i cur prev opts cmd COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" @@ -8,8 +8,8 @@ _prefix() { for i in ${COMP_WORDS[@]} do - case "${i}" in - "$1") + case "${cmd},${i}" in + ",$1") cmd="prefix" ;; *) @@ -19,12 +19,20 @@ _prefix() { case "${cmd}" in prefix) - opts="-h -V -d -v -s --help --version --delimiter --value --strip ..." + opts="-c -d -s -z -t -v -h -V --color --delimiter --strip --summary --tag --value --help --version [message]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --color) + COMPREPLY=($(compgen -W "always auto never" -- "${cur}")) + return 0 + ;; + -c) + COMPREPLY=($(compgen -W "always auto never" -- "${cur}")) + return 0 + ;; --delimiter) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -33,6 +41,14 @@ _prefix() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --summary) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -z) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -43,4 +59,8 @@ _prefix() { esac } -complete -F _prefix -o bashdefault -o default prefix +if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then + complete -F _prefix -o nosort -o bashdefault -o default prefix +else + complete -F _prefix -o bashdefault -o default prefix +fi diff --git a/completion/prefix.fish b/completion/prefix.fish index 36e0524..864c8cf 100644 --- a/completion/prefix.fish +++ b/completion/prefix.fish @@ -1,5 +1,8 @@ -complete -c prefix -s d -l delimiter -d 'Set delimiter string to print after each FIX field.' -r -complete -c prefix -s h -l help -d 'Print help information' -complete -c prefix -s V -l version -d 'Print version information' -complete -c prefix -s v -l value -d 'Translate the values of some tags. (for Side: 1 -> Buy)' -complete -c prefix -s s -l strip -d 'Strip the whitespace around the = in each field. Less human readable but closer to real FIX.' +complete -c prefix -s c -l color -d 'Adds colour to the delimiter and = in for FIX fields, auto will colour only when printing directly into a tty' -r -f -a "{always\t'',auto\t'',never\t''}" +complete -c prefix -s d -l delimiter -d 'Set delimiter string to print after each FIX field' -r +complete -c prefix -s z -l summary -d 'Summarise each fix message based on an optional template' -r +complete -c prefix -s s -l strip -d 'Strip the whitespace around the = in each field' +complete -c prefix -s t -l tag -d 'Translate all numbers to tag names whether part of a message or not' +complete -c prefix -s v -l value -d 'Translate the values of some tags (for Side: 1 -> Buy)' +complete -c prefix -s h -l help -d 'Print help' +complete -c prefix -s V -l version -d 'Print version' diff --git a/man/prefix.1 b/man/prefix.1 index 4cd31f0..9f00602 100644 --- a/man/prefix.1 +++ b/man/prefix.1 @@ -1,31 +1,43 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' -.TH prefix 1 "prefix 1.0.4" +.TH prefix 1 "prefix 1.1.0" .SH NAME prefix \- A customizable pretty printer for FIX messages .SH SYNOPSIS -\fBprefix\fR [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fB\-d\fR|\fB\-\-delimiter\fR] [\fB\-v\fR|\fB\-\-value\fR] [\fB\-s\fR|\fB\-\-strip\fR] [\fImessage\fR] +\fBprefix\fR [\fB\-c\fR|\fB\-\-color\fR] [\fB\-d\fR|\fB\-\-delimiter\fR] [\fB\-s\fR|\fB\-\-strip\fR] [\fB\-z\fR|\fB\-\-summary\fR] [\fB\-t\fR|\fB\-\-tag\fR] [\fB\-v\fR|\fB\-\-value\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fImessage\fR] .SH DESCRIPTION A customizable pretty printer for FIX messages .SH OPTIONS .TP -\fB\-h\fR, \fB\-\-help\fR -Print help information +\fB\-c\fR, \fB\-\-color\fR=\fIwhen\fR [default: auto] +Adds colour to the delimiter and = in for FIX fields, auto will colour only when printing directly into a tty +.br + +.br +[\fIpossible values: \fRalways, auto, never] .TP -\fB\-V\fR, \fB\-\-version\fR -Print version information +\fB\-d\fR, \fB\-\-delimiter\fR=\fIdelimiter\fR [default: \\n] +Set delimiter string to print after each FIX field +.TP +\fB\-s\fR, \fB\-\-strip\fR +Strip the whitespace around the = in each field +.TP +\fB\-z\fR, \fB\-\-summary\fR=\fItemplate\fR +Summarise each fix message based on an optional template .TP -\fB\-d\fR, \fB\-\-delimiter\fR [default: -] -Set delimiter string to print after each FIX field. +\fB\-t\fR, \fB\-\-tag\fR +Translate all numbers to tag names whether part of a message or not .TP \fB\-v\fR, \fB\-\-value\fR -Translate the values of some tags. (for Side: 1 \-> Buy) +Translate the values of some tags (for Side: 1 \-> Buy) .TP -\fB\-s\fR, \fB\-\-strip\fR -Strip the whitespace around the = in each field. Less human readable but closer to real FIX. +\fB\-h\fR, \fB\-\-help\fR +Print help +.TP +\fB\-V\fR, \fB\-\-version\fR +Print version .TP [\fImessage\fR] FIX message to be parsed, if not provided will look for a message piped through stdin .SH VERSION -v1.0.4 +v1.1.0 diff --git a/src/command.rs b/src/command.rs index adbbaf5..d25a6d7 100644 --- a/src/command.rs +++ b/src/command.rs @@ -22,7 +22,7 @@ pub fn make_command() -> Command { .action(ArgAction::SetTrue) ) .arg( - arg!(-z --summarise [template] "Summarise each fix message based on an optional template") + arg!(-z --summary [template] "Summarise each fix message based on an optional template") .default_missing_value("") ) .arg( diff --git a/src/prefix/mod.rs b/src/prefix/mod.rs index f05e78c..620dde8 100644 --- a/src/prefix/mod.rs +++ b/src/prefix/mod.rs @@ -1,10 +1,9 @@ mod tags; + use clap::ArgMatches; +use once_cell::sync::Lazy; use regex::Regex; -use std::{ - io::{self, IsTerminal}, - str::FromStr, -}; +use std::io::{self, IsTerminal}; #[derive(Debug, PartialEq)] struct Field { @@ -16,11 +15,15 @@ pub struct Options { delimiter: String, colour: bool, strip: bool, - summarise: Option, + summary: Option, tag: bool, value: bool, } +static FIX_MSG_REGEX: Lazy = + Lazy::new(|| Regex::new(r"(?P[0-9]+)=(?P[^\^\|\x01\n]+)").expect("bad regex")); +static FIX_TAG_REGEX: Lazy = Lazy::new(|| Regex::new(r"[0-9]+").unwrap()); + pub fn matches_to_flags(matches: &ArgMatches) -> Options { let when = matches.get_one::("color").unwrap(); let use_colour = (io::stdout().is_terminal() && when == "auto") || when == "always"; @@ -28,7 +31,7 @@ pub fn matches_to_flags(matches: &ArgMatches) -> Options { delimiter: matches.get_one::("delimiter").unwrap().to_string(), colour: use_colour, strip: matches.get_flag("strip"), - summarise: matches.get_one::("summarise").cloned(), + summary: matches.get_one::("summary").cloned(), tag: matches.get_flag("tag"), value: matches.get_flag("value"), } @@ -48,7 +51,10 @@ pub fn run(input: &Vec, flags: Options) { continue; } }; - if let Some(ref template) = flags.summarise { + if let Some(ref template) = flags.summary { + /* TODO: Majority of the time is spent parsing the FIX message, but we likely only use + * a few fields. Could likely get alot of speed up for --summary if we only parse the + * fields in the template. */ println!("{}", format_to_summary(parsed, template, flags.value)); } else { println!("{}", format_to_string(parsed, &flags)); @@ -60,17 +66,17 @@ fn parse_fix_msg(input: &str) -> Option> { let input = input.trim(); // matches against a number followed by an = followed by anything excluding the given delimiters // Current delimiters used: ^ | SOH \n - let regex = Regex::new(r"(?P[0-9]+)=(?P[^\^\|\x01\n]+)").expect("bad regex"); - let mut result = Vec::::new(); + let regex = &FIX_MSG_REGEX; if !regex.is_match(input) { // If a log file is being piped in, it's expected to have some lines without FIX messages. return None; } + let mut result = Vec::new(); for i in regex.captures_iter(input) { result.push(Field { - tag: FromStr::from_str(&i["tag"]).expect("cannot parse tag"), + tag: i["tag"].parse().ok()?, value: i["value"].to_string(), }) } @@ -79,7 +85,7 @@ fn parse_fix_msg(input: &str) -> Option> { fn parse_tags(input: &str) -> String { let mut result = input.to_owned(); - let regex = Regex::new(r"[0-9]+").unwrap(); + let regex = &FIX_TAG_REGEX; for m in regex.find_iter(input) { let tag = m.as_str().parse::().unwrap(); result = result.replace(m.as_str(), tags::TAGS.get(tag).unwrap_or(&m.as_str())); @@ -96,26 +102,21 @@ fn add_colour(input: &str, use_colour: bool) -> String { } fn format_to_string(input: Vec, flags: &Options) -> String { - let mut result = String::new(); - for field in input { + input.iter().fold(String::new(), |result, field| { // Allow custom tags to still be printed without translation - if field.tag >= tags::TAGS.len() { - result.push_str(&field.tag.to_string()); - } else { - result.push_str(tags::TAGS[field.tag]); - } - if flags.strip { - result.push_str(&add_colour("=", flags.colour)); + let tag = if field.tag >= tags::TAGS.len() { + &field.tag.to_string() } else { - result.push_str(&add_colour(" = ", flags.colour)); - } - result.push_str(match flags.value { - true => translate_value(&field), + tags::TAGS[field.tag] + }; + let separator = add_colour(if flags.strip { "=" } else { " = " }, flags.colour); + let value = match flags.value { + true => translate_value(field), false => &field.value, - }); - result.push_str(&add_colour(&flags.delimiter, flags.colour)); - } - result + }; + let delimiter = add_colour(&flags.delimiter, flags.colour); + result + tag + &separator + value + &delimiter + }) } fn format_to_summary(input: Vec, template: &str, value_flag: bool) -> String { @@ -129,8 +130,6 @@ fn format_to_summary(input: Vec, template: &str, value_flag: bool) -> Str }; if !template.is_empty() { // Replace tag numbers in template to tag name. - /* TODO: How in efficient is attempting to search and replace for each field? - * Profile and consider parsing which tags are in template first. */ result = result.replace(&field.tag.to_string(), value); } if field.tag == 35 { @@ -256,10 +255,10 @@ mod tests { let flags = Options { delimiter: String::from("|"), colour: false, - strip: true, - summarise: None, + strip: false, + summary: None, tag: false, - value: false, + value: true, }; let result = format_to_string(parsed, &flags); let expected = String::from(