Merge pull request #5514 from Luv-Ray/fix-printf-issue5468

`printf`: support %q
This commit is contained in:
Terts Diepraam 2023-11-09 09:02:44 +01:00 committed by GitHub
commit 9946e77adb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 7 deletions

View file

@ -78,6 +78,9 @@ Fields
second parameter is min-width, integer
output below that width is padded with leading zeroes
* `%q`: ARGUMENT is printed in a format that can be reused as shell input, escaping non-printable
characters with the proposed POSIX $'' syntax.
* `%f` or `%F`: decimal floating point value
* `%e` or `%E`: scientific notation floating point value
* `%g` or `%G`: shorter of specially interpreted decimal or SciNote floating point value.
@ -181,6 +184,11 @@ All string fields have a 'max width' parameter
still be interpreted and not throw a warning, you will have problems if you use this for a
literal whose code begins with zero, as it will be viewed as in `\\0NNN` form.)
* `%q`: escaped string - the string in a format that can be reused as input by most shells.
Non-printable characters are escaped with the POSIX proposed $'' syntax,
and shell meta-characters are quoted appropriately.
This is an equivalent format to ls --quoting=shell-escape output.
#### CHAR SUBSTITUTIONS
The character field does not have a secondary parameter.

View file

@ -10,6 +10,7 @@
//! Subs which have numeric field chars make use of the num_format
//! submodule
use crate::error::{UError, UResult};
use crate::quoting_style::{escape_name, QuotingStyle};
use itertools::{put_back_n, PutBackN};
use std::error::Error;
use std::fmt::Display;
@ -91,7 +92,7 @@ impl Sub {
// for more dry printing, field characters are grouped
// in initialization of token.
let field_type = match field_char {
's' | 'b' => FieldType::Strf,
's' | 'b' | 'q' => FieldType::Strf,
'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf,
'f' | 'F' => FieldType::Floatf,
'a' | 'A' => FieldType::CninetyNineHexFloatf,
@ -189,7 +190,7 @@ impl SubParser {
let mut legal_fields = [
// 'a', 'A', //c99 hex float implementation not yet complete
'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 's', 'u', 'x', 'X',
'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 'q', 's', 'u', 'x', 'X',
];
let mut specifiers = ['h', 'j', 'l', 'L', 't', 'z'];
legal_fields.sort_unstable();
@ -260,7 +261,6 @@ impl SubParser {
}
x if legal_fields.binary_search(&x).is_ok() => {
self.field_char = Some(ch);
self.text_so_far.push(ch);
break;
}
x if specifiers.binary_search(&x).is_ok() => {
@ -331,7 +331,7 @@ impl SubParser {
if (field_char == 's' && self.min_width_tmp == Some(String::from("0")))
|| (field_char == 'c'
&& (self.min_width_tmp == Some(String::from("0")) || self.past_decimal))
|| (field_char == 'b'
|| ((field_char == 'b' || field_char == 'q')
&& (self.min_width_tmp.is_some()
|| self.past_decimal
|| self.second_field_tmp.is_some()))
@ -391,6 +391,7 @@ impl Sub {
// if %s just return arg
// if %b use UnescapedText module's unescape-fn
// if %c return first char of arg
// if %q return arg which non-printable characters are escaped
FieldType::Strf | FieldType::Charf => {
match pf_arg {
Some(arg_string) => {
@ -404,11 +405,18 @@ impl Sub {
UnescapedText::from_it_core(writer, &mut a_it, true);
None
}
// for 'c': get iter of string vals,
'q' => Some(escape_name(
arg_string.as_ref(),
&QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control: false,
},
)),
// get opt<char> of first val
// and map it to opt<String>
/* 'c' | */
_ => arg_string.chars().next().map(|x| x.to_string()),
'c' => arg_string.chars().next().map(|x| x.to_string()),
_ => unreachable!(),
}
}
None => None,

View file

@ -112,6 +112,15 @@ fn sub_b_string_handle_escapes() {
.stdout_only("hello \tworld");
}
#[test]
fn sub_b_string_validate_field_params() {
new_ucmd!()
.args(&["hello %7b", "world"])
.run()
.stdout_is("hello ")
.stderr_is("printf: %7b: invalid conversion specification\n");
}
#[test]
fn sub_b_string_ignore_subs() {
new_ucmd!()
@ -120,6 +129,31 @@ fn sub_b_string_ignore_subs() {
.stdout_only("hello world %% %i");
}
#[test]
fn sub_q_string_non_printable() {
new_ucmd!()
.args(&["non-printable: %q", "\"$test\""])
.succeeds()
.stdout_only("non-printable: '\"$test\"'");
}
#[test]
fn sub_q_string_validate_field_params() {
new_ucmd!()
.args(&["hello %7q", "world"])
.run()
.stdout_is("hello ")
.stderr_is("printf: %7q: invalid conversion specification\n");
}
#[test]
fn sub_q_string_special_non_printable() {
new_ucmd!()
.args(&["non-printable: %q", "test~"])
.succeeds()
.stdout_only("non-printable: test~");
}
#[test]
fn sub_char() {
new_ucmd!()