1
1
use crossterm:: style:: Stylize ;
2
2
use dialoguer:: { theme:: ColorfulTheme , Confirm , Select } ;
3
3
use miette:: { IntoDiagnostic , Result } ;
4
- use serialport:: { available_ports, SerialPortInfo , SerialPortType } ;
4
+ use serialport:: { available_ports, SerialPortInfo , SerialPortType , UsbPortInfo } ;
5
5
6
6
use super :: { config:: Config , ConnectOpts } ;
7
7
use crate :: { cli:: config:: UsbDevice , error:: Error } ;
@@ -21,19 +21,23 @@ pub fn get_serial_port_info(
21
21
// and PID match the configured values.
22
22
let ports = detect_usb_serial_ports ( ) . unwrap_or_default ( ) ;
23
23
24
- let maybe_port = if let Some ( serial) = & matches. serial {
25
- find_serial_port ( & ports, serial. to_owned ( ) )
24
+ if let Some ( serial) = & matches. serial {
25
+ find_serial_port ( & ports, serial)
26
26
} else if let Some ( serial) = & config. connection . serial {
27
- find_serial_port ( & ports, serial. to_owned ( ) )
28
- } else if !ports . is_empty ( ) {
27
+ find_serial_port ( & ports, serial)
28
+ } else {
29
29
let ( port, matches) = select_serial_port ( ports, config) ?;
30
+
30
31
match & port. port_type {
31
32
SerialPortType :: UsbPort ( usb_info) if !matches => {
32
- if Confirm :: with_theme ( & ColorfulTheme :: default ( ) )
33
+ let remember = Confirm :: with_theme ( & ColorfulTheme :: default ( ) )
33
34
. with_prompt ( "Remember this serial port for future use?" )
34
35
. interact_opt ( ) ?
35
- . unwrap_or_default ( )
36
- {
36
+ . unwrap_or_default ( ) ;
37
+
38
+ if remember {
39
+ // Allow this operation to fail without terminating the
40
+ // application, but inform the user if something goes wrong.
37
41
if let Err ( e) = config. save_with ( |config| {
38
42
config. usb_device . push ( UsbDevice {
39
43
vid : usb_info. vid ,
@@ -47,25 +51,22 @@ pub fn get_serial_port_info(
47
51
_ => { }
48
52
}
49
53
50
- Some ( port)
51
- } else {
52
- None
53
- } ;
54
-
55
- if let Some ( port_info) = maybe_port {
56
- Ok ( port_info)
57
- } else {
58
- Err ( Error :: NoSerial )
54
+ Ok ( port)
59
55
}
60
56
}
61
57
62
58
/// Given a vector of `SerialPortInfo` structs, attempt to find and return one
63
59
/// whose `port_name` field matches the provided `name` argument.
64
- fn find_serial_port ( ports : & [ SerialPortInfo ] , name : String ) -> Option < SerialPortInfo > {
65
- ports
60
+ fn find_serial_port ( ports : & [ SerialPortInfo ] , name : & str ) -> Result < SerialPortInfo , Error > {
61
+ let port_info = ports
66
62
. iter ( )
67
- . find ( |port| port. port_name . to_lowercase ( ) == name. to_lowercase ( ) )
68
- . map ( |port| port. to_owned ( ) )
63
+ . find ( |port| port. port_name . to_lowercase ( ) == name. to_lowercase ( ) ) ;
64
+
65
+ if let Some ( port) = port_info {
66
+ Ok ( port. to_owned ( ) )
67
+ } else {
68
+ Err ( Error :: SerialNotFound ( name. to_owned ( ) ) )
69
+ }
69
70
}
70
71
71
72
/// serialport's autodetect doesn't provide any port information when using musl
@@ -78,19 +79,19 @@ fn detect_usb_serial_ports() -> Result<Vec<SerialPortInfo>> {
78
79
path:: PathBuf ,
79
80
} ;
80
81
81
- use serialport:: UsbPortInfo ;
82
-
83
82
let ports = available_ports ( ) . into_diagnostic ( ) ?;
84
83
let ports = ports
85
84
. into_iter ( )
86
85
. filter_map ( |port_info| {
87
86
// with musl, the paths we get are `/sys/class/tty/*`
88
87
let path = PathBuf :: from ( & port_info. port_name ) ;
89
88
90
- // this will give something like `/sys/devices/pci0000:00/0000:00:07.1/0000:0c:00.3/usb5/5-3/5-3.1/5-3.1:1.0/ttyUSB0/tty/ttyUSB0`
89
+ // this will give something like:
90
+ // `/sys/devices/pci0000:00/0000:00:07.1/0000:0c:00.3/usb5/5-3/5-3.1/5-3.1:1.0/ttyUSB0/tty/ttyUSB0`
91
91
let mut parent_dev = path. canonicalize ( ) . ok ( ) ?;
92
92
93
- // walk up 3 dirs to get to the device hosting the tty `/sys/devices/pci0000:00/0000:00:07.1/0000:0c:00.3/usb5/5-3/5-3.1/5-3.1:1.0`
93
+ // walk up 3 dirs to get to the device hosting the tty:
94
+ // `/sys/devices/pci0000:00/0000:00:07.1/0000:0c:00.3/usb5/5-3/5-3.1/5-3.1:1.0`
94
95
parent_dev. pop ( ) ;
95
96
parent_dev. pop ( ) ;
96
97
parent_dev. pop ( ) ;
@@ -164,7 +165,7 @@ fn select_serial_port(
164
165
if ports. len ( ) > 1 {
165
166
// Multiple serial ports detected
166
167
println ! (
167
- "Detected {} serial ports. Ports with match a known common dev board are highlighted.\n " ,
168
+ "Detected {} serial ports. Ports which match a known common dev board are highlighted.\n " ,
168
169
ports. len( )
169
170
) ;
170
171
@@ -177,30 +178,36 @@ fn select_serial_port(
177
178
} else {
178
179
port_info. port_name . as_str ( ) . reset ( )
179
180
} ;
181
+
180
182
if let Some ( product) = & info. product {
181
183
format ! ( "{} - {}" , formatted, product)
182
184
} else {
183
- format ! ( "{}" , formatted)
185
+ formatted. to_string ( )
184
186
}
185
187
}
186
188
_ => port_info. port_name . clone ( ) ,
187
189
} )
188
190
. collect :: < Vec < _ > > ( ) ;
191
+
189
192
let index = Select :: with_theme ( & ColorfulTheme :: default ( ) )
190
193
. items ( & port_names)
191
194
. default ( 0 )
192
195
. interact_opt ( ) ?
193
196
. ok_or ( Error :: Canceled ) ?;
194
197
195
198
match ports. get ( index) {
196
- Some (
197
- port_info @ SerialPortInfo {
198
- port_type : SerialPortType :: UsbPort ( usb_info) ,
199
- ..
200
- } ,
201
- ) => Ok ( ( port_info. clone ( ) , device_matches ( usb_info) ) ) ,
202
- Some ( port_info) => Ok ( ( port_info. clone ( ) , false ) ) ,
203
- None => Err ( Error :: NoSerial ) ,
199
+ Some ( port_info) => {
200
+ let matches = if let SerialPortType :: UsbPort ( usb_info) = & port_info. port_type {
201
+ device_matches ( usb_info)
202
+ } else {
203
+ false
204
+ } ;
205
+
206
+ Ok ( ( port_info. to_owned ( ) , matches) )
207
+ }
208
+ None => Err ( Error :: SerialNotFound (
209
+ port_names. get ( index) . unwrap ( ) . to_string ( ) ,
210
+ ) ) ,
204
211
}
205
212
} else if let [ port] = ports. as_slice ( ) {
206
213
// Single serial port detected
@@ -211,24 +218,27 @@ fn select_serial_port(
211
218
} ;
212
219
213
220
if device_matches ( port_info) {
214
- Ok ( ( port. clone ( ) , true ) )
215
- } else if Confirm :: with_theme ( & ColorfulTheme :: default ( ) )
216
- . with_prompt ( {
217
- if let Some ( product) = & port_info. product {
218
- format ! ( "Use serial port '{}' - {}?" , port_name, product)
219
- } else {
220
- format ! ( "Use serial port '{}'?" , port_name)
221
- }
222
- } )
223
- . interact_opt ( ) ?
224
- . ok_or ( Error :: Canceled ) ?
225
- {
226
- Ok ( ( port. clone ( ) , false ) )
221
+ Ok ( ( port. to_owned ( ) , true ) )
222
+ } else if confirm_port ( & port_name, port_info) ? {
223
+ Ok ( ( port. to_owned ( ) , false ) )
227
224
} else {
228
- Err ( Error :: NoSerial )
225
+ Err ( Error :: SerialNotFound ( port_name ) )
229
226
}
230
227
} else {
231
228
// No serial ports detected
232
229
Err ( Error :: NoSerial )
233
230
}
234
231
}
232
+
233
+ fn confirm_port ( port_name : & str , port_info : & UsbPortInfo ) -> Result < bool , Error > {
234
+ Confirm :: with_theme ( & ColorfulTheme :: default ( ) )
235
+ . with_prompt ( {
236
+ if let Some ( product) = & port_info. product {
237
+ format ! ( "Use serial port '{}' - {}?" , port_name, product)
238
+ } else {
239
+ format ! ( "Use serial port '{}'?" , port_name)
240
+ }
241
+ } )
242
+ . interact_opt ( ) ?
243
+ . ok_or ( Error :: Canceled )
244
+ }
0 commit comments