55
66// spell-checker:ignore (ToDO) cmdline evec nonrepeating seps shufable rvec fdata
77
8- use clap:: builder:: ValueParser ;
9- use clap:: { Arg , ArgAction , Command } ;
10- use rand:: Rng ;
11- use rand:: seq:: { IndexedRandom , SliceRandom } ;
128use std:: ffi:: { OsStr , OsString } ;
139use std:: fs:: File ;
1410use std:: io:: { BufReader , BufWriter , Error , Read , Write , stdin, stdout} ;
1511use std:: ops:: RangeInclusive ;
1612use std:: path:: { Path , PathBuf } ;
1713use std:: str:: FromStr ;
14+
15+ use clap:: { Arg , ArgAction , Command , builder:: ValueParser } ;
16+ use rand:: rngs:: ThreadRng ;
17+ use rand:: {
18+ Rng ,
19+ seq:: { IndexedRandom , SliceRandom } ,
20+ } ;
21+
1822use uucore:: display:: { OsWrite , Quotable } ;
1923use uucore:: error:: { FromIo , UResult , USimpleError , UUsageError } ;
2024use uucore:: format_usage;
2125use uucore:: translate;
2226
2327mod compat_random_source;
2428mod nonrepeating_iterator;
29+ mod random_seed;
2530
31+ use compat_random_source:: RandomSourceAdapter ;
2632use nonrepeating_iterator:: NonrepeatingIterator ;
33+ use random_seed:: SeededRng ;
2734
2835enum Mode {
2936 Default ( PathBuf ) ,
@@ -36,17 +43,24 @@ const BUF_SIZE: usize = 64 * 1024;
3643struct Options {
3744 head_count : u64 ,
3845 output : Option < PathBuf > ,
39- random_source : Option < PathBuf > ,
46+ random_source : RandomSource ,
4047 repeat : bool ,
4148 sep : u8 ,
4249}
4350
51+ enum RandomSource {
52+ None ,
53+ Seed ( String ) ,
54+ File ( PathBuf ) ,
55+ }
56+
4457mod options {
4558 pub static ECHO : & str = "echo" ;
4659 pub static INPUT_RANGE : & str = "input-range" ;
4760 pub static HEAD_COUNT : & str = "head-count" ;
4861 pub static OUTPUT : & str = "output" ;
4962 pub static RANDOM_SOURCE : & str = "random-source" ;
63+ pub static RANDOM_SEED : & str = "random-seed" ;
5064 pub static REPEAT : & str = "repeat" ;
5165 pub static ZERO_TERMINATED : & str = "zero-terminated" ;
5266 pub static FILE_OR_ARGS : & str = "file-or-args" ;
@@ -80,6 +94,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
8094 Mode :: Default ( file. into ( ) )
8195 } ;
8296
97+ let random_source = if let Some ( filename) = matches. get_one ( options:: RANDOM_SOURCE ) . cloned ( ) {
98+ RandomSource :: File ( filename)
99+ } else if let Some ( seed) = matches. get_one ( options:: RANDOM_SEED ) . cloned ( ) {
100+ RandomSource :: Seed ( seed)
101+ } else {
102+ RandomSource :: None
103+ } ;
104+
83105 let options = Options {
84106 // GNU shuf takes the lowest value passed, so we imitate that.
85107 // It's probably a bug or an implementation artifact though.
@@ -92,7 +114,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
92114 . min ( )
93115 . unwrap_or ( u64:: MAX ) ,
94116 output : matches. get_one ( options:: OUTPUT ) . cloned ( ) ,
95- random_source : matches . get_one ( options :: RANDOM_SOURCE ) . cloned ( ) ,
117+ random_source,
96118 repeat : matches. get_flag ( options:: REPEAT ) ,
97119 sep : if matches. get_flag ( options:: ZERO_TERMINATED ) {
98120 b'\0'
@@ -120,14 +142,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
120142 }
121143
122144 let mut rng = match options. random_source {
123- Some ( ref r) => {
145+ RandomSource :: None => WrappedRng :: Default ( rand:: rng ( ) ) ,
146+ RandomSource :: Seed ( ref seed) => WrappedRng :: Seed ( SeededRng :: new ( seed) ) ,
147+ RandomSource :: File ( ref r) => {
124148 let file = File :: open ( r) . map_err_context (
125149 || translate ! ( "shuf-error-failed-to-open-random-source" , "file" => r. quote( ) ) ,
126150 ) ?;
127151 let file = BufReader :: new ( file) ;
128- WrappedRng :: RngFile ( compat_random_source:: RandomSourceAdapter :: new ( file) )
152+ WrappedRng :: File ( compat_random_source:: RandomSourceAdapter :: new ( file) )
129153 }
130- None => WrappedRng :: RngDefault ( rand:: rng ( ) ) ,
131154 } ;
132155
133156 match mode {
@@ -191,6 +214,15 @@ pub fn uu_app() -> Command {
191214 . value_parser ( ValueParser :: path_buf ( ) )
192215 . value_hint ( clap:: ValueHint :: FilePath ) ,
193216 )
217+ . arg (
218+ Arg :: new ( options:: RANDOM_SEED )
219+ . long ( options:: RANDOM_SEED )
220+ . value_name ( "STRING" )
221+ . help ( translate ! ( "shuf-help-random-seed" ) )
222+ . value_parser ( ValueParser :: string ( ) )
223+ . value_hint ( clap:: ValueHint :: Other )
224+ . conflicts_with ( options:: RANDOM_SOURCE ) ,
225+ )
194226 . arg (
195227 Arg :: new ( options:: RANDOM_SOURCE )
196228 . long ( options:: RANDOM_SOURCE )
@@ -402,36 +434,33 @@ fn parse_range(input_range: &str) -> Result<RangeInclusive<u64>, String> {
402434}
403435
404436enum WrappedRng {
405- RngDefault ( rand:: rngs:: ThreadRng ) ,
406- RngFile ( compat_random_source:: RandomSourceAdapter < BufReader < File > > ) ,
437+ Default ( ThreadRng ) ,
438+ Seed ( SeededRng ) ,
439+ File ( RandomSourceAdapter < BufReader < File > > ) ,
407440}
408441
409442impl WrappedRng {
410443 fn choose < T : Copy > ( & mut self , vals : & [ T ] ) -> UResult < T > {
411444 match self {
412- Self :: RngDefault ( rng) => Ok ( * vals. choose ( rng) . unwrap ( ) ) ,
413- Self :: RngFile ( adapter) => {
414- assert ! ( !vals. is_empty( ) ) ;
415- let idx = adapter. get_value ( vals. len ( ) as u64 - 1 ) ? as usize ;
416- Ok ( vals[ idx] )
417- }
445+ Self :: Default ( rng) => Ok ( * vals. choose ( rng) . unwrap ( ) ) ,
446+ Self :: Seed ( rng) => Ok ( rng. choose_from_slice ( vals) ) ,
447+ Self :: File ( rng) => rng. choose_from_slice ( vals) ,
418448 }
419449 }
420450
421451 fn shuffle < ' a , T > ( & mut self , vals : & ' a mut [ T ] , amount : usize ) -> UResult < & ' a mut [ T ] > {
422452 match self {
423- Self :: RngDefault ( rng) => Ok ( vals. partial_shuffle ( rng, amount) . 0 ) ,
424- Self :: RngFile ( adapter) => adapter. shuffle ( vals, amount) ,
453+ Self :: Default ( rng) => Ok ( vals. partial_shuffle ( rng, amount) . 0 ) ,
454+ Self :: Seed ( rng) => Ok ( rng. shuffle ( vals, amount) ) ,
455+ Self :: File ( rng) => rng. shuffle ( vals, amount) ,
425456 }
426457 }
427458
428459 fn choose_from_range ( & mut self , range : RangeInclusive < u64 > ) -> UResult < u64 > {
429460 match self {
430- Self :: RngDefault ( rng) => Ok ( rng. random_range ( range) ) ,
431- Self :: RngFile ( adapter) => {
432- let offset = adapter. get_value ( * range. end ( ) - * range. start ( ) ) ?;
433- Ok ( * range. start ( ) + offset)
434- }
461+ Self :: Default ( rng) => Ok ( rng. random_range ( range) ) ,
462+ Self :: Seed ( rng) => Ok ( rng. choose_from_range ( range) ) ,
463+ Self :: File ( rng) => rng. choose_from_range ( range) ,
435464 }
436465 }
437466}
0 commit comments