saleae 0.1.0

a rust library for interacting with saleae devices
Documentation
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <style>html, body {
  margin: 0;
  padding: 0;
}

.app {
  margin: 10px;
  padding: 0;
}

.files-list {
  margin: 10px 0 0;
  width: 100%;
  border-collapse: collapse;
}
.files-list__head {
  border: 1px solid #999;
}
.files-list__head > tr > th {
  padding: 10px;
  border: 1px solid #999;
  text-align: left;
  font-weight: normal;
  background: #ddd;
}
.files-list__body {
}
.files-list__file {
  cursor: pointer;
}
.files-list__file:hover {
  background: #ccf;
}
.files-list__file > td {
  padding: 10px;
  border: 1px solid #999;
}
.files-list__file > td:first-child::before {
  content: '\01F4C4';
  margin-right: 1em;
}
.files-list__file_low {
  background: #fcc;
}
.files-list__file_medium {
  background: #ffc;
}
.files-list__file_high {
  background: #cfc;
}
.files-list__file_folder > td:first-child::before {
  content: '\01F4C1';
  margin-right: 1em;
}

.file-header {
  border: 1px solid #999;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.file-header__back {
  margin: 10px;
  cursor: pointer;
  flex-shrink: 0;
  flex-grow: 0;
  text-decoration: underline;
  color: #338;
}

.file-header__name {
  margin: 10px;
  flex-shrink: 2;
  flex-grow: 2;
}

.file-header__stat {
  margin: 10px;
  flex-shrink: 0;
  flex-grow: 0;
}

.file-content {
  margin: 10px 0 0;
  border: 1px solid #999;
  padding: 10px;
}

