From 9918422f1dc7a7b276bdf7b4020aa335bf688850 Mon Sep 17 00:00:00 2001 From: Code Carefully Date: Thu, 26 Mar 2020 19:00:42 +0200 Subject: initial version --- .gitignore | 15 ++++ README.md | 18 ++++- parse.go | 137 ++++++++++++++++++++++++++++++++ renderfloat/renderfloat.go | 194 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 parse.go create mode 100644 renderfloat/renderfloat.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66fd13c --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/README.md b/README.md index 460c37c..d58bbfc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ # myCOVIDcli -just a fast covid cli fetcher + +Hi! +I'm pretty new to Go, but I decided to use some of my downtime in this terrible situation to be able to see a +nice printout of the places I'm monitoring from the CLI. + +Please be respectful of the other connected projects! + +Credits: + +COVID19 updates from (Novel Coronavirus (COVID-19) Cases, provided by JHU CSSE) +https://github.com/CSSEGISandData/COVID-19/ + +Some .gitignore inspiration from: https://github.com/github/gitignore + +"DownloadFile" from https://golangcode.com/download-a-file-from-a-url/ + +renderfloat from https://gist.github.com/gorhill/5285193 (WTFPL) \ No newline at end of file diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..4b22082 --- /dev/null +++ b/parse.go @@ -0,0 +1,137 @@ +package main + +import ( + "bufio" + "coronaVirus/renderfloat" + "encoding/csv" + "fmt" + "github.com/olekukonko/tablewriter" + "io" + "log" + "net/http" + "os" + "strconv" + "time" +) + +// DownloadFile will download a url to a local file. It's efficient because it will +// write as it downloads and not load the whole file into memory. +func DownloadFile(filepath string, url string) error { + + // Get the data + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + return err +} + +func main() { + currentTime := time.Now() //get current time/date + currentTime = currentTime.AddDate(0, 0, -1) //go to yesterday, this source updates only daily + strcurrentdate := currentTime.Format("01-02-2006") //reformat for URL format + COVIDurl := "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/"+strcurrentdate+".csv" + + //fmt.Println(COVIDurl) + + filepath := "covid.csv" // path to save the CSV to. + if err := DownloadFile(filepath, COVIDurl); err != nil { + panic(err) + } + + csvFile, _ := os.Open(filepath) + reader := csv.NewReader(bufio.NewReader(csvFile)) + + //zero out all variables + usconfirmed := 0 + usdeaths := 0 + region := "" + state := "" + confirmed := "" + deaths:="" + + data := [][]string{} + + for { + line, error := reader.Read() //read in a line + if error == io.EOF { + break + } else if error != nil { + log.Fatal(error) + } + + region = line[3] + state = line[1] + confirmed = line[7] + deaths = line[8] + + if region == "US" { + + confirmed, err := strconv.Atoi(line[7]) //convert confirmed to int + if err != nil { + // handle error + fmt.Println(err) + os.Exit(2) + } + deaths, err := strconv.Atoi(line[8]) //convert deaths to int + if err != nil { + // handle error + fmt.Println(err) + os.Exit(2) + } + usconfirmed = usconfirmed + confirmed + usdeaths = usdeaths + deaths + } + + //create array for table output + if state == "New York City" { + f, _ := strconv.ParseFloat(confirmed, 8) + data = append(data, []string{state, renderfloat.RenderFloat("#,###.", f), deaths}) + //fmt.Println(state + " Deaths: " + deaths + " Confirmed: " + confirmed ) + } + + if region == "Israel" { + f, _ := strconv.ParseFloat(confirmed, 8) + data = append(data, []string{region, renderfloat.RenderFloat("#,###.", f), deaths}) + //fmt.Println(region + " Deaths: " + deaths + " Confirmed: " + confirmed ) + } + + if region == "Italy" { + f, _ := strconv.ParseFloat(confirmed, 8) + d, _ := strconv.ParseFloat(deaths, 8) + data = append(data, []string{region, renderfloat.RenderFloat("#,###.", f), renderfloat.RenderFloat("#,###.", d)}) + //fmt.Println(region + " Deaths: " + deaths + " Confirmed: " + confirmed ) + } + + if region == "Estonia" { + f, _ := strconv.ParseFloat(confirmed, 8) + data = append(data, []string{region, renderfloat.RenderFloat("#,###.", f), deaths}) + //fmt.Println(region + " Deaths: " + deaths + " Confirmed: " + confirmed ) + } + } + + usconfirmed1, _ := strconv.ParseFloat(strconv.Itoa(usconfirmed), 8) + usdeaths1, _ := strconv.ParseFloat(strconv.Itoa(usdeaths), 8) + data = append(data, []string{"USA", renderfloat.RenderFloat("#,###.", usconfirmed1), renderfloat.RenderFloat("#,###.", usdeaths1) }) + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Area", "Confirmed", "Deaths"}) + + for _, v := range data { + table.Append(v) + } + table.Render() // Send output + + } + diff --git a/renderfloat/renderfloat.go b/renderfloat/renderfloat.go new file mode 100644 index 0000000..042a36d --- /dev/null +++ b/renderfloat/renderfloat.go @@ -0,0 +1,194 @@ +/* + +Author: https://github.com/gorhill +Source: https://gist.github.com/gorhill/5285193 + +A Go function to render a number to a string based on +the following user-specified criteria: + +* thousands separator +* decimal separator +* decimal precision + +Usage: s := RenderFloat(format, n) + +The format parameter tells how to render the number n. + +http://play.golang.org/p/LXc1Ddm1lJ + +Examples of format strings, given n = 12345.6789: + +"#,###.##" => "12,345.67" +"#,###." => "12,345" +"#,###" => "12345,678" +"#\u202F###,##" => "12 345,67" +"#.###,###### => 12.345,678900 +"" (aka default format) => 12,345.67 + +The highest precision allowed is 9 digits after the decimal symbol. +There is also a version for integer number, RenderInteger(), +which is convenient for calls within template. + +I didn't feel it was worth to publish a library just for this piece +of code, hence the snippet. Feel free to reuse as you wish. +[my note: the WTFPL was attached: DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE] + +*/ +package renderfloat + +import ( +"math" +"strconv" +) + +var renderFloatPrecisionMultipliers = [10]float64{ +1, +10, +100, +1000, +10000, +100000, +1000000, +10000000, +100000000, +1000000000, +} + +var renderFloatPrecisionRounders = [10]float64{ +0.5, +0.05, +0.005, +0.0005, +0.00005, +0.000005, +0.0000005, +0.00000005, +0.000000005, +0.0000000005, +} + +func RenderFloat(format string, n float64) string { +// Special cases: +// NaN = "NaN" +// +Inf = "+Infinity" +// -Inf = "-Infinity" +if math.IsNaN(n) { +return "NaN" +} +if n > math.MaxFloat64 { +return "Infinity" +} +if n < -math.MaxFloat64 { +return "-Infinity" +} + +// default format +precision := 2 +decimalStr := "." +thousandStr := "," +positiveStr := "" +negativeStr := "-" + +if len(format) > 0 { +// If there is an explicit format directive, +// then default values are these: +precision = 9 +thousandStr = "" + +// collect indices of meaningful formatting directives +formatDirectiveChars := []rune(format) +formatDirectiveIndices := make([]int, 0) +for i, char := range formatDirectiveChars { +if char != '#' && char != '0' { +formatDirectiveIndices = append(formatDirectiveIndices, i) +} +} + +if len(formatDirectiveIndices) > 0 { +// Directive at index 0: +// Must be a '+' +// Raise an error if not the case +// index: 0123456789 +// +0.000,000 +// +000,000.0 +// +0000.00 +// +0000 +if formatDirectiveIndices[0] == 0 { +if formatDirectiveChars[formatDirectiveIndices[0]] != '+' { +panic("RenderFloat(): invalid positive sign directive") +} +positiveStr = "+" +formatDirectiveIndices = formatDirectiveIndices[1:] +} + +// Two directives: +// First is thousands separator +// Raise an error if not followed by 3-digit +// 0123456789 +// 0.000,000 +// 000,000.00 +if len(formatDirectiveIndices) == 2 { +if (formatDirectiveIndices[1] - formatDirectiveIndices[0]) != 4 { +panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") +} +thousandStr = string(formatDirectiveChars[formatDirectiveIndices[0]]) +formatDirectiveIndices = formatDirectiveIndices[1:] +} + +// One directive: +// Directive is decimal separator +// The number of digit-specifier following the separator indicates wanted precision +// 0123456789 +// 0.00 +// 000,0000 +if len(formatDirectiveIndices) == 1 { +decimalStr = string(formatDirectiveChars[formatDirectiveIndices[0]]) +precision = len(formatDirectiveChars) - formatDirectiveIndices[0] - 1 +} +} +} + +// generate sign part +var signStr string +if n >= 0.000000001 { +signStr = positiveStr +} else if n <= -0.000000001 { +signStr = negativeStr +n = -n +} else { +signStr = "" +n = 0.0 +} + +// split number into integer and fractional parts +intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) + +// generate integer part string +intStr := strconv.Itoa(int(intf)) + +// add thousand separator if required +if len(thousandStr) > 0 { +for i := len(intStr); i > 3; { +i -= 3 +intStr = intStr[:i] + thousandStr + intStr[i:] +} +} + +// no fractional part, we can leave now +if precision == 0 { +return signStr + intStr +} + +// generate fractional part +fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) +// may need padding +if len(fracStr) < precision { +fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr +} + +return signStr + intStr + decimalStr + fracStr +} + +func RenderInteger(format string, n int) string { +return RenderFloat(format, float64(n)) +} -- cgit 1.4.1-2-gfad0