Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- use lazy_static::lazy_static;
- use regex::Regex;
- use scraper::Html;
- use serde::{Deserialize, Serialize};
- use std::collections::HashSet;
- use crate::units::{is_commons, is_rights, is_units, is_warrants};
- // Logic for facts table
- #[derive(Debug, PartialEq, Deserialize, Serialize)]
- pub struct ManagementMember {
- #[doc = "The name of the SPAC member, for example \"John Doe\"."]
- pub name: String,
- #[doc = "Member age, for example 59."]
- pub age: u8,
- #[doc = "Member position, for example \"Chairman, Chief Executive Officer and Director\"."]
- #[serde(skip_serializing_if = "Option::is_none")]
- pub position: Option<String>,
- }
- #[derive(Debug, PartialEq, Deserialize, Serialize)]
- pub struct Underwriter {
- #[doc = "Name of the underwriting bank, for example \"Deutsche Bank Securities Inc.\"."]
- pub name: String,
- #[doc = "Amount of units belonging to that bank."]
- #[serde(skip_serializing_if = "Option::is_none")]
- pub units: Option<u64>,
- }
- #[derive(Debug, PartialEq, Deserialize, Serialize)]
- pub struct Tickers {
- #[serde(skip_serializing_if = "Option::is_none")]
- pub common: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub units: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub warrants: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub rights: Option<String>,
- }
- #[derive(Debug, PartialEq, Deserialize, Serialize)]
- pub enum FinancialType {
- #[serde(rename = "ipo")]
- IPO,
- #[serde(rename = "expense")]
- Expense,
- }
- #[derive(Debug, PartialEq, Deserialize, Serialize)]
- pub struct Financial {
- #[doc = "Type of the financial, either ipo or expenses."]
- #[serde(rename = "type")]
- pub value_type: FinancialType,
- #[doc = "Currency symbol."]
- pub currency: String,
- #[doc = "Unit price value."]
- pub per_unit_value: f32,
- #[doc = "Total value = unit price × amount."]
- pub total_value: u64,
- }
- #[derive(Debug, PartialEq, Deserialize, Serialize)]
- pub struct S1Info {
- pub tickers: Tickers,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub dilution: Option<String>,
- pub financial: Vec<Financial>,
- pub management: Vec<ManagementMember>,
- pub underwriters: Vec<Underwriter>,
- }
- pub struct S1Parser {
- pub document: Html,
- }
- lazy_static! {
- static ref TICKERS: Regex = Regex::new(r#"[“"](([A-Z]+[. -]?){3,7})[,”"]"#).unwrap();
- static ref TICKER_SYMBOLS: Regex = Regex::new(r#"(under\s+the\s+symbol[s]?)\s*(.*)"#).unwrap();
- static ref IPO: Regex = Regex::new(r"offering|Public").unwrap();
- static ref EXPENSE: Regex = Regex::new(r"(C|c)ommissions").unwrap();
- static ref PRICE: Regex = Regex::new(r"(\$)\s?(\d{1,9})").unwrap();
- static ref PRICE_DOUBLE: Regex = Regex::new(r"(\$)\s?(\d{1,3}\.\d{1,2})").unwrap();
- static ref DOUBLE_DOT: Regex = Regex::new(r"\.{2,}$").unwrap();
- static ref DILUTION: Regex =
- Regex::new(r".*(of approximately)[\s.]([0-9]{1,3}([,|.][0-9]{1,10})?%)").unwrap();
- static ref BANNED_MANAGEMENT_NAMES: Regex =
- Regex::new(r"individual|name|officer|director|founder").unwrap();
- static ref BANNED_UNDERWRITER_NAMES: Regex = Regex::new(
- r"offering|book value|shares|proceeds|expenses|commissions|fees|trust account|denominator|(U|u)nderwriter|miscellaneous|total|\[●\]|\[•\]|\u{200b}"
- ).unwrap();
- }
- /// Helper method to normalize spaces, remove soft hyphens and replace excessive whitespaces
- /// with a single whitespace.
- fn clear_content(content: &str) -> String {
- content.replace("\u{00ad}", " ").replace("\u{200b}", " ")
- }
- /// Helper method to extract ticker names in quotes, such as “ACEV.U,” or “ACEV”
- fn extract_tickers(text: &str) -> HashSet<&str> {
- TICKERS
- .captures_iter(text)
- .map(|mat| mat.get(1).unwrap().as_str())
- .collect()
- }
- impl S1Parser {
- /// Create a new instance and load S-1 filling HTML into memory.
- pub fn new(data: &str) -> Self {
- S1Parser {
- document: Html::parse_document(clear_content(data).as_str()),
- }
- }
- /// Get the SPAC market capitalization and expenses.
- pub fn get_market_cap(&self) -> Vec<Financial> {
- let mut results: Vec<Financial> = Vec::new();
- let table_selector = scraper::Selector::parse("table").unwrap();
- let mut tables = self.document.select(&table_selector).filter(|item| {
- let text = item.inner_html();
- text.contains("Public offering")
- || text.contains("Price to Public")
- || text.contains("Price to public")
- });
- if let Some(parent) = tables.next() {
- let tr_selector = scraper::Selector::parse("tr").unwrap();
- let tr = parent.select(&tr_selector).into_iter();
- // Select only 1-3 columns
- for (index, cell) in tr.enumerate() {
- let mut value_type: Option<FinancialType> = None;
- let mut currency: Option<String> = None;
- let mut per_unit_value: f32 = 0.0;
- let mut total_value: u64 = 0;
- if index > 0 && index < 4 {
- let td_selector = scraper::Selector::parse("td").unwrap();
- let tds = cell
- .select(&td_selector)
- .map(|item| {
- item.text()
- .collect::<String>()
- .trim()
- .replace(r",", "")
- .split_whitespace()
- .collect::<Vec<_>>()
- .join(" ")
- })
- .filter(|item| !item.is_empty() && item.is_ascii())
- .into_iter();
- for (i, td) in tds.enumerate() {
- let content = td.as_str();
- // Header
- if i == 0 {
- if IPO.is_match(content) {
- value_type = Some(FinancialType::IPO);
- }
- if EXPENSE.is_match(content) {
- value_type = Some(FinancialType::Expense);
- }
- continue;
- }
- // Currency sign (per unit)
- if i == 1 {
- let captures = PRICE_DOUBLE.captures(content);
- // Check if value is merged with currency sign
- if let Some(matches) = captures {
- per_unit_value = matches
- .get(2)
- .unwrap()
- .as_str()
- .parse::<f32>()
- .unwrap_or(0.0);
- }
- continue;
- }
- // Per single unit
- if i == 2 && per_unit_value == 0.0 {
- per_unit_value = content.parse::<f32>().unwrap_or(0.0);
- continue;
- } else {
- let captures = PRICE.captures(content);
- // Check if value is merged with currency sign
- if let Some(matches) = captures {
- currency = Some(matches.get(1).unwrap().as_str().to_string());
- let value = matches.get(2).unwrap().as_str();
- total_value = value.parse::<u64>().unwrap_or(0);
- continue;
- }
- }
- // Currency sign (total)
- if i == 3 && currency.is_none() {
- currency = Some(content.to_string());
- continue;
- }
- // Total value
- if i == 4 && total_value == 0 {
- total_value = content.parse::<u64>().unwrap_or(0);
- continue;
- }
- }
- }
- if !value_type.is_none() && total_value > 10 && per_unit_value > 0.0 {
- results.insert(
- 0,
- Financial {
- value_type: value_type.unwrap(),
- currency: currency.unwrap_or("$".to_owned()),
- per_unit_value,
- total_value,
- },
- );
- }
- }
- }
- results
- }
- /// Parse the dilution to public shareholders percentage.
- pub fn get_dilution(&self) -> Option<String> {
- let content = self.document.root_element().text().collect::<String>();
- let captures = DILUTION.captures(content.as_str());
- // We have a match
- if let Some(matches) = captures {
- return Some(matches.get(2).unwrap().as_str().to_string());
- }
- None
- }
- /// Parse the management team table, return a vector of members (age, name and position).
- pub fn get_management_members(&self) -> Vec<ManagementMember> {
- let mut results: Vec<ManagementMember> = Vec::new();
- let table_selector = scraper::Selector::parse("table").unwrap();
- // Get header row
- let mut tables = self
- .document
- .select(&table_selector)
- .filter(|item| item.inner_html().contains("Age") && item.inner_html().contains("Name"));
- if let Some(parent) = tables.next() {
- let tr_selector = scraper::Selector::parse("tr").unwrap();
- let tr = parent.select(&tr_selector).into_iter();
- for (index, cell) in tr.enumerate() {
- let mut name: Option<String> = None;
- let mut age: u8 = 0;
- let mut position: Option<String> = None;
- // Skip the very first, header row
- if index != 0 {
- let td_selector = scraper::Selector::parse("td").unwrap();
- let tds = cell
- .select(&td_selector)
- .map(|item| {
- item.text()
- .collect::<String>()
- .trim()
- .split_whitespace()
- .collect::<Vec<_>>()
- .join(" ")
- })
- .filter(|item| item.is_ascii() && !item.is_empty())
- .into_iter();
- // Iterate through each cell
- for (i, td) in tds.enumerate() {
- let content = td.as_str();
- // Check the cell number
- match i {
- // Cell contains name value
- 0 => {
- let content = DOUBLE_DOT.replace_all(&content, "");
- if !BANNED_MANAGEMENT_NAMES.is_match(content.trim()) {
- name = Some(content.to_string());
- } else {
- name = None;
- }
- }
- // Cell contains age
- 1 => {
- age = content.parse::<u8>().unwrap_or(0);
- }
- // Cell contains position
- 2 => {
- position = Some(content.to_string());
- }
- // Skip all other cells
- _ => (),
- }
- }
- }
- if !name.is_none() && age > 0 {
- results.insert(
- 0,
- ManagementMember {
- name: name.unwrap(),
- age,
- position,
- },
- );
- }
- }
- }
- results
- }
- /// Parse the table of underwriters from S1 filling and return an array.
- pub fn get_underwriters(&self) -> Vec<Underwriter> {
- let mut results: Vec<Underwriter> = Vec::new();
- let table_selector = scraper::Selector::parse("table").unwrap();
- // Get header row
- let mut tables = self
- .document
- .select(&table_selector)
- .filter(|item| item.inner_html().contains("Underwriter"));
- if let Some(parent) = tables.next() {
- let tr_selector = scraper::Selector::parse("tr").unwrap();
- let tr = parent.select(&tr_selector).into_iter();
- for (index, cell) in tr.enumerate() {
- let mut name: Option<String> = None;
- let mut units: Option<u64> = None;
- // Skip the very first, header row
- if index != 0 {
- let td_selector = scraper::Selector::parse("td").unwrap();
- let tds = cell
- .select(&td_selector)
- .map(|item| {
- // Remove excessive whitespaces
- item.text()
- .collect::<String>()
- .trim()
- .split_whitespace()
- .collect::<Vec<_>>()
- .join(" ")
- })
- .filter(|item| !item.is_empty())
- .into_iter();
- // Iterate through each cell
- for (i, td) in tds.enumerate() {
- let content = td.as_str();
- // Cell contains name value
- if i == 0 {
- // An array of banned words, exclude them from end results
- if !BANNED_UNDERWRITER_NAMES.is_match(content.trim()) {
- name = Some(content.to_string());
- } else {
- name = None;
- }
- }
- // Cell contains age
- if i == 1 {
- let parsed_units = content.parse::<u64>().unwrap_or(0);
- if parsed_units == 0 {
- units = None;
- } else {
- units = Some(parsed_units);
- }
- }
- }
- }
- if !name.is_none() && !(name == Some("Total".to_string()) && units.is_none()) {
- results.insert(
- 0,
- Underwriter {
- name: name.unwrap(),
- units,
- },
- );
- }
- }
- }
- results
- }
- /// Extract common stock, unit, warrant and right tickers from S-1 filling.
- pub fn get_ticker_symbols(&self) -> Tickers {
- let mut units: Option<String> = None;
- let mut warrants: Option<String> = None;
- let mut rights: Option<String> = None;
- let mut common: Option<String> = None;
- let content = self.document.root_element().text().collect::<String>();
- // Find all the paragraphs "under the symbol(s)"
- for m in TICKER_SYMBOLS.captures_iter(&content) {
- let tickers = extract_tickers(m.get(0).unwrap().as_str());
- for t in tickers {
- // Replace warrant symbol "WS" with just "W" at the end. This is an exception.
- // Only happens in rare cases.
- // Also trim ticker and remove spaces/dots.
- let ticker = t
- .trim()
- .replace(r" WS", "W")
- .replace(".", "")
- .replace(" ", "");
- if is_units(&ticker) && units.is_none() {
- units = Some(ticker);
- continue;
- }
- if is_warrants(&ticker) && warrants.is_none() {
- warrants = Some(ticker);
- continue;
- }
- if is_rights(&ticker) && rights.is_none() {
- rights = Some(ticker);
- continue;
- }
- if is_commons(&ticker) && common.is_none() && &ticker != "SEC" {
- common = Some(ticker);
- continue;
- }
- }
- }
- return Tickers {
- units,
- warrants,
- rights,
- common,
- };
- }
- /// Get all scraped data (dilution, market cap, underwriters, management, tickers).
- pub fn get_all(&self) -> S1Info {
- return S1Info {
- dilution: self.get_dilution(),
- tickers: self.get_ticker_symbols(),
- financial: self.get_market_cap(),
- management: self.get_management_members(),
- underwriters: self.get_underwriters(),
- };
- }
- }
- #[cfg(test)]
- mod tests {
- // Note this useful idiom: importing names from outer (for mod tests) scope.
- use super::*;
- use scraper::Selector;
- use std::fs;
- struct Setup {
- parser_pacx: S1Parser,
- parser_dkdca: S1Parser,
- parser_eggf: S1Parser,
- parser_bpac: S1Parser,
- parser_aftr: S1Parser,
- }
- impl Setup {
- fn new() -> Self {
- let pacx = fs::read_to_string("fixtures/pacx.htm").unwrap();
- let dkdca = fs::read_to_string("fixtures/dkdca.htm").unwrap();
- let eggf = fs::read_to_string("fixtures/eggf.htm").unwrap();
- let bpac = fs::read_to_string("fixtures/bpac.htm").unwrap();
- let aftr = fs::read_to_string("fixtures/aftr.htm").unwrap();
- let parser_pacx = S1Parser::new(&pacx);
- let parser_dkdca = S1Parser::new(&dkdca);
- let parser_eggf = S1Parser::new(&eggf);
- let parser_bpac = S1Parser::new(&bpac);
- let parser_aftr = S1Parser::new(&aftr);
- Self {
- parser_pacx,
- parser_dkdca,
- parser_eggf,
- parser_bpac,
- parser_aftr,
- }
- }
- }
- #[test]
- fn ut_s1parser_clear_content() {
- let bad_content = "Test\u{00ad}\u{00ad}\u{00ad}\u{00ad}\u{200b} here is some text";
- let actual = clear_content(bad_content);
- assert_eq!(actual, "Test here is some text")
- }
- #[test]
- fn ut_s1parser_new() {
- // Must instantiate S1 Parser
- let html = r#"
- <ul>
- <li>Foo</li>
- <li>Bar</li>
- <li>Baz</li>
- </ul>
- "#;
- let fragment = S1Parser::new(html);
- let selector = Selector::parse("ul > li:nth-child(2)").unwrap();
- let li = fragment.document.select(&selector).next().unwrap();
- assert_eq!("<li>Bar</li>", li.html());
- }
- #[test]
- fn ut_s1parser_get_market_cap() {
- // Try to get market cap
- let setup = Setup::new();
- let actual_pacx = setup.parser_pacx.get_market_cap();
- let actual_dkdca = setup.parser_dkdca.get_market_cap();
- let actual_eggf = setup.parser_eggf.get_market_cap();
- let actual_bpac = setup.parser_bpac.get_market_cap();
- let actual_aftr = setup.parser_aftr.get_market_cap();
- assert_eq!(
- actual_pacx,
- vec![
- Financial {
- value_type: FinancialType::Expense,
- currency: "$".to_owned(),
- per_unit_value: 0.55,
- total_value: 19250000,
- },
- Financial {
- value_type: FinancialType::IPO,
- currency: "$".to_owned(),
- per_unit_value: 10.00,
- total_value: 350000000,
- }
- ]
- );
- assert_eq!(
- actual_dkdca,
- vec![
- Financial {
- value_type: FinancialType::Expense,
- currency: "$".to_owned(),
- per_unit_value: 0.55,
- total_value: 5500000,
- },
- Financial {
- value_type: FinancialType::IPO,
- currency: "$".to_owned(),
- per_unit_value: 10.00,
- total_value: 100000000,
- }
- ]
- );
- assert_eq!(
- actual_eggf,
- vec![
- Financial {
- value_type: FinancialType::Expense,
- currency: "$".to_owned(),
- per_unit_value: 0.55,
- total_value: 13750000,
- },
- Financial {
- value_type: FinancialType::IPO,
- currency: "$".to_owned(),
- per_unit_value: 10.00,
- total_value: 250000000,
- }
- ]
- );
- assert_eq!(
- actual_bpac,
- vec![
- Financial {
- value_type: FinancialType::Expense,
- currency: "$".to_owned(),
- per_unit_value: 0.55,
- total_value: 11000000,
- },
- Financial {
- value_type: FinancialType::IPO,
- currency: "$".to_owned(),
- per_unit_value: 10.00,
- total_value: 200000000,
- }
- ]
- );
- assert_eq!(
- actual_aftr,
- vec![
- Financial {
- value_type: FinancialType::Expense,
- currency: "$".to_owned(),
- per_unit_value: 0.55,
- total_value: 16500000,
- },
- Financial {
- value_type: FinancialType::IPO,
- currency: "$".to_owned(),
- per_unit_value: 10.00,
- total_value: 300000000,
- }
- ]
- );
- }
- #[test]
- fn ut_s1parser_get_dilution() {
- let setup = Setup::new();
- let actual_pacx = setup.parser_pacx.get_dilution();
- let actual_dkdca = setup.parser_dkdca.get_dilution();
- let actual_eggf = setup.parser_eggf.get_dilution();
- let actual_bpac = setup.parser_bpac.get_dilution();
- let actual_aftr = setup.parser_aftr.get_dilution();
- assert_eq!(actual_pacx, Some(String::from("95.2%")));
- assert_eq!(actual_dkdca, Some(String::from("88.4%")));
- assert_eq!(actual_eggf, Some(String::from("93.4%")));
- assert_eq!(actual_bpac, Some(String::from("135.5%")));
- assert_eq!(actual_aftr, None);
- }
- #[test]
- fn ut_s1parser_get_dilution_none() {
- let html = r#"
- <div style="color: #000000; font-family: 'Times New Roman', Times, serif; font-size: 10pt; text-align: justify;">
- Our letter agreement with our initial shareholders, officers and directors contain provisions relating to transfer restrictions of our founder shares and private placement warrants, indemnification of the trust account, waiver of redemption rights and participation in liquidating distributions from the trust account. The letter agreement and the registration rights agreement may be amended, and provisions therein may be waived, without shareholder approval (although releasing the parties from the restriction contained in the letter agreement
- </div>
- "#;
- let parser = S1Parser::new(html);
- let dilution = parser.get_dilution();
- assert_eq!(dilution, None);
- }
- #[test]
- fn ut_s1parser_get_management_members() {
- let setup = Setup::new();
- let actual_pacx = setup.parser_pacx.get_management_members();
- let actual_dkdca = setup.parser_dkdca.get_management_members();
- let actual_eggf = setup.parser_eggf.get_management_members();
- let actual_bpac = setup.parser_bpac.get_management_members();
- let actual_aftr = setup.parser_aftr.get_management_members();
- assert_eq!(
- actual_pacx,
- vec![
- ManagementMember {
- name: "Todd Davis".to_owned(),
- age: 52,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Mitchell Caplan".to_owned(),
- age: 63,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Matthew Corey".to_owned(),
- age: 36,
- position: Some("Chief Financial Officer".to_owned())
- },
- ManagementMember {
- name: "Scott Carpenter".to_owned(),
- age: 49,
- position: Some("Chief Operating Officer".to_owned())
- },
- ManagementMember {
- name: "Ryan Khoury".to_owned(),
- age: 37,
- position: Some("Chief Executive Officer".to_owned())
- },
- ManagementMember {
- name: "Oscar Salazar".to_owned(),
- age: 43,
- position: Some("Co-President and Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Rick Gerson".to_owned(),
- age: 45,
- position: Some("Co-President".to_owned())
- },
- ManagementMember {
- name: "Jonathan Christodoro".to_owned(),
- age: 44,
- position: Some("Chairman".to_owned())
- }
- ]
- );
- assert_eq!(
- actual_dkdca,
- vec![
- ManagementMember {
- name: "Julianne Huh".to_owned(),
- age: 52,
- position: Some("Director nominee".to_owned())
- },
- ManagementMember {
- name: "Syed Musheer Ahmed".to_owned(),
- age: 37,
- position: Some("Director nominee".to_owned())
- },
- ManagementMember {
- name: "Firdauz Edmin Bin Mokhtar".to_owned(),
- age: 48,
- position: Some("Chief Financial Officer and Secretary".to_owned())
- },
- ManagementMember {
- name: "Barry Anderson".to_owned(),
- age: 44,
- position: Some("Chairman, Chief Executive Officer and Director".to_owned())
- }
- ]
- );
- assert_eq!(
- actual_eggf,
- vec![
- ManagementMember {
- name: "Noorsurainah (Su) Tengah".to_owned(),
- age: 38,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Jonathan Silver".to_owned(),
- age: 63,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Linda Hall Daschle".to_owned(),
- age: 65,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Louise Curbishley".to_owned(),
- age: 47,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Sophia Park Mullen".to_owned(),
- age: 42,
- position: Some("President and Director".to_owned())
- },
- ManagementMember {
- name: "Gary Fegel".to_owned(),
- age: 47,
- position: Some("Chairman".to_owned())
- },
- ManagementMember {
- name: "Gregg S. Hymowitz".to_owned(),
- age: 55,
- position: Some("Chief Executive Officer and Director".to_owned())
- }
- ]
- );
- assert_eq!(
- actual_bpac,
- vec![
- ManagementMember {
- name: "Les Ottolenghi".to_owned(),
- age: 59,
- position: Some("Director".to_owned())
- },
- ManagementMember {
- name: "Brett Calapp".to_owned(),
- age: 46,
- position: Some("Director".to_owned())
- },
- ManagementMember {
- name: "Melissa Blau".to_owned(),
- age: 52,
- position: Some("Director".to_owned())
- },
- ManagementMember {
- name: "Duncan Davidson".to_owned(),
- age: 68,
- position: Some("Executive Vice President".to_owned())
- },
- ManagementMember {
- name: "Eric Wiesen".to_owned(),
- age: 46,
- position: Some("President".to_owned())
- },
- ManagementMember {
- name: "David VanEgmond".to_owned(),
- age: 32,
- position: Some("Chief Executive Officer".to_owned())
- },
- ManagementMember {
- name: "Paul Martino".to_owned(),
- age: 46,
- position: Some("Executive Chairman".to_owned())
- }
- ]
- );
- assert_eq!(
- actual_aftr,
- vec![
- ManagementMember {
- name: "Bharat Sundaram".to_owned(),
- age: 43,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Bill Miller".to_owned(),
- age: 54,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Christopher H. Hunter".to_owned(),
- age: 52,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Dr. Julie Gerberding".to_owned(),
- age: 65,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "A.G. Breitenstein".to_owned(),
- age: 52,
- position: Some("Director Nominee".to_owned())
- },
- ManagementMember {
- name: "Nehal Raj".to_owned(),
- age: 42,
- position: Some("Director".to_owned())
- },
- ManagementMember {
- name: "Jeffrey Rhodes".to_owned(),
- age: 46,
- position: Some("Director".to_owned())
- },
- ManagementMember {
- name: "Martin Davidson".to_owned(),
- age: 45,
- position: Some("Chief Financial Officer".to_owned())
- },
- ManagementMember {
- name: "Anthony Colaluca".to_owned(),
- age: 54,
- position: Some("President and Director".to_owned())
- },
- ManagementMember {
- name: "R. Halsey Wise".to_owned(),
- age: 56,
- position: Some("Chief Executive Officer and Chairman".to_owned())
- }
- ]
- )
- }
- #[test]
- fn ut_s1parser_get_underwriters() {
- let setup = Setup::new();
- let actual_pacx = setup.parser_pacx.get_underwriters();
- let actual_dkdca = setup.parser_dkdca.get_underwriters();
- let actual_eggf = setup.parser_eggf.get_underwriters();
- let actual_bpac = setup.parser_bpac.get_underwriters();
- let actual_aftr = setup.parser_aftr.get_underwriters();
- assert_eq!(
- actual_pacx,
- vec![Underwriter {
- name: "Citigroup Global Markets Inc.".to_owned(),
- units: None
- }]
- );
- assert_eq!(
- actual_dkdca,
- vec![Underwriter {
- name: "Kingswood Capital Markets, division of Benchmark Investments, Inc."
- .to_owned(),
- units: None
- }]
- );
- assert_eq!(
- actual_eggf,
- vec![Underwriter {
- name: "BTIG, LLC".to_owned(),
- units: None
- }]
- );
- assert_eq!(
- actual_bpac,
- vec![Underwriter {
- name: "Citigroup Global Markets Inc.".to_owned(),
- units: None
- }]
- );
- assert_eq!(
- actual_aftr,
- vec![
- Underwriter {
- name: "BofA Securities, Inc.".to_owned(),
- units: None
- },
- Underwriter {
- name: "Deutsche Bank Securities Inc.".to_owned(),
- units: None
- },
- Underwriter {
- name: "Goldman Sachs & Co. LLC.".to_owned(),
- units: None
- }
- ]
- );
- }
- #[test]
- fn ut_extract_tickers() {
- let text = r"A ordinary shares and warrants on Nasdaq under the symbols “ACEV.U,” “ACEV” and “ACEV WS,” respectively.";
- let tickers = extract_tickers(text);
- assert!(
- tickers.contains("ACEV") && tickers.contains("ACEV.U") && tickers.contains("ACEV WS")
- );
- assert_eq!(tickers.len(), 3);
- }
- #[test]
- fn ut_s1parser_get_ticker_symbols() {
- let setup = Setup::new();
- let actual_pacx = setup.parser_pacx.get_ticker_symbols();
- let actual_dkdca = setup.parser_dkdca.get_ticker_symbols();
- let actual_eggf = setup.parser_eggf.get_ticker_symbols();
- let actual_bpac = setup.parser_bpac.get_ticker_symbols();
- let actual_aftr = setup.parser_aftr.get_ticker_symbols();
- assert_eq!(
- actual_pacx,
- Tickers {
- common: Some("PACX".to_owned()),
- units: Some("PACXU".to_owned()),
- warrants: Some("PACXW".to_owned()),
- rights: None
- }
- );
- assert_eq!(
- actual_dkdca,
- Tickers {
- common: Some("DKDCA".to_owned()),
- units: Some("DKDCU".to_owned()),
- warrants: Some("DKDCW".to_owned()),
- rights: Some("DKDCR".to_owned())
- }
- );
- assert_eq!(
- actual_eggf,
- Tickers {
- common: Some("EGGF".to_owned()),
- units: Some("EGGFU".to_owned()),
- warrants: Some("EGGFW".to_owned()),
- rights: None
- }
- );
- assert_eq!(
- actual_bpac,
- Tickers {
- common: Some("BPAC".to_owned()),
- units: Some("BPACU".to_owned()),
- warrants: Some("BPACW".to_owned()),
- rights: None
- }
- );
- assert_eq!(
- actual_aftr,
- Tickers {
- common: Some("AFTR".to_owned()),
- units: Some("AFTRU".to_owned()),
- warrants: Some("AFTRW".to_owned()),
- rights: None
- }
- );
- }
- #[test]
- fn ut_s1parser_get_all() {
- let setup = Setup::new();
- let actual_pacx = setup.parser_pacx.get_all();
- let actual_dkdca = setup.parser_dkdca.get_all();
- let actual_eggf = setup.parser_eggf.get_all();
- let actual_bpac = setup.parser_bpac.get_all();
- let actual_aftr = setup.parser_aftr.get_all();
- // Serialize it to a JSON string.
- let pacx_all_json = serde_json::to_string(&actual_pacx).unwrap();
- let dkdca_all_json = serde_json::to_string(&actual_dkdca).unwrap();
- let eggf_all_json = serde_json::to_string(&actual_eggf).unwrap();
- let bpac_all_json = serde_json::to_string(&actual_bpac).unwrap();
- let aftr_all_json = serde_json::to_string(&actual_aftr).unwrap();
- assert_eq!(pacx_all_json, "{\"tickers\":{\"common\":\"PACX\",\"units\":\"PACXU\",\"warrants\":\"PACXW\"},\"dilution\":\"95.2%\",\"financial\":[{\"type\":\"expense\",\"currency\":\"$\",\"per_unit_value\":0.55,\"total_value\":19250000},{\"type\":\"ipo\",\"currency\":\"$\",\"per_unit_value\":10.0,\"total_value\":350000000}],\"management\":[{\"name\":\"Todd Davis\",\"age\":52,\"position\":\"Director Nominee\"},{\"name\":\"Mitchell Caplan\",\"age\":63,\"position\":\"Director Nominee\"},{\"name\":\"Matthew Corey\",\"age\":36,\"position\":\"Chief Financial Officer\"},{\"name\":\"Scott Carpenter\",\"age\":49,\"position\":\"Chief Operating Officer\"},{\"name\":\"Ryan Khoury\",\"age\":37,\"position\":\"Chief Executive Officer\"},{\"name\":\"Oscar Salazar\",\"age\":43,\"position\":\"Co-President and Director Nominee\"},{\"name\":\"Rick Gerson\",\"age\":45,\"position\":\"Co-President\"},{\"name\":\"Jonathan Christodoro\",\"age\":44,\"position\":\"Chairman\"}],\"underwriters\":[{\"name\":\"Citigroup Global Markets Inc.\"}]}");
- assert_eq!(dkdca_all_json, "{\"tickers\":{\"common\":\"DKDCA\",\"units\":\"DKDCU\",\"warrants\":\"DKDCW\",\"rights\":\"DKDCR\"},\"dilution\":\"88.4%\",\"financial\":[{\"type\":\"expense\",\"currency\":\"$\",\"per_unit_value\":0.55,\"total_value\":5500000},{\"type\":\"ipo\",\"currency\":\"$\",\"per_unit_value\":10.0,\"total_value\":100000000}],\"management\":[{\"name\":\"Julianne Huh\",\"age\":52,\"position\":\"Director nominee\"},{\"name\":\"Syed Musheer Ahmed\",\"age\":37,\"position\":\"Director nominee\"},{\"name\":\"Firdauz Edmin Bin Mokhtar\",\"age\":48,\"position\":\"Chief Financial Officer and Secretary\"},{\"name\":\"Barry Anderson\",\"age\":44,\"position\":\"Chairman, Chief Executive Officer and Director\"}],\"underwriters\":[{\"name\":\"Kingswood Capital Markets, division of Benchmark Investments, Inc.\"}]}");
- assert_eq!(eggf_all_json, "{\"tickers\":{\"common\":\"EGGF\",\"units\":\"EGGFU\",\"warrants\":\"EGGFW\"},\"dilution\":\"93.4%\",\"financial\":[{\"type\":\"expense\",\"currency\":\"$\",\"per_unit_value\":0.55,\"total_value\":13750000},{\"type\":\"ipo\",\"currency\":\"$\",\"per_unit_value\":10.0,\"total_value\":250000000}],\"management\":[{\"name\":\"Noorsurainah (Su) Tengah\",\"age\":38,\"position\":\"Director Nominee\"},{\"name\":\"Jonathan Silver\",\"age\":63,\"position\":\"Director Nominee\"},{\"name\":\"Linda Hall Daschle\",\"age\":65,\"position\":\"Director Nominee\"},{\"name\":\"Louise Curbishley\",\"age\":47,\"position\":\"Director Nominee\"},{\"name\":\"Sophia Park Mullen\",\"age\":42,\"position\":\"President and Director\"},{\"name\":\"Gary Fegel\",\"age\":47,\"position\":\"Chairman\"},{\"name\":\"Gregg S. Hymowitz\",\"age\":55,\"position\":\"Chief Executive Officer and Director\"}],\"underwriters\":[{\"name\":\"BTIG, LLC\"}]}");
- assert_eq!(bpac_all_json, "{\"tickers\":{\"common\":\"BPAC\",\"units\":\"BPACU\",\"warrants\":\"BPACW\"},\"dilution\":\"135.5%\",\"financial\":[{\"type\":\"expense\",\"currency\":\"$\",\"per_unit_value\":0.55,\"total_value\":11000000},{\"type\":\"ipo\",\"currency\":\"$\",\"per_unit_value\":10.0,\"total_value\":200000000}],\"management\":[{\"name\":\"Les Ottolenghi\",\"age\":59,\"position\":\"Director\"},{\"name\":\"Brett Calapp\",\"age\":46,\"position\":\"Director\"},{\"name\":\"Melissa Blau\",\"age\":52,\"position\":\"Director\"},{\"name\":\"Duncan Davidson\",\"age\":68,\"position\":\"Executive Vice President\"},{\"name\":\"Eric Wiesen\",\"age\":46,\"position\":\"President\"},{\"name\":\"David VanEgmond\",\"age\":32,\"position\":\"Chief Executive Officer\"},{\"name\":\"Paul Martino\",\"age\":46,\"position\":\"Executive Chairman\"}],\"underwriters\":[{\"name\":\"Citigroup Global Markets Inc.\"}]}");
- assert_eq!(aftr_all_json, "{\"tickers\":{\"common\":\"AFTR\",\"units\":\"AFTRU\",\"warrants\":\"AFTRW\"},\"financial\":[{\"type\":\"expense\",\"currency\":\"$\",\"per_unit_value\":0.55,\"total_value\":16500000},{\"type\":\"ipo\",\"currency\":\"$\",\"per_unit_value\":10.0,\"total_value\":300000000}],\"management\":[{\"name\":\"Bharat Sundaram\",\"age\":43,\"position\":\"Director Nominee\"},{\"name\":\"Bill Miller\",\"age\":54,\"position\":\"Director Nominee\"},{\"name\":\"Christopher H. Hunter\",\"age\":52,\"position\":\"Director Nominee\"},{\"name\":\"Dr. Julie Gerberding\",\"age\":65,\"position\":\"Director Nominee\"},{\"name\":\"A.G. Breitenstein\",\"age\":52,\"position\":\"Director Nominee\"},{\"name\":\"Nehal Raj\",\"age\":42,\"position\":\"Director\"},{\"name\":\"Jeffrey Rhodes\",\"age\":46,\"position\":\"Director\"},{\"name\":\"Martin Davidson\",\"age\":45,\"position\":\"Chief Financial Officer\"},{\"name\":\"Anthony Colaluca\",\"age\":54,\"position\":\"President and Director\"},{\"name\":\"R. Halsey Wise\",\"age\":56,\"position\":\"Chief Executive Officer and Chairman\"}],\"underwriters\":[{\"name\":\"BofA Securities, Inc.\"},{\"name\":\"Deutsche Bank Securities Inc.\"},{\"name\":\"Goldman Sachs & Co. LLC.\"}]}");
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement