1
1
// Copyright (C) 2016, Paul Osborne <[email protected] >
2
2
3
- use toml ;
3
+ use glob :: glob ;
4
4
use rustc_serialize:: { Decodable } ;
5
+ use std:: collections:: { HashMap , BTreeSet } ;
6
+ use std:: fs:: { self , File } ;
5
7
use std:: io;
8
+ use std:: io:: prelude:: * ;
6
9
use std:: path:: Path ;
7
- use std:: collections:: HashMap ;
8
10
use sysfs_gpio:: Direction ;
11
+ use toml;
9
12
10
- #[ derive( RustcDecodable , Debug ) ]
13
+ #[ derive( RustcDecodable , Clone , Debug ) ]
11
14
pub struct PinConfig {
12
15
num : u64 ,
13
16
direction : Option < String > ,
14
- aliases : Option < Vec < String > > ,
17
+ aliases : Option < BTreeSet < String > > ,
15
18
export : Option < bool > ,
16
19
active_low : Option < bool > ,
17
20
}
18
21
19
- #[ derive( RustcDecodable , Debug ) ]
22
+ #[ derive( RustcDecodable , Clone , Debug ) ]
20
23
pub struct GpioConfig {
21
24
pins : HashMap < String , PinConfig > ,
22
25
}
23
26
24
27
#[ derive( Debug ) ]
25
28
pub enum Error {
26
29
IoError ( io:: Error ) ,
27
- ParseError ,
30
+ ParserErrors ( Vec < toml :: ParserError > ) ,
28
31
NoConfigFound ,
29
32
}
30
33
@@ -38,6 +41,18 @@ fn to_direction(dirstr: &str) -> Option<Direction> {
38
41
}
39
42
}
40
43
44
+ impl From < io:: Error > for Error {
45
+ fn from ( e : io:: Error ) -> Self {
46
+ Error :: IoError ( e)
47
+ }
48
+ }
49
+
50
+ impl From < Vec < toml:: ParserError > > for Error {
51
+ fn from ( e : Vec < toml:: ParserError > ) -> Self {
52
+ Error :: ParserErrors ( e)
53
+ }
54
+ }
55
+
41
56
impl GpioConfig {
42
57
43
58
/// Load a GPIO Config from the system
@@ -57,26 +72,69 @@ impl GpioConfig {
57
72
/// Each config file found in these locations will be loaded and then they
58
73
/// will be pulled together to form a unified configuration via the
59
74
/// `combine` method.
60
- pub fn load ( configs : Vec < String > ) -> Result < GpioConfig , Error > {
61
- Err ( Error :: NoConfigFound )
75
+ pub fn load ( configs : & [ & str ] ) -> Result < GpioConfig , Error > {
76
+ let mut config_instances: Vec < GpioConfig > = Vec :: new ( ) ;
77
+
78
+ // check /etc/gpio.toml
79
+ if fs:: metadata ( "/etc/gpio.toml" ) . is_ok ( ) {
80
+ config_instances. push ( try!( Self :: from_file ( "/etc/gpio.toml" ) ) ) ;
81
+ }
82
+
83
+ // /etc/gpio.d/*.toml
84
+ for fragment in glob ( "/etc/gpio.d/*.toml" ) . unwrap ( ) . filter_map ( Result :: ok) {
85
+ config_instances. push ( try!( Self :: from_file ( "/etc/gpio.toml" ) ) ) ;
86
+ }
87
+
88
+ // additional from command-line
89
+ for fragment in configs {
90
+ config_instances. push ( try!( Self :: from_file ( fragment) ) ) ;
91
+ }
92
+
93
+ if config_instances. len ( ) == 0 {
94
+ Err ( Error :: NoConfigFound )
95
+ } else {
96
+ Ok ( config_instances[ 1 ..] . iter ( ) . fold ( config_instances[ 0 ] . clone ( ) , |a, b| {
97
+ a. merge ( b)
98
+ } ) )
99
+ }
62
100
}
63
101
64
102
/// Load a GPIO configuration for the provided toml string
65
103
pub fn from_str ( config : & str ) -> Result < GpioConfig , Error > {
66
- let root = toml:: Parser :: new ( config) . parse ( ) . unwrap ( ) ;
104
+ let mut parser = toml:: Parser :: new ( config) ;
105
+ let root = try!( parser. parse ( ) . ok_or ( parser. errors ) ) ;
67
106
let mut d = toml:: Decoder :: new ( toml:: Value :: Table ( root) ) ;
68
107
Ok ( Decodable :: decode ( & mut d) . unwrap ( ) )
69
108
}
70
109
71
110
/// Load a GPIO config from the specified path
72
111
pub fn from_file < P : AsRef < Path > > ( path : P ) -> Result < GpioConfig , Error > {
73
- Err ( Error :: NoConfigFound )
112
+ let mut contents = String :: new ( ) ;
113
+ let mut f = try!( File :: open ( path) ) ;
114
+ try!( f. read_to_string ( & mut contents) ) ;
115
+ GpioConfig :: from_str ( & contents[ ..] )
116
+ }
117
+
118
+ /// Merge this config with other yielding a new, merged version
119
+ ///
120
+ /// If in conflict, the other GPIO config takes priority.
121
+ pub fn merge ( & self , other : & GpioConfig ) -> GpioConfig {
122
+ // TODO: This needs to actually resolve conflicts rather than
123
+ // blindly writing over as it does now.
124
+ let mut pins = HashMap :: new ( ) ;
125
+ pins. extend ( self . pins . clone ( ) ) ;
126
+ pins. extend ( other. pins . clone ( ) ) ;
127
+ GpioConfig {
128
+ pins : pins
129
+ }
74
130
}
75
131
}
76
132
77
133
#[ cfg( test) ]
78
134
mod test {
79
135
use super :: * ;
136
+ use std:: iter:: FromIterator ;
137
+ use std:: collections:: BTreeSet ;
80
138
81
139
#[ test]
82
140
fn test_parse_basic ( ) {
@@ -99,12 +157,23 @@ error_led = { num = 11, direction = "in", export = false}
99
157
"# ;
100
158
let config = GpioConfig :: from_str ( configstr) . unwrap ( ) ;
101
159
let status_led = config. pins . get ( "status_led" ) . unwrap ( ) ;
160
+ let mut aliases = BTreeSet :: from_iter ( vec ! ( String :: from( "A27" ) , String :: from( "green_led" ) ) ) ;
102
161
assert_eq ! ( status_led. num, 37 ) ;
103
- assert_eq ! ( status_led. aliases,
104
- Some ( vec!( String :: from( "A27" ) ,
105
- String :: from( "green_led" ) ) ) ) ;
162
+ assert_eq ! ( status_led. aliases, Some ( aliases) ) ;
106
163
assert_eq ! ( status_led. direction, Some ( String :: from( "out" ) ) ) ;
107
164
assert_eq ! ( status_led. active_low, None ) ;
108
165
assert_eq ! ( status_led. export, None ) ;
109
166
}
167
+
168
+ #[ test]
169
+ fn test_parse_error_bad_toml ( ) {
170
+ // basically, just garbage data
171
+ let configstr = r#"
172
+ [] -*-..asdf=-=-@#$%^&*()
173
+ "# ;
174
+ match GpioConfig :: from_str ( configstr) {
175
+ Err ( Error :: ParserErrors ( e) ) => { } ,
176
+ _ => panic ! ( "Did not receive parse error when expected" ) ,
177
+ }
178
+ }
110
179
}
0 commit comments