12 Commits
Author SHA1 Message Date
phoenix 56c5aaa1d3 Adding license (#10)
clean_file Build / Check (push) Successful in 1m6s
clean_file Build / Rustfmt (push) Successful in 25s
clean_file Build / Clippy (push) Successful in 58s
clean_file Build / build (push) Successful in 1m55s
clean_file Build / Test Suite (push) Successful in 4m29s
Reviewed-on: #10
2026-07-14 11:28:25 -04:00
phoenix 235174b404 Dependency name change (#9)
clean_file Build / Check (push) Successful in 46s
clean_file Build / Rustfmt (push) Successful in 42s
clean_file Build / Test Suite (push) Successful in 1m28s
clean_file Build / build (push) Successful in 1m7s
clean_file Build / Clippy (push) Successful in 2m15s
Reviewed-on: #9
2026-07-13 12:06:24 -04:00
phoenix f837b0f5d0 Update (#8)
Rust Build / Check (push) Successful in 1m18s
Rust Build / build (push) Successful in 58s
Rust Build / Test Suite (push) Successful in 1m54s
Rust Build / Rustfmt (push) Successful in 43s
Rust Build / Clippy (push) Successful in 1m16s
clean_file pr / Check (pull_request) Successful in 38s
clean_file pr / Rustfmt (pull_request) Successful in 35s
clean_file pr / Clippy (pull_request) Successful in 58s
Reviewed-on: #8
2026-06-14 18:08:36 -04:00
phoenix 79a3e47a1b Update deps (#7)
Rust Build / Check (push) Successful in 1m2s
Rust Build / Rustfmt (push) Successful in 36s
Rust Build / Test Suite (push) Successful in 1m23s
Rust Build / build (push) Successful in 52s
Rust Build / Clippy (push) Successful in 42s
Reviewed-on: #7
2026-06-13 13:09:55 -04:00
phoenix d8cb2cee19 Update dependencies (#6)
Rust Build / Check (push) Successful in 31s
Rust Build / Rustfmt (push) Successful in 32s
Rust Build / Test Suite (push) Successful in 1m3s
Rust Build / build (push) Successful in 1m1s
Rust Build / Clippy (push) Successful in 41s
Reviewed-on: #6
2026-06-07 15:33:57 -04:00
phoenix 7d9142dc13 Merge pull request 'v1.0' (#1) from v1.x into main
Rust Build / Check (push) Successful in 26s
Rust Build / Test Suite (push) Successful in 47s
Rust Build / Rustfmt (push) Successful in 33s
Rust Build / build (push) Successful in 29s
Rust Build / Clippy (push) Successful in 47s
Rust Build / Check (pull_request) Successful in 1m17s
Rust Build / Rustfmt (pull_request) Successful in 30s
Rust Build / Clippy (pull_request) Successful in 40s
Rust Build / Test Suite (pull_request) Successful in 2m4s
Rust Build / build (pull_request) Successful in 58s
Reviewed-on: #1
2026-06-06 14:12:01 -04:00
phoenix f9339ae9a1 bump: clean_file
Rust Build / Check (pull_request) Successful in 33s
Rust Build / Test Suite (pull_request) Successful in 37s
Rust Build / Rustfmt (pull_request) Successful in 45s
Rust Build / Clippy (pull_request) Successful in 27s
Rust Build / build (pull_request) Successful in 1m13s
2026-06-06 14:08:18 -04:00
phoenix a0bf202b39 Workflow branch update
Rust Build / Check (pull_request) Successful in 26s
Rust Build / Test Suite (pull_request) Successful in 38s
Rust Build / Rustfmt (pull_request) Successful in 50s
Rust Build / Clippy (pull_request) Successful in 28s
Rust Build / build (pull_request) Successful in 45s
2026-06-06 14:07:23 -04:00
phoenix f5cb2ed892 Add tests (#5)
Rust Build / Check (push) Successful in 33s
Rust Build / Rustfmt (push) Successful in 35s
Rust Build / Test Suite (push) Successful in 38s
Rust Build / Clippy (push) Successful in 29s
Rust Build / build (push) Successful in 42s
Reviewed-on: #5
2026-06-06 14:03:42 -04:00
phoenix cd0ea8a387 Migrate (#4)
Rust Build / Clippy (push) Successful in 33s
Rust Build / Check (push) Successful in 26s
Rust Build / Rustfmt (push) Successful in 32s
Rust Build / Test Suite (push) Successful in 47s
Rust Build / build (push) Successful in 30s
Reviewed-on: #4
2026-06-06 12:48:58 -04:00
phoenix e7f6ddec9a Adding rust files (#3)
Rust Build / Check (push) Successful in 38s
Rust Build / Rustfmt (push) Successful in 35s
Rust Build / Test Suite (push) Successful in 38s
Rust Build / Clippy (push) Successful in 37s
Rust Build / build (push) Successful in 42s
Reviewed-on: #3
2026-06-05 18:49:53 -04:00
phoenix 9ec9fa1516 Clear out (#2)
Reviewed-on: #2
2026-06-05 18:06:38 -04:00
19 changed files with 2423 additions and 385 deletions
+65
View File
@@ -0,0 +1,65 @@
name: clean_file pr
on:
pull_request:
branches:
- main
jobs:
check:
name: Check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo check
fmt:
name: Rustfmt
runs-on: ubuntu-24.04
needs: setup_ssh
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-24.04
needs: setup_ssh
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo clippy -- -D warnings
-44
View File
@@ -1,44 +0,0 @@
name: Go
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: '1.26.2'
- name: Build
run: |
echo "Initializing config"
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender_models_deploy_key
chmod 600 ~/.ssh/textsender_models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender_models_deploy_key
go env -w GOPRIVATE='${{ secrets.GIT_HOST_ROOT }}'
echo "Creating local .gitconfig"
touch ~/.gitconfig
cat > ~/.gitconfig << "EOF"
[url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"]
insteadOf = https://${{ secrets.GIT_HOST_ROOT }}
EOF
make build
- name: Test
run: go test -v ./...
+103
View File
@@ -0,0 +1,103 @@
name: clean_file Build
on:
push:
branches:
- main
jobs:
check:
name: Check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo check
test:
name: Test Suite
runs-on: ubuntu-24.04
needs: setup_ssh
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo test
fmt:
name: Rustfmt
runs-on: ubuntu-24.04
needs: setup_ssh
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-24.04
needs: setup_ssh
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo clippy -- -D warnings
build:
name: build
runs-on: ubuntu-24.04
needs: setup_ssh
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/deploy_key
cargo build --release
+1 -2
View File
@@ -1,3 +1,2 @@
/vendor
/clean_file
target/
numbers.json
Generated
+1899
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "clean_file"
version = "1.1.1"
edition = "2024"
license = "MIT"
description = "A tool to sanitize US phone numbers"
rust-version = "1.96"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.52.3", features = ["full"] }
serde_json = { version = "1.0.150" }
schedtxt_models = { git = "ssh://git@git.kundeng.us/phoenix/schedtxt_models.git", tag = "v0.5.3", features = ["contact"] }
[dev-dependencies]
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2026 Kun Deng.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-22
View File
@@ -1,22 +0,0 @@
VERSION ?= $(shell git describe --tags 2>/dev/null || echo "dev")
COMMIT ?= $(shell git rev-parse --short HEAD)
BUILD_TIME ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
GO_VERSION ?= $(shell go version | awk '{print $$3}')
.PHONY: build
build:
go build -ldflags="\
-X 'git.kundeng.us/phoenix/clean_file/internal/version.Version=$(VERSION)' \
-X 'git.kundeng.us/phoenix/clean_file/internal/version.BuildTime=$(BUILD_TIME)' \
-X 'git.kundeng.us/phoenix/clean_file/internal/version.Commit=$(COMMIT)' \
-X 'git.kundeng.us/phoenix/clean_file/internal/version.GoVersion=$(GO_VERSION)'" \
-o clean_file cmd/clean_file/main.go
.PHONY: install
install:
go install -ldflags="\
-X 'git.kundeng.us/phoenix/clean_file/internal/version.Version=$(VERSION)' \
-X 'git.kundeng.us/phoenix/clean_file/internal/version.BuildTime=$(BUILD_TIME)' \
-X 'git.kundeng.us/phoenix/clean_file/internal/version.Commit=$(COMMIT)' \
-X 'git.kundeng.us/phoenix/clean_file/internal/version.GoVersion=$(GO_VERSION)'"
-o clean_file cmd/clean_file/main.go
+3 -8
View File
@@ -1,8 +1,3 @@
CLI software that processes a text file containing US phone numbers
and sanitizes the numbers. The result is contained in a json file.
## Building
```
make build
```
# clean_file
A CLI tool to sanitize a phone numbers. Takes unformatted US phone numbers and
standardizes them.
-39
View File
@@ -1,39 +0,0 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"git.kundeng.us/phoenix/clean_file/internal/parser"
"git.kundeng.us/phoenix/clean_file/internal/version"
)
func main() {
args := os.Args
if len(args) < 2 {
log.Println("Invalid arguments provided")
os.Exit(-1)
}
versionFlag := flag.Bool("version", false, "Print version information")
flag.Parse()
if *versionFlag {
fmt.Println(version.String())
return
}
log.Println("clean_file")
filepath := args[1]
log.Println("File path:", filepath)
prsr := parser.NumberParser{FilePath: filepath}
numbers := prsr.FileDump()
prsr.PrintValues(numbers, true)
prsr.SaveFile(numbers)
}
-7
View File
@@ -1,7 +0,0 @@
module git.kundeng.us/phoenix/clean_file
go 1.26.2
require git.kundeng.us/phoenix/textsender-models v0.2.1
require github.com/google/uuid v1.6.0 // indirect
-4
View File
@@ -1,4 +0,0 @@
git.kundeng.us/phoenix/textsender-models v0.2.1 h1:21br4NF58aUFuCx8laKxC5RvZMl4GsSIaMX4bvf5plw=
git.kundeng.us/phoenix/textsender-models v0.2.1/go.mod h1:nu5QWy9o+spx/t9NFipaGmF5qiBJS/0QhxyCjoi3Z3E=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-169
View File
@@ -1,169 +0,0 @@
package parser
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
"strings"
"unicode"
"git.kundeng.us/phoenix/textsender-models/tx0/contact"
)
const Letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
type NumberParser struct {
FilePath string
}
func (prsr *NumberParser) FileDump() []contact.Contact {
log.Println("Dumping files")
objs := []contact.Contact{}
for _, line := range prsr.readLines(prsr.FilePath) {
if strings.ContainsAny(line, Letters) {
continue
}
myObj := contact.Contact{PhoneNumber: line}
if myObj.PhoneNumber != "" {
objs = append(objs, myObj)
}
}
objs = prsr.updateContacts(objs)
objs = prsr.removeDups(objs)
return objs
}
func (prsr *NumberParser) updateContacts(contacts []contact.Contact) []contact.Contact {
updated := []contact.Contact{}
for _, ct := range contacts {
parsed := ct.PhoneNumber
if len(parsed) < 2 {
log.Println("Invalid number:", parsed)
continue
}
pars := prsr.removeSomeData(parsed)
parsed = pars
log.Println("Parsed:", parsed)
updatedParsed := prsr.addPrefix(parsed)
updated = append(updated, contact.Contact{PhoneNumber: updatedParsed})
}
return updated
}
func (prsr *NumberParser) removeDups(contacts []contact.Contact) []contact.Contact {
updated := []contact.Contact{}
uniqueItems := make(map[string]string)
for _, val := range contacts {
uniqueItems[val.PhoneNumber] = val.PhoneNumber
}
for _, item := range uniqueItems {
updated = append(updated, contact.Contact{PhoneNumber: item})
}
return updated
}
func (prsr NumberParser) PrintValues(contacts []contact.Contact, printTotal bool) {
fmt.Println("Printing numbers")
for _, ct := range contacts {
fmt.Println("Number:", ct.PhoneNumber)
}
if printTotal {
fmt.Println("Total Numbers:", len(contacts))
}
}
func (prsr NumberParser) SaveFile(vals []contact.Contact) {
filename := "numbers.json"
// Create json and save it to the filesystem
jsonData, err := json.MarshalIndent(&vals, "", " ")
if err != nil {
log.Println("Error marshaling JSON:", err)
return
}
err = os.WriteFile(filename, jsonData, 0644)
if err != nil {
log.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)
}
}
}
log.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
}
-73
View File
@@ -1,73 +0,0 @@
package parser
import (
"fmt"
"log"
"testing"
"git.kundeng.us/phoenix/textsender-models/tx0/contact"
)
const Test_File_Path = "../../tests/numbers.txt"
// Remove duplicates
func Test_RemoveDups(t *testing.T) {
parser := NumberParser{FilePath: Test_File_Path}
finalAmount := parser.FileDump()
if len(finalAmount) < 1 {
t.Error("Dump should not be empty")
}
testValues := []contact.Contact{
{PhoneNumber: "+12175550194"},
{PhoneNumber: "+16025550177"},
{PhoneNumber: "+17135550167"},
{PhoneNumber: "+16085550190"},
{PhoneNumber: "+13055550112"},
{PhoneNumber: "+14155550135"},
{PhoneNumber: "+13175550123"},
{PhoneNumber: "+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("PhoneNumber:", val.PhoneNumber)
}
fmt.Println("\nPrinting test values")
for _, val := range testValues {
fmt.Println("PhoneNumber:", val.PhoneNumber)
}
t.Error("Data has not been updated")
} else {
log.Println("The duplicates have been removed")
}
}
func dataEqual(a, b []contact.Contact) 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
}
-17
View File
@@ -1,17 +0,0 @@
package version
import "fmt"
var (
Version = "dev"
BuildTime = "unknown"
Commit = "unknown"
GoVersion = "unknown"
)
func String() string {
return fmt.Sprintf(
"Version: %s\nBuild Date: %s\nCommit: %s\nGo Version: %s",
Version, BuildTime, Commit, GoVersion,
)
}
+32
View File
@@ -0,0 +1,32 @@
pub mod parser;
pub mod version;
#[tokio::main]
async fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
println!("Invalid arguments provided");
std::process::exit(-1);
}
if version::contains_version(&args) {
version::print_version();
std::process::exit(-1);
}
let filepath = &args[1];
println!("Filepath: {filepath:?}");
let prsr = parser::NumberParser {
filepath: filepath.clone(),
};
match prsr.file_dump().await {
Ok(contacts) => {
prsr.print_phone_numbers(&contacts);
prsr.save_file(contacts, parser::DEFAULT_OUTPUT_FILE).await;
}
Err(err) => {
eprintln!("Error: {err:?}");
}
};
}
+263
View File
@@ -0,0 +1,263 @@
use std::io::BufRead;
pub const DEFAULT_OUTPUT_FILE: &str = "numbers.json";
const LETTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
#[derive(Clone, Debug, Default)]
pub struct NumberParser {
pub filepath: String,
}
impl NumberParser {
pub async fn file_dump(
&self,
) -> Result<Vec<schedtxt_models::contact::Contact>, std::io::Error> {
println!("Dumping files");
let mut objs: Vec<schedtxt_models::contact::Contact> = Vec::new();
match self.read_lines(&self.filepath).await {
Ok(lines) => {
for line in lines {
let contains_letters = line.chars().any(|c| LETTERS.contains(c));
if contains_letters {
continue;
} else {
let my_obj = schedtxt_models::contact::Contact {
phone_number: line.clone(),
..Default::default()
};
if !my_obj.phone_number.is_empty() {
objs.push(my_obj);
}
}
}
let updated_contacts = self.update_contacts(&objs).await;
let removed_duplicates = self.remove_dups(&updated_contacts).await;
Ok(removed_duplicates)
}
Err(err) => Err(err),
}
}
pub async fn save_file(&self, numbers: Vec<schedtxt_models::contact::Contact>, filename: &str) {
match serde_json::to_string(&numbers) {
Ok(json_string) => match std::fs::write(filename, json_string) {
Ok(_) => {
println!("Saved to: {filename:?}");
}
Err(err) => {
eprintln!("Error: {err:?}");
}
},
Err(err) => {
eprintln!("Error: {err:?}");
}
};
}
pub fn print_phone_numbers(&self, contacts: &Vec<schedtxt_models::contact::Contact>) {
println!("Printing phone numbers");
for contact in contacts {
println!("Phone Number: {:?}", contact.phone_number);
}
println!("Total phone numbers: {:?}", contacts.len());
}
async fn read_lines(&self, filename: &String) -> Result<Vec<String>, std::io::Error> {
match std::fs::File::open(filename) {
Ok(file) => {
let reader = std::io::BufReader::new(file);
reader.lines().collect()
}
Err(err) => Err(err),
}
}
async fn remove_dups(
&self,
contacts: &Vec<schedtxt_models::contact::Contact>,
) -> Vec<schedtxt_models::contact::Contact> {
let mut unique_contacts: Vec<schedtxt_models::contact::Contact> = Vec::new();
let mut unique_items = std::collections::HashSet::new();
for val in contacts {
unique_items.insert(val.phone_number.clone());
}
for unique_item in unique_items {
let contact = schedtxt_models::contact::Contact {
phone_number: unique_item,
..Default::default()
};
unique_contacts.push(contact);
}
unique_contacts
}
async fn update_contacts(
&self,
contacts: &Vec<schedtxt_models::contact::Contact>,
) -> Vec<schedtxt_models::contact::Contact> {
let mut updated: Vec<schedtxt_models::contact::Contact> = Vec::new();
for ct in contacts {
let phone_number = ct.phone_number.clone();
if phone_number.len() < 2 {
println!("Invalid number: {phone_number:?}");
continue;
}
let pars = self.remove_some_data(&phone_number).await;
match self.add_prefix(&pars) {
Ok(updated_parsed) => {
let contact = schedtxt_models::contact::Contact {
phone_number: updated_parsed,
..Default::default()
};
updated.push(contact);
}
Err(err) => {
eprintln!("Error: {err:?}");
std::process::exit(-2);
}
};
}
updated
}
fn add_prefix(&self, raw: &String) -> Result<String, std::io::Error> {
if raw.is_empty() {
Err(std::io::Error::other("Empty"))
} else {
let mut parsed: String = String::new();
let first_char = raw.chars().nth(0).unwrap();
if first_char == '1' {
parsed = format!("+{raw}");
} else if first_char != '+' {
parsed = format!("+1{raw}");
}
Ok(parsed)
}
}
async fn remove_some_data(&self, unparsed: &str) -> String {
let mut parsed: String = String::new();
for ch in unparsed.chars() {
if ch == ' ' {
continue;
} else {
if ch.is_numeric() {
parsed.push(ch);
}
}
}
parsed
}
}
#[cfg(test)]
mod tests {
use schedtxt_models::contact::Contact;
const TEST_NUMBERS_0: &str = "tests/numbers-0.txt";
#[tokio::test]
async fn test_file_dump() {
let file_exists = {
let path = std::path::Path::new(TEST_NUMBERS_0);
path.exists()
};
assert_eq!(true, file_exists, "File does not exists {TEST_NUMBERS_0:?}");
let filename = String::from(TEST_NUMBERS_0);
let prsr = super::NumberParser { filepath: filename };
let original: Vec<Contact> = vec![
Contact {
phone_number: String::from("+13055550112"),
..Default::default()
},
Contact {
phone_number: String::from("+13175550123"),
..Default::default()
},
Contact {
phone_number: String::from("+14155550135"),
..Default::default()
},
Contact {
phone_number: String::from("+16085550190"),
..Default::default()
},
Contact {
phone_number: String::from("+17135550167"),
..Default::default()
},
Contact {
phone_number: String::from("+16025550177"),
..Default::default()
},
Contact {
phone_number: String::from("+13605550158"),
..Default::default()
},
Contact {
phone_number: String::from("+12175550194"),
..Default::default()
},
];
match prsr.file_dump().await {
Ok(contacts) => {
assert!((contacts.len() > 0), "No contacts has been parsed");
let phone_numbers_1: std::collections::HashSet<_> =
contacts.iter().map(|p| p.phone_number.clone()).collect();
let phone_numbers_2: std::collections::HashSet<_> =
original.iter().map(|p| p.phone_number.clone()).collect();
assert_eq!(phone_numbers_1, phone_numbers_2, "No match");
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
};
}
#[tokio::test]
async fn test_save_file() {
let prsr = super::NumberParser {
filepath: String::from(TEST_NUMBERS_0),
};
let results = prsr.file_dump().await;
assert!(results.is_ok(), "File dump not successful");
let test_output_file: &str = "test-numbers.json";
let contacts = results.unwrap();
prsr.save_file(contacts, test_output_file).await;
let file_exists = {
let path = std::path::Path::new(test_output_file);
path.exists()
};
assert!(file_exists, "Contacts were not saved");
match std::fs::remove_file(test_output_file) {
Ok(_) => {
println!("File removed");
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
pub fn print_version() {
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
println!("{name:?} {version:?}");
}
pub fn contains_version(args: &Vec<String>) -> bool {
let valid_flags = vec!["-v", "--version"];
for arg in args {
for valid_flag in &valid_flags {
if valid_flag == arg {
return true;
}
}
}
false
}