2020-05-17 21:52:39 +02:00
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of the
// MPL was not distributed with this file, You can
// obtain one at https://mozilla.org/MPL/2.0/.
2020-05-19 22:14:28 +02:00
use regex ::{ RegexSet , Regex } ;
2020-05-18 10:32:16 +02:00
use std ::collections ::HashSet ;
use std ::error ;
use std ::fmt ;
2020-05-17 01:17:19 +02:00
use std ::fs ::File ;
use std ::io ::BufRead ;
use std ::io ::BufReader ;
2020-05-18 16:57:21 +02:00
use failure ::Error ;
2020-05-17 21:52:39 +02:00
2020-05-18 14:57:55 +02:00
#[ derive(Debug, Default) ]
2020-05-17 21:52:39 +02:00
pub struct Dir {
pub path : String ,
pub group : String ,
pub owner : String ,
pub mode : String , //TODO implement as bitmask
2020-05-19 22:14:28 +02:00
pub revert_tag : String ,
pub salvage_from : String ,
pub facets : HashSet < Facet > ,
}
#[ derive(Hash, Eq, PartialEq, Debug, Default) ]
pub struct Facet {
pub name : String ,
pub value : String ,
2020-05-17 21:52:39 +02:00
}
2020-05-17 01:17:19 +02:00
2020-05-18 14:57:55 +02:00
#[ derive(Debug, Default) ]
2020-05-17 01:17:19 +02:00
pub struct Attr {
2020-05-17 21:52:39 +02:00
pub key : String ,
pub values : Vec < String > ,
pub properties : HashSet < Property > ,
}
2020-05-19 22:14:28 +02:00
#[ derive(Hash, Eq, PartialEq, Debug, Default) ]
2020-05-17 21:52:39 +02:00
pub struct Property {
pub key : String ,
pub value : String ,
2020-05-17 01:17:19 +02:00
}
2020-05-18 14:57:55 +02:00
#[ derive(Debug, Default) ]
2020-05-17 01:17:19 +02:00
pub struct Manifest {
2020-05-17 21:52:39 +02:00
pub attributes : Vec < Attr > ,
2020-05-19 22:14:28 +02:00
pub directories : Vec < Dir > ,
2020-05-17 01:17:19 +02:00
}
impl Manifest {
pub fn new ( ) -> Manifest {
2020-05-18 10:32:16 +02:00
return Manifest {
2020-05-17 21:52:39 +02:00
attributes : Vec ::new ( ) ,
2020-05-19 22:14:28 +02:00
directories : Vec ::new ( ) ,
2020-05-17 01:17:19 +02:00
} ;
}
}
2020-05-17 21:52:39 +02:00
enum ActionKind {
Attr ,
Dir ,
File ,
Dependency ,
User ,
Group ,
Driver ,
License ,
Link ,
2020-05-18 16:57:21 +02:00
Legacy ,
Unknown { action : String } ,
2020-05-17 21:52:39 +02:00
}
2020-05-18 16:57:21 +02:00
//TODO Multierror and no failure for these cases
#[ derive(Debug, Fail) ]
2020-05-17 01:17:19 +02:00
pub enum ManifestError {
2020-05-18 16:57:21 +02:00
#[ fail(display = " unknown action {} at line {} " , action, line) ]
UnknownAction {
line : usize ,
action : String ,
} ,
2020-05-19 22:14:28 +02:00
#[ fail(display = " action string \" {} \" at line {} is invalid: {} " , action, line, message) ]
InvalidAction {
line : usize ,
action : String ,
message : String ,
} ,
2020-05-17 01:17:19 +02:00
}
2020-05-18 16:57:21 +02:00
pub fn parse_manifest_file ( filename : String ) -> Result < Manifest , Error > {
let mut m = Manifest ::new ( ) ;
let f = File ::open ( filename ) ? ;
2020-05-17 01:17:19 +02:00
let file = BufReader ::new ( & f ) ;
2020-05-18 16:57:21 +02:00
for ( line_nr , line_read ) in file . lines ( ) . enumerate ( ) {
2020-05-19 10:39:06 +02:00
handle_manifest_line ( & mut m , line_read ? . trim_start ( ) , line_nr ) ? ;
2020-05-18 10:32:16 +02:00
}
2020-05-18 16:57:21 +02:00
return Ok ( m ) ;
2020-05-17 01:17:19 +02:00
}
2020-05-18 16:57:21 +02:00
pub fn parse_manifest_string ( manifest : String ) -> Result < Manifest , Error > {
2020-05-17 01:17:19 +02:00
let mut m = Manifest ::new ( ) ;
2020-05-18 16:57:21 +02:00
for ( line_nr , line ) in manifest . lines ( ) . enumerate ( ) {
2020-05-19 10:39:06 +02:00
handle_manifest_line ( & mut m , line . trim_start ( ) , line_nr ) ? ;
}
return Ok ( m ) ;
}
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
fn handle_manifest_line ( manifest : & mut Manifest , line : & str , line_nr : usize ) -> Result < ( ) , Error > {
match determine_action_kind ( & line ) {
ActionKind ::Attr = > {
manifest . attributes . push ( parse_attr_action ( String ::from ( line ) ) ? ) ;
}
ActionKind ::Dir = > {
2020-05-19 22:14:28 +02:00
manifest . directories . push ( parse_dir_action ( String ::from ( line ) , line_nr ) ? ) ;
2020-05-19 10:39:06 +02:00
}
ActionKind ::File = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::Dependency = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::User = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::Group = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::Driver = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::License = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::Link = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::Legacy = > {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind ::Unknown { action } = > {
Err ( ManifestError ::UnknownAction { action , line : line_nr } ) ? ;
2020-05-17 01:17:19 +02:00
}
}
2020-05-19 10:39:06 +02:00
Ok ( ( ) )
2020-05-17 01:17:19 +02:00
}
2020-05-18 16:57:21 +02:00
fn determine_action_kind ( line : & str ) -> ActionKind {
let mut act = String ::new ( ) ;
for c in line . trim_start ( ) . chars ( ) {
if c = = ' ' {
break
}
act . push ( c )
}
return match act . as_str ( ) {
" set " = > ActionKind ::Attr ,
" depend " = > ActionKind ::Dependency ,
" dir " = > ActionKind ::Dir ,
" file " = > ActionKind ::File ,
" license " = > ActionKind ::License ,
" hardlink " = > ActionKind ::Link ,
" link " = > ActionKind ::Link ,
" driver " = > ActionKind ::Driver ,
" group " = > ActionKind ::Group ,
" user " = > ActionKind ::User ,
" legacy " = > ActionKind ::Legacy ,
_ = > ActionKind ::Unknown { action : act } ,
2020-05-17 01:17:19 +02:00
}
}
2020-05-19 22:14:28 +02:00
fn parse_dir_action ( line : String , line_nr : usize ) -> Result < Dir , Error > {
let mut act = Dir ::default ( ) ;
let regex = Regex ::new ( r # "(([^ ]+)=([^"][^ ]+[^"])|([^ ]+)=([^"][^ ]+[^"]))"# ) ? ;
for cap in regex . captures_iter ( line . trim_start ( ) ) {
match & cap [ 1 ] {
" path " = > act . path = String ::from ( & cap [ 2 ] ) . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ,
" owner " = > act . owner = String ::from ( & cap [ 2 ] ) . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ,
" group " = > act . group = String ::from ( & cap [ 2 ] ) . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ,
" mode " = > act . mode = String ::from ( & cap [ 2 ] ) . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ,
" revert-tag " = > act . revert_tag = String ::from ( & cap [ 2 ] ) . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ,
" salvage-from " = > act . salvage_from = String ::from ( & cap [ 2 ] ) . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ,
_ = > {
let key_val_string = String ::from ( & cap [ 1 ] ) . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ;
if key_val_string . contains ( " facet. " ) {
let key = match key_val_string . find ( " . " ) {
Some ( idx ) = > {
key_val_string . clone ( ) . split_off ( idx + 1 )
} ,
None = > return Err ( ManifestError ::InvalidAction { action : line , line : line_nr , message : String ::from ( " separation dot not found but string contains facet. " ) } ) ?
} ;
let value = match key_val_string . find ( " = " ) {
Some ( idx ) = > {
key_val_string . clone ( ) . split_off ( idx + 1 )
} ,
None = > return Err ( ManifestError ::InvalidAction { action : line , line : line_nr , message : String ::from ( " no value present for facet " ) } ) ?
} ;
if ! act . facets . insert ( Facet { name : key , value : value } ) {
return Err ( ManifestError ::InvalidAction { action : line , line : line_nr , message : String ::from ( " double declaration of facet " ) } ) ?
}
}
}
}
}
Ok ( act )
}
fn parse_attr_action ( line : String ) -> Result < Attr , Error > {
2020-05-18 14:57:55 +02:00
// Do a full line match to see if we can fast path this.
// This also catches values with spaces, that have not been properly escaped.
// Note: values with spaces must be properly escaped or the rest here will fail. Strings with
// unescaped spaces are never valid but sadly present in the wild.
// Fast path will fail if a value has multiple values or a '=' sign in the values
2020-05-18 16:57:21 +02:00
let full_line_regex = Regex ::new ( r "^set name=([^ ]+) value=(.+)$" ) ? ;
2020-05-18 14:57:55 +02:00
if full_line_regex . is_match ( line . trim_start ( ) ) {
match full_line_regex . captures ( line . trim_start ( ) ) {
Some ( captures ) = > {
let mut fast_path_fail = false ;
let mut val = String ::from ( & captures [ 2 ] ) ;
if val . contains ( " = " ) {
fast_path_fail = true ;
}
if val . contains ( " value= " ) {
fast_path_fail = true ;
}
if val . contains ( " name= " ) {
fast_path_fail = true ;
}
val = val . replace ( & [ '"' , '\\' ] [ .. ] , " " ) ;
2020-05-18 16:57:21 +02:00
//TODO knock out single quotes somehow
2020-05-18 14:57:55 +02:00
if ! fast_path_fail {
return Ok ( Attr {
key : String ::from ( & captures [ 1 ] ) ,
values : vec ! [ val ] ,
.. Attr ::default ( )
} ) ;
}
}
None = > ( ) ,
} ;
}
2020-05-17 21:52:39 +02:00
//Todo move regex initialisation out of for loop into static area
2020-05-18 16:57:21 +02:00
let name_regex = Regex ::new ( r "name=([^ ]+) value=" ) ? ;
2020-05-17 21:52:39 +02:00
let mut key = String ::new ( ) ;
2020-05-17 01:17:19 +02:00
for cap in name_regex . captures_iter ( line . trim_start ( ) ) {
2020-05-17 21:52:39 +02:00
key = String ::from ( & cap [ 1 ] ) ;
2020-05-17 01:17:19 +02:00
}
let mut values = Vec ::new ( ) ;
2020-05-18 16:57:21 +02:00
let value_no_space_regex = Regex ::new ( r # "value="(.+)""# ) ? ;
2020-05-17 01:17:19 +02:00
2020-05-18 16:57:21 +02:00
let value_space_regex = Regex ::new ( r # "value=([^"][^ ]+[^"])"# ) ? ;
2020-05-17 01:17:19 +02:00
2020-05-17 21:52:39 +02:00
let mut properties = HashSet ::new ( ) ;
2020-05-18 16:57:21 +02:00
let optionals_regex_no_quotes = Regex ::new ( r # "([^ ]+)=([^"][^ ]+[^"])"# ) ? ;
let optionals_regex_quotes = Regex ::new ( r # "([^ ]+)=([^"][^ ]+[^"])"# ) ? ;
2020-05-17 21:52:39 +02:00
2020-05-17 01:17:19 +02:00
for cap in value_no_space_regex . captures_iter ( line . trim_start ( ) ) {
2020-05-17 21:52:39 +02:00
values . push ( String ::from ( cap [ 1 ] . trim ( ) ) ) ;
2020-05-17 01:17:19 +02:00
}
for cap in value_space_regex . captures_iter ( line . trim_start ( ) ) {
2020-05-17 21:52:39 +02:00
values . push ( String ::from ( cap [ 1 ] . trim ( ) ) ) ;
}
for cap in optionals_regex_quotes . captures_iter ( line . trim_start ( ) ) {
if cap [ 1 ] . trim ( ) . starts_with ( " name " ) | | cap [ 1 ] . trim ( ) . starts_with ( " value " ) {
continue ;
}
2020-05-18 10:32:16 +02:00
properties . insert ( Property {
2020-05-17 22:02:35 +02:00
key : String ::from ( cap [ 1 ] . trim ( ) ) ,
value : String ::from ( cap [ 2 ] . trim ( ) ) ,
2020-05-17 21:52:39 +02:00
} ) ;
}
for cap in optionals_regex_no_quotes . captures_iter ( line . trim_start ( ) ) {
if cap [ 1 ] . trim ( ) . starts_with ( " name " ) | | cap [ 1 ] . trim ( ) . starts_with ( " value " ) {
continue ;
}
2020-05-18 10:32:16 +02:00
properties . insert ( Property {
2020-05-17 22:02:35 +02:00
key : String ::from ( cap [ 1 ] . trim ( ) ) ,
value : String ::from ( cap [ 2 ] . trim ( ) ) ,
2020-05-17 21:52:39 +02:00
} ) ;
2020-05-17 01:17:19 +02:00
}
2020-05-18 10:32:16 +02:00
Ok ( Attr {
2020-05-17 21:52:39 +02:00
key ,
values ,
properties ,
2020-05-17 01:17:19 +02:00
} )
2020-05-18 10:32:16 +02:00
}