Skip to content

Commit 6db2aed

Browse files
RawkMattias Wallin
authored andcommitted
feat(lib): Allow more output modes
Allow multiple "output modes", and add two new: - `OutputMode::SingleFile`: Write all bindings to a single file. Previously the only mode. - `OutputMode::Stdout`: Write all bindings to stdout. - `OutputMode::NoOutput`: Do not write anything. Useful when only checking. This is a small step towards outputting a file per module (issue #53).
1 parent a138db7 commit 6db2aed

File tree

2 files changed

+153
-111
lines changed

2 files changed

+153
-111
lines changed

rasn-compiler-tests/tests/parse_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rasn_compiler::{prelude::RasnBackend, Compiler};
1+
use rasn_compiler::{prelude::RasnBackend, Compiler, OutputMode};
22

33
#[test]
44
#[ignore]
@@ -57,7 +57,7 @@ fn compile_etsi() {
5757
// .add_asn_by_path("../rasn-compiler/test_asn1/ngap_ies.asn")
5858
// .add_asn_by_path("../rasn-compiler/test_asn1/ngap_pdus.asn")
5959
.add_asn_by_path("../rasn-compiler/test_asn1/test.asn")
60-
.set_output_path("./tests")
60+
.set_output_mode(OutputMode::SingleFile("./tests".into()))
6161
.compile()
6262
);
6363
}

rasn-compiler/src/lib.rs

