mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Merge pull request #5514 from Luv-Ray/fix-printf-issue5468
`printf`: support %q
This commit is contained in:
commit
9946e77adb
3 changed files with 57 additions and 7 deletions
|
@ -78,6 +78,9 @@ Fields
|
||||||
second parameter is min-width, integer
|
second parameter is min-width, integer
|
||||||
output below that width is padded with leading zeroes
|
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
|
* `%f` or `%F`: decimal floating point value
|
||||||
* `%e` or `%E`: scientific notation floating point value
|
* `%e` or `%E`: scientific notation floating point value
|
||||||
* `%g` or `%G`: shorter of specially interpreted decimal or SciNote 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
|
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.)
|
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
|
#### CHAR SUBSTITUTIONS
|
||||||
|
|
||||||
The character field does not have a secondary parameter.
|
The character field does not have a secondary parameter.
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
//! Subs which have numeric field chars make use of the num_format
|
//! Subs which have numeric field chars make use of the num_format
|
||||||
//! submodule
|
//! submodule
|
||||||
use crate::error::{UError, UResult};
|
use crate::error::{UError, UResult};
|
||||||
|
use crate::quoting_style::{escape_name, QuotingStyle};
|
||||||
use itertools::{put_back_n, PutBackN};
|
use itertools::{put_back_n, PutBackN};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
@ -91,7 +92,7 @@ impl Sub {
|
||||||
// for more dry printing, field characters are grouped
|
// for more dry printing, field characters are grouped
|
||||||
// in initialization of token.
|
// in initialization of token.
|
||||||
let field_type = match field_char {
|
let field_type = match field_char {
|
||||||
's' | 'b' => FieldType::Strf,
|
's' | 'b' | 'q' => FieldType::Strf,
|
||||||
'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf,
|
'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf,
|
||||||
'f' | 'F' => FieldType::Floatf,
|
'f' | 'F' => FieldType::Floatf,
|
||||||
'a' | 'A' => FieldType::CninetyNineHexFloatf,
|
'a' | 'A' => FieldType::CninetyNineHexFloatf,
|
||||||
|
@ -189,7 +190,7 @@ impl SubParser {
|
||||||
|
|
||||||
let mut legal_fields = [
|
let mut legal_fields = [
|
||||||
// 'a', 'A', //c99 hex float implementation not yet complete
|
// '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'];
|
let mut specifiers = ['h', 'j', 'l', 'L', 't', 'z'];
|
||||||
legal_fields.sort_unstable();
|
legal_fields.sort_unstable();
|
||||||
|
@ -260,7 +261,6 @@ impl SubParser {
|
||||||
}
|
}
|
||||||
x if legal_fields.binary_search(&x).is_ok() => {
|
x if legal_fields.binary_search(&x).is_ok() => {
|
||||||
self.field_char = Some(ch);
|
self.field_char = Some(ch);
|
||||||
self.text_so_far.push(ch);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
x if specifiers.binary_search(&x).is_ok() => {
|
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")))
|
if (field_char == 's' && self.min_width_tmp == Some(String::from("0")))
|
||||||
|| (field_char == 'c'
|
|| (field_char == 'c'
|
||||||
&& (self.min_width_tmp == Some(String::from("0")) || self.past_decimal))
|
&& (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.min_width_tmp.is_some()
|
||||||
|| self.past_decimal
|
|| self.past_decimal
|
||||||
|| self.second_field_tmp.is_some()))
|
|| self.second_field_tmp.is_some()))
|
||||||
|
@ -391,6 +391,7 @@ impl Sub {
|
||||||
// if %s just return arg
|
// if %s just return arg
|
||||||
// if %b use UnescapedText module's unescape-fn
|
// if %b use UnescapedText module's unescape-fn
|
||||||
// if %c return first char of arg
|
// if %c return first char of arg
|
||||||
|
// if %q return arg which non-printable characters are escaped
|
||||||
FieldType::Strf | FieldType::Charf => {
|
FieldType::Strf | FieldType::Charf => {
|
||||||
match pf_arg {
|
match pf_arg {
|
||||||
Some(arg_string) => {
|
Some(arg_string) => {
|
||||||
|
@ -404,11 +405,18 @@ impl Sub {
|
||||||
UnescapedText::from_it_core(writer, &mut a_it, true);
|
UnescapedText::from_it_core(writer, &mut a_it, true);
|
||||||
None
|
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
|
// get opt<char> of first val
|
||||||
// and map it to opt<String>
|
// and map it to opt<String>
|
||||||
/* 'c' | */
|
'c' => arg_string.chars().next().map(|x| x.to_string()),
|
||||||
_ => arg_string.chars().next().map(|x| x.to_string()),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
|
|
|
@ -112,6 +112,15 @@ fn sub_b_string_handle_escapes() {
|
||||||
.stdout_only("hello \tworld");
|
.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]
|
#[test]
|
||||||
fn sub_b_string_ignore_subs() {
|
fn sub_b_string_ignore_subs() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
@ -120,6 +129,31 @@ fn sub_b_string_ignore_subs() {
|
||||||
.stdout_only("hello world %% %i");
|
.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]
|
#[test]
|
||||||
fn sub_char() {
|
fn sub_char() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue