11/// Module related to the ``create`` command.
22
33use rand:: distributions:: Uniform ;
4- use std:: path:: { Path , PathBuf } ;
5- use std:: io:: { Read , Write } ;
6- use serde_json as sj;
74use rand:: { thread_rng, Rng } ;
5+
6+ use anyhow:: { Context , Result , anyhow} ;
7+ use serde_json as sj;
88use clap:: ValueEnum ;
9+
10+ use std:: path:: { Path , PathBuf } ;
11+ use std:: io:: { Read , Write } ;
912use std:: fs:: File ;
1013use std:: time;
1114use std:: env;
@@ -30,10 +33,10 @@ pub fn command_create(
3033 studis_csv_filepath : & PathBuf ,
3134 response_json_filepath : & PathBuf ,
3235 tex_template_filepath : & PathBuf ,
33- section : & String ,
36+ section : & str ,
3437 format : & OutputFormat ,
3538 output_filepath : & Option < PathBuf >
36- ) -> String {
39+ ) -> Result < String > {
3740 const E_NOT_MAPPING : & str = "Not a JSON mapping" ;
3841
3942 const C_MEAN_CSV_KEY : & str = "Povprečje" ;
@@ -48,25 +51,25 @@ pub fn command_create(
4851 let mut file: File ;
4952 let mut output_fdata: String = String :: new ( ) ;
5053 let mut output_parts = Vec :: new ( ) ;
51- file = File :: open ( tex_template_filepath) . expect ( & format ! ( "could not open tex file ({tex_template_filepath:?})" ) ) ;
52- file. read_to_string ( & mut output_fdata) . expect ( & format ! ( "Unable to read file {tex_template_filepath:?}" ) ) ;
54+ file = File :: open ( tex_template_filepath) . with_context ( || format ! ( "could not open tex file ({tex_template_filepath:?})" ) ) ? ;
55+ file. read_to_string ( & mut output_fdata) . with_context ( || format ! ( "Unable to read file {tex_template_filepath:?}" ) ) ? ;
5356
5457 if !output_fdata. contains ( C_OUTPUT_LATEX_REPLACE_KEY ) {
55- panic ! (
58+ return Err ( anyhow ! (
5659 "output file ({tex_template_filepath:?}) does not mark the location \
5760 of automatically-generated content (generated by this script). Mark it by writing \
5861 \" {C_OUTPUT_LATEX_REPLACE_KEY}\" somewhere in the file"
59- ) ;
62+ ) ) ;
6063 }
6164
6265 // Process STUDIS CSV file.
63- let fdata = fs:: read_file_universal ( studis_csv_filepath) . expect ( "unable to read STUDIS CSV" ) ;
64- let csvgrades = preproc:: extract_section_columns ( preproc:: preprocess_candidate_csv ( fdata) , section) ;
66+ let fdata = fs:: read_file_universal ( studis_csv_filepath) . with_context ( || "unable to read STUDIS CSV" ) ? ;
67+ let csvgrades = preproc:: extract_section_columns ( preproc:: preprocess_candidate_csv ( fdata) , section) ? ;
6568
6669 // Process JSON file. This is the file containing responses for each category and each grade.
67- file = File :: open ( response_json_filepath) . expect ( & format ! ( "could not open responses file ({response_json_filepath:?})" ) ) ;
68- let json_map: sj:: Map < _ , _ > = sj:: from_reader ( file) . expect ( E_NOT_MAPPING ) ;
69- let categories: & sj:: Map < _ , _ > = & json_map[ C_JSON_MAP_QUESTION_KEY ] . as_object ( ) . expect ( E_NOT_MAPPING ) ;
70+ file = File :: open ( response_json_filepath) . with_context ( || format ! ( "could not open responses file ({response_json_filepath:?})" ) ) ? ;
71+ let json_map: sj:: Map < _ , _ > = sj:: from_reader ( file) . with_context ( || E_NOT_MAPPING ) ? ;
72+ let categories: & sj:: Map < _ , _ > = json_map[ C_JSON_MAP_QUESTION_KEY ] . as_object ( ) . with_context ( || E_NOT_MAPPING ) ? ;
7073 let mut idx: usize ;
7174 let mut start_size: usize ;
7275
@@ -80,21 +83,22 @@ pub fn command_create(
8083 // Iterate each category/question of the JSON responses file
8184 for ( cat, grades_json) in categories {
8285 // Get index of the question matching JSON category
83- idx = csvgrades. get ( C_JSON_MAP_QUESTION_KEY ) . expect ( "CSV is missing questions key." )
84- . iter ( ) . position ( |x| x == cat) . expect ( & format ! ( "CSV is missing category \" {cat}\" " ) ) ;
86+ idx = csvgrades. get ( C_JSON_MAP_QUESTION_KEY ) . with_context ( || "CSV is missing questions key." ) ?
87+ . iter ( ) . position ( |x| x == cat) . with_context ( || format ! ( "CSV is missing category \" {cat}\" " ) ) ? ;
8588
8689 // Read the String of the mean and std, then parse them to float
87- smean = & csvgrades. get ( C_MEAN_CSV_KEY ) . expect ( "CSV is missing the mean grade value key" ) [ idx] ;
88- mean = smean. parse ( ) . unwrap ( ) ;
89- sstd = & csvgrades. get ( C_STD_CSV_KEY ) . expect ( "CSV is missing the std of grade key" ) [ idx] ;
90- // std = sstd.parse().unwrap();
90+ smean = & csvgrades. get ( C_MEAN_CSV_KEY ) . with_context ( || "CSV is missing the mean grade value key" ) ?[ idx] ;
91+ mean = smean. parse ( ) . with_context ( || format ! ( "failed to parse {smean} as a float" ) ) ?;
92+ sstd = & csvgrades. get ( C_STD_CSV_KEY ) . with_context ( || "CSV is missing the std of grade key" ) ?[ idx] ;
9193
9294 // Obtain the mapping of min. grade => array of String responses
93- let grades_json = grades_json. as_object ( ) . cloned ( ) . expect ( E_NOT_MAPPING ) ;
94- let mut grades: Vec < ( & String , f64 ) > = grades_json. keys ( ) . map (
95- // Save grades in format (String (original), parsed float)
96- |x| ( x, x. parse ( ) . expect ( & format ! ( "grades must be floats (\" {x}\" )" ) ) )
97- ) . collect ( ) ;
95+ let grades_json = grades_json. as_object ( ) . cloned ( ) . with_context ( || E_NOT_MAPPING ) ?;
96+ let mut grades = Vec :: with_capacity ( grades_json. len ( ) ) ;
97+ // Try to parse each grade into a float. If any fails, return the error.
98+ for k in grades_json. keys ( ) {
99+ let parsed: f64 = k. parse ( ) . with_context ( || format ! ( "grades must be floats (\" {k}\" )" ) ) ?;
100+ grades. push ( ( k, parsed) ) ;
101+ }
98102
99103 // Sort the grades by the parsed value
100104 grades. sort_by ( |( _, a) , ( _, b) | b. total_cmp ( a) ) ;
@@ -110,26 +114,30 @@ pub fn command_create(
110114 let response_str;
111115 for ( sgrade, grade) in grades. iter ( ) {
112116 if ( grade * 10000.0 ) as usize <= ( mean * 10000.0 ) as usize { // Prevent influence of numeric error
113- let v = grades_json. get ( * sgrade) . unwrap ( ) ;
117+ let v = & grades_json[ * sgrade] ; // no need to check existence,because grades is generated from grades_json
114118
115119 // Query elapsed nanoseconds in order to improve randomness.
116120 epoch_ns = time:: SystemTime :: now ( )
117- . duration_since ( time:: UNIX_EPOCH )
118- . expect ( "SystemTime is before EPOCH!" )
121+ . duration_since ( time:: UNIX_EPOCH ) ?
119122 . as_nanos ( ) ;
120- responses = v. as_array ( )
121- . expect ( & format ! ( "value of Category->Grade->Value must be an array of strings. Found {v:?}" ) ) ;
122123
123- if responses. len ( ) == 0 {
124- panic ! ( "there are no defined responses for grade {sgrade}, category {cat:?}" ) ;
124+ // Try to parse the array of possible responses under the given grade.
125+ responses = v. as_array ( ) . with_context ( || format ! (
126+ "value of Category->Grade->Value must be an array of strings. Found {v:?}"
127+ ) ) ?;
128+
129+ if responses. is_empty ( ) {
130+ return Err ( anyhow ! ( "there are no defined responses for grade {sgrade}, category {cat:?}" ) ) ;
125131 }
126132
127133 response_idx = (
128134 ( epoch_ns % responses. len ( ) as u128
129135 + rgn. sample ( Uniform :: new ( 0 , responses. len ( ) ) ) as u128 ) % responses. len ( ) as u128
130136 ) as usize ;
131137 response = & responses[ response_idx] ;
132- response_str = response. as_str ( ) . expect ( & format ! ( "responses must be strings ({response} is not)" ) ) ;
138+ response_str = response. as_str ( ) . with_context (
139+ || format ! ( "responses must be strings ({response} is not)" )
140+ ) ?;
133141 output_parts. push (
134142 response_str. replace ( C_OUTPUT_LATEX_MEAN_KEY , smean)
135143 . replace ( C_OUTPUT_LATEX_STD_KEY , sstd)
@@ -140,7 +148,7 @@ pub fn command_create(
140148
141149 // Check if loop was not break-ed;
142150 if start_size == output_parts. len ( ) {
143- panic ! ( "could not find grade below mean ({mean}) for category \" {cat}\" " ) ;
151+ return Err ( anyhow ! ( "could not find grade below mean ({mean}) for category \" {cat}\" " ) ) ;
144152 }
145153 }
146154
@@ -172,43 +180,19 @@ pub fn command_create(
172180 output += ".tex" ;
173181 }
174182
175- file = File :: create ( & output) . expect ( "could not write output LaTex" ) ;
176- file. write_all ( output_fdata. as_bytes ( ) ) . unwrap ( ) ;
183+ file = File :: create ( & output) . with_context ( || "could not write output LaTex" ) ? ;
184+ file. write_all ( output_fdata. as_bytes ( ) ) ? ;
177185 } ,
178186 OutputFormat :: Pdf => {
179187 if !output. ends_with ( ".pdf" ) {
180188 output += ".pdf" ;
181189 }
182190
183- let pdfdata = with_parent_path ! ( tex_template_filepath, { compiler:: compile_latex( output_fdata) } ) ;
184- file = File :: create ( & output) . expect ( "could not create final PDF" ) ;
185- file. write_all ( & pdfdata) . unwrap ( ) ;
191+ let pdfdata = with_parent_path ! ( tex_template_filepath, { compiler:: compile_latex( output_fdata) ? } ) ;
192+ file = File :: create ( & output) . with_context ( || "could not create final PDF" ) ? ;
193+ file. write_all ( & pdfdata) ? ;
186194 }
187195 }
188196
189- return output;
190- }
191-
192-
193- #[ cfg( test) ]
194- mod tests {
195- use std:: path:: PathBuf ;
196- use super :: * ;
197-
198- const CSV : & str = "anketa.csv" ;
199- const JSON : & str = "odzivi.json" ;
200- const LATEX : & str = "mnenja-template/mnenje.tex" ;
201-
202- #[ test]
203- fn test_create ( ) {
204- let path = command_create (
205- & PathBuf :: from ( CSV ) ,
206- & PathBuf :: from ( JSON ) ,
207- & PathBuf :: from ( LATEX ) ,
208- & "Anketa o izvajalcu" . to_string ( ) ,
209- & OutputFormat :: Pdf ,
210- & None
211- ) ;
212- std:: fs:: remove_file ( path) . expect ( "ouput PDF file not created" ) ;
213- }
197+ Ok ( output)
214198}
0 commit comments