Lines changed: 151 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::{
1313
cell::RefCell,
1414
collections::BTreeMap,
1515
fs::{self, read_to_string},
16+
io::Write,
1617
path::PathBuf,
1718
rc::Rc,
1819
vec,
@@ -133,15 +134,15 @@ impl Default for CompilerMissingParams {
133134
/// Typestate representing compiler that is ready to compile
134135
pub struct CompilerReady {
135136
sources: Vec<AsnSource>,
136-
output_path: PathBuf,
137+
output_mode: OutputMode,
137138
}
138139

139-
/// Typestate representing compiler that has the output path set, but is missing ASN1 sources
140+
/// Typestate representing compiler that has the output mode set, but is missing ASN1 sources
140141
pub struct CompilerOutputSet {
141-
output_path: PathBuf,
142+
output_mode: OutputMode,
142143
}
143144

144-
/// Typestate representing compiler that knows about ASN1 sources, but doesn't have an output path set
145+
/// Typestate representing compiler that knows about ASN1 sources, but doesn't have an output mode set
145146
pub struct CompilerSourcesSet {
146147
sources: Vec<AsnSource>,
147148
}
@@ -254,19 +255,26 @@ impl<B: Backend> Compiler<B, CompilerMissingParams> {
254255
}
255256
}
256257

257-
/// Set the output path for the generated rust representation.
258-
/// * `output_path` - path to an output file or directory, if path indicates
259-
/// a directory, the output file is named `rasn_generated.rs`
258+
/// Set the output path for the generated bindings.
259+
/// * `output_path` - path to an output file or directory, if path indicates a directory, the
260+
/// output file is named `generated` with the extension used by the Backend.
261+
#[deprecated = "Use `Self::set_output_mode` instead"]
260262
pub fn set_output_path(
261263
self,
262264
output_path: impl Into<PathBuf>,
263265
) -> Compiler<B, CompilerOutputSet> {
264-
let mut path: PathBuf = output_path.into();
265-
if path.is_dir() {
266-
path.set_file_name("rasn_generated.rs");
266+
Compiler {
267+
state: CompilerOutputSet {
268+
output_mode: OutputMode::SingleFile(output_path.into()),
269+
},
270+
backend: self.backend,
267271
}
272+
}
273+
274+
/// Set the output destination for the generated bindings.
275+
pub fn set_output_mode(self, output_mode: OutputMode) -> Compiler<B, CompilerOutputSet> {
268276
Compiler {
269-
state: CompilerOutputSet { output_path: path },
277+
state: CompilerOutputSet { output_mode },
270278
backend: self.backend,
271279
}
272280
}
@@ -279,7 +287,7 @@ impl<B: Backend> Compiler<B, CompilerOutputSet> {
279287
Compiler {
280288
state: CompilerReady {
281289
sources: vec![AsnSource::Path(path_to_source.into())],
282-
output_path: self.state.output_path,
290+
output_mode: self.state.output_mode,
283291
},
284292
backend: self.backend,
285293
}
@@ -296,7 +304,7 @@ impl<B: Backend> Compiler<B, CompilerOutputSet> {
296304
sources: paths_to_sources
297305
.map(|p| AsnSource::Path(p.into()))
298306
.collect(),
299-
output_path: self.state.output_path,
307+
output_mode: self.state.output_mode,
300308
},
301309
backend: self.backend,
302310
}
@@ -315,7 +323,7 @@ impl<B: Backend> Compiler<B, CompilerOutputSet> {
315323
Compiler {
316324
state: CompilerReady {
317325
sources: vec![AsnSource::Literal(literal.into())],
318-
output_path: self.state.output_path,
326+
output_mode: self.state.output_mode,
319327
},
320328
backend: self.backend,
321329
}
@@ -369,15 +377,26 @@ impl<B: Backend> Compiler<B, CompilerSourcesSet> {
369377
}
370378
}
371379

372-
/// Set the output path for the generated rust representation.
373-
/// * `output_path` - path to an output file or directory, if path points to
374-
/// a directory, the compiler will generate a file for every ASN.1 module.
375-
/// If the path points to a file, all modules will be written to that file.
380+
/// Set the output path for the generated bindings.
381+
/// * `output_path` - path to an output file or directory, if path indicates a directory, the
382+
/// output file is named `generated` with the extension used by the Backend.
383+
#[deprecated = "Use `Self::set_output_mode` instead"]
376384
pub fn set_output_path(self, output_path: impl Into<PathBuf>) -> Compiler<B, CompilerReady> {
377385
Compiler {
378386
state: CompilerReady {
379387
sources: self.state.sources,
380-
output_path: output_path.into(),
388+
output_mode: OutputMode::SingleFile(output_path.into()),
389+
},
390+
backend: self.backend,
391+
}
392+
}
393+
394+
/// Set the output destination for the generated bindings.
395+
pub fn set_output_mode(self, output_mode: OutputMode) -> Compiler<B, CompilerReady> {
396+
Compiler {
397+
state: CompilerReady {
398+
sources: self.state.sources,
399+
output_mode,
381400
},
382401
backend: self.backend,
383402
}
@@ -387,64 +406,9 @@ impl<B: Backend> Compiler<B, CompilerSourcesSet> {
387406
/// Returns a Result wrapping a compilation result:
388407
/// * _Ok_ - tuple containing the stringified bindings for the ASN1 spec as well as a vector of warnings raised during the compilation
389408
/// * _Err_ - Unrecoverable error, no bindings were generated
390-
pub fn compile_to_string(mut self) -> Result<CompileResult, CompilerError> {
391-
self.internal_compile().map(CompileResult::fmt::<B>)
392-
}
393-
394-
fn internal_compile(&mut self) -> Result<CompileResult, CompilerError> {
395-
let mut generated_modules = vec![];
396-
let mut warnings = Vec::<CompilerError>::new();
397-
let mut modules: Vec<ToplevelDefinition> = vec![];
398-
for src in &self.state.sources {
399-
let stringified_src = match src {
400-
AsnSource::Path(p) => read_to_string(p).map_err(LexerError::from)?,
401-
AsnSource::Literal(l) => l.clone(),
402-
};
403-
modules.append(
404-
&mut asn_spec(&stringified_src)?
405-
.into_iter()
406-
.flat_map(|(header, tlds)| {
407-
let header_ref = Rc::new(RefCell::new(header));
408-
tlds.into_iter().map(move |mut tld| {
409-
tld.apply_tagging_environment(&header_ref.borrow().tagging_environment);
410-
tld.set_module_header(header_ref.clone());
411-
tld
412-
})
413-
})
414-
.collect(),
415-
);
416-
}
417-
let (valid_items, mut validator_errors) = Validator::new(modules).validate()?;
418-
let modules = valid_items.into_iter().fold(
419-
BTreeMap::<String, Vec<ToplevelDefinition>>::new(),
420-
|mut modules, tld| {
421-
let key = tld
422-
.get_module_header()
423-
.map_or(<_>::default(), |module| module.borrow().name.clone());
424-
match modules.entry(key) {
425-
std::collections::btree_map::Entry::Vacant(v) => {
426-
v.insert(vec![tld]);
427-
}
428-
std::collections::btree_map::Entry::Occupied(ref mut e) => {
429-
e.get_mut().push(tld)
430-
}
431-
}
432-
modules
433-
},
434-
);
435-
for (_, module) in modules {
436-
let mut generated_module = self.backend.generate_module(module)?;
437-
if let Some(m) = generated_module.generated {
438-
generated_modules.push(m);
439-
}
440-
warnings.append(&mut generated_module.warnings);
441-
}
442-
warnings.append(&mut validator_errors);
443-
444-
Ok(CompileResult {
445-
generated: generated_modules.join("\n"),
446-
warnings,
447-
})
409+
pub fn compile_to_string(self) -> Result<CompileResult, CompilerError> {
410+
self.set_output_mode(OutputMode::NoOutput)
411+
.compile_to_string()
448412
}
449413
}
450414

@@ -456,7 +420,7 @@ impl<B: Backend> Compiler<B, CompilerReady> {
456420
sources.push(AsnSource::Path(path_to_source.into()));
457421
Compiler {
458422
state: CompilerReady {
459-
output_path: self.state.output_path,
423+
output_mode: self.state.output_mode,
460424
sources,
461425
},
462426
backend: self.backend,
@@ -474,7 +438,7 @@ impl<B: Backend> Compiler<B, CompilerReady> {
474438
Compiler {
475439
state: CompilerReady {
476440
sources,
477-
output_path: self.state.output_path,
441+
output_mode: self.state.output_mode,
478442
},
479443
backend: self.backend,
480444
}
@@ -494,7 +458,7 @@ impl<B: Backend> Compiler<B, CompilerReady> {
494458
sources.push(AsnSource::Literal(literal.into()));
495459
Compiler {
496460
state: CompilerReady {
497-
output_path: self.state.output_path,
461+
output_mode: self.state.output_mode,
498462
sources,
499463
},
500464
backend: self.backend,
@@ -505,43 +469,121 @@ impl<B: Backend> Compiler<B, CompilerReady> {
505469
/// Returns a Result wrapping a compilation result:
506470
/// * _Ok_ - tuple containing the stringified bindings for the ASN1 spec as well as a vector of warnings raised during the compilation
507471
/// * _Err_ - Unrecoverable error, no bindings were generated
508-
pub fn compile_to_string(self) -> Result<CompileResult, CompilerError> {
509-
Compiler {
510-
state: CompilerSourcesSet {
511-
sources: self.state.sources,
512-
},
513-
backend: self.backend,
514-
}
515-
.compile_to_string()
472+
pub fn compile_to_string(mut self) -> Result<CompileResult, CompilerError> {
473+
self.internal_compile().map(CompileResult::fmt::<B>)
516474
}
517475

518476
/// Runs the rasn compiler command.
519477
/// Returns a Result wrapping a compilation result:
520478
/// * _Ok_ - Vector of warnings raised during the compilation
521479
/// * _Err_ - Unrecoverable error, no bindings were generated
522-
pub fn compile(self) -> Result<Vec<CompilerError>, CompilerError> {
523-
let result = Compiler {
524-
state: CompilerSourcesSet {
525-
sources: self.state.sources,
480+
pub fn compile(mut self) -> Result<Vec<CompilerError>, CompilerError> {
481+
let result = self.internal_compile()?.fmt::<B>();
482+
483+
self.output_generated(&result.generated)?;
484+
485+
Ok(result.warnings)
486+
}
487+
488+
fn internal_compile(&mut self) -> Result<CompileResult, CompilerError> {
489+
let mut generated_modules = vec![];
490+
let mut warnings = Vec::<CompilerError>::new();
491+
let mut modules: Vec<ToplevelDefinition> = vec![];
492+
for src in &self.state.sources {
493+
let stringified_src = match src {
494+
AsnSource::Path(p) => read_to_string(p).map_err(LexerError::from)?,
495+
AsnSource::Literal(l) => l.clone(),
496+
};
497+
modules.append(
498+
&mut asn_spec(&stringified_src)?
499+
.into_iter()
500+
.flat_map(|(header, tlds)| {
501+
let header_ref = Rc::new(RefCell::new(header));
502+
tlds.into_iter().map(move |mut tld| {
503+
tld.apply_tagging_environment(&header_ref.borrow().tagging_environment);
504+
tld.set_module_header(header_ref.clone());
505+
tld
506+
})
507+
})
508+
.collect(),
509+
);
510+
}
511+
let (valid_items, mut validator_errors) = Validator::new(modules).validate()?;
512+
let modules = valid_items.into_iter().fold(
513+
BTreeMap::<String, Vec<ToplevelDefinition>>::new(),
514+
|mut modules, tld| {
515+
let key = tld
516+
.get_module_header()
517+
.map_or(<_>::default(), |module| module.borrow().name.clone());
518+
match modules.entry(key) {
519+
std::collections::btree_map::Entry::Vacant(v) => {
520+
v.insert(vec![tld]);
521+
}
522+
std::collections::btree_map::Entry::Occupied(ref mut e) => {
523+
e.get_mut().push(tld)
524+
}
525+
}
526+
modules
526527
},
527-
backend: self.backend,
528+
);
529+
for (_, module) in modules {
530+
let mut generated_module = self.backend.generate_module(module)?;
531+
if let Some(m) = generated_module.generated {
532+
generated_modules.push(m);
533+
}
534+
warnings.append(&mut generated_module.warnings);
528535
}
529-
.internal_compile()?
530-
.fmt::<B>();
531-
532-
let output_file = if self.state.output_path.is_dir() {
533-
self.state
534-
.output_path
535-
.join(format!("generated{}", B::FILE_EXTENSION))
536-
} else {
537-
self.state.output_path
538-
};
539-
fs::write(output_file, result.generated).map_err(|e| GeneratorError {
540-
top_level_declaration: None,
541-
details: e.to_string(),
542-
kind: GeneratorErrorType::IO,
543-
})?;
536+
warnings.append(&mut validator_errors);
544537

545-
Ok(result.warnings)
538+
Ok(CompileResult {
539+
generated: generated_modules.join("\n"),
540+
warnings,
541+
})
546542
}
543+
544+
fn output_generated(&self, generated: &str) -> Result<(), GeneratorError> {
545+
match &self.state.output_mode {
546+
OutputMode::SingleFile(path) => {
547+
let path = if path.is_dir() {
548+
&path.join(format!("generated{}", B::FILE_EXTENSION))
549+
} else {
550+
path
551+
};
552+
fs::write(path, generated).map_err(|e| {
553+
GeneratorError::new(
554+
None,
555+
&format!(
556+
"Failed to write generated bindings to {}: {e}",
557+
path.display()
558+
),
559+
GeneratorErrorType::IO,
560+
)
561+
})
562+
}
563+
OutputMode::Stdout => {
564+
std::io::stdout()
565+
.write_all(generated.as_bytes())
566+
.map_err(|err| {
567+
GeneratorError::new(
568+
None,
569+
&format!("Failed to write generated bindings to stdout: {err}"),
570+
GeneratorErrorType::IO,
571+
)
572+
})
573+
}
574+
OutputMode::NoOutput => Ok(()),
575+
}
576+
}
577+
}
578+
579+
/// Where the [Compiler] output should go.
580+
#[derive(Debug)]
581+
pub enum OutputMode {
582+
/// Write all compiled modules to a single file. Uses a default filename if path is a
583+
/// directory.
584+
SingleFile(PathBuf),
585+
/// Write all compiled modules to stdout.
586+
Stdout,
587+
/// Do not write anything, only check.
588+
NoOutput,
547589
}

0 commit comments

Comments
 (0)