.code-line {
  margin: 0;
  padding: 0.3em;
  height: 1em;
}
.code-line_covered {
  background: #cfc;
}
.code-line_uncovered {
  background: #fcc;
}
</style>
</head>
<body>
    <div id="root"></div>
    <script>var data = {"files":[{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","src","client.rs"],"content":"//! This module defines the client data structure - the main entry point of communication\n//! to the saleae\n\nuse anyhow::Result;\nuse std::io::prelude::{Read, Write};\nuse std::io::{BufReader, BufWriter};\nuse std::net::TcpStream;\n\nuse crate::device::ConnectedDevice;\nuse crate::performance::PerformanceOption;\nuse crate::request::Request;\nuse crate::response::Response;\nuse crate::samplerate::SampleRate;\n\n#[faux::create]\n#[derive(Debug)]\npub struct Connection {\n    stream: TcpStream,\n}\n\n#[faux::methods]\nimpl Connection {\n    pub fn new(ip_port: \u0026str) -\u003e Self {\n        Connection {\n            stream: TcpStream::connect(ip_port).unwrap(),\n        }\n    }\n\n    pub fn general_recieve_ack(\u0026mut self) -\u003e Result\u003cbool\u003e {\n        let r: String = std::str::from_utf8(\u0026self.read_line()?)?.to_string();\n        Ok(Response::verify_ack(\u0026r))\n    }\n\n    pub fn general_recieve_message(\u0026mut self) -\u003e Result\u003cString\u003e {\n        let msg: String = std::str::from_utf8(\u0026self.read_line()?)?.to_string();\n        Response::verify_ack(\u0026msg);\n        Ok(msg)\n    }\n\n    fn read_line(\u0026mut self) -\u003e Result\u003cVec\u003cu8\u003e\u003e {\n        let mut reader = BufReader::new(\u0026self.stream);\n        let mut buf = [0; 500];\n        let len = reader.read(\u0026mut buf)?;\n        if len \u003c 1 {\n            panic!(\"read buffer len \u003c 0\");\n        }\n        Ok(buf[..len].to_vec())\n    }\n\n    //TODO Support for parameters\n    pub fn run_command(\u0026mut self, command: \u0026str) -\u003e Result\u003c()\u003e {\n        let mut writer = BufWriter::new(\u0026self.stream);\n        let len = writer.write(command.as_bytes()).unwrap();\n        if len \u003c 1 {\n            panic!(\"write buffer len \u003c 0\");\n        }\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\n/// Main interface for communication to Saleae Logic\npub struct Client {\n    /// tcp stream with connection to saleae\n    connection: Connection,\n}\n\n/// Constructor\nimpl Client {\n    /// constructor\n    //TODO make this create a connection from a string\n    pub fn new(connection: Connection) -\u003e Result\u003cClient\u003e {\n        Ok(Client { connection })\n    }\n}\n\n/// Interface for setting and getting Logic information\nimpl Client {\n    /// Set a trigger of channels\n    /// TODO create trigger methods/structs/tests\n\n    pub fn set_num_samples(\u0026mut self, num: u32) -\u003e Result\u003cbool\u003e {\n        self.connection\n            .run_command(\u0026format!(\"set_num_samples, {}\\0\", num))?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    pub fn get_num_samples(\u0026mut self) -\u003e Result\u003cu32\u003e {\n        self.connection.run_command(\"get_num_samples\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        if Response::verify_ack(\u0026response) {\n            Ok(Response::parse_num_samples(\u0026Response::remove_ack(\n                \u0026response,\n            )))\n        } else {\n            Err(anyhow!(\"No ACK found\"))\n        }\n    }\n\n    /// Set the capture duration to a length of time\n    pub fn set_capture_seconds(\u0026mut self, seconds: f32) -\u003e Result\u003cbool\u003e {\n        self.connection\n            .run_command(\u0026format!(\"set_capture_seconds, {}\\0\", seconds))?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    /// Set sample rate of saleae\n    ///\n    /// Note: Make sure to run `get_all_sample_rates` and set it from a available\n    /// sample rate\n    pub fn set_sample_rate(\u0026mut self, rate: \u0026SampleRate) -\u003e Result\u003cbool\u003e {\n        self.connection.run_command(\u0026format!(\n            \"set_sample_rate, {}, {}\\0\",\n            rate.DigitalSampleRate, rate.AnalogSampleRate\n        ))?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    pub fn get_sample_rate(\u0026mut self) -\u003e Result\u003cSampleRate\u003e {\n        self.connection.run_command(\"get_sample_rate\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        Ok(Response::parse_get_sample_rate(\u0026Response::remove_ack(\n            \u0026response,\n        )))\n    }\n\n    pub fn get_all_sample_rates(\u0026mut self) -\u003e Result\u003cVec\u003cSampleRate\u003e\u003e {\n        self.connection.run_command(\"get_all_sample_rates\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        Ok(Response::parse_get_all_sample_rates(\u0026Response::remove_ack(\n            \u0026response,\n        )))\n    }\n\n    /// Return current performance level of Logic\n    pub fn get_performance(\u0026mut self) -\u003e Result\u003cu8\u003e {\n        self.connection.run_command(\"get_performance\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        Ok(Response::parse_performance(\u0026Response::remove_ack(\n            \u0026response,\n        )))\n    }\n\n    /// Set the performance value, controlling the USB traffic and quality\n    pub fn set_performance(\u0026mut self, perf: PerformanceOption) -\u003e Result\u003cbool\u003e {\n        let input = String::from(\u0026format!(\"set_performance, {}\\0\", perf as i32));\n        self.connection.run_command(\u0026input)?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    //TODO get_capture_pretrigger_buffer_size\n\n    /// Return current connected devices of Logic\n    pub fn get_connected_devices(\u0026mut self) -\u003e Result\u003cVec\u003cConnectedDevice\u003e\u003e {\n        self.connection.run_command(\"get_connected_devices\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        Ok(Response::parse_connected_devices(\u0026Response::remove_ack(\n            \u0026response,\n        )))\n    }\n\n    /// Find index of device from the list of devices connected to Saleae\n    ///\n    /// Get current index of connected devices and find equal device to parameter.\n    /// Send that device as active device to logic.\n    ///\n    /// Note: Indices start at 1, not 0\n    /// TODO: test with multiple saleae\n    pub fn select_active_device(\u0026mut self, device: ConnectedDevice) -\u003e Result\u003cbool\u003e {\n        let b = self\n            .get_connected_devices()\n            .unwrap()\n            .into_iter()\n            .position(|a| a == device);\n        self.connection\n            .run_command(\u0026format!(\"select_active_device, {}\", b.unwrap() + 1))?;\n        // Weirdly doesn't return an ACK\n        Ok(true)\n    }\n\n    /// Return current active device of Logic\n    pub fn get_active_device(\u0026mut self) -\u003e Result\u003cConnectedDevice\u003e {\n        self.connection.run_command(\"get_connected_devices\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        Ok(\n            Response::parse_connected_devices(\u0026Response::remove_ack(\u0026response))\n                .into_iter()\n                .find(|a| a.is_active)\n                .unwrap(),\n        )\n    }\n\n    /// Parse the get active channels command into tuples of digital and analog\n    /// channels that are current\n    pub fn get_active_channels(\u0026mut self) -\u003e Result\u003cVec\u003cVec\u003cu8\u003e\u003e\u003e {\n        self.connection.run_command(\"get_active_channels\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        Ok(Response::parse_get_active_channels(\u0026Response::remove_ack(\n            \u0026response,\n        ))?)\n    }\n\n    /// Set the active channels for the Logic program\n    ///\n    /// # Example\n    /// TODO add get_active_channels\n    pub fn set_active_channels(\n        \u0026mut self,\n        digital_channels: \u0026[u8],\n        analog_channels: \u0026[u8],\n    ) -\u003e Result\u003cbool\u003e {\n        self.connection\n            .run_command(\u0026Request::prepare_set_active_channels(\n                \u0026digital_channels,\n                \u0026analog_channels,\n            )?)?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    /// Reset Active Channel\n    pub fn reset_active_channels(\u0026mut self) -\u003e Result\u003cbool\u003e {\n        self.connection.run_command(\"reset_active_channels\\0\")?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    //TODO get_digital_voltage_options OR get_full_scale_voltage_range\n    //TODO set_full_scale_voltage_range\n\n    /// Start Capture, without wating for ack/nack\n    pub fn start_capture(\u0026mut self) -\u003e Result\u003cbool\u003e {\n        self.connection.run_command(\"capture\\0\")?;\n        Ok(true)\n        // Doesn't return ACK\n    }\n\n    /// Start Capture, then wait until ack\n    pub fn start_capture_block_until_finished(\u0026mut self) -\u003e Result\u003cbool\u003e {\n        self.start_capture()?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    /// Check if processing is complete\n    pub fn is_processing_complete(\u0026mut self) -\u003e Result\u003cbool\u003e {\n        self.connection.run_command(\"is_processing_complete\\0\")?;\n        let response = self.connection.general_recieve_message()?;\n        Ok(Response::parse_processing_complete(\u0026Response::remove_ack(\n            \u0026response,\n        )))\n    }\n\n    /// Stop the saleae capture\n    pub fn stop_capture(\u0026mut self) -\u003e Result\u003cbool\u003e {\n        self.connection.run_command(\"stop_capture\\0\")?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    //TODO capture_to_file\n    //TODO save_to_file\n    //TODO load_from_file\n\n    /// Close all tabs\n    pub fn close_all_tabs(\u0026mut self) -\u003e Result\u003cbool\u003e {\n        self.connection.run_command(\"close_all_tabs\\0\")?;\n        Ok(self.connection.general_recieve_ack()?)\n    }\n\n    //TODO export_data2\n    //TODO get_analyzers\n    //TODO export_analyzer\n    //TODO is_analyzer_complete\n    //TODO get_capture_range\n    //TODO get_viewstate\n    //TODO get_viewstate\n    //TODO exit\n}\n","traces":[{"line":23,"address":4253312,"length":1,"stats":{"Line":0}},{"line":25,"address":4253326,"length":1,"stats":{"Line":0}},{"line":29,"address":4253392,"length":1,"stats":{"Line":0}},{"line":30,"address":4253407,"length":1,"stats":{"Line":0}},{"line":31,"address":4254006,"length":1,"stats":{"Line":0}},{"line":34,"address":4254512,"length":1,"stats":{"Line":0}},{"line":35,"address":4254527,"length":1,"stats":{"Line":0}},{"line":36,"address":4255117,"length":1,"stats":{"Line":0}},{"line":37,"address":4255160,"length":1,"stats":{"Line":0}},{"line":40,"address":null,"length":0,"stats":{"Line":0}},{"line":41,"address":4255695,"length":1,"stats":{"Line":0}},{"line":42,"address":4255762,"length":1,"stats":{"Line":0}},{"line":43,"address":4255819,"length":1,"stats":{"Line":0}},{"line":44,"address":4256050,"length":1,"stats":{"Line":0}},{"line":45,"address":4256119,"length":1,"stats":{"Line":0}},{"line":47,"address":4256061,"length":1,"stats":{"Line":0}},{"line":51,"address":4256336,"length":1,"stats":{"Line":0}},{"line":52,"address":4256358,"length":1,"stats":{"Line":0}},{"line":53,"address":4256387,"length":1,"stats":{"Line":0}},{"line":54,"address":4256545,"length":1,"stats":{"Line":0}},{"line":55,"address":4256565,"length":1,"stats":{"Line":0}},{"line":57,"address":null,"length":0,"stats":{"Line":0}},{"line":72,"address":4233664,"length":1,"stats":{"Line":6}},{"line":73,"address":4233674,"length":1,"stats":{"Line":6}},{"line":82,"address":4233808,"length":1,"stats":{"Line":1}},{"line":83,"address":4233834,"length":1,"stats":{"Line":1}},{"line":84,"address":4233839,"length":1,"stats":{"Line":1}},{"line":85,"address":4234352,"length":1,"stats":{"Line":1}},{"line":88,"address":4234720,"length":1,"stats":{"Line":2}},{"line":89,"address":4234742,"length":1,"stats":{"Line":2}},{"line":90,"address":4234993,"length":1,"stats":{"Line":2}},{"line":91,"address":4235286,"length":1,"stats":{"Line":2}},{"line":92,"address":4235414,"length":1,"stats":{"Line":1}},{"line":93,"address":4235379,"length":1,"stats":{"Line":1}},{"line":96,"address":4235344,"length":1,"stats":{"Line":1}},{"line":101,"address":4235776,"length":1,"stats":{"Line":1}},{"line":102,"address":4235804,"length":1,"stats":{"Line":1}},{"line":103,"address":4235809,"length":1,"stats":{"Line":1}},{"line":104,"address":4236321,"length":1,"stats":{"Line":1}},{"line":111,"address":4236688,"length":1,"stats":{"Line":1}},{"line":112,"address":4236718,"length":1,"stats":{"Line":1}},{"line":113,"address":4236723,"length":1,"stats":{"Line":1}},{"line":114,"address":4236730,"length":1,"stats":{"Line":1}},{"line":116,"address":4237352,"length":1,"stats":{"Line":1}},{"line":119,"address":4237728,"length":1,"stats":{"Line":1}},{"line":120,"address":4237750,"length":1,"stats":{"Line":1}},{"line":121,"address":4237973,"length":1,"stats":{"Line":1}},{"line":122,"address":4238305,"length":1,"stats":{"Line":1}},{"line":123,"address":4238260,"length":1,"stats":{"Line":1}},{"line":127,"address":4238688,"length":1,"stats":{"Line":0}},{"line":128,"address":4238710,"length":1,"stats":{"Line":0}},{"line":129,"address":4238933,"length":1,"stats":{"Line":0}},{"line":130,"address":4239262,"length":1,"stats":{"Line":0}},{"line":131,"address":4239217,"length":1,"stats":{"Line":0}},{"line":136,"address":4239648,"length":1,"stats":{"Line":1}},{"line":137,"address":4239670,"length":1,"stats":{"Line":1}},{"line":138,"address":4239893,"length":1,"stats":{"Line":1}},{"line":139,"address":4240225,"length":1,"stats":{"Line":1}},{"line":140,"address":4240180,"length":1,"stats":{"Line":1}},{"line":145,"address":4240592,"length":1,"stats":{"Line":1}},{"line":146,"address":4240618,"length":1,"stats":{"Line":1}},{"line":147,"address":4240941,"length":1,"stats":{"Line":1}},{"line":148,"address":4241221,"length":1,"stats":{"Line":1}},{"line":154,"address":4241760,"length":1,"stats":{"Line":1}},{"line":155,"address":4241782,"length":1,"stats":{"Line":1}},{"line":156,"address":4242005,"length":1,"stats":{"Line":1}},{"line":157,"address":4242334,"length":1,"stats":{"Line":1}},{"line":158,"address":4242289,"length":1,"stats":{"Line":1}},{"line":169,"address":4242720,"length":1,"stats":{"Line":0}},{"line":170,"address":4242738,"length":1,"stats":{"Line":0}},{"line":171,"address":null,"length":0,"stats":{"Line":0}},{"line":172,"address":null,"length":0,"stats":{"Line":0}},{"line":173,"address":null,"length":0,"stats":{"Line":0}},{"line":174,"address":4230688,"length":1,"stats":{"Line":0}},{"line":175,"address":4242973,"length":1,"stats":{"Line":0}},{"line":176,"address":4242981,"length":1,"stats":{"Line":0}},{"line":182,"address":4243792,"length":1,"stats":{"Line":0}},{"line":183,"address":4243814,"length":1,"stats":{"Line":0}},{"line":184,"address":4244037,"length":1,"stats":{"Line":0}},{"line":186,"address":4244321,"length":1,"stats":{"Line":0}},{"line":187,"address":null,"length":0,"stats":{"Line":0}},{"line":188,"address":4230800,"length":1,"stats":{"Line":0}},{"line":189,"address":null,"length":0,"stats":{"Line":0}},{"line":195,"address":4244944,"length":1,"stats":{"Line":1}},{"line":196,"address":4244966,"length":1,"stats":{"Line":1}},{"line":197,"address":4245205,"length":1,"stats":{"Line":1}},{"line":198,"address":4245537,"length":1,"stats":{"Line":1}},{"line":199,"address":4245492,"length":1,"stats":{"Line":1}},{"line":207,"address":4246448,"length":1,"stats":{"Line":1}},{"line":212,"address":4246483,"length":1,"stats":{"Line":1}},{"line":213,"address":4246524,"length":1,"stats":{"Line":1}},{"line":214,"address":4246504,"length":1,"stats":{"Line":1}},{"line":215,"address":4246514,"length":1,"stats":{"Line":1}},{"line":217,"address":4247085,"length":1,"stats":{"Line":1}},{"line":221,"address":4247760,"length":1,"stats":{"Line":1}},{"line":222,"address":4247782,"length":1,"stats":{"Line":1}},{"line":223,"address":4248002,"length":1,"stats":{"Line":1}},{"line":230,"address":4248288,"length":1,"stats":{"Line":1}},{"line":231,"address":4248307,"length":1,"stats":{"Line":1}},{"line":237,"address":4248576,"length":1,"stats":{"Line":1}},{"line":238,"address":4248591,"length":1,"stats":{"Line":1}},{"line":239,"address":4248794,"length":1,"stats":{"Line":1}},{"line":243,"address":4249088,"length":1,"stats":{"Line":1}},{"line":244,"address":4249110,"length":1,"stats":{"Line":1}},{"line":245,"address":4249333,"length":1,"stats":{"Line":1}},{"line":246,"address":4249665,"length":1,"stats":{"Line":1}},{"line":247,"address":4249620,"length":1,"stats":{"Line":1}},{"line":252,"address":4250032,"length":1,"stats":{"Line":1}},{"line":253,"address":4250054,"length":1,"stats":{"Line":1}},{"line":254,"address":4250274,"length":1,"stats":{"Line":1}},{"line":262,"address":4250560,"length":1,"stats":{"Line":1}},{"line":263,"address":4250582,"length":1,"stats":{"Line":1}},{"line":264,"address":4250802,"length":1,"stats":{"Line":1}}],"covered":71,"coverable":113},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","src","device.rs"],"content":"//! This module defines devices that are connected to the saleae logic program.\n//!\n//! ## Example of response from Logic\n//! ```text\n//! 1, Logic Pro 16, LOGIC_PRO_16_DEVICE, 0xdf03c43d1f3aa2f3, ACTIVE\n//! ```\n//! ## C# struct\n//! ```text\n//! struct ConnectedDevices\n//! {\n//!   String type;\n//!   String name;\n//!   int device_id;\n//!   int index;\n//!   bool is_active;\n//! }\n//! ```\n\nuse anyhow::Result;\nuse std::str::FromStr;\n\ncustom_derive! {\n    /// Device id for saleae devices\n    #[allow(non_camel_case_types)]\n    #[derive(Debug, EnumFromStr, PartialEq)]\n    pub enum DeviceID {\n        /// Regular 4 Wire device\n        LOGIC_4_DEVICE,\n        /// Regular 8 Wire device\n        LOGIC_8_DEVICE,\n        /// Pro 8 Wire device\n        LOGIC_PRO_8_DEVICE,\n        /// Pro 16 Wire device\n        LOGIC_PRO_16_DEVICE,\n    }\n}\n\n/// Connected Device to saleae, the main usability comes from the fromStr trait.\n#[derive(Debug, PartialEq)]\npub struct ConnectedDevice {\n    /// type of device\n    pub d_type: String,\n    /// name of device\n    pub name: String,\n    /// id of device\n    pub device_id: DeviceID,\n    /// index of device\n    pub index: String,\n    /// if device is active\n    pub is_active: bool,\n}\n\nimpl FromStr for ConnectedDevice {\n    type Err = std::num::ParseIntError;\n    fn from_str(response: \u0026str) -\u003e Result\u003cSelf, Self::Err\u003e {\n        let v: Vec\u003c\u0026str\u003e = response.split(',').map(|a| a.trim_start()).collect();\n\n        /* parse into device_id */\n        let device_id: DeviceID = v[2].parse().unwrap();\n\n        /*\n         * last element is ACTIVE, if that element doesn't don't cause a panic by\n         * checking that element\n         */\n        let is_active = v.len() == 5 \u0026\u0026 v[4] == \"ACTIVE\";\n\n        Ok(ConnectedDevice {\n            d_type: v[0].to_string(),\n            name: v[1].to_string(),\n            device_id,\n            index: v[3].to_string(),\n            is_active,\n        })\n    }\n}\n","traces":[{"line":55,"address":4227568,"length":1,"stats":{"Line":3}},{"line":56,"address":4227588,"length":1,"stats":{"Line":6}},{"line":59,"address":4227696,"length":1,"stats":{"Line":3}},{"line":65,"address":4227784,"length":1,"stats":{"Line":3}},{"line":67,"address":4228081,"length":1,"stats":{"Line":3}},{"line":68,"address":4227847,"length":1,"stats":{"Line":3}},{"line":69,"address":4227942,"length":1,"stats":{"Line":3}},{"line":70,"address":4227999,"length":1,"stats":{"Line":3}},{"line":71,"address":4228020,"length":1,"stats":{"Line":3}},{"line":72,"address":4228074,"length":1,"stats":{"Line":3}}],"covered":10,"coverable":10},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","src","request.rs"],"content":"//! This module helps create requests to the Saleae Logic software\n//!\n//! The function here parse and type check the input from the API into strings to send into\n//! the Saleae socket\nuse anyhow::Result;\n\npub struct Request {}\n\nimpl Request {\n    pub fn prepare_set_active_channels(\n        digital_channels: \u0026[u8],\n        analog_channels: \u0026[u8],\n    ) -\u003e Result\u003cString\u003e {\n        // Check only one kind of empty\n        if digital_channels.is_empty() \u0026\u0026 analog_channels.is_empty() {\n            return Err(anyhow!(\n                \"Logic requires at least one active channel, no active channels found\"\n            ));\n        }\n\n        let d_str = if !digital_channels.is_empty() {\n            format!(\n                \", digital_channels, {}\",\n                Request::create_channel_str(digital_channels)?\n            )\n        } else {\n            \"\".to_string()\n        };\n\n        let a_str = if !analog_channels.is_empty() {\n            format!(\n                \", analog_channels, {}\",\n                Request::create_channel_str(analog_channels)?\n            )\n        } else {\n            \"\".to_string()\n        };\n\n        Ok(format!(\"set_active_channels{}{}\\0\", d_str, a_str))\n    }\n}\n\n/// Helper functions\nimpl Request {\n    pub fn create_channel_str(v: \u0026[u8]) -\u003e Result\u003cString\u003e {\n        let s = v\n            .iter()\n            .map(|a| format!(\"{}, \", a.to_string()))\n            .collect::\u003cString\u003e();\n        Ok(s[..s.len() - 2].to_string())\n    }\n}\n","traces":[{"line":10,"address":4286880,"length":1,"stats":{"Line":2}},{"line":15,"address":4286922,"length":1,"stats":{"Line":2}},{"line":16,"address":4287143,"length":1,"stats":{"Line":1}},{"line":17,"address":null,"length":0,"stats":{"Line":0}},{"line":21,"address":4287106,"length":1,"stats":{"Line":2}},{"line":22,"address":4287351,"length":1,"stats":{"Line":0}},{"line":23,"address":4287253,"length":1,"stats":{"Line":2}},{"line":24,"address":4287260,"length":1,"stats":{"Line":2}},{"line":27,"address":4287228,"length":1,"stats":{"Line":1}},{"line":30,"address":4287758,"length":1,"stats":{"Line":2}},{"line":31,"address":4287946,"length":1,"stats":{"Line":0}},{"line":32,"address":4287844,"length":1,"stats":{"Line":2}},{"line":33,"address":4287851,"length":1,"stats":{"Line":2}},{"line":36,"address":4287815,"length":1,"stats":{"Line":1}},{"line":39,"address":4288349,"length":1,"stats":{"Line":2}},{"line":45,"address":4289584,"length":1,"stats":{"Line":2}},{"line":46,"address":4289604,"length":1,"stats":{"Line":2}},{"line":47,"address":null,"length":0,"stats":{"Line":0}},{"line":48,"address":4232032,"length":1,"stats":{"Line":2}},{"line":49,"address":null,"length":0,"stats":{"Line":0}},{"line":50,"address":4289710,"length":1,"stats":{"Line":2}}],"covered":16,"coverable":21},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","src","response.rs"],"content":"//! This module help discern and parse the responses from saleae\n\nuse crate::device::ConnectedDevice;\nuse crate::samplerate::SampleRate;\nuse anyhow::Result;\nuse std::str::FromStr;\n\n/// struct to handle responses\npub struct Response {}\n\n//TODO add errors\nimpl Response {\n    /// Return string without \"\\nACK\" string line nor extra 0 char's from buffer\n    pub fn remove_ack(response: \u0026str) -\u003e String {\n        response\n            .trim_end_matches(char::from(0))\n            .trim_end_matches(\"\\nACK\")\n            .to_string()\n    }\n\n    /// Check if last string is ACK\n    pub fn verify_ack(response: \u0026str) -\u003e bool {\n        response.lines().last().unwrap() == \"ACK\"\n    }\n\n    /// Parse the performance response\n    ///\n    /// # Sample Input\n    /// The following values are expected as input:\n    /// ```text, no_run\n    /// 100\n    /// 80\n    /// 60\n    /// 40\n    /// 20\n    /// ```\n    pub fn parse_performance(response: \u0026str) -\u003e u8 {\n        response.parse::\u003cu8\u003e().unwrap()\n    }\n\n    pub fn parse_num_samples(response: \u0026str) -\u003e u32 {\n        response.parse::\u003cu32\u003e().unwrap()\n    }\n\n    ///\n    pub fn parse_get_sample_rate(response: \u0026str) -\u003e SampleRate {\n        let mut iter = response.lines();\n        SampleRate {\n            DigitalSampleRate: iter.next().unwrap().parse::\u003cu32\u003e().unwrap(),\n            AnalogSampleRate: iter.next().unwrap().parse::\u003cu32\u003e().unwrap(),\n        }\n    }\n\n    /// Parse get all sample rates\n    ///\n    /// # Sample input\n    /// ```text\n    /// 5000000, 1250000\n    /// 10000000, 625000\n    /// ```\n    pub fn parse_get_all_sample_rates(response: \u0026str) -\u003e Vec\u003cSampleRate\u003e {\n        response\n            .lines()\n            .map(|a| SampleRate::from_str(\u0026a).unwrap())\n            .collect()\n    }\n\n    /// Parse the connected_devices reponse into ConnectedDevice\n    ///\n    /// # Sample Input\n    /// ```text\n    /// 1, Logic Pro 16, LOGIC_PRO_16_DEVICE, 0xdf03c43d1f3aa2f3, ACTIVE\n    /// ```\n    pub fn parse_connected_devices(response: \u0026str) -\u003e Vec\u003cConnectedDevice\u003e {\n        response\n            .lines()\n            .map(|a| ConnectedDevice::from_str(\u0026a).unwrap())\n            .collect()\n    }\n\n    pub fn parse_get_active_channels(response: \u0026str) -\u003e Result\u003cVec\u003cVec\u003cu8\u003e\u003e\u003e {\n        println!(\"{}\", response);\n        let v: Vec\u003c\u0026str\u003e = response.split(',').map(|a| a.trim_start()).collect();\n\n        // Find position of starter word\n        let digital_pos = v.iter().position(|a| *a == \"digital_channels\").unwrap();\n        let analog_pos = v.iter().position(|a| *a == \"analog_channels\").unwrap();\n\n        // Parse in between words to find values\n        let digital_res: Vec\u003cu8\u003e = v[digital_pos + 1..analog_pos]\n            .iter()\n            .map(|a| a.parse().unwrap())\n            .collect();\n\n        let analog_res: Vec\u003cu8\u003e = v[analog_pos + 1..v.len()]\n            .iter()\n            .map(|a| a.parse().unwrap())\n            .collect();\n        Ok(vec![digital_res, analog_res])\n    }\n\n    /// Parse if processing is complete\n    ///\n    /// # Sample Input\n    /// ```text\n    /// FALSE\n    /// ```\n    /// ```text\n    /// TRUE\n    /// ```\n    pub fn parse_processing_complete(response: \u0026str) -\u003e bool {\n        response == \"TRUE\"\n    }\n}\n","traces":[{"line":14,"address":4279104,"length":1,"stats":{"Line":5}},{"line":15,"address":4279123,"length":1,"stats":{"Line":5}},{"line":22,"address":4279264,"length":1,"stats":{"Line":3}},{"line":23,"address":4279281,"length":1,"stats":{"Line":3}},{"line":37,"address":4279392,"length":1,"stats":{"Line":2}},{"line":38,"address":4279406,"length":1,"stats":{"Line":2}},{"line":41,"address":4279488,"length":1,"stats":{"Line":2}},{"line":42,"address":4279502,"length":1,"stats":{"Line":2}},{"line":46,"address":4279568,"length":1,"stats":{"Line":1}},{"line":47,"address":4279585,"length":1,"stats":{"Line":1}},{"line":49,"address":4279633,"length":1,"stats":{"Line":1}},{"line":50,"address":4279784,"length":1,"stats":{"Line":1}},{"line":61,"address":4279984,"length":1,"stats":{"Line":1}},{"line":62,"address":4280004,"length":1,"stats":{"Line":1}},{"line":74,"address":4280080,"length":1,"stats":{"Line":2}},{"line":75,"address":4280100,"length":1,"stats":{"Line":2}},{"line":81,"address":4280176,"length":1,"stats":{"Line":2}},{"line":82,"address":4280202,"length":1,"stats":{"Line":2}},{"line":83,"address":4280435,"length":1,"stats":{"Line":4}},{"line":86,"address":4280520,"length":1,"stats":{"Line":4}},{"line":87,"address":4280729,"length":1,"stats":{"Line":4}},{"line":90,"address":4280912,"length":1,"stats":{"Line":2}},{"line":91,"address":null,"length":0,"stats":{"Line":0}},{"line":92,"address":4336736,"length":1,"stats":{"Line":2}},{"line":93,"address":4281093,"length":1,"stats":{"Line":2}},{"line":95,"address":4281101,"length":1,"stats":{"Line":2}},{"line":96,"address":null,"length":0,"stats":{"Line":0}},{"line":97,"address":4336832,"length":1,"stats":{"Line":2}},{"line":98,"address":null,"length":0,"stats":{"Line":0}},{"line":99,"address":4281308,"length":1,"stats":{"Line":2}},{"line":111,"address":4281696,"length":1,"stats":{"Line":2}},{"line":112,"address":4281710,"length":1,"stats":{"Line":2}}],"covered":29,"coverable":32},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","src","samplerate.rs"],"content":"//! This module defines the sample rates returned and used by the saleae Logic program\n//!\n//! # C# Example\n//! ```text\n//! struct SampleRate\n//! {\n//!     public int AnalogSampleRate;\n//!     public int DigitalSampleRate;\n//! }\n//! ```\nuse std::str::FromStr;\n\n#[allow(non_snake_case)]\n#[derive(Debug, PartialEq)]\npub struct SampleRate {\n    pub DigitalSampleRate: u32,\n    pub AnalogSampleRate: u32,\n}\n\nimpl FromStr for SampleRate {\n    type Err = std::num::ParseIntError;\n    fn from_str(response: \u0026str) -\u003e Result\u003cSelf, Self::Err\u003e {\n        let v: Vec\u003c\u0026str\u003e = response.split(',').map(|a| a.trim_start()).collect();\n\n        Ok(SampleRate {\n            DigitalSampleRate: v[0].parse::\u003cu32\u003e().unwrap(),\n            AnalogSampleRate: v[1].parse::\u003cu32\u003e().unwrap(),\n        })\n    }\n}\n","traces":[{"line":22,"address":4211904,"length":1,"stats":{"Line":2}},{"line":23,"address":4211924,"length":1,"stats":{"Line":4}},{"line":25,"address":4212233,"length":1,"stats":{"Line":2}},{"line":26,"address":4212025,"length":1,"stats":{"Line":2}},{"line":27,"address":4212138,"length":1,"stats":{"Line":2}}],"covered":5,"coverable":5},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","tests","client.rs"],"content":"#[cfg(test)]\nmod tests {\n    use saleae::client::{Client, Connection};\n    use saleae::device::DeviceID;\n    use saleae::ConnectedDevice;\n    use saleae::PerformanceOption;\n    use saleae::SampleRate;\n\n    #[test]\n    fn set_num_samples() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.set_num_samples(500).unwrap();\n        assert_eq!(true, response);\n    }\n\n    #[test]\n    fn get_num_samples() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_message).then(|_| Ok(\"3000\\nACK\".to_string())) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.get_num_samples().unwrap();\n        assert_eq!(3000, response);\n    }\n\n    #[test]\n    #[should_panic]\n    fn get_num_samples_fail() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_message).then(|_| Ok(\"3000\".to_string())) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.get_num_samples().unwrap();\n        assert_eq!(3000, response);\n    }\n\n    #[test]\n    fn set_capture_seconds() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.set_capture_seconds(2.2).unwrap();\n        assert_eq!(true, response);\n    }\n\n    #[test]\n    fn set_sample_rate() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn\n            .set_sample_rate(\u0026SampleRate {\n                AnalogSampleRate: 6250000,\n                DigitalSampleRate: 1562500,\n            })\n            .unwrap();\n        assert_eq!(true, response);\n    }\n\n    #[test]\n    fn get_sample_rate() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe {\n            faux::when!(conn.general_recieve_message).then(|_| Ok(\"1000000\\n0\\n\".to_string()))\n        }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.get_sample_rate().unwrap();\n        assert_eq!(response.DigitalSampleRate, 1000000);\n        assert_eq!(response.AnalogSampleRate, 0);\n    }\n\n    #[test]\n    fn get_performance() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_message).then(|_| Ok(\"100\".to_string())) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.get_performance().unwrap();\n        assert_eq!(response, 100);\n    }\n\n    #[test]\n    fn set_performance() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.set_performance(PerformanceOption::Full).unwrap();\n        assert_eq!(response, true);\n\n        let mut conn2 = Connection::faux();\n        unsafe { faux::when!(conn2.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn2.general_recieve_ack).then(|_| Ok(false)) }\n\n        let mut conn2 = Client::new(conn2).unwrap();\n        let response2 = conn2.set_performance(PerformanceOption::Low).unwrap();\n        assert_eq!(response2, false);\n    }\n\n    #[test]\n    fn get_connected_devices() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe {\n            faux::when!(conn.general_recieve_message).then(|_| Ok(\"1, Logic 8, LOGIC_8_DEVICE, 0x2dc9, ACTIVE\\n2, Logic Pro 8, LOGIC_PRO_8_DEVICE, 0x7243\\n3, Logic Pro 16, LOGIC_PRO_16_DEVICE, 0x673f\\n4, Logic 4, LOGIC_4_DEVICE, 0x6709\\nACK\".to_string()))\n        }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.get_connected_devices().unwrap();\n        assert_eq!(\n            response[0],\n            ConnectedDevice {\n                d_type: \"1\".to_string(),\n                name: \"Logic 8\".to_string(),\n                device_id: DeviceID::LOGIC_8_DEVICE,\n                index: \"0x2dc9\".to_string(),\n                is_active: true\n            }\n        );\n        assert_eq!(\n            response[1],\n            ConnectedDevice {\n                d_type: \"2\".to_string(),\n                name: \"Logic Pro 8\".to_string(),\n                device_id: DeviceID::LOGIC_PRO_8_DEVICE,\n                index: \"0x7243\".to_string(),\n                is_active: false\n            }\n        );\n        assert_eq!(\n            response[2],\n            ConnectedDevice {\n                d_type: \"3\".to_string(),\n                name: \"Logic Pro 16\".to_string(),\n                device_id: DeviceID::LOGIC_PRO_16_DEVICE,\n                index: \"0x673f\".to_string(),\n                is_active: false\n            }\n        );\n        assert_eq!(\n            response[3],\n            ConnectedDevice {\n                d_type: \"4\".to_string(),\n                name: \"Logic 4\".to_string(),\n                device_id: DeviceID::LOGIC_4_DEVICE,\n                index: \"0x6709\".to_string(),\n                is_active: false\n            }\n        );\n    }\n\n    #[test]\n    fn select_active_device() {\n        //TODO can't test b/c of get_connected_devices not being fauxable\n        //let device = ConnectedDevice {\n        //    d_type: \"4\".to_string(),\n        //    name: \"Logic 4\".to_string(),\n        //    device_id: DeviceID::LOGIC_4_DEVICE,\n        //    index: \"0x6709\".to_string(),\n        //    is_active: false,\n        //};\n\n        //let mut conn = Connection::faux();\n        //unsafe { faux::when!(conn.get_connected_devices).then(|_| Ok(device)) };\n        //unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n\n        //let mut conn = Client::new(conn).unwrap();\n        //let response = conn.select_active_device(device).unwrap();\n        //assert_eq!(response, true);\n    }\n\n    #[test]\n    fn get_active_device() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe {\n            faux::when!(conn.general_recieve_message).then(|_| {\n                Ok(\"digital_channels, 0, 4, 5, 7, analog_channels, 0, 1, 2, 5, 8\".to_string())\n            })\n        }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.get_active_channels().unwrap();\n        assert_eq!(response[0], [0, 4, 5, 7]);\n        assert_eq!(response[1], [0, 1, 2, 5, 8]);\n    }\n\n    #[test]\n    fn set_active_channels() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn\n            .set_active_channels(\u0026[0, 4, 5, 7], \u0026[0, 1, 2, 5, 8])\n            .unwrap();\n        assert_eq!(response, true);\n    }\n\n    #[test]\n    fn reset_active_channels() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.reset_active_channels().unwrap();\n        assert_eq!(response, true);\n    }\n\n    #[test]\n    fn start_capture() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.start_capture().unwrap();\n        assert_eq!(response, true);\n    }\n\n    #[test]\n    fn start_capture_block_until_finished() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.start_capture_block_until_finished().unwrap();\n        assert_eq!(response, true);\n    }\n\n    #[test]\n    fn is_processing_complete() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_message).then(|_| Ok(\"TRUE\\nACK\".to_string())) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.is_processing_complete().unwrap();\n        assert_eq!(response, true);\n    }\n\n    #[test]\n    fn stop_capture() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.stop_capture().unwrap();\n        assert_eq!(response, true);\n    }\n\n    #[test]\n    fn close_all_tabs() {\n        let mut conn = Connection::faux();\n        unsafe { faux::when!(conn.run_command).then(|_| Ok(())) }\n        unsafe { faux::when!(conn.general_recieve_ack).then(|_| Ok(true)) }\n\n        let mut conn = Client::new(conn).unwrap();\n        let response = conn.close_all_tabs().unwrap();\n        assert_eq!(response, true);\n    }\n}\n","traces":[{"line":10,"address":4240032,"length":1,"stats":{"Line":2}},{"line":11,"address":4240039,"length":1,"stats":{"Line":1}},{"line":12,"address":4240083,"length":1,"stats":{"Line":2}},{"line":13,"address":4240122,"length":1,"stats":{"Line":2}},{"line":15,"address":4240161,"length":1,"stats":{"Line":1}},{"line":16,"address":4240294,"length":1,"stats":{"Line":1}},{"line":17,"address":4240383,"length":1,"stats":{"Line":1}},{"line":21,"address":4240912,"length":1,"stats":{"Line":2}},{"line":22,"address":4240919,"length":1,"stats":{"Line":1}},{"line":23,"address":4240963,"length":1,"stats":{"Line":2}},{"line":24,"address":4241002,"length":1,"stats":{"Line":2}},{"line":26,"address":4241041,"length":1,"stats":{"Line":1}},{"line":27,"address":4241174,"length":1,"stats":{"Line":1}},{"line":28,"address":4241256,"length":1,"stats":{"Line":1}},{"line":33,"address":4241776,"length":1,"stats":{"Line":2}},{"line":34,"address":4241783,"length":1,"stats":{"Line":1}},{"line":35,"address":4241827,"length":1,"stats":{"Line":2}},{"line":36,"address":4241866,"length":1,"stats":{"Line":2}},{"line":38,"address":4241905,"length":1,"stats":{"Line":1}},{"line":39,"address":4242038,"length":1,"stats":{"Line":1}},{"line":40,"address":4242120,"length":1,"stats":{"Line":0}},{"line":44,"address":4242640,"length":1,"stats":{"Line":2}},{"line":45,"address":4242647,"length":1,"stats":{"Line":1}},{"line":46,"address":4242691,"length":1,"stats":{"Line":2}},{"line":47,"address":4242730,"length":1,"stats":{"Line":2}},{"line":49,"address":4242769,"length":1,"stats":{"Line":1}},{"line":50,"address":4242902,"length":1,"stats":{"Line":1}},{"line":51,"address":4242994,"length":1,"stats":{"Line":1}},{"line":55,"address":4243520,"length":1,"stats":{"Line":2}},{"line":56,"address":4243527,"length":1,"stats":{"Line":1}},{"line":57,"address":4243571,"length":1,"stats":{"Line":2}},{"line":58,"address":4243610,"length":1,"stats":{"Line":2}},{"line":60,"address":4243649,"length":1,"stats":{"Line":1}},{"line":61,"address":4243789,"length":1,"stats":{"Line":1}},{"line":62,"address":4243782,"length":1,"stats":{"Line":1}},{"line":67,"address":4243873,"length":1,"stats":{"Line":1}},{"line":71,"address":4244400,"length":1,"stats":{"Line":2}},{"line":72,"address":4244407,"length":1,"stats":{"Line":1}},{"line":73,"address":4244451,"length":1,"stats":{"Line":2}},{"line":75,"address":4244490,"length":1,"stats":{"Line":2}},{"line":78,"address":4244529,"length":1,"stats":{"Line":1}},{"line":79,"address":4244671,"length":1,"stats":{"Line":1}},{"line":80,"address":4244768,"length":1,"stats":{"Line":1}},{"line":81,"address":4244865,"length":1,"stats":{"Line":1}},{"line":85,"address":4245728,"length":1,"stats":{"Line":2}},{"line":86,"address":4245735,"length":1,"stats":{"Line":1}},{"line":87,"address":4245779,"length":1,"stats":{"Line":2}},{"line":88,"address":4245818,"length":1,"stats":{"Line":2}},{"line":90,"address":4245857,"length":1,"stats":{"Line":1}},{"line":91,"address":4245990,"length":1,"stats":{"Line":1}},{"line":92,"address":4246072,"length":1,"stats":{"Line":1}},{"line":96,"address":4246608,"length":1,"stats":{"Line":2}},{"line":97,"address":4246615,"length":1,"stats":{"Line":1}},{"line":98,"address":4246670,"length":1,"stats":{"Line":2}},{"line":99,"address":4246712,"length":1,"stats":{"Line":2}},{"line":101,"address":4246754,"length":1,"stats":{"Line":1}},{"line":102,"address":4246899,"length":1,"stats":{"Line":1}},{"line":103,"address":4247005,"length":1,"stats":{"Line":1}},{"line":105,"address":4247100,"length":1,"stats":{"Line":1}},{"line":106,"address":4247437,"length":1,"stats":{"Line":2}},{"line":107,"address":4247487,"length":1,"stats":{"Line":2}},{"line":109,"address":4247529,"length":1,"stats":{"Line":1}},{"line":110,"address":4247674,"length":1,"stats":{"Line":1}},{"line":111,"address":4247774,"length":1,"stats":{"Line":1}},{"line":115,"address":4248400,"length":1,"stats":{"Line":2}},{"line":116,"address":4248407,"length":1,"stats":{"Line":1}},{"line":117,"address":4248454,"length":1,"stats":{"Line":2}},{"line":119,"address":4248496,"length":1,"stats":{"Line":2}},{"line":122,"address":4248538,"length":1,"stats":{"Line":1}},{"line":123,"address":4248683,"length":1,"stats":{"Line":1}},{"line":124,"address":4249034,"length":1,"stats":{"Line":1}},{"line":125,"address":4248774,"length":1,"stats":{"Line":1}},{"line":126,"address":4248908,"length":1,"stats":{"Line":1}},{"line":127,"address":4248789,"length":1,"stats":{"Line":1}},{"line":128,"address":4248831,"length":1,"stats":{"Line":1}},{"line":129,"address":4248858,"length":1,"stats":{"Line":1}},{"line":130,"address":4248866,"length":1,"stats":{"Line":1}},{"line":134,"address":4249805,"length":1,"stats":{"Line":1}},{"line":135,"address":4249557,"length":1,"stats":{"Line":1}},{"line":136,"address":4249679,"length":1,"stats":{"Line":1}},{"line":137,"address":4249572,"length":1,"stats":{"Line":1}},{"line":138,"address":4249599,"length":1,"stats":{"Line":1}},{"line":139,"address":4249626,"length":1,"stats":{"Line":1}},{"line":140,"address":4249634,"length":1,"stats":{"Line":1}},{"line":144,"address":4250576,"length":1,"stats":{"Line":1}},{"line":145,"address":4250328,"length":1,"stats":{"Line":1}},{"line":146,"address":4250450,"length":1,"stats":{"Line":1}},{"line":147,"address":4250343,"length":1,"stats":{"Line":1}},{"line":148,"address":4250370,"length":1,"stats":{"Line":1}},{"line":149,"address":4250397,"length":1,"stats":{"Line":1}},{"line":150,"address":4250405,"length":1,"stats":{"Line":1}},{"line":154,"address":4251305,"length":1,"stats":{"Line":1}},{"line":155,"address":4251063,"length":1,"stats":{"Line":1}},{"line":156,"address":4251182,"length":1,"stats":{"Line":1}},{"line":157,"address":4251075,"length":1,"stats":{"Line":1}},{"line":158,"address":4251102,"length":1,"stats":{"Line":1}},{"line":159,"address":4251129,"length":1,"stats":{"Line":1}},{"line":160,"address":4251137,"length":1,"stats":{"Line":1}},{"line":167,"address":4252160,"length":1,"stats":{"Line":2}},{"line":187,"address":4252176,"length":1,"stats":{"Line":2}},{"line":188,"address":4252183,"length":1,"stats":{"Line":1}},{"line":189,"address":4252230,"length":1,"stats":{"Line":2}},{"line":191,"address":4252272,"length":1,"stats":{"Line":2}},{"line":192,"address":4265934,"length":1,"stats":{"Line":1}},{"line":196,"address":4252314,"length":1,"stats":{"Line":1}},{"line":197,"address":4252459,"length":1,"stats":{"Line":1}},{"line":198,"address":4252546,"length":1,"stats":{"Line":1}},{"line":199,"address":4252698,"length":1,"stats":{"Line":1}},{"line":203,"address":4253600,"length":1,"stats":{"Line":2}},{"line":204,"address":4253607,"length":1,"stats":{"Line":1}},{"line":205,"address":4253651,"length":1,"stats":{"Line":2}},{"line":206,"address":4253690,"length":1,"stats":{"Line":2}},{"line":208,"address":4253729,"length":1,"stats":{"Line":1}},{"line":209,"address":4253876,"length":1,"stats":{"Line":1}},{"line":210,"address":4253862,"length":1,"stats":{"Line":1}},{"line":212,"address":4253971,"length":1,"stats":{"Line":1}},{"line":216,"address":4254496,"length":1,"stats":{"Line":2}},{"line":217,"address":4254503,"length":1,"stats":{"Line":1}},{"line":218,"address":4254547,"length":1,"stats":{"Line":2}},{"line":219,"address":4254586,"length":1,"stats":{"Line":2}},{"line":221,"address":4254625,"length":1,"stats":{"Line":1}},{"line":222,"address":4254758,"length":1,"stats":{"Line":1}},{"line":223,"address":4254842,"length":1,"stats":{"Line":1}},{"line":227,"address":4255360,"length":1,"stats":{"Line":2}},{"line":228,"address":4255367,"length":1,"stats":{"Line":1}},{"line":229,"address":4255411,"length":1,"stats":{"Line":2}},{"line":231,"address":4255450,"length":1,"stats":{"Line":1}},{"line":232,"address":4255583,"length":1,"stats":{"Line":1}},{"line":233,"address":4255667,"length":1,"stats":{"Line":1}},{"line":237,"address":4256192,"length":1,"stats":{"Line":2}},{"line":238,"address":4256199,"length":1,"stats":{"Line":1}},{"line":239,"address":4256243,"length":1,"stats":{"Line":2}},{"line":240,"address":4256282,"length":1,"stats":{"Line":2}},{"line":242,"address":4256321,"length":1,"stats":{"Line":1}},{"line":243,"address":4256454,"length":1,"stats":{"Line":1}},{"line":244,"address":4256538,"length":1,"stats":{"Line":1}},{"line":248,"address":4257056,"length":1,"stats":{"Line":2}},{"line":249,"address":4257063,"length":1,"stats":{"Line":1}},{"line":250,"address":4257107,"length":1,"stats":{"Line":2}},{"line":251,"address":4257146,"length":1,"stats":{"Line":2}},{"line":253,"address":4257185,"length":1,"stats":{"Line":1}},{"line":254,"address":4257318,"length":1,"stats":{"Line":1}},{"line":255,"address":4257402,"length":1,"stats":{"Line":1}},{"line":259,"address":4257920,"length":1,"stats":{"Line":2}},{"line":260,"address":4257927,"length":1,"stats":{"Line":1}},{"line":261,"address":4257971,"length":1,"stats":{"Line":2}},{"line":262,"address":4258010,"length":1,"stats":{"Line":2}},{"line":264,"address":4258049,"length":1,"stats":{"Line":1}},{"line":265,"address":4258182,"length":1,"stats":{"Line":1}},{"line":266,"address":4258266,"length":1,"stats":{"Line":1}},{"line":270,"address":4258784,"length":1,"stats":{"Line":2}},{"line":271,"address":4258791,"length":1,"stats":{"Line":1}},{"line":272,"address":4258835,"length":1,"stats":{"Line":2}},{"line":273,"address":4258874,"length":1,"stats":{"Line":2}},{"line":275,"address":4258913,"length":1,"stats":{"Line":1}},{"line":276,"address":4259046,"length":1,"stats":{"Line":1}},{"line":277,"address":4259130,"length":1,"stats":{"Line":1}}],"covered":156,"coverable":157},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","tests","device.rs"],"content":"#[cfg(test)]\nmod tests {\n    use saleae::device::{ConnectedDevice, DeviceID};\n    use std::str::FromStr;\n\n    // Sample from C# API\n    //1, Logic 8, LOGIC_8_DEVICE, 0x2dc9, ACTIVE\n    //2, Logic Pro 8, LOGIC_PRO_8_DEVICE, 0x7243\n    //3, Logic Pro 16, LOGIC_PRO_16_DEVICE, 0x673f\n    //4, Logic 4, LOGIC_4_DEVICE, 0x6709\n    #[test]\n    fn test_from_str() {\n        // test whether from_str performs as expected\n        assert_eq!(\n            ConnectedDevice::from_str(\n                \u0026\"1, Logic Pro 16, LOGIC_PRO_16_DEVICE, 0xdf03c43d1f3aa2f3, ACTIVE\"\n            )\n            .unwrap(),\n            ConnectedDevice {\n                d_type: \"1\".to_string(),\n                name: \"Logic Pro 16\".to_string(),\n                device_id: DeviceID::LOGIC_PRO_16_DEVICE,\n                index: \"0xdf03c43d1f3aa2f3\".to_string(),\n                is_active: true,\n            }\n        );\n        assert_eq!(\n            ConnectedDevice::from_str(\u0026\"2, Logic 8, LOGIC_8_DEVICE, 0xffffffffff, ACTIVE\").unwrap(),\n            ConnectedDevice {\n                d_type: \"2\".to_string(),\n                name: \"Logic 8\".to_string(),\n                device_id: DeviceID::LOGIC_8_DEVICE,\n                index: \"0xffffffffff\".to_string(),\n                is_active: true,\n            }\n        );\n        assert_eq!(\n            ConnectedDevice::from_str(\u0026\"1, Logic Pro 16, LOGIC_PRO_16_DEVICE, 0xdf03c43d1f3aa2f3\")\n                .unwrap(),\n            ConnectedDevice {\n                d_type: \"1\".to_string(),\n                name: \"Logic Pro 16\".to_string(),\n                device_id: DeviceID::LOGIC_PRO_16_DEVICE,\n                index: \"0xdf03c43d1f3aa2f3\".to_string(),\n                is_active: false,\n            }\n        );\n    }\n}\n","traces":[{"line":12,"address":4214576,"length":1,"stats":{"Line":2}},{"line":14,"address":4214687,"length":1,"stats":{"Line":0}},{"line":15,"address":4214597,"length":1,"stats":{"Line":1}},{"line":16,"address":4214583,"length":1,"stats":{"Line":1}},{"line":19,"address":4214779,"length":1,"stats":{"Line":1}},{"line":20,"address":4214660,"length":1,"stats":{"Line":1}},{"line":21,"address":4214702,"length":1,"stats":{"Line":1}},{"line":22,"address":4214729,"length":1,"stats":{"Line":1}},{"line":23,"address":4214737,"length":1,"stats":{"Line":1}},{"line":27,"address":4215515,"length":1,"stats":{"Line":0}},{"line":28,"address":4215428,"length":1,"stats":{"Line":1}},{"line":29,"address":4215610,"length":1,"stats":{"Line":1}},{"line":30,"address":4215488,"length":1,"stats":{"Line":1}},{"line":31,"address":4215533,"length":1,"stats":{"Line":1}},{"line":32,"address":4215560,"length":1,"stats":{"Line":1}},{"line":33,"address":4215568,"length":1,"stats":{"Line":1}},{"line":37,"address":4216304,"length":1,"stats":{"Line":0}},{"line":38,"address":4216217,"length":1,"stats":{"Line":1}},{"line":40,"address":4216399,"length":1,"stats":{"Line":1}},{"line":41,"address":4216277,"length":1,"stats":{"Line":1}},{"line":42,"address":4216322,"length":1,"stats":{"Line":1}},{"line":43,"address":4216349,"length":1,"stats":{"Line":1}},{"line":44,"address":4216357,"length":1,"stats":{"Line":1}}],"covered":20,"coverable":23},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","tests","request.rs"],"content":"#[cfg(test)]\nmod tests {\n    use saleae::request::Request;\n    /// Regular function tests\n    #[test]\n    fn test_prepare_set_active_channels() {\n        let expected1: String =\n            \"set_active_channels, digital_channels, 1, 7, 8, 9, analog_channels, 0, 9, 8\\0\"\n                .to_string();\n        assert_eq!(\n            expected1,\n            Request::prepare_set_active_channels(\u0026[1, 7, 8, 9], \u0026[0, 9, 8]).unwrap(),\n        );\n\n        // Empty digital channel\n        let expected2: String = \"set_active_channels, analog_channels, 0, 9, 8\\0\".to_string();\n        assert_eq!(\n            expected2,\n            Request::prepare_set_active_channels(\u0026[], \u0026[0, 9, 8]).unwrap(),\n        );\n\n        // Empty analog channel\n        let expected3: String = \"set_active_channels, digital_channels, 1, 7, 8, 9\\0\".to_string();\n        assert_eq!(\n            expected3,\n            Request::prepare_set_active_channels(\u0026[1, 7, 8, 9], \u0026[]).unwrap(),\n        );\n    }\n\n    #[test]\n    #[should_panic(\n        expected = \"Logic requires at least one active channel, no active channels found\"\n    )]\n    fn test_prepare_set_active_channels_panic() {\n        // Empty both channels (should panic / error)\n        let expected4: String = \"set_active_channels\\0\".to_string();\n        assert_eq!(\n            expected4,\n            Request::prepare_set_active_channels(\u0026[], \u0026[]).unwrap(),\n        );\n    }\n\n    /// Help function tests\n    #[test]\n    fn test_create_channel_str() {\n        let input = [0, 4, 5, 7];\n        assert_eq!(\"0, 4, 5, 7\", Request::create_channel_str(\u0026input).unwrap());\n\n        let input = [1, 2, 3, 5, 8];\n        assert_eq!(\n            \"1, 2, 3, 5, 8\",\n            Request::create_channel_str(\u0026input).unwrap()\n        );\n    }\n}\n","traces":[{"line":6,"address":4211296,"length":1,"stats":{"Line":2}},{"line":8,"address":4211310,"length":1,"stats":{"Line":1}},{"line":10,"address":4211445,"length":1,"stats":{"Line":1}},{"line":12,"address":4211348,"length":1,"stats":{"Line":1}},{"line":16,"address":4211934,"length":1,"stats":{"Line":1}},{"line":17,"address":4212058,"length":1,"stats":{"Line":1}},{"line":19,"address":4211961,"length":1,"stats":{"Line":1}},{"line":23,"address":4212505,"length":1,"stats":{"Line":1}},{"line":24,"address":4212631,"length":1,"stats":{"Line":1}},{"line":26,"address":4212532,"length":1,"stats":{"Line":1}},{"line":34,"address":4213248,"length":1,"stats":{"Line":2}},{"line":36,"address":4213262,"length":1,"stats":{"Line":1}},{"line":37,"address":4213405,"length":1,"stats":{"Line":0}},{"line":39,"address":4213297,"length":1,"stats":{"Line":1}},{"line":45,"address":4213904,"length":1,"stats":{"Line":2}},{"line":46,"address":4213911,"length":1,"stats":{"Line":1}},{"line":47,"address":4213943,"length":1,"stats":{"Line":1}},{"line":49,"address":4214500,"length":1,"stats":{"Line":1}},{"line":50,"address":4214540,"length":1,"stats":{"Line":1}},{"line":52,"address":4214547,"length":1,"stats":{"Line":1}}],"covered":19,"coverable":20},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","tests","response.rs"],"content":"#[cfg(test)]\nmod tests {\n    use saleae::device::{ConnectedDevice, DeviceID};\n    use saleae::response::Response;\n    use saleae::samplerate::SampleRate;\n\n    #[test]\n    fn test_remove_ack() {\n        let test0 = String::from(\"let\\nmy\\npeople\\ngo\\nACK\");\n        assert_eq!(\"let\\nmy\\npeople\\ngo\", Response::remove_ack(\u0026test0));\n\n        let test1 = String::from(\"\\nACK\");\n        assert_eq!(\"\", Response::remove_ack(\u0026test1));\n\n        let test2 = String::from(\"let\\nmy\\npeople\\ngo\\nACK\\u{0}\\u{0}\");\n        assert_eq!(\"let\\nmy\\npeople\\ngo\", Response::remove_ack(\u0026test2));\n    }\n\n    #[test]\n    fn test_verify_ack() {\n        let test0 = String::from(\"let\\nmy\\npeople\\ngo\\nACK\");\n        assert!(Response::verify_ack(\u0026test0));\n\n        let test1 = String::from(\"ACK\");\n        assert!(Response::verify_ack(\u0026test1));\n\n        let test2 = String::from(\"ack\");\n        assert_eq!(false, Response::verify_ack(\u0026test2));\n\n        let test3 = String::from(\"let\\nmy\\npeople\\ngo\\n\");\n        assert_eq!(false, Response::verify_ack(\u0026test3));\n    }\n\n    #[test]\n    fn test_parse_performance() {\n        assert_eq!(Response::parse_performance(\"100\"), 100);\n        assert_eq!(Response::parse_performance(\"80\"), 80);\n        assert_eq!(Response::parse_performance(\"60\"), 60);\n        assert_eq!(Response::parse_performance(\"40\"), 40);\n        assert_eq!(Response::parse_performance(\"20\"), 20);\n    }\n\n    #[test]\n    fn test_get_all_sample_rates() {\n        let one = SampleRate {\n            DigitalSampleRate: 25000000,\n            AnalogSampleRate: 3125000,\n        };\n        let two = SampleRate {\n            DigitalSampleRate: 6250000,\n            AnalogSampleRate: 1562500,\n        };\n        let result = Response::parse_get_all_sample_rates(\"25000000, 3125000\\n6250000, 1562500\\n\");\n        assert_eq!(result[0], one);\n        assert_eq!(result[1], two);\n    }\n\n    #[test]\n    fn test_connected_devices() {\n        let one = ConnectedDevice {\n            d_type: \"2\".to_string(),\n            name: \"Logic 8\".to_string(),\n            device_id: DeviceID::LOGIC_8_DEVICE,\n            index: \"0xffffffffff\".to_string(),\n            is_active: true,\n        };\n        let two = ConnectedDevice {\n            d_type: \"1\".to_string(),\n            name: \"Logic Pro 16\".to_string(),\n            device_id: DeviceID::LOGIC_PRO_16_DEVICE,\n            index: \"0xdf03c43d1f3aa2f3\".to_string(),\n            is_active: false,\n        };\n        let expected = vec![one, two];\n        let result = Response::parse_connected_devices(\n            \"2, Logic 8, LOGIC_8_DEVICE, 0xffffffffff, ACTIVE\n             1, Logic Pro 16, LOGIC_PRO_16_DEVICE, 0xdf03c43d1f3aa2f3\",\n        );\n        assert_eq!(result[0], expected[0]);\n        assert_eq!(result[1], expected[1]);\n    }\n\n    #[test]\n    fn test_parse_into_active_channels() {\n        // Test regular input\n        let input: String = \"digital_channels, 0, 4, 5, 7, analog_channels, 0, 1, 2, 5\".to_string();\n        let result = Response::parse_get_active_channels(\u0026input).unwrap();\n        assert_eq!([0, 4, 5, 7], result[0].as_slice());\n        assert_eq!([0, 1, 2, 5], result[1].as_slice());\n\n        // Test with no analog channels\n        let input: String = \"digital_channels, 0, 4, 5, 7, analog_channels\".to_string();\n        let result = Response::parse_get_active_channels(\u0026input).unwrap();\n        assert_eq!([0, 4, 5, 7], result[0].as_slice());\n        assert!(result[1].as_slice().is_empty());\n\n        // Test with no digital channels\n        let input: String = \"digital_channels, analog_channels, 1, 2, 5\".to_string();\n        let result = Response::parse_get_active_channels(\u0026input).unwrap();\n        assert!(result[0].as_slice().is_empty());\n        assert_eq!([1, 2, 5], result[1].as_slice());\n    }\n\n    #[test]\n    fn test_is_processing_complete() {\n        assert_eq!(true, Response::parse_processing_complete(\"TRUE\"));\n        assert_eq!(false, Response::parse_processing_complete(\"FALSE\"));\n    }\n\n    #[test]\n    fn test_parse_num_samples() {\n        assert_eq!(10000, Response::parse_num_samples(\"10000\"));\n        assert_eq!(20, Response::parse_num_samples(\"20\"));\n    }\n}\n","traces":[{"line":8,"address":4212736,"length":1,"stats":{"Line":2}},{"line":9,"address":4212750,"length":1,"stats":{"Line":1}},{"line":10,"address":4212788,"length":1,"stats":{"Line":1}},{"line":12,"address":4213386,"length":1,"stats":{"Line":1}},{"line":13,"address":4213413,"length":1,"stats":{"Line":1}},{"line":15,"address":4213984,"length":1,"stats":{"Line":1}},{"line":16,"address":4214011,"length":1,"stats":{"Line":1}},{"line":20,"address":4214720,"length":1,"stats":{"Line":2}},{"line":21,"address":4214734,"length":1,"stats":{"Line":1}},{"line":22,"address":4214780,"length":1,"stats":{"Line":1}},{"line":24,"address":4214865,"length":1,"stats":{"Line":1}},{"line":25,"address":4214933,"length":1,"stats":{"Line":1}},{"line":27,"address":4215021,"length":1,"stats":{"Line":1}},{"line":28,"address":4215081,"length":1,"stats":{"Line":1}},{"line":30,"address":4215291,"length":1,"stats":{"Line":1}},{"line":31,"address":4215654,"length":1,"stats":{"Line":1}},{"line":35,"address":4216304,"length":1,"stats":{"Line":2}},{"line":36,"address":4216318,"length":1,"stats":{"Line":1}},{"line":37,"address":4216440,"length":1,"stats":{"Line":1}},{"line":38,"address":4216901,"length":1,"stats":{"Line":1}},{"line":39,"address":4217362,"length":1,"stats":{"Line":1}},{"line":40,"address":4217799,"length":1,"stats":{"Line":1}},{"line":44,"address":4218528,"length":1,"stats":{"Line":2}},{"line":45,"address":4218542,"length":1,"stats":{"Line":1}},{"line":49,"address":4218564,"length":1,"stats":{"Line":1}},{"line":53,"address":4218586,"length":1,"stats":{"Line":1}},{"line":54,"address":4218637,"length":1,"stats":{"Line":1}},{"line":55,"address":4218790,"length":1,"stats":{"Line":1}},{"line":59,"address":4219584,"length":1,"stats":{"Line":2}},{"line":60,"address":4219598,"length":1,"stats":{"Line":1}},{"line":61,"address":4219606,"length":1,"stats":{"Line":1}},{"line":62,"address":4219644,"length":1,"stats":{"Line":1}},{"line":63,"address":4219671,"length":1,"stats":{"Line":1}},{"line":64,"address":4219679,"length":1,"stats":{"Line":1}},{"line":67,"address":4219988,"length":1,"stats":{"Line":1}},{"line":68,"address":4219847,"length":1,"stats":{"Line":1}},{"line":69,"address":4219892,"length":1,"stats":{"Line":1}},{"line":70,"address":4219938,"length":1,"stats":{"Line":1}},{"line":71,"address":4219946,"length":1,"stats":{"Line":1}},{"line":74,"address":4220116,"length":1,"stats":{"Line":1}},{"line":75,"address":4220455,"length":1,"stats":{"Line":1}},{"line":79,"address":4220498,"length":1,"stats":{"Line":1}},{"line":80,"address":4220693,"length":1,"stats":{"Line":1}},{"line":84,"address":4221680,"length":1,"stats":{"Line":2}},{"line":86,"address":4221694,"length":1,"stats":{"Line":1}},{"line":87,"address":4221740,"length":1,"stats":{"Line":1}},{"line":88,"address":4221843,"length":1,"stats":{"Line":1}},{"line":89,"address":4222073,"length":1,"stats":{"Line":1}},{"line":92,"address":4222646,"length":1,"stats":{"Line":1}},{"line":93,"address":4223041,"length":1,"stats":{"Line":1}},{"line":94,"address":4223147,"length":1,"stats":{"Line":1}},{"line":95,"address":4223390,"length":1,"stats":{"Line":1}},{"line":98,"address":4223843,"length":1,"stats":{"Line":1}},{"line":99,"address":4223911,"length":1,"stats":{"Line":1}},{"line":100,"address":4224029,"length":1,"stats":{"Line":1}},{"line":101,"address":4224119,"length":1,"stats":{"Line":1}},{"line":105,"address":4224880,"length":1,"stats":{"Line":2}},{"line":106,"address":4224894,"length":1,"stats":{"Line":1}},{"line":107,"address":4225026,"length":1,"stats":{"Line":1}},{"line":111,"address":4225776,"length":1,"stats":{"Line":2}},{"line":112,"address":4225790,"length":1,"stats":{"Line":1}},{"line":113,"address":4225919,"length":1,"stats":{"Line":1}}],"covered":62,"coverable":62},{"path":["/","home","wcampbell","projects","wcampbell","code","saleae","tests","samplerate.rs"],"content":"#[cfg(test)]\nmod tests {\n    use saleae::samplerate::SampleRate;\n    use std::str::FromStr;\n\n    #[test]\n    fn test_from_str() {\n        assert_eq!(\n            SampleRate::from_str(\u0026\"5000000, 1250000\").unwrap(),\n            SampleRate {\n                DigitalSampleRate: 5000000,\n                AnalogSampleRate: 1250000\n            }\n        );\n        assert_eq!(\n            SampleRate::from_str(\u0026\"10000000, 625000\").unwrap(),\n            SampleRate {\n                DigitalSampleRate: 10000000,\n                AnalogSampleRate: 625000\n            }\n        );\n    }\n}\n","traces":[{"line":7,"address":4212176,"length":1,"stats":{"Line":2}},{"line":8,"address":4213225,"length":1,"stats":{"Line":1}},{"line":9,"address":4213159,"length":1,"stats":{"Line":1}},{"line":15,"address":4213684,"length":1,"stats":{"Line":1}},{"line":16,"address":4213317,"length":1,"stats":{"Line":1}}],"covered":5,"coverable":5}]};</script>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <script>const e = React.createElement;

function pathToString(path) {
  if (path[0] === '/') {
    return '/' + path.slice(1).join('/');
  } else {
    return path.join('/');
  }
}

function findCommonPath(files) {
  if (!files || !files.length) {
    return [];
  }

  function isPrefix(arr, prefix) {
    if (arr.length < prefix.length) {
      return false;
    }
    for (let i = prefix.length - 1; i >= 0; --i) {
      if (arr[i] !== prefix[i]) {
        return false;
      }
    }
    return true;
  }

  let commonPath = files[0].path.slice(0, -1);
  while (commonPath.length) {
    if (files.every(file => isPrefix(file.path, commonPath))) {
      break;
    }
    commonPath.pop();
  }
  return commonPath;
}

function findFolders(files) {
  if (!files || !files.length) {
    return [];
  }

  let folders = files.filter(file => file.path.length > 1).map(file => file.path[0]);
  folders = [...new Set(folders)]; // unique
  folders.sort();

  folders = folders.map(folder => {
    let filesInFolder = files
      .filter(file => file.path[0] === folder)
      .map(file => ({
        ...file,
        path: file.path.slice(1),
        parent: [...file.parent, file.path[0]],
      }));

    const children = findFolders(filesInFolder); // recursion

    return {
      is_folder: true,
      path: [folder],
      parent: files[0].parent,
      children,
      covered: children.reduce((sum, file) => sum + file.covered, 0),
      coverable: children.reduce((sum, file) => sum + file.coverable, 0),
    };
  });

  return [
    ...folders,
    ...files.filter(file => file.path.length === 1),
  ];
}

class App extends React.Component {
  constructor(...args) {
    super(...args);

    this.state = {
      current: [],
    };
  }

  componentDidMount() {
    this.updateStateFromLocation();
    window.addEventListener("hashchange", () => this.updateStateFromLocation(), false);
  }

  updateStateFromLocation() {
    if (window.location.hash.length > 1) {
      const current = window.location.hash.substr(1).split('/');
      this.setState({current});
    } else {
      this.setState({current: []});
    }
  }

  getCurrentPath() {
    let file = this.props.root;
    let path = [file];
    for (let p of this.state.current) {
      file = file.children.find(file => file.path[0] === p);
      if (!file) {
        return path;
      }
      path.push(file);
    }
    return path;
  }

  render() {
    const path = this.getCurrentPath();
    const file = path[path.length - 1];

    let w = null;
    if (file.is_folder) {
      w = e(FilesList, {
        folder: file,
        onSelectFile: this.selectFile.bind(this),
        onBack: path.length > 1 ? this.back.bind(this) : null,
      });
    } else {
      w = e(DisplayFile, {
        file,
        onBack: this.back.bind(this),
      });
    }

    return e('div', {className: 'app'}, w);
  }

  selectFile(file) {
    this.setState(({current}) => {
      return {current: [...current, file.path[0]]};
    }, () => this.updateHash());
  }

  back(file) {
    this.setState(({current}) => {
      return {current: current.slice(0, current.length - 1)};
    }, () => this.updateHash());
  }

  updateHash() {
    if (!this.state.current || !this.state.current.length) {
      window.location = '#';
    } else {
      window.location = '#' + this.state.current.join('/');
    }
  }
}

function FilesList({folder, onSelectFile, onBack}) {
  let files = folder.children;
  return e('div', {className: 'display-folder'},
    e(FileHeader, {file: folder, onBack}),
    e('table', {className: 'files-list'},
      e('thead', {className: 'files-list__head'},
        e('tr', null,
          e('th', null, "Path"),
          e('th', null, "Coverage")
        )
      ),
      e('tbody', {className: 'files-list__body'},
        files.map(file => e(File, {file, onClick: onSelectFile}))
      )
    )
  );
}

function File({file, onClick}) {
  const coverage = file.coverable ? file.covered / file.coverable * 100 : -1;

  return e('tr', {
      className: 'files-list__file'
        + (coverage >= 0 && coverage < 50 ? ' files-list__file_low': '')
        + (coverage >= 50 && coverage < 80 ? ' files-list__file_medium': '')
        + (coverage >= 80 ? ' files-list__file_high': '')
        + (file.is_folder ? ' files-list__file_folder': ''),
      onClick: () => onClick(file),
    },
    e('td', null, pathToString(file.path)),
    e('td', null,
      file.covered + ' / ' + file.coverable +
      (coverage >= 0 ? ' (' + coverage.toFixed(2) + '%)' : '')
    )
  );
}

function DisplayFile({file, onBack}) {
  return e('div', {className: 'display-file'},
    e(FileHeader, {file, onBack}),
    e(FileContent, {file})
  );
}

function FileHeader({file, onBack}) {
  return e('div', {className: 'file-header'},
    onBack ? e('a', {className: 'file-header__back', onClick: onBack}, 'Back') : null,
    e('div', {className: 'file-header__name'}, pathToString([...file.parent, ...file.path])),
    e('div', {className: 'file-header__stat'},
      'Covered: ' + file.covered + ' of ' + file.coverable +
      (file.coverable ? ' (' + (file.covered / file.coverable * 100).toFixed(2) + '%)' : '')
    )
  );
}

function FileContent({file}) {
  return e('div', {className: 'file-content'},
    file.content.split(/\r?\n/).map((line, index) => {
      const trace = file.traces.find(trace => trace.line === index + 1);
      const covered = trace && trace.stats.Line;
      const uncovered = trace && !trace.stats.Line;
      return e('pre', {
          className: 'code-line'
            + (covered ? ' code-line_covered' : '')
            + (uncovered ? ' code-line_uncovered' : ''),
          title: trace ? JSON.stringify(trace.stats, null, 2) : null,
        }, line);
    })
  );
}

(function(){
  const commonPath = findCommonPath(data.files);
  const files = data.files.map(file => ({...file, path: file.path.slice(commonPath.length), parent: commonPath}));
  const children = findFolders(files);

  const root = {
    is_folder: true,
    children,
    path: commonPath,
    parent: [],
    covered: children.reduce((sum, file) => sum + file.covered, 0),
    coverable: children.reduce((sum, file) => sum + file.coverable, 0),
  };

  ReactDOM.render(e(App, {root}), document.getElementById('root'));
}());
</script>
</body>
</html>