diff --git a/.gitea/workflows/workflow.yaml b/.gitea/workflows/workflow.yaml new file mode 100644 index 0000000..665dfa4 --- /dev/null +++ b/.gitea/workflows/workflow.yaml @@ -0,0 +1,46 @@ +name: Go + +on: + push: + branches: [ main, go_migr ] + pull_request: + branches: [ main, go_migr ] + +jobs: + build: + runs-on: ubuntu-24.04 # You can change this to macos-latest or windows-latest if needed + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: '1.24.6' # You can specify a specific version or 'stable' + + - name: Build + run: | + go build -v -ldflags "\ + -X main.version=${{ github.ref_name }} \ + -X main.commit=${{ github.sha }} \ + -X main.date=$(date +%Y-%m-%dT%H:%M:%S%z)" \ + -o clean_file + + - name: Test + run: go test -v ./... + + - name: Run gofmt (optional) + # Uncomment to check code formatting with gofmt + run: | + if [ -n "$(gofmt -l.)" ]; then + echo "Go code is not formatted. Please run 'gofmt -w.' to fix it." + exit 1 + fi + + - name: Run golint (optional) + # Uncomment to check code style with golint + run: | + if [ -n "$(golint./...)" ]; then + echo "Go code has style issues. Please run 'golint./...' to see them." + exit 1 + fi diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml deleted file mode 100644 index 3ffe9a0..0000000 --- a/.github/workflows/rust.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Rust - -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] - -env: - # Customize the profile here (e.g., release, debug) - PROFILE: release - -jobs: - build: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v3 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.88 - override: true - - - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release # Or --debug, depending on the env.PROFILE - - - name: Run tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --release # Or --debug, depending on the env.PROFILE - diff --git a/.gitignore b/.gitignore index ea8c4bf..a5b7afc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target +/clean_file +numbers.json diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 504eab9..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,96 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "clean_file" -version = "0.4.0" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "2.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index bcfd562..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "clean_file" -description = "Software tool that sanitizes a list of phone numbers" -version = "0.4.0" -edition = "2024" -rust-version = "1.88" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" diff --git a/README.md b/README.md index d133863..94a454e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ CLI software that processes a text file containing US phone numbers and sanitizes the numbers. The result is contained in a json file. + +## Building +``` +go build +``` + + +To include version information +``` +go build -ldflags "-X main.version=3.0.101 -X main.commit=$(git rev-parse HEAD) -X main.date=$(date +%Y-%m-%dT%H:%M:%S%z)" -o clean_file +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..871e015 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.kundeng.us/phoenix/clean_file + +go 1.24.6 diff --git a/main.go b/main.go new file mode 100644 index 0000000..821bac8 --- /dev/null +++ b/main.go @@ -0,0 +1,28 @@ +package main + +import "fmt" +import "os" +import "git.kundeng.us/phoenix/clean_file/parser" + +func main() { + fmt.Println("clean_file") + args := os.Args + + if len(args) < 2 { + fmt.Println("Invalid arguments provided") + os.Exit(-1) + } + + filepath := args[1] + + fmt.Println("File path:", filepath) + prsr := parser.NumberParser{FilePath: filepath} + + rawValues := prsr.FileDump() + parsedValues := prsr.UpdateValues(rawValues) + newUpdated := prsr.RemoveDups(parsedValues) + + prsr.PrintValues(newUpdated, true) + + prsr.SaveFile(newUpdated) +} diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..79edf74 --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,173 @@ +package parser + +import "fmt" +import "os" +import "unicode" +import ( + "bufio" + "log" +) +import "encoding/json" +import "strings" + +const Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +type SomeObject struct { + Value string `json:"value"` +} + +type NumberParser struct { + FilePath string +} + +func (prsr NumberParser) FileDump() []SomeObject { + fmt.Println("Dumping files") + objs := []SomeObject{} + + for _, line := range prsr.readLines(prsr.FilePath) { + if strings.ContainsAny(line, Letters) { + continue + } + + myObj := SomeObject{Value: line} + if myObj.Value != "" { + objs = append(objs, myObj) + } + } + + return objs +} + +func (prsr NumberParser) UpdateValues(vals []SomeObject) []SomeObject { + updated := []SomeObject{} + + for _, val := range vals { + parsed := val.Value + if len(parsed) < 2 { + fmt.Println("Invalid number:", parsed) + continue + } + + pars := prsr.removeSomeData(parsed) + parsed = pars + + fmt.Println(parsed) + + updatedParsed := prsr.addPrefix(parsed) + updated = append(updated, SomeObject{Value: updatedParsed}) + } + + return updated +} + +func (prsr NumberParser) RemoveDups(vals []SomeObject) []SomeObject { + updated := []SomeObject{} + uniqueItems := make(map[string]string) + + for _, val := range vals { + uniqueItems[val.Value] = val.Value + } + + for _, item := range uniqueItems { + updated = append(updated, SomeObject{Value: item}) + } + + return updated +} + +func (prsr NumberParser) PrintValues(vals []SomeObject, printTotal bool) { + fmt.Println("Printing values") + + for _, val := range vals { + fmt.Println("Value:", val.Value) + } + + if printTotal { + fmt.Println("Total Numbers:", len(vals)) + } +} + +func (prsr NumberParser) SaveFile(vals []SomeObject) { + filename := "numbers.json" + allNumbers := []string{} + + for _, val := range vals { + allNumbers = append(allNumbers, val.Value) + } + + // Create json and save it to the filesystem + jsonData, err := json.MarshalIndent(allNumbers, "", " ") + if err != nil { + fmt.Println("Error marshaling JSON:", err) + return + } + + err = os.WriteFile(filename, jsonData, 0644) + if err != nil { + fmt.Println("Error writing file:", err) + return + } +} + +func (prsr NumberParser) readLines(filename string) []string { + // Open the file + file, err := os.Open(filename) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + // Create a scanner to read the file line by line + scanner := bufio.NewScanner(file) + lines := []string{} + + // Read line by line + for scanner.Scan() { + line := scanner.Text() + lines = append(lines, line) + } + + // Check for any errors during scanning + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + return lines +} + +func (prsr NumberParser) removeSomeData(unparsed string) string { + parsed := "" + + for _, ch := range unparsed { + if ch == ' ' { + continue + } else { + if unicode.IsDigit(ch) { + parsed += string(ch) + } + } + } + + fmt.Println("Parsed:", parsed) + + return parsed +} + +func (prsr NumberParser) addPrefix(raw string) string { + parsed := raw + + firstChar := raw[0] + secondChar := raw[1] + + if firstChar == '1' { + parsed = "+" + parsed + } else if firstChar != '+' { + if secondChar != '1' { + parsed = "+1" + parsed + } else { + parsed = "+1" + parsed + } + } + + return parsed +} diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..bdc1eae --- /dev/null +++ b/parser/parser_test.go @@ -0,0 +1,129 @@ +package parser + +import "testing" +import "fmt" + +const Test_File_Path = "../tests/numbers.txt" + +// Add three tests +// File dump +func Test_FileDump(t *testing.T) { + parser := NumberParser{FilePath: Test_File_Path} + rawValues := parser.FileDump() + + testValues := []SomeObject{ + {Value: "(217) 555-0194"}, + {Value: "(602) 555-0177"}, + {Value: "(713) 555-0167"}, + {Value: "(608) 555-0190"}, + {Value: "(305) 555-0112"}, + {Value: "(415) 555-0135"}, + {Value: "(608) 555-0190"}, + {Value: "(602) 555-0177"}, + {Value: "(317) 555-0123"}, + {Value: "(360) 555-0158"}, + } + + if len(rawValues) < 1 { + t.Error("Dump should not be empty") + } + + if !dataEqual(rawValues, testValues) { + t.Error("File dump was not correctly dump") + } +} + +// Parse values +func Test_Parsed(t *testing.T) { + parser := NumberParser{FilePath: Test_File_Path} + rawValues := parser.FileDump() + + if len(rawValues) < 1 { + t.Error("Dump should not be empty") + } + + parsed := parser.UpdateValues(rawValues) + testValues := []SomeObject{ + {Value: "+12175550194"}, + {Value: "+16025550177"}, + {Value: "+17135550167"}, + {Value: "+16085550190"}, + {Value: "+13055550112"}, + {Value: "+14155550135"}, + {Value: "+16085550190"}, + {Value: "+16025550177"}, + {Value: "+13175550123"}, + {Value: "+13605550158"}, + } + + aSize := len(parsed) + bSize := len(testValues) + if aSize != bSize { + t.Error("Data has varrying sizes A", aSize, " B", bSize) + } + if !dataEqual(parsed, testValues) { + t.Error("Data has not be updated") + } +} + +// Remove duplicates +func Test_RemoveDups(t *testing.T) { + parser := NumberParser{FilePath: Test_File_Path} + rawValues := parser.FileDump() + + if len(rawValues) < 1 { + t.Error("Dump should not be empty") + } + + parsed := parser.UpdateValues(rawValues) + finalAmount := parser.RemoveDups(parsed) + testValues := []SomeObject{ + {Value: "+12175550194"}, + {Value: "+16025550177"}, + {Value: "+17135550167"}, + {Value: "+16085550190"}, + {Value: "+13055550112"}, + {Value: "+14155550135"}, + {Value: "+13175550123"}, + {Value: "+13605550158"}, + } + + aSize := len(finalAmount) + bSize := len(testValues) + if aSize != bSize { + t.Error("Data has varrying sizes A", aSize, " B", bSize) + } + if !dataEqual(finalAmount, testValues) { + fmt.Println("Printing final amount") + for _, val := range finalAmount { + fmt.Println("Value:", val.Value) + } + fmt.Println("Printing test values") + for _, val := range testValues { + fmt.Println("Value:", val.Value) + } + + t.Error("Data has not be updated") + } +} + +func dataEqual(a, b []SomeObject) bool { + if len(a) != len(b) { + return false + } + + for i := range a { + foundIt := false + for j := range b { + if a[i] == b[j] { + foundIt = true + } + } + + if !foundIt { + return false + } + } + + return true +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index a3a1f81..0000000 --- a/src/main.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::env; - -mod parser; - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() < 2 { - println!("Provide path to file"); - return; - } - - let filepath: &String = args.get(1).unwrap(); - - println!("Filepath is: {:?}", filepath); - - let myparser = parser::parser::NumberParser { - filepath: filepath.clone(), - }; - - let rawvalues = myparser.filedump(); - - let parsed_values = myparser.updatevalues(&rawvalues); - - let newupdated = myparser.removedups(&parsed_values); - - myparser.print_values(&newupdated, true); - - let result = myparser.save_file(parsed_values); - - match result { - Ok(o) => { - println!("Successfully saved file: {:?}", o); - } - Err(er) => { - println!("Error saving file: {:?}", er); - } - } -} diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index e046ad0..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,212 +0,0 @@ -pub mod parser { - use std::collections::HashSet; - use std::fs::File; - use std::io::prelude::*; - use std::io::{BufRead, BufReader}; - - use serde::Deserialize; - use serde::Serialize; - - #[derive(Clone, Deserialize, Serialize)] - pub struct SomeObject { - pub value: String, - } - - pub struct NumberParser { - pub filepath: String, - } - - impl NumberParser { - pub fn filedump(&self) -> Vec { - println!("\nDumping files"); - let mut objs: Vec = Vec::new(); - - if let Ok(lines) = self.read_lines(&self.filepath) { - for line in lines { - if let Ok(phonenumber) = line { - let contains_letters = phonenumber.chars().any(|c| c.is_alphabetic()); - - if contains_letters { - continue; - } - - let myobj: SomeObject = SomeObject { value: phonenumber }; - - if !myobj.value.is_empty() { - objs.push(myobj); - } - } - } - } - - return objs; - } - - pub fn updatevalues(&self, vals: &Vec) -> Vec { - println!("\nUpdating values"); - let mut updated: Vec = Vec::new(); - - for a in vals { - let mut parsed: String = a.value.clone(); - if a.value.len() < 2 { - println!("{} is an invalid number", a.value); - continue; - } - - let pars = self.remove_some_data(&parsed); - parsed.clear(); - parsed = pars; - - println!("{}", parsed.clone()); - - let updated_parsed = self.add_prefix(parsed); - - let myobj: SomeObject = SomeObject { - value: updated_parsed.clone(), - }; - - updated.push(myobj); - } - - return updated; - } - - pub fn removedups(&self, vals: &Vec) -> Vec { - println!("\nRemoving duplicates"); - let mut tmp: HashSet = HashSet::new(); - let mut mm: Vec = Vec::new(); - mm.clone_from(vals); - mm.retain(|e| tmp.insert(e.value.clone())); - - let mut updated: Vec = Vec::new(); - - for t in tmp { - let so: SomeObject = SomeObject { value: t.clone() }; - - updated.push(so); - } - - return updated; - } - pub fn print_values(&self, vals: &Vec, print_total: bool) { - println!("\nPrinting values"); - - for o in vals { - println!("Value: {}", o.value); - } - - if print_total { - println!("\nTotal numbers: {:?}", vals.len()); - } - } - - pub fn save_file(&self, vals: Vec) -> std::io::Result<()> { - println!("\nSaving file"); - let filename: String = String::from("numbers.json"); - let mut all_numbers: Vec = Vec::new(); - - for val in &vals { - all_numbers.push(val.value.clone()); - } - - let json = serde_json::to_string_pretty(&all_numbers).unwrap(); - let mut file = File::create(filename)?; - file.write_all(json.as_bytes())?; - Ok(()) - } - - fn read_lines(&self, filename: &str) -> std::io::Result>> { - let file = File::open(filename)?; - Ok(BufReader::new(file).lines()) - } - - fn remove_some_data(&self, unparsed: &String) -> String { - println!("Unparsed: {}", unparsed); - - let mut parsed: String = String::new(); - let mychars = unparsed.chars(); - - for i in 0..unparsed.len() { - let mychar_wrap = mychars.clone().nth(i); - - if let None = mychar_wrap { - continue; - } - - let mychar = mychar_wrap.unwrap(); - - if mychar.is_numeric() { - parsed.push(mychar.clone()); - } - } - - println!("Parsed: {}", parsed); - - return parsed; - } - - fn add_prefix(&self, raw: String) -> String { - let mut mychars = raw.chars().clone(); - let first_char: char = mychars.nth(0).clone().unwrap(); - let second_char: char = mychars.nth(1).clone().unwrap(); - - let mut parsed: String = raw.clone(); - - if first_char == '1' { - parsed.insert(0, '+'); - } else if first_char != '+' { - if second_char != '1' { - parsed.insert(0, '+'); - parsed.insert(1, '1'); - } else { - parsed.insert(0, '+'); - parsed.insert(1, '1'); - } - } - - return parsed; - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn removing_text() { - let sometext: String = String::from("a1234567890"); - let numberparser = NumberParser { - filepath: String::new(), - }; - let removetext = numberparser.remove_some_data(&sometext); - let mut result = true; - - for i in 0..removetext.len() { - let c = removetext.chars().nth(i).unwrap(); - - if !c.is_numeric() { - result = false; - break; - } - } - - assert_eq!(result, true); - } - - #[test] - fn adding_prefix() { - let sometext: String = String::from("1234567890"); - let numberparser = NumberParser { - filepath: String::new(), - }; - let modified = numberparser.add_prefix(sometext); - - let chars = modified.chars().clone(); - let firstcharcheck: char = chars.clone().nth(0).unwrap(); - let secondcharcheck: char = chars.clone().nth(1).unwrap(); - - assert_eq!(firstcharcheck, '+'); - assert_eq!(secondcharcheck, '1'); - } - } -} diff --git a/tests/numbers.txt b/tests/numbers.txt new file mode 100644 index 0000000..ea2d2ad --- /dev/null +++ b/tests/numbers.txt @@ -0,0 +1,10 @@ +(217) 555-0194 +(602) 555-0177 +(713) 555-0167 +(608) 555-0190 +(305) 555-0112 +(415) 555-0135 +(608) 555-0190 +(602) 555-0177 +(317) 555-0123 +(360) 555-0158 diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..874433c --- /dev/null +++ b/version/version.go @@ -0,0 +1,7 @@ +package version + +var ( + Version = "dev" + Commit = "none" + Date = "unknown" +)