├── os ├── osutil │ ├── filepath_testdata │ │ └── exist.txt │ ├── constants.go │ ├── cmd │ │ ├── cp │ │ │ └── main.go │ │ ├── subdirs │ │ │ └── main.go │ │ └── file_sizes │ │ │ └── main.go │ ├── dir_entry_sort.go │ ├── filepath_test.go │ ├── copy.go │ ├── osutil_darwin.go │ └── read.go ├── fileext │ └── fileext.go └── executil │ ├── executil_test.go │ ├── grep │ └── grep.go │ └── executil.go ├── encoding ├── csvutil │ ├── testdata │ │ ├── simple.csv │ │ └── utf8bom.csv │ ├── csvutil_test.go │ └── one_col_list.go ├── base10 │ ├── base10.go │ └── base10_test.go ├── jsonutil │ ├── json_hash.go │ ├── json_hash_test.go │ ├── jsonraw │ │ ├── unescape.go │ │ └── equal.go │ └── json_error.go ├── padding.go ├── xmlutil │ └── xmlutil.go ├── jsonpointer │ ├── transform_test.go │ ├── parser_test.go │ └── parser.go ├── hexutil │ └── hexutil.go └── base62 │ └── base62_test.go ├── ml └── ml.go ├── .gitignore ├── time ├── duration │ ├── durations.go │ ├── duration_test.go │ ├── spec.go │ ├── duration_info_test.go │ └── durationbin │ │ └── bins_test.go ├── timeutil │ ├── zone.go │ ├── syscall.go │ ├── month.go │ ├── week_test.go │ ├── timeutil_projection.go │ ├── month_test.go │ ├── xox_times.go │ ├── compare_test.go │ ├── parse.go │ ├── syscall_test.go │ └── time_series.go ├── timezone │ ├── constants.go │ └── examples │ │ └── build_constants │ │ └── main.go ├── examples │ ├── timezone │ │ └── main.go │ ├── diff_days │ │ └── main.go │ └── time_custom │ │ └── time_custom.go ├── customtime │ ├── rfc3339fulldate.go │ └── rfc3339millisecond.go ├── year │ ├── year.go │ └── year_test.go ├── month │ ├── strconv_test.go │ ├── parse_test.go │ └── parse.go └── cmd │ └── dateadddays │ └── main.go ├── crypto ├── shautil │ └── testdata │ │ └── gopher_color.png ├── crypter.go ├── token │ └── cmd │ │ └── gentoken │ │ └── main.go ├── x509util │ ├── cert_pool.go │ └── README.md ├── tlsutil │ ├── cmd │ │ └── httpsversioncheck │ │ │ └── main.go │ └── constants.go ├── pkcs12util │ └── cmd │ │ └── crypto_pkcs12 │ │ └── crypto_pkcs12.go ├── randutil │ └── string.go ├── argon2util │ ├── argon2_test.go │ └── argon2.go ├── aesutil │ └── aesecb │ │ └── aesecb_test.go └── rsautil │ └── pkcs1util.go ├── text ├── languageutil │ ├── alphabets.go │ ├── examples │ │ └── language │ │ │ └── main.go │ ├── ietfbcp47.go │ ├── language.go │ └── language_test.go ├── constants.go ├── currencyutil │ ├── internal │ │ └── currencies.go │ ├── exchange_rates.go │ ├── currency_codes_meta.go │ └── currencies.go ├── usstate │ └── state.go ├── foobar │ └── foobar.go ├── text.go ├── textutil │ └── text_test.go ├── stringcase │ └── caser.go ├── markdown │ ├── table_gfm_test.go │ ├── markdown_test.go │ ├── times.go │ ├── skype.go │ └── table_gfm.go ├── emoji │ └── emoji_test.go └── password │ ├── generate_test.go │ └── generate.go ├── image ├── padding │ └── testdata │ │ └── padding_example.png ├── imageutil │ ├── read_testdata │ │ ├── gopher_color.jpg │ │ ├── gopher_color.png │ │ ├── gopher_color.webp │ │ ├── gopher_biplane.jpg │ │ └── gopher_appengine_color.jpg │ ├── new.go │ ├── read_test.go │ ├── lines.go │ ├── animate.go │ └── cmd │ │ └── webp2jpeg │ │ └── main.go └── colors │ └── reference.go ├── net ├── http │ ├── httputilmore │ │ ├── vars.go │ │ ├── http_transport.go │ │ ├── http_method_test.go │ │ ├── httputil_test.go │ │ ├── nethttputil.go │ │ ├── http_error.go │ │ ├── endpoint_test.go │ │ ├── http_server.go │ │ └── transport_headers.go │ ├── har │ │ ├── har_util.go │ │ └── examples │ │ │ └── read_har │ │ │ └── main.go │ └── httpsimple │ │ └── cmd │ │ └── httpreq │ │ └── main.go ├── mailutil │ └── constants.go ├── netutil │ ├── constants.go │ └── connection.go ├── urlutil │ ├── slug.go │ ├── pattern.go │ ├── url_template_test.go │ ├── url_template.go │ ├── scheme.go │ ├── url_path.go │ ├── scheme_test.go │ └── urlvalidator.go └── smtputil │ ├── smtputil.go │ └── addr_format_test.go ├── type ├── stringsutil │ ├── if.go │ ├── markdown.go │ ├── case.go │ ├── quote.go │ ├── transform │ │ └── transform.go │ ├── reverse.go │ ├── matrix.go │ ├── lines_test.go │ ├── split_test.go │ ├── identify.go │ ├── join_test.go │ ├── reverse_test.go │ ├── strutil.go │ └── compare.go ├── boolutil │ └── boolutil.go ├── strslices │ ├── strings.go │ ├── modify.go │ └── index_test.go ├── maputil │ ├── map_string_float32.go │ ├── map_string_slice_test.go │ ├── map_comp_int.go │ ├── map_comp_comp.go │ └── map_string_int_test.go ├── slicesutil │ ├── index.go │ ├── slice_test.go │ └── index_test.go └── number │ ├── constraints.go │ └── slice.go ├── .github ├── dependabot.yaml └── workflows │ ├── lint.yaml │ ├── ci.yaml │ └── sast_codeql.yaml ├── database ├── sqlutil │ ├── identifier.go │ ├── sql_insert_test.go │ ├── sql_insert.go │ └── sql_test.go ├── datasource │ ├── datasource_set.go │ └── sslmode.go ├── examples │ └── sql │ │ └── main.go └── breadops.go ├── math ├── mathutil │ ├── xox.go │ ├── angle.go │ ├── int.go │ ├── overlap.go │ ├── int_test.go │ └── rangeint64_test.go ├── accounting │ └── interest.go ├── ratio │ ├── ratio.go │ ├── ratio_definitions.go │ └── ratio_test.go └── bigint │ ├── encode.go │ └── encode_test.go ├── errors └── errorsutil │ ├── messages.go │ ├── errorsutil_test.go │ ├── error_with_location_test.go │ └── error_with_location.go ├── html ├── htmlutil │ ├── constants.go │ ├── text.go │ ├── htmlutil_test.go │ ├── token_filter.go │ └── descriptionlist_definitions.go └── raymondhelpers │ └── raymondhelpers.go ├── git └── cmd │ ├── gitremovehistory │ └── README.md │ └── gitremoteaddupstream │ └── main.go ├── io ├── fsutil │ └── direntry.go └── ioutil │ └── ioutil_test.go ├── fmt └── fmtutil │ ├── sprintf.go │ ├── if.go │ ├── sprintf_test.go │ └── if_test.go ├── location └── locutil.go ├── strconv ├── phonenumber │ ├── e164_test.go │ ├── e164.go │ ├── phonenumberletters.go │ ├── cmd │ │ └── areacode_distance │ │ │ └── areacode_distance.go │ └── phonenumberletters_test.go ├── humannameparser │ └── humannameparser.go └── strconvutil │ └── strconv_lang_test.go ├── cmp └── cmputil │ ├── cmputil.go │ └── cmputil_test.go ├── log ├── slogutil │ ├── logger.go │ └── slogutil.go ├── logutil │ └── logutil.go └── severity │ └── severity_test.go ├── codegen ├── nestedstructtopointer_test.go └── apps │ └── nestedstruct2pointer │ └── main.go ├── config ├── env_test.go ├── env.go └── examples │ └── dotenv2json │ └── main.go ├── pointer └── simple_create.go ├── .golangci.yaml ├── mime └── mimeutil │ └── mediatype_test.go ├── regexp └── regexputil │ ├── regexputil_test.go │ └── matcher.go ├── CREDITS.md ├── api └── path_to_pattern_test.go ├── unicode └── unicodeutil │ ├── characters.go │ ├── unicodeutil.go │ └── unicodeutil_test.go ├── LICENSE ├── archive ├── archivesecure │ └── filepaths.go └── tarutil │ └── tarsecure.go ├── bytes └── bytesutil │ └── bytesutil.go ├── go.mod └── sort └── sortutil └── integersuffix_test.go /os/osutil/filepath_testdata/exist.txt: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /encoding/csvutil/testdata/simple.csv: -------------------------------------------------------------------------------- 1 | bazqux,foobar 2 | 1,2 3 | 3,4 -------------------------------------------------------------------------------- /encoding/csvutil/testdata/utf8bom.csv: -------------------------------------------------------------------------------- 1 | foobar,bazqux 2 | 1,2 3 | 3,4 -------------------------------------------------------------------------------- /ml/ml.go: -------------------------------------------------------------------------------- 1 | package ml 2 | 3 | type Vectorable interface { 4 | Vectors() []float64 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | _* 4 | *.tar 5 | coverage.txt 6 | debug.test 7 | main 8 | main.zip -------------------------------------------------------------------------------- /time/duration/durations.go: -------------------------------------------------------------------------------- 1 | package duration 2 | 3 | import "time" 4 | 5 | type Durations []time.Duration 6 | -------------------------------------------------------------------------------- /crypto/shautil/testdata/gopher_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grokify/mogo/HEAD/crypto/shautil/testdata/gopher_color.png -------------------------------------------------------------------------------- /text/languageutil/alphabets.go: -------------------------------------------------------------------------------- 1 | package languageutil 2 | 3 | const ( 4 | AlphabetEN = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 5 | ) 6 | -------------------------------------------------------------------------------- /image/padding/testdata/padding_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grokify/mogo/HEAD/image/padding/testdata/padding_example.png -------------------------------------------------------------------------------- /image/imageutil/read_testdata/gopher_color.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grokify/mogo/HEAD/image/imageutil/read_testdata/gopher_color.jpg -------------------------------------------------------------------------------- /image/imageutil/read_testdata/gopher_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grokify/mogo/HEAD/image/imageutil/read_testdata/gopher_color.png -------------------------------------------------------------------------------- /image/imageutil/read_testdata/gopher_color.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grokify/mogo/HEAD/image/imageutil/read_testdata/gopher_color.webp -------------------------------------------------------------------------------- /text/constants.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | const ( 4 | WeightLight = "Light" 5 | WeightMedium = "Medium" 6 | WeightHeavy = "Heavy" 7 | ) 8 | -------------------------------------------------------------------------------- /image/imageutil/read_testdata/gopher_biplane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grokify/mogo/HEAD/image/imageutil/read_testdata/gopher_biplane.jpg -------------------------------------------------------------------------------- /image/imageutil/read_testdata/gopher_appengine_color.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grokify/mogo/HEAD/image/imageutil/read_testdata/gopher_appengine_color.jpg -------------------------------------------------------------------------------- /text/currencyutil/internal/currencies.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | //go:embed currencies.tsv 8 | var CurrenciesDataRaw string 9 | -------------------------------------------------------------------------------- /crypto/crypter.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | type Crypter interface { 4 | Encrypt(plaintext string) (string, error) 5 | Decrypt(ciphertext string) (string, error) 6 | } 7 | -------------------------------------------------------------------------------- /net/http/httputilmore/vars.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | jsoniter "github.com/json-iterator/go" 5 | ) 6 | 7 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 8 | -------------------------------------------------------------------------------- /type/stringsutil/if.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | func IfBoolString(boolVal bool, valueA, valueB string) string { 4 | if boolVal { 5 | return valueA 6 | } 7 | return valueB 8 | } 9 | -------------------------------------------------------------------------------- /type/boolutil/boolutil.go: -------------------------------------------------------------------------------- 1 | package boolutil 2 | 3 | func Flip(b bool) bool { 4 | return !b 5 | } 6 | 7 | func ToInt(b bool) int { 8 | if b { 9 | return 1 10 | } 11 | return 0 12 | } 13 | -------------------------------------------------------------------------------- /os/osutil/constants.go: -------------------------------------------------------------------------------- 1 | package osutil 2 | 3 | import "os" 4 | 5 | const ( 6 | ModeDir0700 os.FileMode = 0700 7 | ModeDir0755 os.FileMode = 0755 8 | ModeFile0600 os.FileMode = 0600 9 | ModeFile0644 os.FileMode = 0644 10 | ) 11 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /database/sqlutil/identifier.go: -------------------------------------------------------------------------------- 1 | package sqlutil 2 | 3 | import "regexp" 4 | 5 | var rxUnquotedIdentifier = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`) 6 | 7 | func IsUnquotedIdentifier(s string) bool { 8 | return rxUnquotedIdentifier.MatchString(s) 9 | } 10 | -------------------------------------------------------------------------------- /time/timeutil/zone.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import "time" 4 | 5 | func TimeUpdateLocation(t time.Time, z string) (time.Time, error) { 6 | if loc, err := time.LoadLocation(z); err != nil { 7 | return t, err 8 | } else { 9 | return t.In(loc), nil 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /time/timeutil/syscall.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "syscall" 5 | "time" 6 | ) 7 | 8 | func Timespec(t syscall.Timespec) time.Time { 9 | return time.Unix(t.Sec, t.Nsec) 10 | } 11 | 12 | func Timeval(t syscall.Timeval) time.Time { 13 | return time.Unix(0, t.Nano()) 14 | } 15 | -------------------------------------------------------------------------------- /math/mathutil/xox.go: -------------------------------------------------------------------------------- 1 | package mathutil 2 | 3 | // PercentChangeToXoX converts a 1.0 == 100% based `float64` to a 4 | // XoX percentage `float64`. 5 | func PercentChangeToXoX(v float64) float64 { 6 | if v < 1.0 { 7 | return -1 * 100.0 * (1.0 - v) 8 | } 9 | return 100.0 * (v - 1.0) 10 | } 11 | -------------------------------------------------------------------------------- /os/fileext/fileext.go: -------------------------------------------------------------------------------- 1 | package fileext 2 | 3 | const ( 4 | ExtDOCX = "docx" 5 | ExtGo = "go" 6 | ExtJPEG = "jpeg" 7 | ExtJSON = "json" 8 | ExtMarkdown = "md" 9 | ExtPDF = "pdf" 10 | ExtPNG = "png" 11 | ExtSVG = "svg" 12 | ExtXLSX = "xlsx" 13 | ) 14 | -------------------------------------------------------------------------------- /net/mailutil/constants.go: -------------------------------------------------------------------------------- 1 | package mailutil 2 | 3 | const ( 4 | HeaderMessageID = "Message-ID" 5 | HeaderBcc = "Bcc" 6 | HeaderCc = "Cc" 7 | HeaderFrom = "From" 8 | HeaderSubject = "Subject" 9 | HeaderTo = "To" 10 | 11 | AddrSep = ", " 12 | HeaderSep = ": " 13 | ) 14 | -------------------------------------------------------------------------------- /errors/errorsutil/messages.go: -------------------------------------------------------------------------------- 1 | package errorsutil 2 | 3 | import "fmt" 4 | 5 | // ErrIndexOutOfRange returns an `error` using the standard message for 6 | // index out of range. 7 | func ErrIndexOutOfRange(idx, len int) error { 8 | return fmt.Errorf("index out of range [%d] with length %d", idx, len) 9 | } 10 | -------------------------------------------------------------------------------- /net/http/har/har_util.go: -------------------------------------------------------------------------------- 1 | package har 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | func ReadLogFile(filename string) (Log, error) { 9 | h := Log{} 10 | 11 | bytes, err := os.ReadFile(filename) 12 | if err != nil { 13 | return h, err 14 | } 15 | 16 | return h, json.Unmarshal(bytes, &h) 17 | } 18 | -------------------------------------------------------------------------------- /net/netutil/constants.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import "net" 4 | 5 | const ( 6 | HostLocalhost = "localhost" 7 | HostLoopbackIPv4 = "127.0.0.1" 8 | HostLoopbackIPv6 = "0:0:0:0:0:0:0:1" 9 | HostLoopbackIPv6Short = "::1" 10 | ) 11 | 12 | var ( 13 | IPv4loopback = net.IPv4(127, 0, 0, 1) 14 | ) 15 | -------------------------------------------------------------------------------- /crypto/token/cmd/gentoken/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/grokify/mogo/crypto/token" 8 | ) 9 | 10 | func main() { 11 | tok, err := token.Base58(32) 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | fmt.Printf("token: %s\n", tok) 16 | 17 | fmt.Println("DONE") 18 | } 19 | -------------------------------------------------------------------------------- /text/languageutil/examples/language/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/text/language" 7 | ) 8 | 9 | func PrintTag(tag language.Tag) { 10 | fmt.Printf("%v\n", tag) 11 | } 12 | 13 | func main() { 14 | lang := language.English 15 | PrintTag(lang) 16 | 17 | fmt.Println("DONE") 18 | } 19 | -------------------------------------------------------------------------------- /text/usstate/state.go: -------------------------------------------------------------------------------- 1 | package usstate 2 | 3 | import "strings" 4 | 5 | func Abbreviate(stateName string) string { 6 | stateNameLc := strings.ToLower(strings.TrimSpace(stateName)) 7 | for abbr, try := range usc { 8 | if strings.ToLower(try) == stateNameLc { 9 | return abbr 10 | } 11 | } 12 | return stateName 13 | } 14 | -------------------------------------------------------------------------------- /html/htmlutil/constants.go: -------------------------------------------------------------------------------- 1 | package htmlutil 2 | 3 | const ( 4 | TagDiv = "div" 5 | AttributeAlt = "alt" 6 | AttributeClass = "class" 7 | AttributeHref = "href" 8 | AttributeName = "name" 9 | AttributeOnclick = "onclick" 10 | AttributeStyle = "style" 11 | DelimitSemicolon = ";" 12 | DelimitSpace = " " 13 | ) 14 | -------------------------------------------------------------------------------- /html/htmlutil/text.go: -------------------------------------------------------------------------------- 1 | package htmlutil 2 | 3 | import "html" 4 | 5 | // Text represents a text string that fulfills the `Stringable` interface. 6 | type Text struct { 7 | Text string 8 | Escaped bool 9 | } 10 | 11 | func (s Text) String() (string, error) { 12 | if s.Escaped { 13 | return s.Text, nil 14 | } 15 | return html.EscapeString(s.Text), nil 16 | } 17 | -------------------------------------------------------------------------------- /text/foobar/foobar.go: -------------------------------------------------------------------------------- 1 | package foobar 2 | 3 | import "strings" 4 | 5 | const VarsString = "foo, bar, baz, qux, quux, corge, grault, garply, waldo, fred, plugh, xyzzy, thud" 6 | 7 | // Vars retursn a list of foobar metasyntactic variables as seen in this list: https://ascii.jp/elem/000/000/061/61404/ 8 | func Vars() []string { 9 | return strings.Split(VarsString, ", ") 10 | } 11 | -------------------------------------------------------------------------------- /math/mathutil/angle.go: -------------------------------------------------------------------------------- 1 | package mathutil 2 | 3 | import "math" 4 | 5 | func DegreesToRadians(degrees float64) float64 { 6 | return degrees * (math.Pi / 180) 7 | } 8 | 9 | func RadiansToDegrees(radians float64) float64 { 10 | return radians * (180 / math.Pi) 11 | } 12 | 13 | func DegreesMinutesSecondsToDecimal(deg, min, sec float64) float64 { 14 | return deg + ((min + (sec / 60)) / 60) 15 | } 16 | -------------------------------------------------------------------------------- /encoding/base10/base10.go: -------------------------------------------------------------------------------- 1 | // base10 supports Base10 encoding. 2 | package base10 3 | 4 | import ( 5 | "crypto/md5" // #nosec G501 6 | "math/big" 7 | ) 8 | 9 | // Encode adapted from https://stackoverflow.com/questions/28128285/ 10 | func Encode(bytes []byte) *big.Int { 11 | bi := big.NewInt(0) 12 | h := md5.New() // #nosec G401 13 | h.Write(bytes) 14 | bi.SetBytes(h.Sum(nil)) 15 | return bi 16 | } 17 | -------------------------------------------------------------------------------- /math/accounting/interest.go: -------------------------------------------------------------------------------- 1 | package accounting 2 | 3 | import "math" 4 | 5 | // AnnualToMonthly uses compounding where uses 100% = 1. 6 | func AnnualToMonthly(annual float64) float64 { 7 | return math.Pow(1+annual, (1.0/12.0)) - 1 8 | } 9 | 10 | // AnnualToQuarterly uses compounding where 100% = 1. 11 | func AnnualToQuarterly(annual float64) float64 { 12 | return math.Pow(1+annual, (1.0/4.0)) - 1 13 | } 14 | -------------------------------------------------------------------------------- /math/mathutil/int.go: -------------------------------------------------------------------------------- 1 | package mathutil 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | MaxInt63 = 1<<62 - 1 // 4,611,686,018,427,387,903 . Since int63 is a signed integer, one bit is used for the sign. That leaves 62 bits for the value. 9 | MinInt63 = -1 << 62 10 | ) 11 | 12 | // IntLen returns the string length of an integer. 13 | func IntLen(i int) int { 14 | return len(fmt.Sprintf("%d", i)) 15 | } 16 | -------------------------------------------------------------------------------- /type/strslices/strings.go: -------------------------------------------------------------------------------- 1 | package strslices 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type Strings []string 8 | 9 | func (strs Strings) FilterIndexes(indexes []int) (Strings, error) { 10 | n := Strings{} 11 | for _, idx := range indexes { 12 | if idx < 0 || idx >= len(strs) { 13 | return n, errors.New("index out of bounds") 14 | } 15 | n = append(n, strs[idx]) 16 | } 17 | return n, nil 18 | } 19 | -------------------------------------------------------------------------------- /type/stringsutil/markdown.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | func URLToMarkdownLinkHostname(url string) string { 9 | rx := regexp.MustCompile(`(?i)^https?://([^/]+)(/[^/])`) 10 | m := rx.FindStringSubmatch(url) 11 | if len(m) > 1 { 12 | suffix := "" 13 | if len(m) > 2 { 14 | suffix = "..." 15 | } 16 | return fmt.Sprintf("[%s%s](%s)", m[1], suffix, url) 17 | } 18 | return url 19 | } 20 | -------------------------------------------------------------------------------- /git/cmd/gitremovehistory/README.md: -------------------------------------------------------------------------------- 1 | # gitremovehistory 2 | 3 | `gitremovehistory` is a CLI app for the `filter-branch` approach documented here: 4 | 5 | https://help.github.com/en/articles/removing-sensitive-data-from-a-repository 6 | 7 | ## Installation 8 | 9 | ```bash 10 | $ go get github.com/grokify/mogo/git/apps/gitremovehistory 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```bash 16 | $ cd {{your_repo}} 17 | $ gitremovehistory {{path_to_file}} 18 | ``` -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | lint: 6 | strategy: 7 | matrix: 8 | go-version: [1.x] 9 | platform: [ubuntu-latest] 10 | runs-on: ${{ matrix.platform }} 11 | steps: 12 | - uses: actions/checkout@v6 13 | - name: golangci-lint 14 | uses: golangci/golangci-lint-action@v9 15 | with: 16 | version: latest 17 | args: --timeout 3m --verbose -------------------------------------------------------------------------------- /os/osutil/cmd/cp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/grokify/mogo/os/osutil" 9 | ) 10 | 11 | func main() { 12 | fmt.Println(len(os.Args)) 13 | if len(os.Args) != 3 { 14 | log.Fatal("Needs 2 arguments") 15 | } 16 | src := os.Args[1] 17 | dst := os.Args[2] 18 | 19 | err := osutil.CopyFile(src, dst) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | fmt.Println("DONE") 25 | } 26 | -------------------------------------------------------------------------------- /type/maputil/map_string_float32.go: -------------------------------------------------------------------------------- 1 | package maputil 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type MapStringFloat32 map[string]float32 9 | 10 | func (m MapStringFloat32) String(layout, sep1, sep2 string) string { 11 | var parts []string 12 | for k, v := range m { 13 | parts = append(parts, strings.Join( 14 | []string{ 15 | k, 16 | fmt.Sprintf(layout, v), 17 | }, sep1)) 18 | } 19 | return strings.Join(parts, sep2) 20 | } 21 | -------------------------------------------------------------------------------- /io/fsutil/direntry.go: -------------------------------------------------------------------------------- 1 | package fsutil 2 | 3 | import ( 4 | "io/fs" 5 | "path/filepath" 6 | ) 7 | 8 | type DirEntries []fs.DirEntry 9 | 10 | func (de DirEntries) Names(dir string) []string { 11 | filenames := []string{} 12 | for _, entry := range de { 13 | if len(dir) > 0 { 14 | filenames = append(filenames, filepath.Join(dir, entry.Name())) 15 | } else { 16 | filenames = append(filenames, entry.Name()) 17 | } 18 | } 19 | return filenames 20 | } 21 | -------------------------------------------------------------------------------- /fmt/fmtutil/sprintf.go: -------------------------------------------------------------------------------- 1 | package fmtutil 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/grokify/mogo/type/number" 7 | ) 8 | 9 | func SprintfFormatLeadingCharLength(char string, length int) string { 10 | if length < 0 { 11 | length = 0 12 | } 13 | return "%" + char + strconv.Itoa(length) + "d" 14 | } 15 | 16 | func SprintfFormatLeadingCharMaxIntVal(char string, value int) string { 17 | return SprintfFormatLeadingCharLength(char, number.IntLength(value)) 18 | } 19 | -------------------------------------------------------------------------------- /html/htmlutil/htmlutil_test.go: -------------------------------------------------------------------------------- 1 | package htmlutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var htmlToTextTests = []struct { 8 | v string 9 | want string 10 | }{ 11 | {"

Foo

Bar

", "Foo\n\nBar"}, 12 | } 13 | 14 | func TestHTMLToText(t *testing.T) { 15 | for _, tt := range htmlToTextTests { 16 | got := HTMLToText(tt.v) 17 | 18 | if got != tt.want { 19 | t.Errorf("htmlutil.TestHTMLToText(`%s`) Error: want [%v], got [%v]", 20 | tt.v, tt.want, got) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /type/strslices/modify.go: -------------------------------------------------------------------------------- 1 | package strslices 2 | 3 | import "strings" 4 | 5 | func Map(s []string, fn func(s string) string) []string { 6 | var out []string 7 | if fn == nil { 8 | fn = func(s string) string { return s } 9 | } 10 | for _, si := range s { 11 | out = append(out, fn(si)) 12 | } 13 | return out 14 | } 15 | 16 | func ToLower(s []string) []string { 17 | return Map(s, strings.ToLower) 18 | } 19 | 20 | func ToUpper(s []string) []string { 21 | return Map(s, strings.ToUpper) 22 | } 23 | -------------------------------------------------------------------------------- /time/timezone/constants.go: -------------------------------------------------------------------------------- 1 | // timezone is a static list of timezones. It is incomplete as of now. 2 | // Eventually it will be autogenerated using the approach as shown 3 | // https://stackoverflow.com/questions/40120056/get-a-list-of-valid-time-zones-in-go 4 | // 5 | // https://stackoverflow.com/questions/61186388/user-friendly-time-zone-names-corresponding-to-iana-names 6 | package timezone 7 | 8 | const ( 9 | TimezoneAmericaChicago = "America/Chicago" 10 | TimezoneAmericaLosAngeles = "America/Los_Angeles" 11 | ) 12 | -------------------------------------------------------------------------------- /crypto/x509util/cert_pool.go: -------------------------------------------------------------------------------- 1 | package x509util 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func NewCertPoolWithFilepaths(certPaths []string) (*x509.CertPool, error) { 10 | out := x509.NewCertPool() 11 | for _, certPath := range certPaths { 12 | if certBytes, err := os.ReadFile(certPath); err != nil { 13 | return nil, err 14 | } else if !out.AppendCertsFromPEM(certBytes) { 15 | return nil, fmt.Errorf("cannot append certs (%s)", string(certBytes)) 16 | } 17 | } 18 | return out, nil 19 | } 20 | -------------------------------------------------------------------------------- /database/datasource/datasource_set.go: -------------------------------------------------------------------------------- 1 | package datasource 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/grokify/mogo/type/maputil" 7 | ) 8 | 9 | type DataSourceSet struct { 10 | Data map[string]DataSource 11 | } 12 | 13 | func (dss DataSourceSet) GetDataSource(key string) (DataSource, error) { 14 | if ds, ok := dss.Data[key]; ok { 15 | return ds, nil 16 | } 17 | return DataSource{}, fmt.Errorf("key not found (%s)", key) 18 | } 19 | 20 | func (dss DataSourceSet) Keys() []string { 21 | return maputil.Keys(dss.Data) 22 | } 23 | -------------------------------------------------------------------------------- /encoding/base10/base10_test.go: -------------------------------------------------------------------------------- 1 | // base10 supports Base10 encoding. 2 | package base10 3 | 4 | import ( 5 | "testing" 6 | ) 7 | 8 | var base10EncodeTests = []struct { 9 | v []byte 10 | want int64 11 | }{ 12 | {[]byte("Hello World!"), int64(6810602152122453388)}} 13 | 14 | func TestBase10Encode(t *testing.T) { 15 | for _, tt := range base10EncodeTests { 16 | enc := Encode(tt.v) 17 | 18 | if enc.Int64() != tt.want { 19 | t.Errorf("base10.Encode(%v): want %v, got %v", tt.v, tt.want, enc.Int64()) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /math/ratio/ratio.go: -------------------------------------------------------------------------------- 1 | package ratio 2 | 3 | // RatioInt generates the missing value in a ratio calculation. 4 | // Missing number should be in second set of coordinates. 5 | func RatioInt(x1, y1, x2, y2 int) (int, int) { 6 | if x2 > 0 && y2 > 0 { 7 | return x2, y2 8 | } else if x2 <= 0 && y2 <= 0 { 9 | return x1, y1 10 | } else if x2 <= 0 { 11 | return int((float64(x1) / float64(y1)) * float64(y2)), y2 12 | } else if y2 <= 0 { 13 | return x2, int((float64(y1) / float64(x1)) * float64(x2)) 14 | } 15 | return x2, y2 16 | } 17 | -------------------------------------------------------------------------------- /net/urlutil/slug.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var ( 8 | rxSlugTextToURL *regexp.Regexp = regexp.MustCompile(`[\s\t\r\n_-]+`) 9 | rxSlugURLToText *regexp.Regexp = regexp.MustCompile(`[_-]+`) 10 | ) 11 | 12 | func SlugTextToURL(s string, underscore bool) string { 13 | sep := "-" 14 | if underscore { 15 | sep = "_'" 16 | } 17 | return rxSlugTextToURL.ReplaceAllString(s, sep) 18 | } 19 | 20 | func SlugURLToText(s string) string { 21 | return rxSlugURLToText.ReplaceAllString(s, " ") 22 | } 23 | -------------------------------------------------------------------------------- /math/ratio/ratio_definitions.go: -------------------------------------------------------------------------------- 1 | package ratio 2 | 3 | const ( 4 | // Ratios are always width/height 5 | RatioTelevisionEarly float64 = 5 / 4 // 1.25/1 6 | RatioTelevision float64 = 4 / 3 // 1.3/1 7 | RatioAcademy float64 = 1.375 / 1.0 8 | RatioPhoto float64 = 3 / 2 9 | RatioGolden float64 = 1.6180 / 1 10 | ) 11 | 12 | func WidthToHeight(width, ratio float64) float64 { 13 | return width / ratio 14 | } 15 | 16 | func HeightToWidth(height, ratio float64) float64 { 17 | return height * ratio 18 | } 19 | -------------------------------------------------------------------------------- /encoding/jsonutil/json_hash.go: -------------------------------------------------------------------------------- 1 | package jsonutil 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/grokify/mogo/crypto/shautil" 8 | "github.com/grokify/mogo/type/stringsutil" 9 | ) 10 | 11 | func SHA512d256Base32(v any, padding rune) (string, error) { 12 | if b, err := json.Marshal(v); err != nil { 13 | return "", err 14 | } else if sha, err := shautil.Sum512d256Base32(bytes.NewReader(b), padding); err != nil { 15 | return "", err 16 | } else { 17 | return stringsutil.RemoveNonPrintable(sha), nil 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /time/timeutil/month.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | func MonthEndDay(year int, month time.Month) int { 9 | if month == time.December { 10 | month = time.January 11 | year++ 12 | } else { 13 | month++ 14 | } 15 | return time.Date(year, month, 1, 0, 0, 0, 0, time.UTC).AddDate(0, 0, -1).Day() 16 | } 17 | 18 | func MonthNames() []string { 19 | data := []string{} 20 | err := json.Unmarshal([]byte(MonthsEN), &data) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return data 25 | } 26 | -------------------------------------------------------------------------------- /os/osutil/dir_entry_sort.go: -------------------------------------------------------------------------------- 1 | package osutil 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // SortDirEntriesModTime sorts `DirEntries` by last modified time. It will panic if 8 | // an entry cannot retrieve `FileInfo` information. 9 | func SortDirEntriesModTime(files DirEntries) { 10 | sort.Slice(files, func(i, j int) bool { 11 | iF, err := files[i].Info() 12 | if err != nil { 13 | panic(err) 14 | } 15 | jF, err := files[j].Info() 16 | if err != nil { 17 | panic(err) 18 | } 19 | return iF.ModTime().Unix() < jF.ModTime().Unix() 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /type/slicesutil/index.go: -------------------------------------------------------------------------------- 1 | package slicesutil 2 | 3 | import "github.com/grokify/mogo/math/mathutil" 4 | 5 | // ReverseIndex returns the forward index value from the end of the string. 6 | func ReverseIndex(n, i uint) uint { 7 | if i >= n { 8 | i = mathutil.ModPyInt(i, n) 9 | } 10 | return n - i - 1 11 | } 12 | 13 | // IndexValueOrDefault returns the value at the supplied index or a supplied default value. 14 | func IndexValueOrDefault[E any](s []E, idx int, def E) E { 15 | if idx < 0 || idx >= len(s) { 16 | return def 17 | } 18 | return s[idx] 19 | } 20 | -------------------------------------------------------------------------------- /net/http/httpsimple/cmd/httpreq/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/grokify/mogo/fmt/fmtutil" 10 | "github.com/grokify/mogo/net/http/httpsimple" 11 | "github.com/jessevdk/go-flags" 12 | ) 13 | 14 | func main() { 15 | cli := httpsimple.CLI{} 16 | _, err := flags.Parse(&cli) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | fmtutil.MustPrintJSON(cli) 22 | err = cli.Do(context.Background(), os.Stdout) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | fmt.Println("DONE") 27 | } 28 | -------------------------------------------------------------------------------- /type/number/constraints.go: -------------------------------------------------------------------------------- 1 | package number 2 | 3 | // Integer is a constraint for all integer types (signed and unsigned). 4 | type Integer interface { 5 | Signed | Unsigned 6 | } 7 | 8 | type Signed interface { 9 | ~int | ~int8 | ~int16 | ~int32 | ~int64 10 | } 11 | 12 | type Unsigned interface { 13 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 14 | } 15 | 16 | // Clamp255Float64 ensures values are within 0–255 range 17 | func Clamp255Float64(v float64) float64 { 18 | if v < 0 { 19 | return 0 20 | } 21 | if v > 255 { 22 | return 255 23 | } 24 | return v 25 | } 26 | -------------------------------------------------------------------------------- /type/stringsutil/case.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import "golang.org/x/text/cases" 4 | 5 | var defaultCaser = cases.Fold() 6 | 7 | // EqualFoldFull provides "full Unicode case-folding", unlike `strings.EqualFold` which 8 | // provides "simple Unicode case-folding". If `caser` is set to `nil`, the default caser 9 | // with no additional `cases.Option` is used. 10 | func EqualFoldFull(s, t string, caser *cases.Caser) bool { 11 | if caser != nil { 12 | return caser.String(s) == caser.String(t) 13 | } 14 | return defaultCaser.String(s) == defaultCaser.String(t) 15 | } 16 | -------------------------------------------------------------------------------- /type/stringsutil/quote.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import "strings" 4 | 5 | type Quoter struct { 6 | Beg string 7 | End string 8 | SkipNesting bool 9 | } 10 | 11 | func (qtr Quoter) Quote(input string) string { 12 | if qtr.Beg == "" && qtr.End == "" { 13 | return input 14 | } else if qtr.SkipNesting && strings.Index(input, qtr.Beg) == 0 && ReverseIndex(input, qtr.End) == 0 { 15 | return input 16 | } else { 17 | return qtr.Beg + input + qtr.End 18 | } 19 | } 20 | 21 | func Quote(str, beg, end string) string { 22 | return beg + str + end 23 | } 24 | -------------------------------------------------------------------------------- /type/stringsutil/transform/transform.go: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/grokify/mogo/type/maputil" 7 | ) 8 | 9 | type TransformFunc func(s string) string 10 | 11 | func TransformMap(xf func(string) string, s []string) (map[string]string, map[string][]string, error) { 12 | out := map[string]string{} 13 | for _, si := range s { 14 | out[si] = xf(si) 15 | } 16 | dupes := maputil.DuplicateValues(out) 17 | if !maputil.UniqueValues(out) { 18 | return out, dupes, errors.New("strcase collisions") 19 | } 20 | return out, dupes, nil 21 | } 22 | -------------------------------------------------------------------------------- /encoding/padding.go: -------------------------------------------------------------------------------- 1 | // encoding provides generic encoding support. 2 | package encoding 3 | 4 | import "github.com/grokify/mogo/math/mathutil" 5 | 6 | func Pad4(encoded, char string) string { 7 | inputLength := len(encoded) 8 | _, rem := mathutil.Divide(int64(inputLength), int64(4)) 9 | if rem == 0 { 10 | return encoded 11 | } 12 | if len(char) == 0 { 13 | char = " " 14 | } 15 | switch rem { 16 | case 1: 17 | rem = 3 18 | case 3: 19 | rem = 1 20 | } 21 | for i := 0; i < int(rem); i++ { 22 | encoded += char 23 | } 24 | return encoded[:inputLength+int(rem)] 25 | } 26 | -------------------------------------------------------------------------------- /fmt/fmtutil/if.go: -------------------------------------------------------------------------------- 1 | package fmtutil 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | // FprintIf is like `fmt.Fprint()` but doesn't fail when `io.Writer` is `nil`. 9 | func FprintIf(w io.Writer, a ...any) (n int, err error) { 10 | if w != nil { 11 | return fmt.Fprint(w, a...) 12 | } else { 13 | return 14 | } 15 | } 16 | 17 | // FprintfIf is like `fmt.Fprintf()` but doesn't fail when `io.Writer` is `nil`. 18 | func FprintfIf(w io.Writer, format string, a ...any) (n int, err error) { 19 | if w != nil { 20 | return fmt.Fprintf(w, format, a...) 21 | } else { 22 | return 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /image/imageutil/new.go: -------------------------------------------------------------------------------- 1 | package imageutil 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | ) 7 | 8 | func NewRGBAColor(rect image.Rectangle, clr color.RGBA) *image.RGBA { 9 | img := image.NewRGBA(rect) 10 | PaintColor(img, clr, img.Bounds()) 11 | return img 12 | } 13 | 14 | func NewRGBATransparent(rect image.Rectangle) *image.RGBA { 15 | img := image.NewRGBA(rect) 16 | PaintColor(img, color.RGBA{255, 255, 255, 0}, img.Bounds()) 17 | return img 18 | } 19 | 20 | func NewRGBAWhite(rect image.Rectangle) *image.RGBA { 21 | return NewRGBAColor(rect, color.RGBA{255, 255, 255, 255}) 22 | } 23 | -------------------------------------------------------------------------------- /location/locutil.go: -------------------------------------------------------------------------------- 1 | package location 2 | 3 | import ( 4 | "github.com/grokify/mogo/strconv/strconvutil" 5 | "google.golang.org/genproto/googleapis/type/latlng" 6 | ) 7 | 8 | type LatLong struct { 9 | Latitude float64 10 | Longitude float64 11 | } 12 | 13 | // LatLngString returns a string. 14 | func LatLngString(loc *latlng.LatLng, sep string, precision int) string { 15 | if loc == nil { 16 | return strconvutil.FormatDecimal(0, precision) + sep + strconvutil.FormatDecimal(0, precision) 17 | } 18 | return strconvutil.FormatDecimal(loc.Latitude, precision) + sep + strconvutil.FormatDecimal(loc.Longitude, precision) 19 | } 20 | -------------------------------------------------------------------------------- /text/text.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Texts []Text 8 | 9 | type Text struct { 10 | Display string 11 | Slug string 12 | Children Texts 13 | } 14 | 15 | func (texts Texts) DisplayForSlug(slug string) (string, error) { 16 | for _, try := range texts { 17 | if slug == try.Slug { 18 | return try.Display, nil 19 | } 20 | } 21 | return "", fmt.Errorf("slug not found [%s]", slug) 22 | } 23 | 24 | func (texts Texts) DisplayTexts() []string { 25 | displays := []string{} 26 | for _, txt := range texts { 27 | displays = append(displays, txt.Display) 28 | } 29 | return displays 30 | } 31 | -------------------------------------------------------------------------------- /type/stringsutil/reverse.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Reverse reverses string using strings.Builder. It's about 3 times faster 8 | // than the one with using a string concatenation 9 | func Reverse(s string) string { 10 | var sb strings.Builder 11 | runes := []rune(s) 12 | for i := len(runes) - 1; i >= 0; i-- { 13 | sb.WriteRune(runes[i]) 14 | } 15 | return sb.String() 16 | } 17 | 18 | // ReverseIndex returns the `Index` after reversing the 19 | // supplied string and substring. 20 | func ReverseIndex(s, substr string) int { 21 | return strings.Index(Reverse(s), Reverse(substr)) 22 | } 23 | -------------------------------------------------------------------------------- /net/http/httputilmore/http_transport.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "net/http" 7 | 8 | "github.com/grokify/mogo/crypto/tlsutil" 9 | ) 10 | 11 | func TransportTLSVersions(tr *http.Transport) (tlsutil.TLSVersion, tlsutil.TLSVersion, error) { 12 | if tr == nil { 13 | return tls.VersionSSL30, tls.VersionSSL30, errors.New("transport not set") 14 | } else if tr.TLSClientConfig == nil { 15 | return tls.VersionSSL30, tls.VersionSSL30, errors.New("tls config not set") 16 | } else { 17 | return tlsutil.TLSVersion(tr.TLSClientConfig.MinVersion), tlsutil.TLSVersion(tr.TLSClientConfig.MaxVersion), nil 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /os/osutil/filepath_test.go: -------------------------------------------------------------------------------- 1 | package osutil 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | var existTests = []struct { 9 | filename string 10 | exists bool 11 | }{ 12 | {"exist.txt", true}, 13 | {"doesnotexist.txt", false}, 14 | } 15 | 16 | func TestExist(t *testing.T) { 17 | for _, tt := range existTests { 18 | exists, err := Exists(filepath.Join("filepath_testdata", tt.filename)) 19 | if err != nil { 20 | t.Errorf("osutil.Exists(\"%s\") Error [%s]", tt.filename, err.Error()) 21 | } 22 | if exists != tt.exists { 23 | t.Errorf("osutil.Exists(\"%s\") Want [%v] Got [%v]", tt.filename, tt.exists, exists) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /type/stringsutil/matrix.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // Matrix2DColRowIndex returns the row index where the string supplied is first 9 | // encountered for a supplied column index. 10 | func Matrix2DColRowIndex[C comparable](mat [][]C, colIdx int, s C) (int, error) { 11 | if colIdx < 0 { 12 | return -1, errors.New("col index cannot be negative") 13 | } 14 | for y, row := range mat { 15 | if colIdx >= len(row) { 16 | return -1, fmt.Errorf("col index out of range [%d] with length %d", colIdx, len(row)) 17 | } 18 | if row[colIdx] == s { 19 | return y, nil 20 | } 21 | } 22 | return -1, nil 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | go-version: [1.25.x, 1.24.x] 14 | platform: [ubuntu-latest, macos-latest, windows-latest] 15 | runs-on: ${{ matrix.platform }} 16 | steps: 17 | - name: Install Go 18 | if: success() 19 | uses: actions/setup-go@v6 20 | with: 21 | go-version: ${{ matrix.go-version }} 22 | - name: Checkout code 23 | uses: actions/checkout@v6 24 | - name: Run tests 25 | run: go test -v -covermode=count ./... 26 | -------------------------------------------------------------------------------- /crypto/tlsutil/cmd/httpsversioncheck/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | # httpsversioncheck 3 | 4 | ## Installation: 5 | 6 | `go install github.com/grokify/mogo/crypto/tlsutil/cmd/httpsversioncheck“ 7 | 8 | ## Usage 9 | 10 | ``` 11 | % httpsversioncheck https://example.com 12 | ``` 13 | */ 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | 20 | "github.com/grokify/mogo/crypto/tlsutil" 21 | "github.com/grokify/mogo/fmt/fmtutil" 22 | ) 23 | 24 | func main() { 25 | if len(os.Args) <= 1 { 26 | fmt.Println("usage: httpsversioncheck ") 27 | os.Exit(1) 28 | } 29 | 30 | res := tlsutil.CheckURLs(os.Args[1:]) 31 | fmtutil.MustPrintJSON(res) 32 | } 33 | -------------------------------------------------------------------------------- /math/mathutil/overlap.go: -------------------------------------------------------------------------------- 1 | package mathutil 2 | 3 | func IsEven(i int) bool { 4 | return i%2 == 0 5 | } 6 | 7 | func IsOdd(i int) bool { 8 | return !IsEven(i) 9 | } 10 | 11 | func IsOverlapSortedInt(x1, x2, y1, y2 int) bool { 12 | return x1 <= y2 && y1 <= x2 13 | } 14 | 15 | func IsOverlapSortedInt32(x1, x2, y1, y2 int32) bool { 16 | return x1 <= y2 && y1 <= x2 17 | } 18 | 19 | func IsOverlapSortedInt64(x1, x2, y1, y2 int64) bool { 20 | return x1 <= y2 && y1 <= x2 21 | } 22 | 23 | func IsOverlapUnsortedInt(x1, x2, y1, y2 int) bool { 24 | return (x1 >= y1 && x1 <= y2) || 25 | (x2 >= y1 && x2 <= y2) || 26 | (y1 >= x1 && y1 <= x2) || 27 | (y2 >= x1 && y2 <= x2) 28 | } 29 | -------------------------------------------------------------------------------- /os/osutil/copy.go: -------------------------------------------------------------------------------- 1 | package osutil 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | func CopyFile(src, dst string) error { 9 | r, err := os.Open(src) 10 | if err != nil { 11 | return err 12 | } 13 | defer r.Close() 14 | 15 | w, err := os.Create(dst) 16 | if err != nil { 17 | return err 18 | } 19 | defer func() { 20 | if e := w.Close(); e != nil { 21 | err = e 22 | } 23 | }() 24 | 25 | _, err = io.Copy(w, r) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | err = w.Sync() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | si, err := os.Stat(src) 36 | if err != nil { 37 | return err 38 | } 39 | return os.Chmod(dst, si.Mode()) 40 | } 41 | -------------------------------------------------------------------------------- /crypto/pkcs12util/cmd/crypto_pkcs12/crypto_pkcs12.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | p12 "github.com/grokify/mogo/crypto/pkcs12util" 9 | "github.com/joho/godotenv" 10 | ) 11 | 12 | func main() { 13 | err := godotenv.Load(os.Getenv("ENV_PATH")) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | opts := p12.Options{ 19 | InKey: os.Getenv("X509_KEY_FILE"), 20 | In: os.Getenv("X509_CERT_FILE"), 21 | Out: os.Getenv("X509_P12_FILE"), 22 | } 23 | fmt.Println(opts.CreateCommand()) 24 | 25 | opts2 := p12.Options{In: os.Getenv("X509_P12_FILE")} 26 | fmt.Println(opts2.InfoCommand()) 27 | 28 | fmt.Println("DONE") 29 | } 30 | -------------------------------------------------------------------------------- /net/smtputil/smtputil.go: -------------------------------------------------------------------------------- 1 | package smtputil 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/grokify/mogo/time/timeutil" 10 | ) 11 | 12 | // GmailHost is the Google mail smtp hostname. 13 | const GmailHost = "gmail.com" 14 | 15 | // GmailUserPlusTime creates a Gmail SMTP address appending 16 | // a time in "DT14" time format. 17 | func GmailAddressPlusTime(smtpUser string) (string, error) { 18 | smtpUser = strings.TrimSpace(smtpUser) 19 | if len(smtpUser) == 0 { 20 | return "", errors.New("no SMTP User address provided") 21 | } 22 | t := time.Now() 23 | return fmt.Sprintf(`%s+%s@%s`, smtpUser, t.Format(timeutil.DT14), GmailHost), nil 24 | } 25 | -------------------------------------------------------------------------------- /os/osutil/cmd/subdirs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/grokify/mogo/log/logutil" 8 | "github.com/grokify/mogo/os/osutil" 9 | flags "github.com/jessevdk/go-flags" 10 | ) 11 | 12 | type Options struct { 13 | Directory string `short:"d" long:"directory" description:"Directory" default:"."` 14 | } 15 | 16 | func main() { 17 | opts := Options{} 18 | _, err := flags.Parse(&opts) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | err = osutil.VisitPath(opts.Directory, nil, true, false, false, func(dir string) error { 24 | fmt.Println(dir) 25 | return nil 26 | }) 27 | logutil.FatalErr(err) 28 | fmt.Println("DONE") 29 | } 30 | -------------------------------------------------------------------------------- /time/examples/timezone/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/grokify/mogo/fmt/fmtutil" 9 | "github.com/grokify/mogo/time/timezone" 10 | ) 11 | 12 | func main() { 13 | zones := timezone.ZonesSystem(timezone.DefaultZoneDirs()) 14 | if 1 == 1 { 15 | zones = timezone.ZonesPortable() 16 | } 17 | fmtutil.MustPrintJSON(zones) 18 | if err := fmtutil.PrintJSONMin(zones); err != nil { 19 | log.Fatal(err) 20 | } 21 | 22 | tz := "America/New_York" 23 | offset, err := timezone.ZoneOffsetSeconds(time.Now(), tz) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | fmt.Printf("NAME [%v] OFFSET [%v]\n", tz, offset) 29 | } 30 | -------------------------------------------------------------------------------- /io/ioutil/ioutil_test.go: -------------------------------------------------------------------------------- 1 | package ioutil 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/grokify/mogo/reflect/reflectutil" 10 | ) 11 | 12 | var isReaderTests = []struct { 13 | v any 14 | want bool 15 | }{ 16 | {bytes.NewReader([]byte{}), true}, 17 | {strings.NewReader(" "), true}, 18 | } 19 | 20 | func TestIsReader(t *testing.T) { 21 | for _, tt := range isReaderTests { 22 | isReader := IsReader(tt.v) 23 | if isReader != tt.want { 24 | t.Errorf("ioutil.IsReader(...) mismatch on type (%s) want (%s) got (%s)", 25 | reflectutil.NameOf(tt.v, true), 26 | strconv.FormatBool(tt.want), strconv.FormatBool(isReader)) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /strconv/phonenumber/e164_test.go: -------------------------------------------------------------------------------- 1 | package phonenumber 2 | 3 | /* 4 | import ( 5 | "testing" 6 | 7 | "github.com/nyaruka/phonenumbers" 8 | ) 9 | 10 | var e164FormatTests = []struct { 11 | v string 12 | want string 13 | }{ 14 | {"+16505550100", "(650) 555-0100"}, 15 | } 16 | 17 | func TestE164Format(t *testing.T) { 18 | for _, tt := range e164FormatTests { 19 | nat, err := E164Format(tt.v, "", phonenumbers.NATIONAL) 20 | if err != nil { 21 | t.Errorf("phonenumber.E164Format(\"%v\") Error: [%v]", tt.v, err.Error()) 22 | } 23 | if nat != tt.want { 24 | t.Errorf("phonenumber.E164Format(\"%v\") Mismatch: want [%v], got [%v]", 25 | tt.v, tt.want, nat) 26 | } 27 | } 28 | } 29 | */ 30 | -------------------------------------------------------------------------------- /cmp/cmputil/cmputil.go: -------------------------------------------------------------------------------- 1 | package cmputil 2 | 3 | import "cmp" 4 | 5 | type Operator string 6 | 7 | const ( 8 | OpEQ Operator = "eq" 9 | OpNEQ Operator = "neq" 10 | OpLT Operator = "lt" 11 | OpLTE Operator = "lte" 12 | OpGT Operator = "gt" 13 | OpGTE Operator = "gte" 14 | ) 15 | 16 | func Compare[T cmp.Ordered](x, y T, op Operator) bool { 17 | v := cmp.Compare(x, y) 18 | switch op { 19 | case OpEQ: 20 | return v == 0 21 | case OpNEQ: 22 | return v != 0 23 | case OpLT: 24 | return v < 0 25 | case OpLTE: 26 | return v <= 0 || v == 0 27 | case OpGT: 28 | return v > 0 29 | case OpGTE: 30 | return v > 0 || v == 0 31 | default: 32 | panic("invalid comparison operator") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /text/textutil/text_test.go: -------------------------------------------------------------------------------- 1 | package textutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var removeDiacriticsTests = []struct { 8 | v string 9 | want string 10 | }{ 11 | {"å", "a"}, 12 | {"ß", "ss"}, 13 | {"Jesús", "Jesus"}, 14 | {"žůžo", "zuzo"}, 15 | } 16 | 17 | func TestRemoveDiacritics(t *testing.T) { 18 | for _, tt := range removeDiacriticsTests { 19 | try, err := RemoveDiacritics(tt.v) 20 | if err != nil { 21 | t.Errorf("strconvutil.RemoveDiacritics(\"%s\") Error: [%s]", 22 | tt.v, err.Error()) 23 | } 24 | if err == nil && try != tt.want { 25 | t.Errorf("strconvutil.RemoveDiacritics(\"%s\" Error: want [%s], got [%s]", 26 | tt.v, tt.want, try) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /type/stringsutil/lines_test.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var toLineFeedsTests = []struct { 8 | inputString string 9 | linefeedString string 10 | }{ 11 | {"hello\r\nworld\nagain", "hello\nworld\nagain"}, 12 | {"hello\rworld\ragain", "hello\nworld\nagain"}, 13 | {"hello\nworld\nagain", "hello\nworld\nagain"}, 14 | } 15 | 16 | func TestToLineFeeds(t *testing.T) { 17 | for _, tt := range toLineFeedsTests { 18 | tryLineFeeds := ToLineFeeds(tt.inputString) 19 | if tryLineFeeds != tt.linefeedString { 20 | t.Errorf("stringsutil.ToLineFeeds(\"%s\") Error: want [%s], got [%s]", 21 | tt.inputString, tt.linefeedString, tryLineFeeds) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /math/mathutil/int_test.go: -------------------------------------------------------------------------------- 1 | package mathutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var intLenTests = []struct { 8 | val int 9 | len int 10 | }{ 11 | {-1000, 5}, 12 | {-1, 2}, 13 | {0, 1}, 14 | {1, 1}, 15 | {1000, 4}, 16 | } 17 | 18 | func TestIntLen(t *testing.T) { 19 | for _, tt := range intLenTests { 20 | intLen := IntLen(tt.val) 21 | if intLen != tt.len { 22 | t.Errorf("mathutil.IntLen(%d) Mismatch: want [%d], got [%d]", 23 | tt.val, tt.len, intLen) 24 | } 25 | } 26 | } 27 | 28 | func TestMaxInt63(t *testing.T) { 29 | want := 4611686018427387903 30 | if MaxInt63 != want { 31 | t.Errorf("mathutil.MaxInt63 Mismatch: want [%d], got [%d]", 32 | want, MaxInt63) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /math/mathutil/rangeint64_test.go: -------------------------------------------------------------------------------- 1 | package mathutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var floorMostSignificantTests = []struct { 8 | v int64 9 | want int64 10 | }{ 11 | {0, 0}, 12 | {-56, -60}, {-123456, -200000}, 13 | {56, 50}, {123456, 100000}, 14 | { 15 | 9223372036854775807, 16 | 9000000000000000000}, 17 | { 18 | -8223372036854775808, 19 | -9000000000000000000}, 20 | } 21 | 22 | func TestFloorMostSignificant(t *testing.T) { 23 | for _, tt := range floorMostSignificantTests { 24 | got := FloorMostSignificant(tt.v) 25 | if got != tt.want { 26 | t.Errorf("mathutil.FloorMostSignificant(%d) Mismatch: want [%d], got [%d]", 27 | tt.v, tt.want, got) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /time/examples/diff_days/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | tu "github.com/grokify/mogo/time/timeutil" 10 | ) 11 | 12 | func main() { 13 | t1raw := os.Args[1] 14 | t2raw := os.Args[2] 15 | 16 | t1, err := time.Parse(tu.RFC3339FullDate, t1raw) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | t2, err := time.Parse(tu.RFC3339FullDate, t2raw) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | t1, t2 = tu.MinMax(t1, t2) 26 | dur := t2.Sub(t1) 27 | hours := dur.Hours() 28 | days := hours / 24.0 29 | fmt.Printf("DAYS: %v\n", days) 30 | weeks := days / 7.0 31 | fmt.Printf("WEEKS: %v\n", weeks) 32 | 33 | fmt.Println("DONE") 34 | } 35 | -------------------------------------------------------------------------------- /math/ratio/ratio_test.go: -------------------------------------------------------------------------------- 1 | package ratio 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var ratioIntTests = []struct { 8 | x1 int 9 | y1 int 10 | x2 int 11 | y2 int 12 | xo int 13 | yo int 14 | }{ 15 | {600, 400, 1950, 1300, 1950, 1300}, 16 | {600, 400, 1950, 0, 1950, 1300}, 17 | {600, 400, 0, 1300, 1950, 1300}, 18 | {600, 400, 0, 0, 600, 400}, 19 | } 20 | 21 | func TestRatioInt(t *testing.T) { 22 | for _, tt := range ratioIntTests { 23 | x, y := RatioInt(tt.x1, tt.y1, tt.x2, tt.y2) 24 | if x != tt.xo || y != tt.yo { 25 | t.Errorf("mathutil.Ratio Error: Ratio(%v, %v, %v, %v) Want(%v, %v) Got(%v, %v)", 26 | tt.x1, tt.y1, tt.x2, tt.y2, 27 | tt.xo, tt.yo, 28 | x, y) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /time/timeutil/week_test.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | var weekdayTests = []struct { 9 | v int 10 | want time.Weekday 11 | }{ 12 | {-14, time.Sunday}, 13 | {-13, time.Monday}, 14 | {-1, time.Saturday}, 15 | {0, time.Sunday}, 16 | {6, time.Saturday}, 17 | {7, time.Sunday}, 18 | {15, time.Monday}, 19 | {23, time.Tuesday}, 20 | {29, time.Monday}, 21 | } 22 | 23 | func TestWeekdayNormalize(t *testing.T) { 24 | for _, tt := range weekdayTests { 25 | got := WeekdayNormalized(time.Weekday(tt.v)) 26 | 27 | if got != tt.want { 28 | t.Errorf("mismatch WeekdayNormalize(%d): want [%s], got [%s]", tt.v, tt.want.String(), got.String()) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crypto/randutil/string.go: -------------------------------------------------------------------------------- 1 | package randutil 2 | 3 | import "github.com/grokify/mogo/encoding/basex" 4 | 5 | // RandString returns a random string of length `length` using the supplied alphabet. 6 | // If no alphabet is provided, `AlphabetBase16`, aha hexadecimal is used. 7 | func RandString(alphabet string, length uint) (string, error) { 8 | if length == 0 { 9 | return "", nil 10 | } 11 | if len(alphabet) == 0 { 12 | alphabet = basex.AlphabetBase16 13 | } 14 | var out string 15 | for i := uint(0); i < length; i++ { 16 | idx, err := CryptoRandIntInRange(0, len(alphabet)-1) 17 | if err != nil { 18 | return "", err 19 | } else { 20 | out += string(alphabet[idx]) 21 | } 22 | } 23 | return out, nil 24 | } 25 | -------------------------------------------------------------------------------- /time/customtime/rfc3339fulldate.go: -------------------------------------------------------------------------------- 1 | package customtime 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/grokify/mogo/time/timeutil" 9 | ) 10 | 11 | type TimeRFC3339 struct { 12 | time.Time 13 | } 14 | 15 | func (ct *TimeRFC3339) UnmarshalJSON(b []byte) (err error) { 16 | if s := strings.Trim(string(b), "\""); s == "null" { 17 | ct.Time = time.Time{} 18 | } else { 19 | ct.Time, err = time.Parse(timeutil.RFC3339FullDate, s) 20 | } 21 | return 22 | } 23 | 24 | func (ct *TimeRFC3339) MarshalJSON() ([]byte, error) { 25 | if ct.Time.IsZero() { 26 | return []byte("null"), nil 27 | } else { 28 | return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(timeutil.RFC3339FullDate))), nil 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /time/timeutil/timeutil_projection.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // QuarterProjection takes a time and numeric value, estimating the 8 | // value at the end of the quarter using a straight-line projection. 9 | func QuarterProjection(dt time.Time, current float64) float64 { 10 | qtStart := quarterStart(dt) 11 | durQ2D := dt.Sub(qtStart) 12 | qtNext := TimeDT6AddNMonths(qtStart, 3) 13 | durQtr := qtNext.Sub(qtStart) 14 | 15 | projection := current / durQ2D.Seconds() * durQtr.Seconds() 16 | return projection 17 | } 18 | 19 | /* 20 | 21 | Gap 22 | Actual 23 | Run Rate 24 | Target 25 | 26 | Target 27 | Actual 28 | Run Rate 29 | Gap 30 | 31 | Target 32 | Shortfall 33 | 34 | Current 35 | 36 | */ 37 | -------------------------------------------------------------------------------- /time/year/year.go: -------------------------------------------------------------------------------- 1 | package year 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/grokify/mogo/time/timeutil" 7 | ) 8 | 9 | // TimesYearStarts returns time series of months given start and end input times. 10 | func TimesYearStarts(times ...time.Time) timeutil.Times { 11 | timesStarts := timeutil.Times{} 12 | if len(times) == 0 { 13 | return timesStarts 14 | } 15 | min, max := timeutil.SliceMinMax(times) 16 | minYear := timeutil.NewTimeMore(min, 0).YearStart() 17 | maxYear := timeutil.NewTimeMore(max, 0).YearStart() 18 | curYear := minYear 19 | for curYear.Before(maxYear) || curYear.Equal(maxYear) { 20 | timesStarts = append(timesStarts, curYear) 21 | curYear = curYear.AddDate(1, 0, 0) 22 | } 23 | return timesStarts 24 | } 25 | -------------------------------------------------------------------------------- /type/strslices/index_test.go: -------------------------------------------------------------------------------- 1 | package strslices 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var indexMultiTests = []struct { 9 | s string 10 | substr []string 11 | idx int 12 | }{ 13 | {"hello", []string{"foo", "bar", "hello"}, 0}, 14 | {"hello", []string{"foo", "bar", "ello"}, 1}, 15 | {"hello", []string{"foo", "bar", "baz"}, -1}, 16 | {"hello", []string{"foo", "bar", "hello"}, 0}, 17 | } 18 | 19 | func TestIndexMulti(t *testing.T) { 20 | for _, tt := range indexMultiTests { 21 | idx := IndexMulti(tt.s, tt.substr...) 22 | if idx != tt.idx { 23 | t.Errorf("stringsutil.IndexMore(%s, []string{%s}) Error: want [%d], got [%d]", 24 | tt.s, strings.Join(tt.substr, ","), tt.idx, idx) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /time/month/strconv_test.go: -------------------------------------------------------------------------------- 1 | package month 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/grokify/mogo/strconv/strconvutil" 8 | ) 9 | 10 | var startEndDT6sTests = []struct { 11 | v []int 12 | min int 13 | max int 14 | }{ 15 | {[]int{}, -1, -1}, 16 | {[]int{202301}, 202301, 0}, 17 | {[]int{202201, 202312}, 202201, 202312}, 18 | } 19 | 20 | func TestStartEndDT6sTests(t *testing.T) { 21 | for _, tt := range startEndDT6sTests { 22 | gotSta, gotEnd := StartEndDT6s(tt.v) 23 | if gotSta != tt.min || gotEnd != tt.max { 24 | t.Errorf("month.StartEndDT6s(%s): want (%d, %d), got (%d, %d)", 25 | strings.Join(strconvutil.SliceItoaMore(tt.v, false, false), ","), 26 | tt.min, tt.max, 27 | gotSta, gotEnd) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /net/http/httputilmore/http_method_test.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var parseHTTPMethodTests = []struct { 8 | v string 9 | wantString string 10 | }{ 11 | {" get ", "GET"}, 12 | {" PaTcH ", "PATCH"}, 13 | } 14 | 15 | func TestParseHTTPMethod(t *testing.T) { 16 | for _, tt := range parseHTTPMethodTests { 17 | canonicalStringTry, err := ParseHTTPMethodString(tt.v) 18 | 19 | if err != nil { 20 | t.Errorf("httputilmore.ParseHTTPMethodString(\"%s\") Error: [%s]", 21 | tt.v, err.Error()) 22 | } 23 | if canonicalStringTry != tt.wantString { 24 | t.Errorf("httputilmore.ParseHTTPMethodString(\"%s\") Fail: want [%s] got [%s]", 25 | tt.v, tt.wantString, canonicalStringTry) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /log/slogutil/logger.go: -------------------------------------------------------------------------------- 1 | package slogutil 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log/slog" 7 | ) 8 | 9 | type NullHandler struct{} 10 | 11 | func (NullHandler) Enabled(_ context.Context, _ slog.Level) bool { return false } 12 | func (NullHandler) Handle(_ context.Context, _ slog.Record) error { return nil } 13 | func (h NullHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h } 14 | func (h NullHandler) WithGroup(_ string) slog.Handler { return h } 15 | 16 | // Null is an inexpensive nop logger. 17 | func Null() *slog.Logger { 18 | return slog.New(NullHandler{}) 19 | } 20 | 21 | // Discard is an expensive nop logger using the stdlib. 22 | func Discard() *slog.Logger { 23 | return slog.New(slog.NewJSONHandler(io.Discard, nil)) 24 | } 25 | -------------------------------------------------------------------------------- /net/http/har/examples/read_har/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/grokify/mogo/fmt/fmtutil" 10 | "github.com/grokify/mogo/net/http/har" 11 | ) 12 | 13 | func main() { 14 | filename := "path/to/my.har" 15 | 16 | bytes, err := os.ReadFile(filename) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | h := har.Log{} 22 | 23 | err = json.Unmarshal(bytes, &h) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | fmtutil.MustPrintJSON(h) 28 | 29 | for _, entry := range h.Log.Entries { 30 | method := entry.Request.Method 31 | url := entry.Request.URL 32 | endpoint := method + " " + url 33 | fmt.Printf("ENDPOINT [%v]\n", endpoint) 34 | } 35 | fmt.Println("DONE") 36 | } 37 | -------------------------------------------------------------------------------- /text/stringcase/caser.go: -------------------------------------------------------------------------------- 1 | package stringcase 2 | 3 | type Caser struct { 4 | display string 5 | } 6 | 7 | func NewCaser(s string) Caser { 8 | return Caser{display: s} 9 | } 10 | 11 | func (c Caser) Display() string { return c.display } 12 | func (c Caser) CamelCase() string { return ToCamelCase(c.display) } 13 | func (c Caser) KebabCase() string { return ToKebabCase(c.display) } 14 | func (c Caser) PascalCase() string { return ToPascalCase(c.display) } 15 | func (c Caser) SnakeCase() string { return ToSnakeCase(c.display) } 16 | 17 | type CaserNoun struct { 18 | Singular Caser 19 | Plural Caser 20 | } 21 | 22 | func NewCaserNoun(singular, plural string) CaserNoun { 23 | return CaserNoun{ 24 | Singular: NewCaser(singular), 25 | Plural: NewCaser(plural)} 26 | } 27 | -------------------------------------------------------------------------------- /codegen/nestedstructtopointer_test.go: -------------------------------------------------------------------------------- 1 | package codegen 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var goCodeNestedstructsToPointersTests = []struct { 8 | v string 9 | want string 10 | }{ 11 | {"type FooBar struct {\nFoo Bar}\n", "type FooBar struct {\nFoo *Bar}\n"}, 12 | {"type FooBar struct {\nFoo []Bar}\n", "type FooBar struct {\nFoo []*Bar}\n"}, 13 | {"type FooBar struct {\nFoo map[string]Bar}\n", "type FooBar struct {\nFoo map[string]*Bar}\n"}, 14 | } 15 | 16 | func TestGoCodeNestedstructsToPointers(t *testing.T) { 17 | for _, tt := range goCodeNestedstructsToPointersTests { 18 | got := GoCodeNestedstructsToPointers(tt.v) 19 | if got != tt.want { 20 | t.Errorf("GoCodeNestedstructsToPointers(\"%v\"): want [%v], got [%v]", tt.v, tt.want, got) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crypto/argon2util/argon2_test.go: -------------------------------------------------------------------------------- 1 | package argon2util 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | var hashTests = []struct { 9 | input string 10 | salt string 11 | want string 12 | }{ 13 | { 14 | "My Secret Password", 15 | "6368616e676520746869732070617373776f726420746f206120736563726574", 16 | "1XESZ8F6OGV200JDNXED8BAPU79UG5JA3KTJW7VMJCICX482S8"}} 17 | 18 | func TestHash(t *testing.T) { 19 | for _, tt := range hashTests { 20 | saltBytes, err := hex.DecodeString(tt.salt) 21 | if err != nil { 22 | t.Errorf("TestRoundTrip cannot decode key: %v", err) 23 | } 24 | 25 | got := HashSimpleBase36([]byte(tt.input), saltBytes) 26 | 27 | if got != tt.want { 28 | t.Errorf("Argon2 Error: want [%v], got [%v]", tt.want, got) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /text/languageutil/ietfbcp47.go: -------------------------------------------------------------------------------- 1 | package languageutil 2 | 3 | // ISO 639-1 language codes 4 | const ( 5 | Arabic = "ar" 6 | Bangla = "bn" 7 | Chinese = "zh" 8 | Czech = "cs" 9 | Danish = "da" 10 | Dutch = "nl" 11 | English = "en" 12 | German = "de" 13 | Greek = "el" 14 | Spanish = "es" 15 | Finnish = "fi" 16 | French = "fr" 17 | Hebrew = "he" 18 | Hindi = "hi" 19 | Hungarian = "hu" 20 | Indonesian = "id" 21 | Italian = "it" 22 | Japanese = "jp" 23 | Korean = "ko" 24 | Norwegian = "no" 25 | Polish = "pl" 26 | Portuguese = "pt" 27 | Romanian = "ro" 28 | Russian = "ru" 29 | Slovak = "sk" 30 | Swedish = "sv" 31 | Tamil = "ta" 32 | Thai = "th" 33 | Turkish = "tr" 34 | ) 35 | -------------------------------------------------------------------------------- /config/env_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | var dmyhm2ParseTests = []struct { 9 | v1 string 10 | v2 string 11 | v3 string 12 | v4 string 13 | delimit string 14 | want string 15 | }{ 16 | {"RINGCENTRAL_TOKEN", "ABC", "RINGCENTRAL_TOKEN_2", "def", "", "ABCdef"}, 17 | } 18 | 19 | // TestJoinEnvNumbered ensures config.JoinEnvNumbered joins environment variables correctly. 20 | func TestJoinEnvNumbered(t *testing.T) { 21 | for _, tt := range dmyhm2ParseTests { 22 | os.Setenv(tt.v1, tt.v2) 23 | os.Setenv(tt.v3, tt.v4) 24 | got := JoinEnvNumbered(tt.v1, tt.delimit, 2, true) 25 | if got != tt.want { 26 | t.Errorf("config.JoinEnvNumbered(\"%v\", \"\", 2, true) Mismatch: want %v, got %v", tt.v1, tt.want, got) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /encoding/jsonutil/json_hash_test.go: -------------------------------------------------------------------------------- 1 | package jsonutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var jsonHashTests = []struct { 8 | v map[string]any 9 | padding rune 10 | want string 11 | }{ 12 | {map[string]any{"foo": "bar"}, rune(0), "4EXQNYHIMEW4LRMDGXXZYWQGHXIQF7ILMNWX5M2AOJTHT26OHYIQ"}, 13 | {map[string]any{"foo": 123}, '=', "EF7MB6CKRBML5YFXLMDZQ3HNEWAX6COQNL43CVHRSPRYQD4KOWKQ===="}, 14 | } 15 | 16 | func TestJSONHashes(t *testing.T) { 17 | for _, tt := range jsonHashTests { 18 | try, err := SHA512d256Base32(tt.v, tt.padding) 19 | if err != nil { 20 | t.Errorf("jsonutil.SHA512d256Base32: err (%s)", err.Error()) 21 | } else if try != tt.want { 22 | t.Errorf("jsonutil.SHA512d256Base32(\"%s\", '%v'): want (%s) got (%s)", tt.v, tt.padding, tt.want, try) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /encoding/xmlutil/xmlutil.go: -------------------------------------------------------------------------------- 1 | package xmlutil 2 | 3 | import ( 4 | "encoding/xml" 5 | "io" 6 | "os" 7 | ) 8 | 9 | func MarshalIndent(v any, prefix, indent string, addDoctype bool) ([]byte, error) { 10 | if data, err := xml.MarshalIndent(v, prefix, indent); err != nil || !addDoctype { 11 | return data, err 12 | } else { 13 | out := []byte(xml.Header) 14 | return append(out, data...), nil 15 | } 16 | } 17 | 18 | func UnmarshalFile(name string, v any) error { 19 | if f, err := os.Open(name); err != nil { 20 | return err 21 | } else { 22 | defer f.Close() 23 | return UnmarshalReader(f, v) 24 | } 25 | } 26 | 27 | func UnmarshalReader(r io.Reader, v any) error { 28 | if data, err := io.ReadAll(r); err != nil { 29 | return err 30 | } else { 31 | return xml.Unmarshal(data, v) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /image/imageutil/read_test.go: -------------------------------------------------------------------------------- 1 | package imageutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var readImageFileTests = []struct { 8 | filename string 9 | formatName string 10 | }{ 11 | {"read_testdata/gopher_color.png", FormatNamePNG}, 12 | {"read_testdata/gopher_color.jpg", FormatNameJPG}, 13 | // {"read_testdata/gopher_color.webp", FormatNameWEBP}, 14 | } 15 | 16 | func TestReadImageFile(t *testing.T) { 17 | for _, tt := range readImageFileTests { 18 | _, formatName, err := ReadImageFile(tt.filename) 19 | if err != nil { 20 | t.Errorf("imageutil.ReadImageFile(\"%s\") Error: [%v]", tt.filename, err.Error()) 21 | } 22 | if formatName != tt.formatName { 23 | t.Errorf("imageutil.ReadImageFile(\"%s\") Format Want [%s], Got [%v]", tt.filename, tt.formatName, formatName) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /encoding/jsonpointer/transform_test.go: -------------------------------------------------------------------------------- 1 | package jsonpointer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var jsonschemaEscapeTests = []struct { 8 | unescaped string 9 | escaped string 10 | }{ 11 | {"a~b", "a~0b"}, 12 | {"a/b", "a~1b"}, 13 | {"a~/~/b", "a~0~1~0~1b"}, 14 | {"a~~~///b", "a~0~0~0~1~1~1b"}, 15 | } 16 | 17 | func TestJSONSchemaEscape(t *testing.T) { 18 | for _, tt := range jsonschemaEscapeTests { 19 | tryEscape := PropertyNameEscape(tt.unescaped) 20 | if tryEscape != tt.escaped { 21 | t.Errorf("jsonutil.PropertyNameEscape: want [%v] got [%v]", tt.escaped, tryEscape) 22 | } 23 | tryUnescape := PropertyNameUnescape(tt.escaped) 24 | if tryUnescape != tt.unescaped { 25 | t.Errorf("jsonutil.PropertyNameUnescape: want [%v] got [%v]", tt.unescaped, tryUnescape) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /net/http/httputilmore/httputil_test.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var parseHeaderTests = []struct { 8 | v string 9 | want [][]string 10 | }{ 11 | {"Foo: Bar", [][]string{{"Foo", "Bar"}}}, 12 | {"Foo: Bar\nBaz: QUX", [][]string{{"Foo", "Bar"}, {"Baz", "QUX"}}}, 13 | {"Foo: Bar\nBaz: QUX\nQUUX: quuz", [][]string{{"Foo", "Bar"}, {"Baz", "QUX"}, {"quux", "quuz"}}}, 14 | } 15 | 16 | func TestParseHeader(t *testing.T) { 17 | for _, tt := range parseHeaderTests { 18 | header := ParseHeader(tt.v) 19 | 20 | for _, pair := range tt.want { 21 | got := header.Get(pair[0]) 22 | want := pair[1] 23 | if got != want { 24 | t.Errorf("httputilmore.ParseHeader() Error: header [%v], want [%v], got [%v]", 25 | pair[0], want, got) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /encoding/jsonutil/jsonraw/unescape.go: -------------------------------------------------------------------------------- 1 | package jsonraw 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type unescapeWrap struct { 9 | Raw string `json:"raw"` 10 | } 11 | 12 | // Unescape is designed to unescape a stringified JSON. It is typically used 13 | // after a stringified JSON has been embedded as a value in an wrapper JSON object. 14 | // When using this, do not include outer quotes. 15 | func Unescape(b []byte, prefix, indent string) ([]byte, error) { 16 | wrapped := fmt.Sprintf("{\"raw\":\"%s\"}", string(b)) 17 | w := &unescapeWrap{} 18 | if err := json.Unmarshal([]byte(wrapped), w); err != nil { 19 | return nil, err 20 | } else if formatted, err := IndentBytes([]byte(w.Raw), prefix, indent); err != nil { 21 | return nil, err 22 | } else { 23 | return formatted, nil 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /text/currencyutil/exchange_rates.go: -------------------------------------------------------------------------------- 1 | package currencyutil 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type ExchangeRateSimple struct { 9 | BaseCurrency string 10 | CurrencyRates map[string]float64 11 | } 12 | 13 | func (xr *ExchangeRateSimple) ConvertToBase(in float64, cur string) (float64, error) { 14 | cur = strings.ToUpper(strings.TrimSpace(cur)) 15 | if multiplier, ok := xr.CurrencyRates[cur]; ok { 16 | return in * multiplier, nil 17 | } 18 | return in, fmt.Errorf("E_EXCHANGE_RATE_CANNOT_FIND [%s]", cur) 19 | } 20 | 21 | func ExampleExchangeRates() ExchangeRateSimple { 22 | return ExchangeRateSimple{ 23 | BaseCurrency: "USD", 24 | CurrencyRates: map[string]float64{ 25 | "AUD": 0.637368, 26 | "CAD": 0.709545, 27 | "EUR": 1.08641, 28 | "GBP": 1.24626, 29 | "USD": 1.0}} 30 | } 31 | -------------------------------------------------------------------------------- /time/timeutil/month_test.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | var monthEndDayTests = []struct { 9 | year int 10 | month time.Month 11 | day int 12 | }{ 13 | {2010, time.January, 31}, 14 | {2020, time.January, 31}, 15 | {2023, time.January, 31}, 16 | {2023, time.February, 28}, 17 | {2023, time.June, 30}, 18 | {2020, time.February, 29}, 19 | {2024, time.February, 29}, 20 | {2040, time.February, 29}, 21 | } 22 | 23 | // TestMonthEndDay returns the last day of a given month and year. 24 | func TestMonthEndDay(t *testing.T) { 25 | for _, tt := range monthEndDayTests { 26 | got := MonthEndDay(tt.year, tt.month) 27 | if got != tt.day { 28 | t.Errorf("timeutil.MonthEndDay(%d, %d) Mismatch: want (%d), got (%d)", tt.year, tt.month, tt.day, got) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /type/number/slice.go: -------------------------------------------------------------------------------- 1 | package number 2 | 3 | import ( 4 | "golang.org/x/exp/constraints" 5 | 6 | "github.com/grokify/mogo/strconv/strconvutil" 7 | ) 8 | 9 | type Integers[C constraints.Integer] []C 10 | 11 | func (ints Integers[C]) MinMax() (*C, *C) { 12 | if len(ints) == 0 { 13 | return nil, nil 14 | } 15 | var min C 16 | var max C 17 | for i, v := range ints { 18 | if i == 0 { 19 | min = v 20 | max = v 21 | } else { 22 | if v < min { 23 | min = v 24 | } 25 | if v > max { 26 | max = v 27 | } 28 | } 29 | } 30 | return &min, &max 31 | } 32 | 33 | func (ints Integers[C]) Sum() C { 34 | var sum C 35 | for _, v := range ints { 36 | sum += v 37 | } 38 | return sum 39 | } 40 | 41 | func (ints Integers[C]) SumString() string { 42 | return strconvutil.Itoa(ints.Sum()) 43 | } 44 | -------------------------------------------------------------------------------- /errors/errorsutil/errorsutil_test.go: -------------------------------------------------------------------------------- 1 | package errorsutil 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | var wrapTests = []struct { 9 | err1String string 10 | err2String string 11 | err2wrapString string 12 | }{ 13 | {"foo", "bar", "bar: [foo]"}, 14 | {"foobar", "bazqux", "bazqux: [foobar]"}, 15 | } 16 | 17 | func TestWrap(t *testing.T) { 18 | for _, tt := range wrapTests { 19 | err1 := errors.New(tt.err1String) 20 | err2 := Wrap(err1, tt.err2String) 21 | if err2.Error() != tt.err2wrapString { 22 | t.Errorf("errorsutil.Wrap: want [%v] got [%v]", tt.err2wrapString, err2.Error()) 23 | } 24 | unwrapped := errors.Unwrap(err2) 25 | if unwrapped.Error() != tt.err1String { 26 | t.Errorf("errorsutil.Wrap/Unwrap: want [%v] got [%v]", tt.err1String, unwrapped.Error()) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pointer/simple_create.go: -------------------------------------------------------------------------------- 1 | // pointer package provides some pointer shortcuts. See: 2 | // 1: https://stackoverflow.com/questions/50830676/set-int-pointer-to-int-value-golang 3 | // 2: https://github.com/openlyinc/pointy 4 | package pointer 5 | 6 | func Clone[E any](e *E) *E { 7 | return Pointer(Dereference(e)) 8 | } 9 | 10 | func Dereference[E any](e *E) E { 11 | if e == nil { 12 | return *new(E) 13 | } else { 14 | return *e 15 | } 16 | } 17 | 18 | func DereferenceSlice[S ~[]*E, E any](s S) []E { 19 | var out []E 20 | for _, e := range s { 21 | out = append(out, *e) 22 | } 23 | return out 24 | } 25 | 26 | func Pointer[E any](e E) *E { return &e } 27 | 28 | func PointerSlice[S ~[]E, E any](s S) []*E { 29 | var out []*E 30 | for i := range s { 31 | out = append(out, &s[i]) 32 | } 33 | return out 34 | } 35 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | go: "1.23" 4 | linters: 5 | enable: 6 | - dogsled 7 | - dupl 8 | - gosec 9 | - misspell 10 | - nakedret 11 | - staticcheck 12 | - unconvert 13 | - unparam 14 | - whitespace 15 | settings: 16 | staticcheck: 17 | checks: 18 | - -QF1008 # Disable the "could remove embedded field" check 19 | - -ST1018 20 | exclusions: 21 | generated: lax 22 | presets: 23 | - comments 24 | - common-false-positives 25 | - legacy 26 | - std-error-handling 27 | paths: 28 | - third_party$ 29 | - builtin$ 30 | - examples$ 31 | formatters: 32 | enable: 33 | - gofmt 34 | - goimports 35 | exclusions: 36 | generated: lax 37 | paths: 38 | - third_party$ 39 | - builtin$ 40 | - examples$ 41 | -------------------------------------------------------------------------------- /errors/errorsutil/error_with_location_test.go: -------------------------------------------------------------------------------- 1 | package errorsutil 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | var errorWithLocationTests = []struct { 10 | fn func() error 11 | errMsgSuffix string 12 | }{ 13 | {func() error { return errors.New("test err") }, `mogo/errors/errorsutil/error_with_location_test.go:22)`}, 14 | } 15 | 16 | func TestErrorWithLocation(t *testing.T) { 17 | for _, tt := range errorWithLocationTests { 18 | tryErr := tt.fn() 19 | if tryErr == nil { 20 | panic("no error") 21 | } 22 | tryWithLocation := NewWithLocation(tryErr.Error()) 23 | if !strings.HasSuffix(tryWithLocation.Error(), tt.errMsgSuffix) { 24 | t.Errorf("errorsutil.NewErrorWithLocation(\"%s\"): mismatch want suffix [%s] got [%s]", tryErr.Error(), tt.errMsgSuffix, tryWithLocation.Error()) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /strconv/phonenumber/e164.go: -------------------------------------------------------------------------------- 1 | package phonenumber 2 | 3 | /* 4 | import ( 5 | "strings" 6 | 7 | "github.com/nyaruka/phonenumbers" 8 | ) 9 | 10 | func E164Format(numberToParse, defaultRegion string, numberFormat phonenumbers.PhoneNumberFormat) (string, error) { 11 | defaultRegion = strings.ToUpper(strings.TrimSpace(defaultRegion)) 12 | if len(defaultRegion) == 0 { 13 | defaultRegion = "US" 14 | } 15 | phone, err := phonenumbers.Parse(numberToParse, defaultRegion) 16 | if err != nil { 17 | return "", err 18 | } 19 | return phonenumbers.Format(phone, numberFormat), nil 20 | } 21 | 22 | func MustE164Format(numberToParse, defaultRegion string, numberFormat phonenumbers.PhoneNumberFormat) string { 23 | pn, err := E164Format(numberToParse, defaultRegion, numberFormat) 24 | if err != nil { 25 | panic(err) 26 | } 27 | return pn 28 | } 29 | */ 30 | -------------------------------------------------------------------------------- /html/htmlutil/token_filter.go: -------------------------------------------------------------------------------- 1 | package htmlutil 2 | 3 | /* 4 | type TokenFilters []TokenFilter 5 | 6 | func (filters TokenFilters) ByTokenType(tt html.TokenType) []TokenFilter { 7 | fils := []TokenFilter{} 8 | for _, fil := range filters { 9 | if fil.TokenType == tt { 10 | fils = append(fils, fil) 11 | } 12 | } 13 | return fils 14 | } 15 | 16 | // find next or 17 | type TokenFilter struct { 18 | TokenType html.TokenType 19 | AtomSet AtomSet 20 | } 21 | 22 | func NewTokenFilter(tokenType html.TokenType, atoms ...atom.Atom) *TokenFilter { 23 | return &TokenFilter{ 24 | TokenType: tokenType, 25 | AtomSet: NewAtomSet(atoms...)} 26 | } 27 | 28 | func (tf *TokenFilter) Match(t html.Token) bool { 29 | if tf.AtomSet.Exists(t.DataAtom) && 30 | t.Type == tf.TokenType { 31 | return true 32 | } 33 | return false 34 | } 35 | */ 36 | -------------------------------------------------------------------------------- /type/stringsutil/split_test.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "golang.org/x/exp/slices" 8 | ) 9 | 10 | var splitTrimSpaceTests = []struct { 11 | v string 12 | sep string 13 | exclEmpty bool 14 | want []string 15 | }{ 16 | {" foo , bar, , baz , qux ", ",", true, []string{"foo", "bar", "baz", "qux"}}, 17 | {" foo , bar, , baz , qux ", ",", false, []string{"foo", "bar", "", "baz", "qux"}}, 18 | } 19 | 20 | func TestSplitTrimSpace(t *testing.T) { 21 | for _, tt := range splitTrimSpaceTests { 22 | got := SplitTrimSpace(tt.v, tt.sep, tt.exclEmpty) 23 | if !slices.Equal(tt.want, got) { 24 | t.Errorf("stringsutil.TestSplitTrimSpace(\"%s\", \"%s\", %v) want (%s) got (%s)", 25 | tt.v, tt.sep, tt.exclEmpty, strings.Join(tt.want, ","), strings.Join(got, ",")) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /net/urlutil/pattern.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var rxPathVarToGeneric = regexp.MustCompile(`{[^}{]*}`) 9 | 10 | func VarsToGeneric(input string) string { 11 | return rxPathVarToGeneric.ReplaceAllString(input, "{}") 12 | } 13 | 14 | func MatchGeneric(path1, path2 string) bool { 15 | gen1 := VarsToGeneric(path1) 16 | gen2 := VarsToGeneric(path2) 17 | return gen1 == gen2 18 | } 19 | 20 | func EndpointString(path, method string, generic bool) string { 21 | path = strings.TrimSpace(path) 22 | method = strings.ToUpper(strings.TrimSpace(method)) 23 | parts := []string{} 24 | if len(path) > 0 { 25 | if generic { 26 | path = VarsToGeneric(path) 27 | } 28 | parts = append(parts, path) 29 | } 30 | if len(method) > 0 { 31 | parts = append(parts, method) 32 | } 33 | return strings.Join(parts, " ") 34 | } 35 | -------------------------------------------------------------------------------- /text/markdown/table_gfm_test.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestTableRowsToMarkdown(t *testing.T) { 9 | var markdownFormatTests = []struct { 10 | columns []string 11 | rows [][]string 12 | }{ 13 | {[]string{"a", "a"}, [][]string{{"bb", "bbb"}, {"cccc", "ccccc"}}}, 14 | } 15 | 16 | for _, tt := range markdownFormatTests { 17 | allRows := [][]string{tt.columns} 18 | allRows = append(allRows, tt.rows...) 19 | 20 | md := TableRowsToMarkdown(allRows, "\n", true, true) 21 | 22 | lines := strings.Split(md, "\n") 23 | 24 | length := 0 25 | for i, l := range lines { 26 | if i == 0 { 27 | length = len(l) 28 | } else if len(l) != length { 29 | t.Errorf("Table.Markdown() Mismatch: first line length (%d) line (%d) length (%d)", 30 | length, i, len(l)) 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mime/mimeutil/mediatype_test.go: -------------------------------------------------------------------------------- 1 | package mimeutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/grokify/mogo/net/http/httputilmore" 7 | ) 8 | 9 | var isTypeTests = []struct { 10 | v string 11 | tryType string 12 | isType bool 13 | }{ 14 | {"IMAGE/PNG", httputilmore.ContentTypeImagePNG, true}, 15 | {"IMAGE/PNG;", httputilmore.ContentTypeImagePNG, true}, 16 | {"IMAGE/PNG ;", httputilmore.ContentTypeImagePNG, true}, 17 | {"XIMAGE/PNG ;", httputilmore.ContentTypeImagePNG, false}, 18 | {httputilmore.ContentTypeAppXMLUtf8, httputilmore.ContentTypeAppXML, true}, 19 | } 20 | 21 | func TestIsType(t *testing.T) { 22 | for _, tt := range isTypeTests { 23 | isType := IsType(tt.tryType, tt.v) 24 | if isType != tt.isType { 25 | t.Errorf("mimeutil.IsType(\"%s\", \"%s\") Fail: want [%v] got [%v]", 26 | tt.v, tt.tryType, tt.isType, isType) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /type/stringsutil/identify.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | func DigitsOnly(input string) string { 8 | return rxNonNumeric.ReplaceAllString(input, "") 9 | } 10 | 11 | var ( 12 | rxAlpha = regexp.MustCompile(`^[A-Za-z]+$`) 13 | rxAlphaNumeric = regexp.MustCompile(`^[0-9A-Za-z]+$`) 14 | rxAlphaNumericNot = regexp.MustCompile(`[^0-9A-Za-z]`) 15 | rxNumeric = regexp.MustCompile(`^[0-9]+$`) 16 | rxNonNumeric = regexp.MustCompile(`[^0-9]`) 17 | ) 18 | 19 | func IsAlpha(s string) bool { 20 | return rxAlpha.MatchString(s) 21 | } 22 | 23 | func IsAlphaNumeric(s string) bool { 24 | return rxAlphaNumeric.MatchString(s) 25 | } 26 | 27 | func IsNumeric(s string) bool { 28 | return rxNumeric.MatchString(s) 29 | } 30 | 31 | func ToAlphaNumeric(s string) string { 32 | return rxAlphaNumericNot.ReplaceAllString(s, "") 33 | } 34 | -------------------------------------------------------------------------------- /net/urlutil/url_template_test.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var parseURLTemplateTests = []struct { 8 | v string 9 | want string 10 | }{ 11 | {"https://{customer}.example.com:{port}/v5", "/v5"}, 12 | {"https://customer.example.com:0/{v5}", "/{v5}"}, 13 | {"https://%7BcustomerId%7D.sexample.com:0/v8.0", "/v8.0"}, 14 | {"https://{customer}.example.com:0/v8.0/", "/v8.0/"}, 15 | } 16 | 17 | func TestParseURLTemplate(t *testing.T) { 18 | for _, tt := range parseURLTemplateTests { 19 | u1, err := ParseURLTemplate(tt.v) 20 | if err != nil { 21 | t.Errorf("urlutil.ParseURLTemplate() Error: input [%v], want [%v], got error [%v]", 22 | tt.v, tt.want, err.Error()) 23 | } 24 | if u1.Path != tt.want { 25 | t.Errorf("urlutil.ParseURLTemplate() Failure: input [%v], want [%v], got [%v]", 26 | tt.v, tt.want, u1.Path) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /time/timezone/examples/build_constants/main.go: -------------------------------------------------------------------------------- 1 | // main this code was sourced from Stack Overflow here: 2 | // https://stackoverflow.com/a/40130882/1908967 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | var zoneDirs = []string{ 12 | // Update path according to your OS 13 | "/usr/share/zoneinfo/", 14 | "/usr/share/lib/zoneinfo/", 15 | "/usr/lib/locale/TZ/", 16 | } 17 | 18 | var zoneDir string 19 | 20 | func main() { 21 | for _, zoneDir = range zoneDirs { 22 | ReadFile("") 23 | } 24 | } 25 | 26 | func ReadFile(path string) { 27 | files, _ := os.ReadDir(zoneDir + path) 28 | for _, f := range files { 29 | if f.Name() != strings.ToUpper(f.Name()[:1])+f.Name()[1:] { 30 | continue 31 | } 32 | if f.IsDir() { 33 | ReadFile(path + "/" + f.Name()) 34 | } else { 35 | fmt.Println((path + "/" + f.Name())[1:]) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /os/osutil/cmd/file_sizes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/grokify/mogo/fmt/fmtutil" 8 | "github.com/grokify/mogo/os/osutil" 9 | flags "github.com/jessevdk/go-flags" 10 | ) 11 | 12 | type Options struct { 13 | Dir string `short:"d" long:"dir" description:"Directory"` 14 | Columns []string `short:"c" long:"columns" description:"Columns"` 15 | } 16 | 17 | func main() { 18 | opts := Options{} 19 | _, err := flags.Parse(&opts) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | ems, err := osutil.ReadDirFiles(opts.Dir, true, true, true) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | rows, err := ems.Rows(false, true, opts.Columns...) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | err = fmtutil.PrintJSON(rows) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | 38 | fmt.Println("DONE") 39 | } 40 | -------------------------------------------------------------------------------- /regexp/regexputil/regexputil_test.go: -------------------------------------------------------------------------------- 1 | package regexputil 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | "testing" 7 | ) 8 | 9 | var findStringSubmatchNamedMapTests = []struct { 10 | v string 11 | v2 string 12 | want map[string]string 13 | }{ 14 | {`(?P\w+):(?P\w+)`, "num:123", map[string]string{"KEY": "num", "VAL": "123"}}, 15 | {`(?P\w+).(?P\w+)`, "foo.bar", map[string]string{"first": "foo", "second": "bar"}}, 16 | } 17 | 18 | func TestFindStringSubmatchNamedMap(t *testing.T) { 19 | for _, tt := range findStringSubmatchNamedMapTests { 20 | rx := regexp.MustCompile(tt.v) 21 | resMss := FindStringSubmatchNamedMap(rx, tt.v2) 22 | 23 | if eq := reflect.DeepEqual(tt.want, resMss); !eq { 24 | t.Errorf("regepxutil.FindStringSubmatchNamedMap() Error: with [%v][%v], want [%v], got [%v]", 25 | tt.v, tt.v2, tt.want, resMss) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /strconv/humannameparser/humannameparser.go: -------------------------------------------------------------------------------- 1 | package humannameparser 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type HumanName struct { 8 | Name string 9 | LeadingInit string 10 | FirstName string 11 | MiddleName string 12 | LastName string 13 | Suffix string 14 | 15 | Suffixes string 16 | Prefixes string 17 | } 18 | 19 | // ParseHumanName is a initial stub function to parse human names. 20 | // The goal is to have a full implementation like a port of: 21 | // https://github.com/jasonpriem/HumanNameParser.php 22 | func ParseHumanName(n string) (*HumanName, error) { 23 | h := &HumanName{} 24 | parts := strings.Split(n, " ") 25 | if len(parts) == 2 { 26 | h.FirstName = parts[0] 27 | h.LastName = parts[1] 28 | } else if len(parts) == 3 { 29 | h.FirstName = parts[0] 30 | h.MiddleName = parts[1] 31 | h.LastName = parts[2] 32 | } 33 | return h, nil 34 | } 35 | -------------------------------------------------------------------------------- /type/stringsutil/join_test.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestJoinLiterary(t *testing.T) { 9 | var joinLiteraryTests = []struct { 10 | v []string 11 | sep string 12 | joinWord string 13 | want string 14 | }{ 15 | {[]string{}, ",", "and", ""}, 16 | {[]string{"Foo"}, ",", "and", "Foo"}, 17 | {[]string{"Foo", "Bar"}, ",", "and", "Foo and Bar"}, 18 | {[]string{"Foo", "Bar", "Baz"}, ",", "and", "Foo, Bar, and Baz"}, 19 | {[]string{"Foo", "Bar", "Bax", "Qux"}, ",", "and", "Foo, Bar, Bax, and Qux"}} 20 | 21 | for _, tt := range joinLiteraryTests { 22 | try := tt.v 23 | got := JoinLiterary(try, tt.sep, tt.joinWord) 24 | if got != tt.want { 25 | t.Errorf("TestJoinLanguage failed: Have [%v] Got [%v] Want [%v]", 26 | strings.Join(tt.v, ", "), 27 | got, 28 | tt.want) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /net/http/httputilmore/nethttputil.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/grokify/mogo/type/stringsutil" 8 | ) 9 | 10 | // https://stackoverflow.com/questions/15407719/in-gos-http-package-how-do-i-get-the-query-string-on-a-post-request 11 | 12 | func GetReqQueryParam(req *http.Request, paramName string) string { 13 | return strings.TrimSpace(req.URL.Query().Get(paramName)) 14 | } 15 | 16 | func GetReqQueryParamSplit(req *http.Request, paramName, sep string) []string { 17 | return stringsutil.SliceTrimSpace( 18 | strings.Split( 19 | GetReqQueryParam(req, paramName), 20 | sep), 21 | true, 22 | ) 23 | } 24 | 25 | /* 26 | type RequestUtil struct { 27 | Request *http.Request 28 | } 29 | 30 | func (ru *RequestUtil) QueryParamString(paramName string) string { 31 | return strings.TrimSpace(ru.Request.URL.Query().Get(paramName)) 32 | } 33 | */ 34 | -------------------------------------------------------------------------------- /crypto/aesutil/aesecb/aesecb_test.go: -------------------------------------------------------------------------------- 1 | package aesecb 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var aesecbTests = []struct { 9 | plaintext string 10 | key string 11 | }{ 12 | {"My Secret Password", "0123456789abcdef"}, 13 | {"My Secret Password", "1234567890123456"}, 14 | } 15 | 16 | func TestEncryptDecrypt(t *testing.T) { 17 | for _, tt := range aesecbTests { 18 | enc, err := EncryptBase64(tt.plaintext, tt.key) 19 | if err != nil { 20 | t.Errorf("aesecb.EncryptBase64 error(%s)", err.Error()) 21 | } 22 | dec, err := DecryptBase64(enc, tt.key) 23 | if err != nil { 24 | t.Errorf("aesecb.DecryptBase64 error(%s)", err.Error()) 25 | } 26 | if dec != tt.plaintext { 27 | fmt.Printf("[%v]\n", []byte(dec)) 28 | fmt.Printf("[%v]\n", []byte(tt.plaintext)) 29 | t.Errorf("encrypt/decrypt AES ECB error: want decrypted (%s), got (%s)", tt.plaintext, dec) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crypto/tlsutil/constants.go: -------------------------------------------------------------------------------- 1 | package tlsutil 2 | 3 | import ( 4 | "crypto/tls" 5 | ) 6 | 7 | type TLSVersion uint16 8 | 9 | const ( 10 | VersionTLS13 TLSVersion = tls.VersionTLS13 11 | VersionTLS12 TLSVersion = tls.VersionTLS12 12 | VersionTLS11 TLSVersion = tls.VersionTLS11 13 | VersionTLS10 TLSVersion = tls.VersionTLS10 14 | VersionSSL30 TLSVersion = tls.VersionSSL30 15 | ) 16 | 17 | func (t TLSVersion) String() string { 18 | switch t { 19 | case VersionTLS13: 20 | return "TLS 1.3" 21 | case VersionTLS12: 22 | return "TLS 1.2" 23 | case VersionTLS11: 24 | return "TLS 1.1" 25 | case VersionTLS10: 26 | return "TLS 1.0" 27 | case VersionSSL30: 28 | return "SSL 3.0" 29 | default: 30 | return "Unknown" 31 | } 32 | } 33 | 34 | func TLSVersions() []TLSVersion { 35 | return []TLSVersion{ 36 | tls.VersionTLS10, 37 | tls.VersionTLS11, 38 | tls.VersionTLS12, 39 | tls.VersionTLS13} 40 | } 41 | -------------------------------------------------------------------------------- /text/markdown/markdown_test.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import "testing" 4 | 5 | var linkifyTests = []struct { 6 | url string 7 | text string 8 | markdown string 9 | }{ 10 | { 11 | "https://example.com", "", "[https://example.com](https://example.com)"}, 12 | { 13 | " https://example.com ", "", "[https://example.com](https://example.com)"}, 14 | { 15 | "https://example.com", "Example", "[Example](https://example.com)"}, 16 | { 17 | " https://example.com", "Example", "[Example](https://example.com)"}, 18 | { 19 | "", "Example", "Example"}, 20 | { 21 | " ", "Example", "Example"}, 22 | } 23 | 24 | func TestLinkify(t *testing.T) { 25 | for _, tt := range linkifyTests { 26 | got := Linkify(tt.url, tt.text) 27 | if got != tt.markdown { 28 | t.Errorf("markdown.Linkify(\"%s\", \"%s\") Mismatch: want [%s] got [%s]", 29 | tt.url, tt.text, tt.markdown, got) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /time/examples/time_custom/time_custom.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/grokify/mogo/fmt/fmtutil" 9 | "github.com/grokify/mogo/time/timeutil" 10 | ) 11 | 12 | // http://choly.ca/post/go-json-marshalling/ 13 | // https://stackoverflow.com/questions/25087960/json-unmarshal-time-that-isnt-in-rfc-3339-format 14 | // https://stackoverflow.com/questions/23695479/format-timestamp-in-outgoing-json-in-golang 15 | // https://blog.charmes.net/post/json-dates-go/#custom-type 16 | 17 | type Data struct { 18 | MyTime timeutil.RFC3339YMDTime 19 | } 20 | 21 | func main() { 22 | jsonStr := `{"MyTime":"2001-02-03"}` 23 | data := Data{} 24 | err := json.Unmarshal([]byte(jsonStr), &data) 25 | if err != nil { 26 | panic(err) 27 | } 28 | fmt.Println(data.MyTime.String()) 29 | fmtutil.MustPrintJSON(data) 30 | 31 | _ = json.NewEncoder(os.Stdout).Encode(data) 32 | } 33 | -------------------------------------------------------------------------------- /net/http/httputilmore/http_error.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strconv" 7 | ) 8 | 9 | var ( 10 | ErrHTTPResponseCannotBeNil = errors.New("'*http.Response' cannot be nil. Expected a non-nil value") 11 | ) 12 | 13 | var ErrStatus404 = errors.New("status " + strconv.Itoa(http.StatusNotFound) + " " + http.StatusText(http.StatusNotFound)) 14 | 15 | type HTTPError struct { 16 | HTTPStatus int `json:"httpStatus"` 17 | Stage string `json:"preOpPost"` 18 | Message string `json:"errorMessage"` 19 | } 20 | 21 | func NewHTTPError(message string, httpStatus int, stage string) *HTTPError { 22 | return &HTTPError{ 23 | Message: message, 24 | HTTPStatus: httpStatus, 25 | Stage: stage} 26 | } 27 | 28 | func (httperr *HTTPError) Bytes() []byte { 29 | bytes, err := json.Marshal(httperr) 30 | if err != nil { 31 | panic(err) 32 | } 33 | return bytes 34 | } 35 | -------------------------------------------------------------------------------- /database/sqlutil/sql_insert_test.go: -------------------------------------------------------------------------------- 1 | package sqlutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/grokify/mogo/text/foobar" 7 | ) 8 | 9 | func TestBuildSQLXInsertSQLNamedParams(t *testing.T) { 10 | var buildSQLXInsertTests = []struct { 11 | vTableName string 12 | vColumnNames []string 13 | want string 14 | }{ 15 | {"foobar", foobar.Vars(), 16 | `INSERT INTO foobar (bar,baz,corge,foo,fred,garply,grault,plugh,quux,qux,thud,waldo,xyzzy) VALUES (:bar,:baz,:corge,:foo,:fred,:garply,:grault,:plugh,:quux,:qux,:thud,:waldo,:xyzzy)`}, 17 | } 18 | 19 | for _, tt := range buildSQLXInsertTests { 20 | got, err := BuildSQLXInsertSQLNamedParams(tt.vTableName, tt.vColumnNames) 21 | if err != nil { 22 | t.Errorf("err sqlutil.BuildSQLXInsert: err (%s)", err.Error()) 23 | } else if got != tt.want { 24 | t.Errorf("mismatch sqlutil.BuildSQLXInsert(): want (%s) got (%s)", tt.want, got) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /os/osutil/osutil_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | 3 | package osutil 4 | 5 | import ( 6 | "path/filepath" 7 | "sort" 8 | "syscall" 9 | ) 10 | 11 | func FileStatT(filename string) (syscall.Stat_t, error) { 12 | var stat syscall.Stat_t 13 | return stat, syscall.Stat(filename, &stat) 14 | } 15 | 16 | func MustFileStatT(filename string) syscall.Stat_t { 17 | stat, err := FileStatT(filename) 18 | if err != nil { 19 | panic(err) 20 | } 21 | return stat 22 | } 23 | 24 | // SortDirEntriesBirthtimeSec sorts `DirEntries` by `Stat_t.Birthtimespec` which 25 | // is available on OS-X but not all systems. It will panic if a entry is not found. 26 | func SortDirEntriesBirthtimeSec(dir string, entries DirEntries) { 27 | sort.Slice(entries, func(i, j int) bool { 28 | return MustFileStatT(filepath.Join(dir, entries[i].Name())).Birthtimespec.Sec < 29 | MustFileStatT(filepath.Join(dir, entries[j].Name())).Birthtimespec.Sec 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /time/duration/duration_test.go: -------------------------------------------------------------------------------- 1 | package duration 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | var durationTests = []struct { 9 | day int 10 | hour int 11 | min int 12 | sec int 13 | nsec int 14 | sum int64 15 | }{ 16 | {0, 1, 1, 1, 1, 3661000000001}, 17 | {1, 1, 1, 1, 1, 90061000000001}, 18 | {2, 2, 2, 2, 2, 180122000000002}, 19 | {2, 0, 0, 0, 0, int64(Day) * 2}, 20 | {1, 0, 0, 0, 0, int64(Day)}, 21 | {0, 1, 0, 0, 0, int64(time.Hour)}, 22 | {0, 0, 1, 0, 0, int64(time.Minute)}, 23 | {0, 0, 0, 1, 0, int64(time.Second)}, 24 | {0, 0, 0, 0, 1, 1}, 25 | } 26 | 27 | func TestNewDuration(t *testing.T) { 28 | for _, tt := range durationTests { 29 | got := NewDuration(tt.day, tt.hour, tt.min, tt.sec, tt.nsec) 30 | if int64(got) != tt.sum { 31 | t.Errorf("timeutil.TimeToDd6(%d,%d,%d,%d,%d) Mismatch: want (%d) got (%d)", 32 | tt.day, tt.hour, tt.min, tt.sec, tt.nsec, tt.sum, got) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crypto/argon2util/argon2.go: -------------------------------------------------------------------------------- 1 | package argon2util 2 | 3 | import ( 4 | "encoding/base32" 5 | "strings" 6 | 7 | "github.com/martinlindhe/base36" 8 | "golang.org/x/crypto/argon2" 9 | ) 10 | 11 | func HashSimple(input, salt []byte) []byte { 12 | return argon2.IDKey(input, salt, 1, 64*1024, 4, 32) 13 | } 14 | 15 | // HashSimple returns an argon2id hashed password encoded in Base36. 16 | // The base36 library always returns upper case. 17 | func HashSimpleBase36(input, salt []byte) string { 18 | return base36.EncodeBytes(HashSimple(input, salt)) 19 | } 20 | 21 | // HashSimple returns an argon2id hashed password encoded in Base36. 22 | // The base36 library always returns upper case. 23 | func HashSimpleBase32(input, salt []byte, trim bool) string { 24 | if trim { 25 | return strings.Trim(base32.StdEncoding.EncodeToString(HashSimple(input, salt)), "=") 26 | } 27 | return base32.StdEncoding.EncodeToString(HashSimple(input, salt)) 28 | } 29 | -------------------------------------------------------------------------------- /text/emoji/emoji_test.go: -------------------------------------------------------------------------------- 1 | package emoji 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var emoji2ASCIITests = []struct { 8 | v string 9 | wantASCII string 10 | wantUnicode string 11 | }{ 12 | {`:-1:`, `-1`, `👎`}, 13 | {`:+1:`, `+1`, `👍`}, 14 | {`:sweat_smile:`, `':)`, `😅`}, 15 | {`:confused: :sweat_smile:`, `>:\ ':)`, `😕 😅`}, 16 | } 17 | 18 | func TestEmojiToASCII(t *testing.T) { 19 | conv := NewConverter() 20 | for _, tt := range emoji2ASCIITests { 21 | gotASCII := conv.ConvertShortcodesString(tt.v, ASCII) 22 | if gotASCII != tt.wantASCII { 23 | t.Errorf("converter.ConvertString(\"%v\", ASCII) Mismatch: want [%v] got [%v]", tt.v, tt.wantASCII, gotASCII) 24 | } 25 | gotUnicode := conv.ConvertShortcodesString(tt.v, Unicode) 26 | if gotUnicode != tt.wantUnicode { 27 | t.Errorf("converter.ConvertString(\"%v\", Unicode) Mismatch: want [%v] got [%v]", tt.v, tt.wantUnicode, gotUnicode) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /type/maputil/map_string_slice_test.go: -------------------------------------------------------------------------------- 1 | package maputil 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | var mssTests = []struct { 9 | data map[string][]string 10 | sortTestKey string 11 | sortTestValues []string 12 | }{ 13 | { 14 | data: map[string][]string{ 15 | "foo": {"foo", "bar", "baz"}}, 16 | sortTestKey: "foo", 17 | sortTestValues: []string{"bar", "baz", "foo"}}, 18 | } 19 | 20 | func TestMapStringSlice(t *testing.T) { 21 | for _, tt := range mssTests { 22 | mss := MapStringSlice{} 23 | for key, vals := range tt.data { 24 | for _, val := range vals { 25 | mss.Add(key, val) 26 | } 27 | } 28 | mss.Sort(true) 29 | valsWant := strings.Join(tt.sortTestValues, ",") 30 | valsTry := strings.Join(mss[tt.sortTestKey], ",") 31 | if valsTry != valsWant { 32 | t.Errorf("maputil.MapStringSlice.Sort() Fail: want [%s], got [%s]", 33 | valsWant, valsTry) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /encoding/hexutil/hexutil.go: -------------------------------------------------------------------------------- 1 | package hexutil 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | ) 7 | 8 | // EncodeToString returns hex string using a supplied separator. The return 9 | // value is lower case. 10 | func EncodeToString(src []byte, sep string) string { 11 | if sep == "" { 12 | return hex.EncodeToString(src) 13 | } 14 | s := "" 15 | for i, b := range src { 16 | s += hex.EncodeToString([]byte{b}) 17 | if i+1 < len(src) { 18 | s += sep 19 | } 20 | } 21 | return s 22 | } 23 | 24 | // EncodeToStrings returns a slice of strings with leading 0s. The `toUpper` 25 | // param optionally returns upper case values. 26 | func EncodeToStrings(src []byte, toUpper bool) []string { 27 | var s []string 28 | for _, b := range src { 29 | if toUpper { 30 | s = append(s, strings.ToUpper(hex.EncodeToString([]byte{b}))) 31 | } else { 32 | s = append(s, hex.EncodeToString([]byte{b})) 33 | } 34 | } 35 | return s 36 | } 37 | -------------------------------------------------------------------------------- /type/stringsutil/reverse_test.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var reverseTests = []struct { 8 | Str string 9 | Reverse string 10 | Substr string 11 | ReverseIndex int 12 | }{ 13 | {"Hello World", "dlroW olleH", " World", 0}, 14 | {" Hello World ", " dlroW olleH ", " World ", 0}, 15 | {" Hello World2 ", " 2dlroW olleH ", " World2", 1}, 16 | {" Hello World3 ", " 3dlroW olleH ", "World4 ", -1}, 17 | } 18 | 19 | func TestReverse(t *testing.T) { 20 | for _, tt := range reverseTests { 21 | rev := Reverse(tt.Str) 22 | if rev != tt.Reverse { 23 | t.Errorf("stringsutil.Reverse(\"%s\") want [%s] got [%s]", 24 | tt.Str, tt.Reverse, rev) 25 | } 26 | rIdx := ReverseIndex(tt.Str, tt.Substr) 27 | if rIdx != tt.ReverseIndex { 28 | t.Errorf("stringsutil.ReverseIndex(\"%s\", \"%s\") want [%d] got [%d]", 29 | tt.Str, tt.Substr, tt.ReverseIndex, rIdx) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | 1. [`alextanhongpin`](https://stackoverflow.com/users/3155971/alextanhongpin) for ["How do I reverse a slice in go?"](https://stackoverflow.com/a/71904070) 4 | 2. [`CyrusBiotechnology`](https://github.com/CyrusBiotechnology) for [`github.com/CyrusBiotechnology/go-har`](https://github.com/CyrusBiotechnology/go-har) 5 | 3. [`dolmen`](https://stackoverflow.com/users/328115/dolmen) for ["What is a concise way to create a 2D slice in Go?"](https://stackoverflow.com/a/71781206) 6 | 4. [`jdeng`](https://github.com/jdeng) for [`github.com/jdeng/goheif`](https://github.com/jdeng/goheif) 7 | 1. [`pete911`](https://stackoverflow.com/users/2800844/pete911) for ["How to format timestamp in outgoing JSON"](https://stackoverflow.com/a/41678233/1908967) 8 | 5. [Shawn Blakesley](https://stackoverflow.com/users/1536242/shawn-blakesley) for ["Golang complex fold grüßen"](https://stackoverflow.com/questions/43059909/golang-complex-fold-gr%c3%bc%c3%9fen) 9 | -------------------------------------------------------------------------------- /time/timeutil/xox_times.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type XOXTimes struct { 8 | CurrentTime time.Time 9 | CurrentStart time.Time 10 | PreviousTime time.Time 11 | PreviousStart time.Time 12 | } 13 | 14 | func QOQTimes(thisTime time.Time) XOXTimes { 15 | xox := XOXTimes{CurrentTime: thisTime.UTC()} 16 | xox.CurrentStart = quarterStart(xox.CurrentTime) 17 | xox.PreviousStart = QuarterAdd(xox.CurrentStart, -1) 18 | 19 | dur := xox.CurrentTime.Sub(xox.CurrentStart) 20 | xox.PreviousTime = xox.PreviousStart.Add(dur) 21 | return xox 22 | } 23 | 24 | func YOYTimes(t time.Time) XOXTimes { 25 | t = t.UTC() 26 | tm := NewTimeMore(t, 0) 27 | xox := XOXTimes{ 28 | CurrentTime: t, 29 | CurrentStart: tm.YearStart()} 30 | xox.PreviousStart = TimeDT4AddNYears(xox.CurrentStart, -1) 31 | 32 | dur := xox.CurrentTime.Sub(xox.CurrentStart) 33 | xox.PreviousTime = xox.PreviousStart.Add(dur) 34 | return xox 35 | } 36 | -------------------------------------------------------------------------------- /type/stringsutil/strutil.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | type StrUtil struct { 8 | RxSpaceBeg *regexp.Regexp 9 | RxSpaceEnd *regexp.Regexp 10 | RxSpacePunct *regexp.Regexp 11 | RxDash *regexp.Regexp 12 | } 13 | 14 | func NewStrUtil() StrUtil { 15 | str := StrUtil{} 16 | str.RxSpaceBeg = regexp.MustCompile(`^[\s\t\r\n\v\f]*`) 17 | str.RxSpaceEnd = regexp.MustCompile(`[\s\t\r\n\v\f]*$`) 18 | str.RxSpacePunct = regexp.MustCompile(`[[:punct:]\s\t\r\n\v\f]`) 19 | str.RxDash = regexp.MustCompile(`-+`) 20 | return str 21 | } 22 | 23 | func (str *StrUtil) Trim(bytes []byte) []byte { 24 | bytes = str.RxSpaceBeg.ReplaceAll(bytes, []byte{}) 25 | bytes = str.RxSpaceEnd.ReplaceAll(bytes, []byte{}) 26 | return bytes 27 | } 28 | 29 | func InterfaceToSliceString(s any) []string { 30 | ss := s.([]any) 31 | a := []string{} 32 | for _, i := range ss { 33 | a = append(a, i.(string)) 34 | } 35 | return a 36 | } 37 | -------------------------------------------------------------------------------- /math/bigint/encode.go: -------------------------------------------------------------------------------- 1 | package bigint 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | var ErrBaseInvalid = errors.New("invalid base - must be between [2,62]") 10 | 11 | // EncodeToString uses [0-9a-zA-Z] for base62. 12 | func EncodeToString(base int, s []byte) (string, error) { 13 | if base < 2 || base > 62 { 14 | return "", ErrBaseInvalid 15 | } 16 | var i big.Int 17 | i.SetBytes(s) 18 | return i.Text(base), nil 19 | } 20 | 21 | // MustEncodeToString uses [0-9a-zA-Z] for base62. 22 | func MustEncodeToString(base int, s []byte) string { 23 | var i big.Int 24 | i.SetBytes(s) 25 | return i.Text(base) 26 | } 27 | 28 | func DecodeString(base int, s string) ([]byte, error) { 29 | if base < 2 || base > 62 { 30 | return []byte{}, ErrBaseInvalid 31 | } 32 | var i big.Int 33 | _, ok := i.SetString(s, base) 34 | if !ok { 35 | return []byte{}, fmt.Errorf("cannot parse base(%d) (%q)", base, s) 36 | } 37 | return i.Bytes(), nil 38 | } 39 | -------------------------------------------------------------------------------- /os/executil/executil_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package executil 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var execTests = []struct { 11 | v string 12 | want []string 13 | }{ 14 | {v: "ls -al .", want: []string{".", "..", "executil.go"}}, 15 | } 16 | 17 | func TestExecSimple(t *testing.T) { 18 | for _, tt := range execTests { 19 | o, _, err := ExecSimple(tt.v) 20 | if err != nil { 21 | t.Errorf("executil.ExecSimple(\"%s\") error (%s)", 22 | tt.v, err.Error()) 23 | } 24 | lines := strings.Split(o.String(), "\n") 25 | missing := []string{} 26 | WANT: 27 | for _, w := range tt.want { 28 | for _, line := range lines { 29 | if strings.Contains(line, w) { 30 | continue WANT 31 | } 32 | } 33 | missing = append(missing, w) 34 | } 35 | if len(missing) > 0 { 36 | t.Errorf("executil.ExecSimple(\"%s\") mismatch want (%s) not found", 37 | tt.v, strings.Join(missing, ",")) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api/path_to_pattern_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var urlToPatternTests = []struct { 8 | v string 9 | want string 10 | }{ 11 | {"/pets", "/pets"}, 12 | {"/pets?q=Fido", "/pets"}, 13 | {"/pets/dog", "/pets/{petId}"}, 14 | {"/pets/dog/vacinations", "/pets/{petId}/vacinations"}, 15 | {"/pets/dog/vacinations/123", "/pets/{petId}/vacinations/{vacinationId}"}} 16 | 17 | func TestURLToPattern(t *testing.T) { 18 | ut := NewURLTransformer() 19 | err := ut.LoadPaths([]string{ 20 | "/pets", 21 | "/pets/{petId}", 22 | "/pets/{petId}/vacinations", 23 | "/pets/{petId}/vacinations/{vacinationId}"}) 24 | if err != nil { 25 | t.Errorf(`err URLTransformer.LoadPaths(...) failed: err [%s]`, err.Error()) 26 | } 27 | 28 | for _, tt := range urlToPatternTests { 29 | got := ut.URLActualToPattern(tt.v) 30 | if got != tt.want { 31 | t.Errorf(`err URLActualToPattern("%v") failed: want [%v], got [%v]`, tt.v, tt.want, got) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /os/executil/grep/grep.go: -------------------------------------------------------------------------------- 1 | package grep 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/grokify/mogo/type/stringsutil" 7 | ) 8 | 9 | type GrepResults []GrepResult 10 | 11 | func (gr GrepResults) Files() []string { 12 | files := []string{} 13 | for _, g := range gr { 14 | g.File = strings.TrimSpace(g.File) 15 | if len(g.File) > 0 { 16 | files = append(files, g.File) 17 | } 18 | } 19 | return stringsutil.SliceCondenseSpace(files, true, true) 20 | } 21 | 22 | type GrepResult struct { 23 | File string 24 | Line string 25 | } 26 | 27 | // ParseGrep will parse the results from `os/executil.ExecSimple`. 28 | func ParseGrep(data []byte) GrepResults { 29 | res := GrepResults{} 30 | lines := strings.Split(string(data), "\n") 31 | for _, line := range lines { 32 | lineParts := strings.SplitN(line, ":", 2) 33 | if len(lineParts) == 2 { 34 | res = append(res, GrepResult{ 35 | File: lineParts[0], 36 | Line: lineParts[1]}) 37 | } 38 | } 39 | return res 40 | } 41 | -------------------------------------------------------------------------------- /cmp/cmputil/cmputil_test.go: -------------------------------------------------------------------------------- 1 | package cmputil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCompare(t *testing.T) { 8 | var compareTests = []struct { 9 | x int 10 | y int 11 | op Operator 12 | want bool 13 | }{ 14 | {1, 1, OpEQ, true}, 15 | {1, 1, OpNEQ, false}, 16 | {1, 1, OpLT, false}, 17 | {1, 1, OpLTE, true}, 18 | {1, 1, OpGT, false}, 19 | {1, 1, OpGTE, true}, 20 | {1, 2, OpEQ, false}, 21 | {1, 2, OpNEQ, true}, 22 | {1, 2, OpLT, true}, 23 | {1, 2, OpLTE, true}, 24 | {1, 2, OpGT, false}, 25 | {1, 2, OpGTE, false}, 26 | {1, 0, OpEQ, false}, 27 | {1, 0, OpNEQ, true}, 28 | {1, 0, OpLT, false}, 29 | {1, 0, OpLTE, false}, 30 | {1, 0, OpGT, true}, 31 | {1, 0, OpGTE, true}, 32 | } 33 | 34 | for _, tt := range compareTests { 35 | try := Compare[int](tt.x, tt.y, tt.op) 36 | if try != tt.want { 37 | t.Errorf("cmputil.Compare(%d , %d, %s) Mismatch: want (%v), got (%v)", tt.x, tt.y, string(tt.op), tt.want, try) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /log/slogutil/slogutil.go: -------------------------------------------------------------------------------- 1 | package slogutil 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log/slog" 7 | ) 8 | 9 | func LogOrNot(ctx context.Context, logger *slog.Logger, level slog.Level, msg string, attrs ...slog.Attr) { 10 | if logger == nil { 11 | return 12 | } else if ctx == nil { 13 | ctx = context.Background() 14 | } 15 | logger.LogAttrs(ctx, level, msg, attrs...) 16 | } 17 | 18 | func LogOrNotAny(ctx context.Context, logger *slog.Logger, level slog.Level, msg string, args ...any) { 19 | attrs := AttrsFromAny(args...) 20 | LogOrNot(ctx, logger, level, msg, attrs...) 21 | } 22 | 23 | func AttrsFromAny(args ...any) []slog.Attr { 24 | var attrs []slog.Attr 25 | for i := 0; i < len(args); i += 2 { 26 | key, ok := args[i].(string) 27 | if !ok { 28 | key = fmt.Sprintf("%v", key) 29 | } 30 | if i < len(args)-1 { 31 | attrs = append(attrs, slog.Any(key, args[i+1])) 32 | } else { 33 | attrs = append(attrs, slog.Any(key, nil)) 34 | } 35 | } 36 | return attrs 37 | } 38 | -------------------------------------------------------------------------------- /net/http/httputilmore/endpoint_test.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var parseEndpointTests = []struct { 8 | v string 9 | wantMethod HTTPMethod 10 | wantURL string 11 | }{ 12 | {"GET https://example.com", MethodGet, "https://example.com"}, 13 | {" PaTcH https://example.com/ ", MethodPatch, "https://example.com/"}, 14 | } 15 | 16 | func TestParseEndpoint(t *testing.T) { 17 | for _, tt := range parseEndpointTests { 18 | ep, err := ParseEndpoint(tt.v) 19 | if err != nil { 20 | t.Errorf("httputilmore.ParseEndpoint(\"%s\") Error: [%s]", 21 | tt.v, err.Error()) 22 | } 23 | if ep.Method != tt.wantMethod { 24 | t.Errorf("httputilmore.ParseEndpoint(\"%s\") Fail Method: want [%s] got [%s]", 25 | tt.v, tt.wantMethod, ep.Method) 26 | } 27 | epURL := ep.URL.String() 28 | if epURL != tt.wantURL { 29 | t.Errorf("httputilmore.ParseEndpoint(\"%s\") Fail URL: want [%s] got [%s]", 30 | tt.v, tt.wantURL, epURL) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /type/maputil/map_comp_int.go: -------------------------------------------------------------------------------- 1 | package maputil 2 | 3 | import "strconv" 4 | 5 | type MapCompInt[C comparable] map[C]int 6 | 7 | func (mci MapCompInt[C]) Percentages() map[C]float32 { 8 | out := map[C]float32{} 9 | sum := 0 10 | for _, v := range mci { 11 | sum += v 12 | } 13 | for k, v := range mci { 14 | out[k] = float32(v) / float32(sum) 15 | } 16 | return out 17 | } 18 | 19 | func (mci MapCompInt[C]) PercentagesInt() map[C]int { 20 | out := map[C]int{} 21 | sum := 0 22 | for _, v := range mci { 23 | sum += v 24 | } 25 | for k, v := range mci { 26 | out[k] = int(100 * (float32(v) / float32(sum))) 27 | } 28 | return out 29 | } 30 | 31 | func (mci MapCompInt[C]) ReverseCounts() map[int]int { 32 | out := map[int]int{} 33 | for _, v := range mci { 34 | out[v]++ 35 | } 36 | return out 37 | } 38 | 39 | func (mci MapCompInt[C]) ReverseCountsString() map[string]int { 40 | out := map[string]int{} 41 | for _, v := range mci { 42 | out[strconv.Itoa(v)]++ 43 | } 44 | return out 45 | } 46 | -------------------------------------------------------------------------------- /database/examples/sql/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/grokify/mogo/config" 9 | "github.com/grokify/mogo/database/sqlutil" 10 | "github.com/grokify/mogo/fmt/fmtutil" 11 | "github.com/grokify/mogo/strconv/strconvutil" 12 | ) 13 | 14 | func main() { 15 | files, err := config.LoadDotEnv( 16 | []string{".env", os.Getenv("ENV_PATH")}, -1) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | fmtutil.MustPrintJSON(files) 21 | 22 | col, err := strconvutil.Atou32(os.Getenv("SQL_CSV_FILE_COL")) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | sqls, values, err := sqlutil.ReadFileCSVToSQLs( 28 | os.Getenv("SQL_FORMAT"), 29 | os.Getenv("SQL_CSV_FILE"), 30 | ',', 31 | true, 32 | true, 33 | col) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | 38 | fmtutil.MustPrintJSON(sqls) 39 | 40 | fmt.Printf("SQL_ITEMS [%v]\n", len(values)) 41 | fmt.Printf("SQL_STATEMENTS [%v]\n", len(sqls)) 42 | 43 | fmt.Println("DONE") 44 | } 45 | -------------------------------------------------------------------------------- /time/timeutil/compare_test.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var sliceMinMaxTests = []struct { 10 | v string 11 | min string 12 | max string 13 | }{ 14 | {"2006-01-01T00:00:00Z,2006-02-01T00:00:00Z,2006-03-01T00:00:00Z", 15 | "2006-01-01T00:00:00Z", 16 | "2006-03-01T00:00:00Z"}, 17 | } 18 | 19 | // TestDMYHM2ParseTime ensures timeutil.DateDMYHM2 is parsed to GMT timezone. 20 | func TestSliceMinMax(t *testing.T) { 21 | for _, tt := range sliceMinMaxTests { 22 | times, err := ParseSlice(time.RFC3339, strings.Split(tt.v, ",")) 23 | if err != nil { 24 | t.Errorf("time.ParseSlice(%v) Error: [%v]", tt.v, err.Error()) 25 | } 26 | min, max := SliceMinMax(times) 27 | if min.Format(time.RFC3339) != tt.min || 28 | max.Format(time.RFC3339) != tt.max { 29 | t.Errorf("timeutil.SliceMinMax(\"%v\") Mismatch: want [%v,%v], got [%v,%v]", tt.v, 30 | tt.min, tt.max, 31 | min.Format(time.RFC3339), max.Format(time.RFC3339)) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /time/cmd/dateadddays/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log/slog" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/grokify/mogo/time/timeutil" 11 | "github.com/jessevdk/go-flags" 12 | ) 13 | 14 | type Options struct { 15 | Date int `short:"d" long:"date" description:"Date in 20060102 format" required:"true"` 16 | Offset int `short:"o" long:"offset" description:"Show verbose debug information" required:"true"` 17 | } 18 | 19 | func main() { 20 | opts := Options{} 21 | _, err := flags.Parse(&opts) 22 | if err != nil { 23 | slog.Error(err.Error()) 24 | os.Exit(1) 25 | } 26 | 27 | t, err := time.Parse(timeutil.DT8, strconv.Itoa(opts.Date)) 28 | if err != nil { 29 | slog.Error(err.Error()) 30 | os.Exit(2) 31 | } 32 | 33 | t2 := t.Add(time.Duration(int64(opts.Offset)) * time.Hour * 24) 34 | fmt.Printf("Original Date: %s\nOffset: %d days\nNew Date: %s\n", 35 | t.Format(time.DateOnly), opts.Offset, t2.Format(time.DateOnly)) 36 | 37 | fmt.Println("DONE") 38 | os.Exit(0) 39 | } 40 | -------------------------------------------------------------------------------- /text/languageutil/language.go: -------------------------------------------------------------------------------- 1 | package languageutil 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "golang.org/x/text/language" 8 | ) 9 | 10 | var languageConjunctionMap = map[language.Tag]string{ 11 | language.English: "and", 12 | } 13 | 14 | func JoinLanguage(slice []string, sep string, joinLang language.Tag) (string, error) { 15 | switch len(slice) { 16 | case 0: 17 | return "", nil 18 | case 1: 19 | return slice[0], nil 20 | case 2: 21 | if joinWord, ok := languageConjunctionMap[joinLang]; ok { 22 | return slice[0] + " " + joinWord + " " + slice[1], nil 23 | } 24 | return strings.Join(slice, sep), fmt.Errorf("join word not found for language [%v]", joinLang) 25 | default: 26 | last, rest := slice[len(slice)-1], slice[:len(slice)-1] 27 | if joinWord, ok := languageConjunctionMap[joinLang]; ok { 28 | rest = append(rest, joinWord+" "+last) 29 | return strings.Join(rest, sep+" "), nil 30 | } 31 | return strings.Join(slice, sep), fmt.Errorf("join word not found for language [%v]", joinLang) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /encoding/jsonutil/jsonraw/equal.go: -------------------------------------------------------------------------------- 1 | package jsonraw 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "os" 7 | "reflect" 8 | ) 9 | 10 | func Equal(x, y io.Reader) (bool, error) { 11 | var ax, ay any 12 | d := json.NewDecoder(x) 13 | if err := d.Decode(&ax); err != nil { 14 | return false, err 15 | } 16 | d = json.NewDecoder(y) 17 | if err := d.Decode(&ay); err != nil { 18 | return false, err 19 | } 20 | return reflect.DeepEqual(ax, ay), nil 21 | } 22 | 23 | func EqualBytes(x, y []byte) (bool, error) { 24 | var ax, ay any 25 | if err := json.Unmarshal(x, &ax); err != nil { 26 | return false, err 27 | } else if err := json.Unmarshal(y, &ay); err != nil { 28 | return false, err 29 | } else { 30 | return reflect.DeepEqual(ax, ay), nil 31 | } 32 | } 33 | 34 | func EqualFiles(x, y string) (bool, error) { 35 | fx, err := os.Open(x) 36 | if err != nil { 37 | return false, err 38 | } 39 | defer fx.Close() 40 | fy, err := os.Open(y) 41 | if err != nil { 42 | return false, err 43 | } 44 | defer fy.Close() 45 | return Equal(fx, fy) 46 | } 47 | -------------------------------------------------------------------------------- /time/duration/spec.go: -------------------------------------------------------------------------------- 1 | package duration 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Spec struct { 9 | Value int64 `json:"value" yaml:"value"` 10 | Unit Unit `json:"unit" yaml:"unit"` 11 | } 12 | 13 | func (s Spec) Duration() (time.Duration, error) { 14 | switch s.Unit { 15 | case UnitYear: 16 | return time.Duration(s.Value) * Year, nil 17 | case UnitMonth: 18 | return time.Duration(s.Value) * 30 * Day, nil 19 | case UnitDay: 20 | return time.Duration(s.Value) * Day, nil 21 | case UnitHour: 22 | return time.Duration(s.Value) * time.Hour, nil 23 | case UnitMinute: 24 | return time.Duration(s.Value) * time.Minute, nil 25 | case UnitSecond: 26 | return time.Duration(s.Value) * time.Second, nil 27 | case UnitMillisecond: 28 | return time.Duration(s.Value) * time.Millisecond, nil 29 | case UnitMicrosecond: 30 | return time.Duration(s.Value) * time.Microsecond, nil 31 | case UnitNanosecond: 32 | return time.Duration(s.Value), nil 33 | default: 34 | return 0, fmt.Errorf("unknown time duration unit (%s)", string(s.Unit)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crypto/x509util/README.md: -------------------------------------------------------------------------------- 1 | x509util 2 | ======== 3 | 4 | Go utility code for handling X.509 certificates. 5 | 6 | Certificate Formats 7 | =================== 8 | 9 | This library handles certificates using the PKCS standards. Key formats 10 | supported include PKCS #1 for private keys and PKCS #8 for public keys. 11 | 12 | Converting OpenSSH key formats to PKCS key formats 13 | -------------------------------------------------- 14 | 15 | This section includes commands on converting OpenSSH key file formats 16 | to the PKCS formats used by this library. 17 | 18 | ### Converting Private Keys from OpenSSH to PKCS #1 19 | 20 | To decrypt OpenSSH Private Key to OpenSSL PKCS1 Private Key Format, run 21 | the following command, assuming the standard id_rsa private key file 22 | name 23 | 24 | > openssl rsa -in id_rsa -out id_rsa.private.pkcs1 25 | 26 | ### Converting Public Keys from OpenSSH to PKCS #8 27 | 28 | To convert OpenSSH Public Key to OpenSSL PKCS8 Public Key Format, 29 | assuming the standard id_rsa.pub public key file name. 30 | 31 | > ssh-keygen -e -m PKCS8 -f id_rsa.pub > id_rsa.public.pkcs8 -------------------------------------------------------------------------------- /net/netutil/connection.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "net" 7 | "net/http" 8 | 9 | "github.com/grokify/mogo/errors/errorsutil" 10 | ) 11 | 12 | // ModifyConnectionRequest updates the HTTP request for network connection. 13 | func ModifyConnectionRequest(conn net.Conn, modRequest func(r *http.Request) error) error { 14 | // Code adapted from: https://stackoverflow.com/a/76684845/1908967 15 | if conn == nil { 16 | return errors.New("net.Conn cannot be nil") 17 | } 18 | defer conn.Close() 19 | 20 | req, err := http.ReadRequest(bufio.NewReader(conn)) 21 | if err != nil { 22 | return errorsutil.Wrap(err, "error reading request") 23 | } 24 | 25 | // Modify the request as needed 26 | if modRequest != nil { 27 | if err := modRequest(req); err != nil { 28 | return errorsutil.Wrap(err, "error modifying request") 29 | } 30 | } 31 | 32 | resp, err := http.DefaultTransport.RoundTrip(req) 33 | if err != nil { 34 | return errorsutil.Wrap(err, "error sending request") 35 | } 36 | defer resp.Body.Close() 37 | 38 | return resp.Write(conn) 39 | } 40 | -------------------------------------------------------------------------------- /html/htmlutil/descriptionlist_definitions.go: -------------------------------------------------------------------------------- 1 | package htmlutil 2 | 3 | // import "github.com/grokify/mogo/html/htmlutil" 4 | 5 | type DescriptionLists []DescriptionList 6 | 7 | func (dls DescriptionLists) Strings() [][][]string { 8 | strs := [][][]string{} 9 | for _, dl := range dls { 10 | strs = append(strs, dl.Strings()) 11 | } 12 | return strs 13 | } 14 | 15 | type DescriptionList []Description 16 | 17 | func (dl DescriptionList) Strings() [][]string { 18 | strs := [][]string{} 19 | for _, d := range dl { 20 | strs = append(strs, d.Strings()) 21 | } 22 | return strs 23 | } 24 | 25 | type Description struct { 26 | Term Tokens 27 | Description Tokens 28 | } 29 | 30 | func (d *Description) Empty() bool { 31 | return len(d.Term) == 0 && len(d.Description) == 0 32 | } 33 | 34 | func (d *Description) TermString() string { 35 | return d.Term.String() 36 | } 37 | 38 | func (d *Description) DescriptionString() string { 39 | return d.Description.String() 40 | } 41 | 42 | func (d *Description) Strings() []string { 43 | return []string{d.TermString(), d.DescriptionString()} 44 | } 45 | -------------------------------------------------------------------------------- /strconv/phonenumber/phonenumberletters.go: -------------------------------------------------------------------------------- 1 | package phonenumber 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const numbers = "2ABC3DEF4GHI5JKL6MNO7PQRS8TUV9WXYZ" 10 | 11 | func LetterToNumberMap() map[string]int { 12 | rx := regexp.MustCompile(`(\d)([A-Z]+)`) 13 | m := rx.FindAllStringSubmatch(numbers, -1) 14 | 15 | l2n := map[string]int{} 16 | 17 | for _, m2 := range m { 18 | if len(m2) >= 3 { 19 | num, err := strconv.Atoi(m2[1]) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | letters := strings.Split(m2[2], "") 25 | 26 | for _, item := range letters { 27 | l2n[item] = num 28 | } 29 | } 30 | } 31 | 32 | return l2n 33 | } 34 | 35 | func StringToNumbers(s string) string { 36 | conv := []string{} 37 | arr := strings.Split(s, "") 38 | l2n := LetterToNumberMap() 39 | for _, letter := range arr { 40 | letter = strings.ToUpper(letter) 41 | if num, ok := l2n[letter]; ok { 42 | conv = append(conv, strconv.Itoa(num)) 43 | } else { 44 | conv = append(conv, letter) 45 | } 46 | } 47 | return strings.Join(conv, "") 48 | } 49 | -------------------------------------------------------------------------------- /text/currencyutil/currency_codes_meta.go: -------------------------------------------------------------------------------- 1 | package currencyutil 2 | 3 | import ( 4 | "golang.org/x/exp/slices" 5 | ) 6 | 7 | // activate when rebuilding this func. 8 | // func CurrencyCodesAll() []string { return []string{} } 9 | 10 | // CurrencyCodes0D returns zero decimal currencies. Listed at: https://stripe.com/docs/currencies 11 | func CurrencyCodes0D() []string { 12 | return []string{ 13 | CurrencyBIF, 14 | CurrencyCLP, 15 | CurrencyDJF, 16 | CurrencyGNF, 17 | CurrencyJPY, 18 | CurrencyKMF, 19 | CurrencyKRW, 20 | CurrencyMGA, 21 | CurrencyPYG, 22 | CurrencyRWF, 23 | CurrencyUGX, 24 | CurrencyVND, 25 | CurrencyVUV, 26 | CurrencyXAF, 27 | CurrencyXOF, 28 | CurrencyXPF} 29 | } 30 | 31 | // CurrencyCodes3D returns three decimal currencies. Listed at: https://stripe.com/docs/currencies 32 | func CurrencyCodes3D() []string { 33 | return []string{ 34 | CurrencyBHD, 35 | CurrencyJOD, 36 | CurrencyKWD, 37 | CurrencyOMR, 38 | CurrencyTND} 39 | } 40 | 41 | func CurrencyCodeKnown(value string) bool { 42 | return slices.Index(CurrencyCodesAll(), value) >= 0 43 | } 44 | -------------------------------------------------------------------------------- /text/password/generate_test.go: -------------------------------------------------------------------------------- 1 | package password 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/grokify/mogo/fmt/fmtutil" 7 | ) 8 | 9 | var buildAlphabetTests = []struct { 10 | v GenerateOpts 11 | want string 12 | }{ 13 | {GenerateOpts{ 14 | InclLower: true, 15 | InclUpper: true, 16 | InclNumbers: true, 17 | InclSymbols: true, 18 | InclAmbiguous: true, 19 | ExclSimilar: false, 20 | }, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*?{}[]()/\\'\"`,;:.<>"}, 21 | {GenerateOpts{ 22 | InclLower: true, 23 | InclUpper: true, 24 | InclNumbers: true, 25 | InclSymbols: true, 26 | InclAmbiguous: true, 27 | ExclSimilar: true, 28 | }, "abcdefghjklmnpqrstuvwxyz23456789ABCDEFGHJKMNPQRSTUVWXYZ!@#$%&*?{}[]()/\\'\"`,;:.<>"}, 29 | } 30 | 31 | func TestBuildAlphabet(t *testing.T) { 32 | for _, tt := range buildAlphabetTests { 33 | got := tt.v.Alphabet() 34 | if got != tt.want { 35 | fmtutil.MustPrintJSON(tt.v) 36 | t.Errorf("password.BuildAlphabet() Mismatch: want (%s) got (%s)", 37 | tt.want, got) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /time/month/parse_test.go: -------------------------------------------------------------------------------- 1 | package month 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var parseTests = []struct { 10 | haystack []string 11 | value string 12 | insensiitve bool 13 | month time.Month 14 | err error 15 | }{ 16 | {[]string{MonthsEnAbbr3}, "Jul", false, time.July, nil}, 17 | {[]string{MonthsEnFull}, " september ", true, time.September, nil}, 18 | {[]string{MonthsEnAbbr3}, "oct", true, time.October, nil}, 19 | {[]string{MonthsEnAbbr3}, "Oct", false, time.October, nil}, 20 | } 21 | 22 | func TestParse(t *testing.T) { 23 | for _, tt := range parseTests { 24 | monthTry, err := Parse(tt.haystack, tt.value, tt.insensiitve) 25 | if err != nil { 26 | t.Errorf(`time.month.Parse("%s". "%s", %v) got error [%v]`, 27 | strings.Join(tt.haystack, ","), tt.value, tt.insensiitve, err.Error()) 28 | } 29 | if monthTry != tt.month { 30 | t.Errorf(`time.month.Parse("%s", "%s", %v) Expected [%s] Got [%s]`, 31 | strings.Join(tt.haystack, ","), tt.value, tt.insensiitve, tt.month.String(), monthTry.String()) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/sqlutil/sql_insert.go: -------------------------------------------------------------------------------- 1 | package sqlutil 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "slices" 7 | "sort" 8 | "strings" 9 | ) 10 | 11 | func BuildSQLXInsertSQLNamedParams(tblName string, colNames []string) (string, error) { 12 | tblName = strings.TrimSpace(tblName) 13 | if tblName == "" { 14 | return "", errors.New("table name cannot be empty") 15 | } 16 | 17 | // clone to prevent unintended side-effects if caller doesn't anticipate 18 | // colNames being sorted. 19 | colNamesLocal := slices.Clone(colNames) 20 | sort.Strings(colNamesLocal) 21 | var colNamesVars []string 22 | for _, colName := range colNamesLocal { 23 | if colName == "" { 24 | return "", errors.New("column name cannot be empty") 25 | } else if !IsUnquotedIdentifier(colName) { 26 | return "", fmt.Errorf("column name (%s) is not a valid unquoted identifier", colName) 27 | } else { 28 | colNamesVars = append(colNamesVars, ":"+colName) 29 | } 30 | } 31 | 32 | return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", 33 | tblName, strings.Join(colNamesLocal, ","), strings.Join(colNamesVars, ",")), nil 34 | } 35 | -------------------------------------------------------------------------------- /errors/errorsutil/error_with_location.go: -------------------------------------------------------------------------------- 1 | package errorsutil 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | // ErrorWithLocation represents an error message with code file and line locaiton. 9 | // It is automatically populated when instantiated with `NewErrorWithLocation()`, 10 | // and follows the `errors.Error` interface. 11 | type ErrorWithLocation struct { 12 | Msg string 13 | File string 14 | Line int 15 | } 16 | 17 | func (e *ErrorWithLocation) Error() string { 18 | return fmt.Sprintf("%s (at %s:%d)", e.Msg, e.File, e.Line) 19 | } 20 | 21 | func WrapWithLocation(err error) error { 22 | if err == nil { 23 | return nil 24 | } 25 | _, file, line, ok := runtime.Caller(1) 26 | if !ok { 27 | file = "unknown" 28 | line = -1 29 | } 30 | return Wrap(err, fmt.Sprintf("error_location: file (%s) line (%d)", file, line)) 31 | } 32 | 33 | func NewWithLocation(msg string) error { 34 | _, file, line, ok := runtime.Caller(1) 35 | if !ok { 36 | file = "unknown" 37 | line = -1 38 | } 39 | return &ErrorWithLocation{ 40 | Msg: msg, 41 | File: file, 42 | Line: line, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/sqlutil/sql_test.go: -------------------------------------------------------------------------------- 1 | package sqlutil 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | var sliceToSQLsTests = []struct { 10 | sqlFormat string 11 | valuesRaw string 12 | valuesParsed []string 13 | want0 string 14 | }{ 15 | {"SELECT * FROM items WHERE name IN (%s)", 16 | "foo,bar,bax,qux", []string{"foo", "bar", "bax", "qux"}, 17 | "SELECT * FROM items WHERE name IN ('foo','bar','bax','qux')"}, 18 | } 19 | 20 | func TestSliceToSQLs(t *testing.T) { 21 | for _, tt := range sliceToSQLsTests { 22 | values := strings.Split(tt.valuesRaw, ",") 23 | if !reflect.DeepEqual(tt.valuesParsed, values) { 24 | t.Errorf("TestSliceToSQLs() panic, bad test: with [%v]", tt.valuesRaw) 25 | } 26 | sqls := BuildSQLsInStrings(tt.sqlFormat, values, MaxSQLLengthSOQL) 27 | if len(sqls) == 0 { 28 | t.Errorf("BuildSQLsInStrings() panic, bad test: with [%v] no results", tt.valuesRaw) 29 | } 30 | 31 | if tt.want0 != sqls[0] { 32 | t.Errorf("sqlBuildSQLsInStrings() Error: with [%v], want [%v], got [%v]", 33 | tt.valuesRaw, tt.want0, sqls[0]) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /encoding/jsonutil/json_error.go: -------------------------------------------------------------------------------- 1 | package jsonutil 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | 7 | "github.com/grokify/mogo/errors/errorsutil" 8 | ) 9 | 10 | func UnmarshalWithLoc(data []byte, v any) error { 11 | err := json.Unmarshal(data, v) 12 | if err == nil { 13 | return nil 14 | } 15 | if syntaxErr, ok := err.(*json.SyntaxError); ok { 16 | line, col := getErrorLine(data, syntaxErr.Offset) 17 | if line >= 0 && col > 0 { 18 | return errorsutil.Wrapf(err, "json syntax error at line (%d), column (%d)", line, col) 19 | } 20 | } 21 | return err 22 | } 23 | 24 | // getErrorLine calculates the line and column number from a byte offset 25 | func getErrorLine(data []byte, offset int64) (line int, col int) { 26 | lines := strings.Split(string(data), "\n") 27 | offsetErr := int(offset) 28 | line = -1 29 | col = -1 30 | cur := 0 31 | 32 | for i, l := range lines { 33 | line = i + 1 34 | offsetLineEnd := cur + len(l) + 1 35 | if offsetErr <= offsetLineEnd { 36 | col = offsetErr - cur 37 | break 38 | } else { 39 | cur += len(l) + 1 40 | } 41 | } 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /os/osutil/read.go: -------------------------------------------------------------------------------- 1 | package osutil 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "os" 7 | 8 | "github.com/grokify/mogo/errors/errorsutil" 9 | ) 10 | 11 | func ReadFileByLine(name string, lineFunc func(idx uint, line string) error) error { 12 | file, err := os.Open(name) 13 | if err != nil { 14 | return err 15 | } 16 | defer file.Close() 17 | 18 | i := uint(0) 19 | scanner := bufio.NewScanner(file) 20 | // optionally, resize scanner's capacity for lines over 64K, see next example 21 | for scanner.Scan() { 22 | txt := scanner.Text() 23 | err := lineFunc(i, txt) 24 | if err != nil { 25 | return err 26 | } 27 | i++ 28 | } 29 | 30 | return scanner.Err() 31 | } 32 | 33 | // ReadFileJSON reads and unmarshals a file. 34 | func ReadFileJSON(file string, v any) error { 35 | bytes, err := os.ReadFile(file) 36 | if err != nil { 37 | return err 38 | } 39 | return json.Unmarshal(bytes, v) 40 | } 41 | 42 | func CloseFileWithError(file *os.File, err error) error { 43 | errFile := file.Close() 44 | if err != nil { 45 | return errorsutil.Wrap(err, errFile.Error()) 46 | } 47 | return err 48 | } 49 | -------------------------------------------------------------------------------- /strconv/phonenumber/cmd/areacode_distance/areacode_distance.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("DONE") 7 | } 8 | 9 | /* 10 | import ( 11 | "fmt" 12 | 13 | "github.com/grokify/mogo/strconv/phonenumber" 14 | geo "github.com/kellydunn/golang-geo" 15 | ) 16 | 17 | const ( 18 | USNYC_AREACODE = 212 19 | USNYC_LAT_GOOG = 40.6976684 20 | USNYC_LON_GOOG = -74.2605588 21 | 22 | USSFO_AREACODE = 415 23 | USSFO_LAT_GOOG = 37.7578149 24 | USSFO_LON_GOOG = -122.5078121 25 | ) 26 | 27 | func GcdGoogle() { 28 | p1 := geo.NewPoint(USNYC_LAT_GOOG, USNYC_LON_GOOG) 29 | p2 := geo.NewPoint(USSFO_LAT_GOOG, USSFO_LON_GOOG) 30 | 31 | dist := p1.GreatCircleDistance(p2) 32 | fmt.Printf("Great circle distance NYC to SFO: %v\n", dist) 33 | } 34 | 35 | func main() { 36 | GcdGoogle() 37 | 38 | a2g := phonenumber.NewAreaCodeToGeo() 39 | a2g.ReadData() 40 | 41 | dist, err := a2g.GcdAreaCodes(USNYC_AREACODE, USSFO_AREACODE) 42 | if err != nil { 43 | panic(err) 44 | } 45 | fmt.Printf("Great circle distance %v to %v: %v\n", USNYC_AREACODE, USSFO_AREACODE, dist) 46 | fmt.Println("DONE") 47 | } 48 | */ 49 | -------------------------------------------------------------------------------- /net/urlutil/url_template.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "net/url" 5 | "regexp" 6 | ) 7 | 8 | const ( 9 | netURLTemplateSubstituteHostname string = "Iaae8QKUZsUZpxDjpkV0GII9ahZLTeWiyFasAO8xPmv" 10 | urlPattern string = `^([0-9a-zA-Z]+://)([^/]+)` 11 | ) 12 | 13 | var urlPatternRx *regexp.Regexp = regexp.MustCompile(urlPattern) 14 | 15 | // ParseURLTemplate exists to parse templates with variables 16 | // that do not meet RFC specifications. For example: 17 | // https://{customer}.example.com:{port}/v5 18 | // "invalid URL escape "%7B"" for `{` within a Hostname or 19 | // "invalid port ":{port}" after host" 20 | func ParseURLTemplate(input string) (*url.URL, error) { 21 | workingURL := input 22 | m := urlPatternRx.FindStringSubmatch(workingURL) 23 | if len(m) == 0 { 24 | return url.Parse(workingURL) 25 | } 26 | 27 | replString := m[1] + netURLTemplateSubstituteHostname 28 | workingURL = urlPatternRx.ReplaceAllString(workingURL, replString) 29 | 30 | parsedURL, err := url.Parse(workingURL) 31 | if err != nil { 32 | return nil, err 33 | } 34 | parsedURL.Host = m[2] 35 | 36 | return parsedURL, nil 37 | } 38 | -------------------------------------------------------------------------------- /time/customtime/rfc3339millisecond.go: -------------------------------------------------------------------------------- 1 | package customtime 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/grokify/mogo/time/timeutil" 9 | ) 10 | 11 | // TimeRFC3339Milli represents a time that is represened 12 | // to the millisecond (3 sigificant digits vs. Go's 6 13 | // significant digits. This iss useful for some Java 14 | // deployments. Read more here: 15 | // https://stackoverflow.com/a/41678233/1908967 16 | type TimeRFC3339Milli struct { 17 | time.Time 18 | } 19 | 20 | func (ct *TimeRFC3339Milli) UnmarshalJSON(b []byte) (err error) { 21 | s := strings.Trim(string(b), "\"") 22 | if s == "null" { 23 | ct.Time = time.Time{} 24 | return 25 | } 26 | ct.Time, err = time.Parse(timeutil.RFC3339Milli, s) 27 | return 28 | } 29 | 30 | func (ct *TimeRFC3339Milli) MarshalJSON() ([]byte, error) { 31 | if ct.Time.UnixNano() == nilTime { 32 | return []byte("null"), nil 33 | } 34 | return []byte(fmt.Sprintf("\"%s\"", ct.Time.Format(timeutil.RFC3339Milli))), nil 35 | } 36 | 37 | var nilTime = (time.Time{}).UnixNano() 38 | 39 | func (ct *TimeRFC3339Milli) IsSet() bool { 40 | return ct.Time.UnixNano() != nilTime 41 | } 42 | -------------------------------------------------------------------------------- /time/timeutil/parse.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | ) 7 | 8 | func ParseTimeCanonical(layout, value string) (time.Time, error) { 9 | return time.Parse(layout, ReplaceMonthCanonical(value)) 10 | } 11 | 12 | func ParseTimeCanonicalFunc(layout string) func(s string) (time.Time, error) { 13 | return func(s string) (time.Time, error) { 14 | return ParseTimeCanonical(layout, s) 15 | } 16 | } 17 | 18 | func CanonicalMonthMap() map[string][]string { 19 | return map[string][]string{ 20 | "Jan": {"January"}, 21 | "Feb": {"February"}, 22 | "Mar": {"March"}, 23 | "Apr": {"April"}, 24 | "May": {""}, 25 | "Jun": {"June"}, 26 | "Jul": {"July"}, 27 | "Aug": {"August"}, 28 | "Sep": {"September", "Sept"}, 29 | "Oct": {"October"}, 30 | "Nov": {"November"}, 31 | "Dec": {"December"}} 32 | } 33 | 34 | func ReplaceMonthCanonical(s string) string { 35 | mm := CanonicalMonthMap() 36 | for k, vals := range mm { 37 | for _, v := range vals { 38 | if v == "" { 39 | continue 40 | } 41 | s = strings.ReplaceAll(s, v, k) 42 | s = strings.ReplaceAll(s, strings.ToLower(v), k) 43 | } 44 | } 45 | return s 46 | } 47 | -------------------------------------------------------------------------------- /fmt/fmtutil/sprintf_test.go: -------------------------------------------------------------------------------- 1 | package fmtutil 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var sprintfFormatLeadingCharMaxIntValTests = []struct { 9 | prefix string 10 | intval int 11 | format string 12 | formatInt int 13 | formatted string 14 | }{ 15 | {"0", 0, "%01d", 0, "0"}, 16 | {"0", 9, "%01d", 1, "1"}, 17 | {"0", 10, "%02d", 1, "01"}, 18 | {"0", 14, "%02d", 1, "01"}, 19 | {"0", 100, "%03d", 1, "001"}, 20 | {"0", 100, "%03d", 11, "011"}, 21 | {"0", 999, "%03d", 11, "011"}, 22 | {"0", 1000, "%04d", 11, "0011"}, 23 | } 24 | 25 | func TestSprintfFormatLeadingCharMaxIntVal(t *testing.T) { 26 | for _, tt := range sprintfFormatLeadingCharMaxIntValTests { 27 | format := SprintfFormatLeadingCharMaxIntVal(tt.prefix, tt.intval) 28 | if tt.format != format { 29 | t.Errorf("fmtutil.SprintfFormatLeadingCharMaxIntVal(\"%s\", %d) Error: want [%s], got [%s]", 30 | tt.prefix, tt.intval, tt.format, format) 31 | } 32 | formatted := fmt.Sprintf(format, tt.formatInt) 33 | if tt.formatted != formatted { 34 | t.Errorf("TestSprintfFormatLeadingCharMaxIntVal: Error: want [%s], got [%s]", 35 | tt.formatted, formatted) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /unicode/unicodeutil/characters.go: -------------------------------------------------------------------------------- 1 | package unicodeutil 2 | 3 | const ( 4 | Apostrophe = "'" 5 | ApostropheName = "Apostrophe" 6 | ApostropheNamePostscript = "quotesingle" 7 | ApostropheUTF16Encoding = 0x0027 8 | QuotationMark = "\"" 9 | QuotationMarkName = "Quotation Mark" 10 | QuotationMarkNamePostscript = "quotedbl" 11 | QuotationMarkUTF16Encoding = 0x0022 12 | LeftDoubleQuotationMark = `“` 13 | LeftDoubleQuotationMarkName = "Left Double Quotation Mark" 14 | LeftDoubleQuotationMarkNamePostscript = "quotedblleft" 15 | LeftDoubleQuotationMarkUTF16Encoding = 0x201C 16 | LeftDoubleQuotationMarkURLCompart = "https://www.compart.com/en/unicode/U+201C" 17 | RightDoubleQuotationMark = `”` 18 | RightDoubleQuotationMarkName = "Right Double Quotation Mark" 19 | RightDoubleQuotationMarkNamePostscript = "quotedblright" 20 | RightDoubleQuotationMarkUTF16Encoding = 0x201D 21 | RightDoubleQuotationMarkURLCompart = "https://www.compart.com/en/unicode/U+201D" 22 | ) 23 | -------------------------------------------------------------------------------- /net/urlutil/scheme.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | const uriSchemePattern string = `^(?i)([a-z][0-9a-z\-\+.]+)://` 9 | 10 | var rxScheme *regexp.Regexp = regexp.MustCompile(uriSchemePattern) 11 | 12 | // https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml 13 | 14 | // URIHasScheme returns a boolean true or false if the string 15 | // has a URI scheme. 16 | func URIHasScheme(uri string) bool { 17 | scheme := URIScheme(uri) 18 | return len(scheme) > 0 19 | } 20 | 21 | // URIScheme extracts the URI scheme from a string. It returns 22 | // an empty string if none is encountered. 23 | func URIScheme(uri string) string { 24 | uri = strings.TrimSpace(uri) 25 | m := rxScheme.FindAllStringSubmatch(uri, -1) 26 | if len(m) > 0 && len(m[0]) == 2 { 27 | return strings.TrimSpace(m[0][1]) 28 | } 29 | return "" 30 | } 31 | 32 | func IsHTTP(uri string, inclHTTP, inclHTTPS bool) bool { 33 | try := strings.ToLower(strings.TrimSpace(uri)) 34 | if strings.Index(try, "http://") == 0 && inclHTTP { 35 | return true 36 | } else if strings.Index(try, "https://") == 0 && inclHTTPS { 37 | return true 38 | } 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2025 grokify, Stack Overflow as applicable in comments. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /image/colors/reference.go: -------------------------------------------------------------------------------- 1 | package colors 2 | 3 | import ( 4 | "image/color" 5 | 6 | "github.com/grokify/mogo/math/mathutil" 7 | ) 8 | 9 | // GoogleChartColorsHex is the color palette for Google Charts as collected by 10 | // Craig Davis here: https://gist.github.com/there4/2579834 11 | var GoogleChartColorsHex = [...]string{ 12 | "#3366CC", 13 | "#DC3912", 14 | "#FF9900", 15 | "#109618", 16 | "#990099", 17 | "#3B3EAC", 18 | "#0099C6", 19 | "#DD4477", 20 | "#66AA00", 21 | "#B82E2E", 22 | "#316395", 23 | "#994499", 24 | "#22AA99", 25 | "#AAAA11", 26 | "#6633CC", 27 | "#E67300", 28 | "#8B0707", 29 | "#329262", 30 | "#5574A6", 31 | "#3B3EAC", 32 | } 33 | 34 | func GetGoogleChartColors() []color.RGBA { 35 | rgbas := []color.RGBA{} 36 | for _, hex := range GoogleChartColorsHex { 37 | rgb, err := ParseHex(hex) 38 | if err != nil { 39 | panic(err) 40 | } 41 | rgbas = append(rgbas, rgb) 42 | } 43 | return rgbas 44 | } 45 | 46 | var GoogleChartColors = GetGoogleChartColors() 47 | 48 | func GoogleChartColorX(index uint32) color.RGBA { 49 | _, remainder := mathutil.Divide(int64(index), 50 | int64(len(GoogleChartColorsHex))) 51 | return GoogleChartColors[remainder] 52 | } 53 | -------------------------------------------------------------------------------- /time/month/parse.go: -------------------------------------------------------------------------------- 1 | package month 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "time" 7 | 8 | "github.com/grokify/mogo/type/stringsutil" 9 | ) 10 | 11 | const ( 12 | MonthsEnAbbr3 = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec" 13 | MonthsEnFull = "January,February,March,April,May,June,July,August,September,October,November,December" 14 | ) 15 | 16 | var ( 17 | ErrMonthsFormatInvalid = errors.New("invalid number of month elements") 18 | ErrMonthNotFound = errors.New("month not found") 19 | ) 20 | 21 | func Parse(months []string, value string, insensitive bool) (time.Month, error) { 22 | enums := months 23 | if len(enums) == 1 { 24 | enums = strings.Split(enums[0], ",") 25 | } 26 | enums = stringsutil.SliceCondenseSpace(enums, true, false) 27 | if len(enums) != 12 { 28 | return time.January, ErrMonthsFormatInvalid 29 | } 30 | value = strings.TrimSpace(value) 31 | if insensitive { 32 | value = strings.ToLower(value) 33 | } 34 | 35 | for i, monthTry := range enums { 36 | if (insensitive && strings.ToLower(monthTry) == value) || 37 | monthTry == value { 38 | return time.Month(i + 1), nil 39 | } 40 | } 41 | return time.January, ErrMonthNotFound 42 | } 43 | -------------------------------------------------------------------------------- /image/imageutil/lines.go: -------------------------------------------------------------------------------- 1 | package imageutil 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | 7 | "github.com/grokify/mogo/image/colors" 8 | ) 9 | 10 | func RowsFilteredColor(img image.Image, c color.Color, cmore ...color.Color) []int { 11 | minPt := img.Bounds().Min 12 | maxPt := img.Bounds().Max 13 | rows := []int{} 14 | 15 | cmore = append([]color.Color{c}, cmore...) 16 | for y := minPt.Y; y <= maxPt.Y; y++ { 17 | for x := minPt.X; x <= maxPt.X; x++ { 18 | for _, ci := range cmore { 19 | if colors.ColorToHex(img.At(x, y), false, false) == colors.ColorToHex(ci, false, false) { 20 | rows = append(rows, y) 21 | continue 22 | } 23 | } 24 | } 25 | } 26 | return rows 27 | } 28 | 29 | func ColsFilteredColor(img image.Image, c ...color.Color) []int { 30 | cols := []int{} 31 | if len(c) == 0 { 32 | return cols 33 | } 34 | clrs := colors.Colors(c) 35 | 36 | minPt := img.Bounds().Min 37 | maxPt := img.Bounds().Max 38 | COL: 39 | for x := minPt.X; x <= maxPt.X; x++ { 40 | for y := minPt.Y; y <= maxPt.Y; y++ { 41 | cx := img.At(x, y) 42 | if !clrs.In(cx) { 43 | continue COL 44 | } 45 | } 46 | cols = append(cols, x) 47 | } 48 | return cols 49 | } 50 | -------------------------------------------------------------------------------- /archive/archivesecure/filepaths.go: -------------------------------------------------------------------------------- 1 | package archivesecure 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // Options for archive path validation 11 | type PathCheckOptions struct { 12 | CheckSymlink bool // tar only 13 | CheckDevice bool // tar only 14 | } 15 | 16 | // IsUnsafePath returns true if the path is unsafe 17 | func IsUnsafePath(name string, opts PathCheckOptions) bool { 18 | // Null byte 19 | if strings.Contains(name, "\x00") { 20 | return true 21 | } 22 | 23 | // Absolute paths 24 | if filepath.IsAbs(name) || strings.HasPrefix(name, "/") { 25 | return true 26 | } 27 | 28 | // Windows drive letters 29 | if len(name) > 2 && name[1] == ':' { 30 | return true 31 | } 32 | 33 | if runtime.GOOS != "windows" && len(name) > 2 && name[1] == ':' { 34 | return true 35 | } 36 | 37 | clean := filepath.Clean(name) 38 | 39 | // Traversal 40 | if clean == ".." || strings.HasPrefix(clean, ".."+string(os.PathSeparator)) { 41 | return true 42 | } 43 | if strings.Contains(name, "../") || strings.Contains(name, `..\`) { 44 | return true 45 | } 46 | 47 | // Symlink or device checks can be done externally by passing opts 48 | return false 49 | } 50 | -------------------------------------------------------------------------------- /regexp/regexputil/matcher.go: -------------------------------------------------------------------------------- 1 | package regexputil 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // StringMatcher provides the ability to match a string 8 | // against multiple regular expressions with a pre-formatting 9 | // function as well. 10 | type StringMatcher struct { 11 | Matchers []*regexp.Regexp 12 | PreMatch func(string) string 13 | } 14 | 15 | // NewStringMatcher returns a new StringMatcher instance. 16 | func NewStringMatcher() StringMatcher { 17 | return StringMatcher{Matchers: []*regexp.Regexp{}} 18 | } 19 | 20 | // AddMatcher adds a regular expression to attempt to 21 | // match against. 22 | func (sm *StringMatcher) AddMatcher(rx *regexp.Regexp) { 23 | if sm.Matchers == nil { 24 | sm.Matchers = []*regexp.Regexp{} 25 | } 26 | sm.Matchers = append(sm.Matchers, rx) 27 | } 28 | 29 | // Match runs the provided string against the prematch 30 | // function and regular expresssions, returning true if 31 | // any of the expressions match. 32 | func (sm *StringMatcher) Match(input string) bool { 33 | if sm.PreMatch != nil { 34 | input = sm.PreMatch(input) 35 | } 36 | for _, rx := range sm.Matchers { 37 | if rx.MatchString(input) { 38 | return true 39 | } 40 | } 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /text/languageutil/language_test.go: -------------------------------------------------------------------------------- 1 | package languageutil 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "golang.org/x/text/language" 8 | ) 9 | 10 | var joinLanguageTests = []struct { 11 | v []string 12 | sep string 13 | joinLang language.Tag 14 | want string 15 | }{ 16 | {[]string{}, ",", language.English, ""}, 17 | {[]string{"Foo"}, ",", language.English, "Foo"}, 18 | {[]string{"Foo", "Bar"}, ",", language.English, "Foo and Bar"}, 19 | {[]string{"Foo", "Bar", "Baz"}, ",", language.English, "Foo, Bar, and Baz"}, 20 | {[]string{"Foo", "Bar", "Bax", "Qux"}, ",", language.English, "Foo, Bar, Bax, and Qux"}} 21 | 22 | func TestJoinLanguage(t *testing.T) { 23 | for _, tt := range joinLanguageTests { 24 | try := tt.v 25 | got, err := JoinLanguage(try, tt.sep, tt.joinLang) 26 | if err != nil { 27 | t.Errorf("languageutil.TestJoinLanguage failed: Have [%v] Got [%v] Want [%v] Err[%v]", 28 | strings.Join(tt.v, ", "), 29 | got, 30 | tt.want, 31 | err.Error()) 32 | } else if got != tt.want { 33 | t.Errorf("languageutil.TestJoinLanguage failed: Have [%v] Got [%v] Want [%v]", 34 | strings.Join(tt.v, ", "), 35 | got, 36 | tt.want) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crypto/rsautil/pkcs1util.go: -------------------------------------------------------------------------------- 1 | package rsautil 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "encoding/base64" 10 | ) 11 | 12 | // SignRS256 signs data with rsa-sha256 13 | func SignRS256(r *rsa.PrivateKey, data []byte) ([]byte, error) { 14 | h := sha256.New() 15 | h.Write(data) 16 | d := h.Sum(nil) 17 | return rsa.SignPKCS1v15(rand.Reader, r, crypto.SHA256, d) 18 | } 19 | 20 | // SignRS384 signs data with rsa-sha384 21 | func SignRS384(r *rsa.PrivateKey, data []byte) ([]byte, error) { 22 | h := sha512.New384() 23 | h.Write(data) 24 | d := h.Sum(nil) 25 | return rsa.SignPKCS1v15(rand.Reader, r, crypto.SHA384, d) 26 | } 27 | 28 | // SignRS512 signs data with rsa-sha512 29 | func SignRS512(r *rsa.PrivateKey, data []byte) ([]byte, error) { 30 | h := sha512.New() 31 | h.Write(data) 32 | d := h.Sum(nil) 33 | return rsa.SignPKCS1v15(rand.Reader, r, crypto.SHA512, d) 34 | } 35 | 36 | // SignRS512 signs data with rsa-sha512 37 | func SignRS512Base64(r *rsa.PrivateKey, data []byte) (string, error) { 38 | sig, err := SignRS512(r, data) 39 | if err != nil { 40 | return "", err 41 | } 42 | return base64.StdEncoding.EncodeToString(sig), nil 43 | } 44 | -------------------------------------------------------------------------------- /log/logutil/logutil.go: -------------------------------------------------------------------------------- 1 | // logutil provides logging utility functions which are useful for 2 | // decreasing lines of code for simple error logging. 3 | package logutil 4 | 5 | import ( 6 | "log" 7 | 8 | // "github.com/go-logfmt/logfmt" 9 | "github.com/grokify/mogo/errors/errorsutil" 10 | ) 11 | 12 | func FatalErr(err error, wrap ...string) { 13 | err = errorsutil.Wrap(err, wrap...) 14 | if err != nil { 15 | log.Fatal(err.Error()) 16 | } 17 | } 18 | 19 | func PrintErr(err error, wrap ...string) { 20 | err = errorsutil.Wrap(err, wrap...) 21 | if err != nil { 22 | log.Print(err.Error()) 23 | } 24 | } 25 | 26 | func PrintlnErr(err error, wrap ...string) { 27 | err = errorsutil.Wrap(err, wrap...) 28 | if err != nil { 29 | log.Println(err.Error()) 30 | } 31 | } 32 | 33 | /* 34 | func LogfmtString(m map[string][]string) (string, error) { 35 | var buf bytes.Buffer 36 | w := bufio.NewWriter(&buf) 37 | e := logfmt.NewEncoder(w) 38 | for k, vs := range m { 39 | for _, v := range vs { 40 | if err := e.EncodeKeyval(k, v); err != nil { 41 | return "", err 42 | } 43 | } 44 | } 45 | if err := e.EndRecord(); err != nil { 46 | return "", err 47 | } 48 | return buf.String(), nil 49 | } 50 | */ 51 | -------------------------------------------------------------------------------- /text/markdown/times.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/grokify/mogo/time/timeutil" 7 | ) 8 | 9 | func GeneratedAtLocationStringsHuman(dt time.Time, locNames []string) (string, error) { 10 | return GeneratedAtLocationStrings(timeutil.HumanDateTime, dt, locNames) 11 | } 12 | 13 | func GeneratedAtLocationStrings(layout string, dt time.Time, locNames []string) (string, error) { 14 | locs := []*time.Location{} 15 | for _, locStr := range locNames { 16 | if loc, err := time.LoadLocation(locStr); err != nil { 17 | return "", err 18 | } else if loc != nil { 19 | locs = append(locs, loc) 20 | } 21 | } 22 | return GeneratedAt(layout, dt, locs), nil 23 | } 24 | 25 | func GeneratedAt(layout string, dt time.Time, locs []*time.Location) string { 26 | prefix := "Generated at" 27 | if len(locs) == 0 { 28 | return prefix + " " + dt.Format(layout) 29 | } else if len(locs) == 1 { 30 | if locs[0] != nil { 31 | dt = dt.In(locs[0]) 32 | } 33 | return prefix + " " + dt.Format(layout) 34 | } 35 | str := prefix + "\n" 36 | for _, loc := range locs { 37 | if loc != nil { 38 | dt = dt.In(loc) 39 | } 40 | str += "* " + dt.Format(layout) + "\n" 41 | } 42 | return str 43 | } 44 | -------------------------------------------------------------------------------- /time/duration/duration_info_test.go: -------------------------------------------------------------------------------- 1 | package duration 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var durationInfoTests = []struct { 8 | whpd float32 9 | wdpw float32 10 | v string 11 | seconds int64 12 | }{ 13 | {8.0, 5.0, "0 minutes", 0}, 14 | {8.0, 5.0, "25 minutes", 1500}, 15 | {8.0, 5.0, "30 minutes", 1800}, 16 | {8.0, 5.0, "1 hour, 5 minutes", 3900}, 17 | {8.0, 5.0, "1 hour, 6 minutes", 3960}, 18 | {8, 5, "7 hours, 35 minutes", 27300}, 19 | {8.0, 5.0, "1 day", 28800}, 20 | {24, 5.0, "1 day", 60 * 60 * 24}, 21 | {8.0, 5.0, "1 day, 6 hours, 55 minutes", 53700}, 22 | {8.0, 5.0, "2 days", 57600}, 23 | {8.0, 5.0, "3 days", 86400}, 24 | {8.0, 5.0, "2 days, 3 hours, 25 minutes", 69900}, 25 | } 26 | 27 | func TestDurationInfo(t *testing.T) { 28 | for _, tt := range durationInfoTests { 29 | di, err := ParseDurationInfo(tt.v) 30 | if err != nil { 31 | t.Errorf("jiraxml.ParseDurationInfo(\"%s\") error: (%s)", tt.v, err.Error()) 32 | } 33 | d := di.Duration(tt.whpd, tt.wdpw) 34 | dursec := int64(d.Seconds()) 35 | if dursec != tt.seconds { 36 | t.Errorf("DurationInfo.Duration(%d,%d) mismatch: want (%d), got (%d)", uint(tt.whpd), uint(tt.wdpw), tt.seconds, dursec) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/sast_codeql.yaml: -------------------------------------------------------------------------------- 1 | name: "CodeQL SAST Analysis" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | schedule: 11 | - cron: '30 1 * * 0' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | analyze: 16 | name: Analyze 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 360 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: ['go'] 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v6 32 | 33 | - name: Initialize CodeQL 34 | uses: github/codeql-action/init@v4 35 | with: 36 | languages: ${{ matrix.language }} 37 | queries: security-extended,security-and-quality 38 | 39 | - name: Set up Go 40 | uses: actions/setup-go@v6 41 | with: 42 | go-version: '1.24.x' 43 | 44 | - name: Autobuild 45 | uses: github/codeql-action/autobuild@v4 46 | 47 | - name: Perform CodeQL Analysis 48 | uses: github/codeql-action/analyze@v4 49 | with: 50 | category: "/language:${{matrix.language}}" -------------------------------------------------------------------------------- /text/markdown/skype.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var ( 9 | rxHTTPURLPrefix *regexp.Regexp = regexp.MustCompile(`^(i?)https?://`) 10 | rxSkypeLink *regexp.Regexp = regexp.MustCompile(`<([^><\|]*?)\|([^>]*?)>`) 11 | rxBacktick3 *regexp.Regexp = regexp.MustCompile(`^\s*` + "```.+```" + `\s*$`) 12 | ) 13 | 14 | // SkypeToMarkdown converts Skype markup to Markdown. This is specifically 15 | // useful for converting Slack messages to Markdown. The `stripURLAutoLink` 16 | // parameter will remove links when they are within 3 backticks and the 17 | // link innerHTML and URL match. Default is `true`. 18 | func SkypeToMarkdown(input string, stripURLAutoLink bool) string { 19 | output := input 20 | backtick3 := rxBacktick3.MatchString(output) 21 | m := rxSkypeLink.FindAllStringSubmatch(input, -1) 22 | for _, n := range m { 23 | rxlink := regexp.MustCompile(regexp.QuoteMeta(n[0])) 24 | if stripURLAutoLink && backtick3 && n[1] == n[2] && rxHTTPURLPrefix.MatchString(n[1]) { 25 | output = rxlink.ReplaceAllString(output, n[1]) 26 | } else { 27 | mkdn := fmt.Sprintf("[%s](%s)", n[2], n[1]) 28 | output = rxlink.ReplaceAllString(output, mkdn) 29 | } 30 | } 31 | return output 32 | } 33 | -------------------------------------------------------------------------------- /type/stringsutil/compare.go: -------------------------------------------------------------------------------- 1 | package stringsutil 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func Equal(str1, str2 string, trim, lower bool) bool { 8 | if trim { 9 | str1 = strings.TrimSpace(str1) 10 | str2 = strings.TrimSpace(str2) 11 | } 12 | if lower { 13 | str1 = strings.ToLower(str1) 14 | str2 = strings.ToLower(str2) 15 | } 16 | if str1 == str2 { 17 | return true 18 | } 19 | return false 20 | } 21 | 22 | func EndsWith(s string, substrs ...string) bool { 23 | for _, substr := range substrs { 24 | idx := strings.Index(s, substr) 25 | if len(s) >= len(substr) && 26 | idx > -1 && 27 | idx == len(s)-len(substr) { 28 | return true 29 | } 30 | } 31 | return false 32 | } 33 | 34 | func ContainsMore(s string, substrs []string, all, lc, trimSpaceSubstr bool) bool { 35 | if lc { 36 | s = strings.ToLower(s) 37 | } 38 | matchAll := true 39 | for _, sub := range substrs { 40 | if lc { 41 | sub = strings.ToLower(sub) 42 | } 43 | if trimSpaceSubstr { 44 | sub = strings.TrimSpace(sub) 45 | } 46 | ok := strings.Contains(s, sub) 47 | if ok && !all { 48 | return true 49 | } else if !ok { 50 | if all { 51 | return false 52 | } 53 | matchAll = false 54 | } 55 | } 56 | return matchAll 57 | } 58 | -------------------------------------------------------------------------------- /log/severity/severity_test.go: -------------------------------------------------------------------------------- 1 | package severity 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var severityTests = []struct { 8 | execSeverity string 9 | itemSeverity string 10 | include bool 11 | errorIsNil bool 12 | }{ 13 | {SeverityDisabled, SeverityCritical, false, true}, 14 | {SeverityError, SeverityDisabled, false, true}, 15 | {SeverityError, SeverityWarning, false, true}, 16 | {SeverityError, SeverityCritical, true, true}, 17 | {SeverityDebug, SeverityWarning, true, true}, 18 | {SeverityDebug, SeverityCritical, true, true}, 19 | {SeverityCritical, SeverityError, false, true}, 20 | {SeverityDebug, SeverityDisabled, false, true}, 21 | {"foo", "bar", false, false}, 22 | } 23 | 24 | func TestSeverity(t *testing.T) { 25 | n := len(severityTests) 26 | for i, tt := range severityTests { 27 | tryIncl, err := SeverityInclude(tt.execSeverity, tt.itemSeverity) 28 | if err != nil && tt.errorIsNil { 29 | t.Errorf("[%d/%d] severity.SeverityInclude(\"%s\",\"%s\") error [%v]", i+1, n, tt.execSeverity, tt.itemSeverity, err.Error()) 30 | } 31 | if tryIncl != tt.include { 32 | t.Errorf("[%d/%d] severity.SeverityInclude(\"%s\",\"%s\") want [%v] got [%v]", i+1, n, tt.execSeverity, tt.itemSeverity, tt.include, tryIncl) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /strconv/strconvutil/strconv_lang_test.go: -------------------------------------------------------------------------------- 1 | package strconvutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/grokify/mogo/text/languageutil" 7 | ) 8 | 9 | var strconvLangTests = []struct { 10 | lang string 11 | strval string 12 | intval int 13 | }{ 14 | {languageutil.English, "1,234,567,890", 1234567890}, 15 | {languageutil.Thai, "1,234,567,890", 1234567890}, 16 | {languageutil.Danish, "1 234 567 890", 1234567890}, 17 | {languageutil.Finnish, "1 234 567 890", 1234567890}, 18 | {languageutil.German, "1 234 567 890", 1234567890}, 19 | {languageutil.French, "1 234 567 890", 1234567890}, 20 | {languageutil.Italian, "1.234.567.890", 1234567890}, 21 | {languageutil.Norwegian, "1.234.567.890", 1234567890}, 22 | {languageutil.Spanish, "1.234.567.890", 1234567890}, 23 | } 24 | 25 | func TestAtoiLang(t *testing.T) { 26 | for _, tt := range strconvLangTests { 27 | tryInt, err := AtoiLang(tt.lang, tt.strval) 28 | if err != nil { 29 | t.Errorf("strconvutil.AtoiLang(\"%s\", \"%s\") Error: [%s]", 30 | tt.lang, tt.strval, err.Error()) 31 | } 32 | if err == nil && tryInt != tt.intval { 33 | t.Errorf("strconvutil.AtoiLang(\"%s\", \"%s\") Error: want [%d], got [%d]", 34 | tt.lang, tt.strval, tt.intval, tryInt) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /time/timeutil/syscall_test.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "syscall" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var syscallTests = []struct { 10 | nsec int64 11 | rfc3339Nano string 12 | }{ 13 | {1674382312 * int64(time.Second), "2023-01-22T10:11:52Z"}, 14 | {1674382312111222333, "2023-01-22T10:11:52.111222333Z"}, 15 | } 16 | 17 | func TestSyscall(t *testing.T) { 18 | for _, tt := range syscallTests { 19 | ts := syscall.NsecToTimespec(tt.nsec) 20 | tsTime := Timespec(ts) 21 | tsTry := tsTime.UTC().Format(time.RFC3339Nano) 22 | 23 | if tsTry != tt.rfc3339Nano { 24 | t.Errorf("Timespec(): nsec [%d] want [%s], got [%s]", tt.nsec, tt.rfc3339Nano, tsTry) 25 | } 26 | 27 | ts2 := syscall.Timespec{Nsec: tt.nsec} 28 | ts2Time := Timespec(ts2) 29 | ts2Try := ts2Time.UTC().Format(time.RFC3339Nano) 30 | 31 | if ts2Try != tt.rfc3339Nano { 32 | t.Errorf("Timespec(): nsec [%d] want [%s], got [%s]", tt.nsec, tt.rfc3339Nano, ts2Try) 33 | } 34 | 35 | tv := syscall.NsecToTimeval(tt.nsec) 36 | tvTime := Timeval(tv) 37 | tvTry := tvTime.UTC().Format(time.RFC3339Nano) 38 | 39 | if tsTry != tt.rfc3339Nano { 40 | t.Errorf("Timeval(): nsec [%d] want [%s], got [%s]", tt.nsec, tt.rfc3339Nano, tvTry) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /encoding/jsonpointer/parser_test.go: -------------------------------------------------------------------------------- 1 | package jsonpointer 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "golang.org/x/exp/slices" 8 | ) 9 | 10 | var jsonPointerTests = []struct { 11 | jsonPointer string 12 | document string 13 | path []string 14 | }{ 15 | {"/components/schemas/FooBar", "", []string{"components", "schemas", "FooBar"}}, 16 | {"#/components/schemas/FooBar", "", []string{"components", "schemas", "FooBar"}}, 17 | {"mydoc.yaml#/components/schemas/FooBar", "mydoc.yaml", []string{"components", "schemas", "FooBar"}}, 18 | } 19 | 20 | // TestParseJSONPointer ensures the `ParseJSONPointer` is working properly. 21 | func TestParseJSONPointer(t *testing.T) { 22 | for _, tt := range jsonPointerTests { 23 | ptr, err := ParseJSONPointer(tt.jsonPointer) 24 | if err != nil { 25 | t.Errorf("openapi3.ParseJSONPointer(\"%s\") Error [%s]", 26 | tt.jsonPointer, err.Error()) 27 | } 28 | if ptr.Document != tt.document { 29 | t.Errorf("JSONPointer.Document Mismatch: want [%v], got [%v]", 30 | tt.document, ptr.Document) 31 | } 32 | if !slices.Equal(ptr.Path, tt.path) { 33 | t.Errorf("JSONPointer.Path Mismatch: want [%v], got [%v]", 34 | strings.Join(tt.path, ", "), strings.Join(ptr.Path, ", ")) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /image/imageutil/animate.go: -------------------------------------------------------------------------------- 1 | package imageutil 2 | 3 | import ( 4 | "image" 5 | "image/gif" 6 | ) 7 | 8 | func BuildGIFAnimationSimpleRead(src *gif.GIF, delay int, names []string, f ToPalettedFunc, consistentSize bool) (*gif.GIF, error) { 9 | imgs, err := ReadImages(names) 10 | if err != nil { 11 | return nil, err 12 | } 13 | if consistentSize { 14 | imgs2 := Images(imgs) 15 | imgs2.ConsistentSize(ScalerBest(), AlignCenter) 16 | imgs = []image.Image(imgs2) 17 | } 18 | return BuildGIFAnimationSimple(src, delay, imgs, f), nil 19 | } 20 | 21 | // BuildGifAnimationSimple assembles a set of images in an animated GIF file. 22 | // Set `delay` to `0`. 23 | func BuildGIFAnimationSimple(src *gif.GIF, delay int, imgs []image.Image, f ToPalettedFunc) *gif.GIF { 24 | if src == nil { 25 | src = &gif.GIF{} 26 | } 27 | for _, img := range imgs { 28 | pimg := imageToPalettedFuncWrap(img, f) 29 | src.Image = append(src.Image, pimg) 30 | src.Delay = append(src.Delay, delay) 31 | } 32 | return src 33 | } 34 | 35 | func imageToPalettedFuncWrap(src image.Image, f ToPalettedFunc) *image.Paletted { 36 | if v, ok := src.(*image.Paletted); ok { 37 | return v 38 | } 39 | if f != nil { 40 | return f(src) 41 | } 42 | return ImageToPalettedPlan9(src) 43 | } 44 | -------------------------------------------------------------------------------- /type/slicesutil/slice_test.go: -------------------------------------------------------------------------------- 1 | package slicesutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var splitMaxLengthTests = []struct { 8 | v []uint 9 | s int 10 | n int 11 | l0 uint 12 | }{ 13 | {v: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, s: 2, n: 5, l0: 9}, 14 | {v: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, s: 3, n: 4, l0: 10}, 15 | {v: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, s: 4, n: 3, l0: 9}, 16 | {v: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, s: 10, n: 1, l0: 1}, 17 | {v: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, s: 100, n: 1, l0: 1}, 18 | 19 | {v: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, s: 2, n: 7, l0: 13}, 20 | {v: []uint{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, s: 5, n: 3, l0: 11}, 21 | } 22 | 23 | func TestSplitMaxLength(t *testing.T) { 24 | for _, tt := range splitMaxLengthTests { 25 | res := SplitMaxLength(tt.v, tt.s) 26 | if len(res) != tt.n { 27 | t.Errorf("slicesutil.SplitMaxLength(%v, %d): mismatch on len: want (%d), got (%d)", 28 | tt.v, tt.s, tt.n, len(res)) 29 | } 30 | if len(res) > 0 { 31 | l0 := len(res) - 1 32 | row := res[l0] 33 | if tt.l0 != row[0] { 34 | t.Errorf("slicesutil.SplitMaxLength(%v, %d): mismatch on value: want (%d), got (%d)", 35 | tt.v, tt.s, tt.l0, l0) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /archive/tarutil/tarsecure.go: -------------------------------------------------------------------------------- 1 | package tarutil 2 | 3 | import ( 4 | "archive/tar" 5 | "errors" 6 | "io" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/grokify/mogo/archive/archivesecure" 11 | ) 12 | 13 | func FindUnsafeTarPaths(tr *tar.Reader) ([]string, error) { 14 | var bad []string 15 | for { 16 | hdr, err := tr.Next() 17 | if err != nil { 18 | if errors.Is(err, io.EOF) { 19 | break 20 | } 21 | return nil, err 22 | } 23 | 24 | if archivesecure.IsUnsafePath(hdr.Name, archivesecure.PathCheckOptions{ 25 | CheckSymlink: hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink, 26 | CheckDevice: hdr.Typeflag == tar.TypeChar || hdr.Typeflag == tar.TypeBlock, 27 | }) { 28 | bad = append(bad, hdr.Name) 29 | } 30 | 31 | if hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink { 32 | target := hdr.Linkname 33 | if strings.HasPrefix(filepath.Clean(target), "..") { 34 | bad = append(bad, hdr.Name+" -> "+target) 35 | } 36 | } 37 | 38 | if hdr.Typeflag == tar.TypeChar || hdr.Typeflag == tar.TypeBlock { 39 | bad = append(bad, hdr.Name+" (device file)") 40 | } 41 | } 42 | 43 | if len(bad) > 0 { 44 | return bad, errors.New("unsafe paths detected in tar") 45 | } 46 | return nil, nil 47 | } 48 | -------------------------------------------------------------------------------- /git/cmd/gitremoteaddupstream/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | 8 | "github.com/grokify/mogo/os/executil" 9 | "github.com/grokify/mogo/path/filepathutil" 10 | flags "github.com/jessevdk/go-flags" 11 | ) 12 | 13 | // $ go get github.com/grokify/mogo/git/apps/gitremoteaddupstream 14 | 15 | type cliOptions struct { 16 | Parent string `short:"p" long:"parent" description:"GitHub parent user" required:"true"` 17 | Exec []bool `short:"e" long:"exec" description:"execute" required:"false"` 18 | } 19 | 20 | func main() { 21 | opts := cliOptions{} 22 | _, err := flags.Parse(&opts) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | leafDir, err := filepathutil.CurLeafDir() 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | gitCmd := fmt.Sprintf("git remote add upstream https://github.com/%s/%s.git", strings.TrimSpace(opts.Parent), leafDir) 33 | fmt.Printf("CMD: %s\n", gitCmd) 34 | if len(opts.Exec) > 0 { 35 | _, _, err := executil.ExecSimple(gitCmd) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | fmt.Println("Executed...") 40 | fmt.Println("Common next steps:\n$ git fetch upstream\n$ git merge upstream/master\n$ git push origin master") 41 | } 42 | 43 | fmt.Println("DONE") 44 | } 45 | -------------------------------------------------------------------------------- /encoding/csvutil/csvutil_test.go: -------------------------------------------------------------------------------- 1 | // base10 supports Base10 encoding. 2 | package csvutil 3 | 4 | import ( 5 | "testing" 6 | ) 7 | 8 | var newReaderTests = []struct { 9 | filename string 10 | separator rune 11 | columns []string 12 | colNameLen []int 13 | }{ 14 | {"testdata/simple.csv", ',', []string{"bazqux", "foobar"}, []int{6, 6}}, 15 | {"testdata/utf8bom.csv", ',', []string{"foobar", "bazqux"}, []int{6, 6}}, 16 | } 17 | 18 | func TestNewReader(t *testing.T) { 19 | for _, tt := range newReaderTests { 20 | csvReader, f, err := NewReaderFile(tt.filename, tt.separator) 21 | if err != nil { 22 | t.Errorf("csvutil.NewReader(\"%s\",...): error [%s]", 23 | tt.filename, err.Error()) 24 | } 25 | defer f.Close() 26 | line, err := csvReader.Read() 27 | if err != nil { 28 | t.Errorf("csvutil.NewReader(\"%s\",...): csvReader.Read() error [%s]", 29 | tt.filename, err.Error()) 30 | } 31 | if len(line) == 0 { 32 | t.Errorf("csvutil.NewReader(\"%s\",...): line is empty", 33 | tt.filename) 34 | } 35 | colName1Try := line[0] 36 | if len(colName1Try) != len(tt.columns[0]) { 37 | t.Errorf("csvutil.NewReader(\"%s\",...): colName mismatch want [%d] got [%d]", 38 | tt.filename, len(tt.columns[0]), len(colName1Try)) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bytes/bytesutil/bytesutil.go: -------------------------------------------------------------------------------- 1 | package bytesutil 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/grokify/mogo/math/mathutil" 7 | ) 8 | 9 | const UTF8BOM = "\xef\xbb\xbf" 10 | 11 | func TrimUTF8BOM(data []byte) []byte { 12 | return bytes.TrimPrefix(data, []byte(UTF8BOM)) 13 | } 14 | 15 | func BytesToInt(s []byte) int { 16 | var res int 17 | for _, v := range s { 18 | res <<= 8 19 | res |= int(v) 20 | } 21 | return res 22 | } 23 | 24 | // https://stackoverflow.com/questions/48178008/convert-byte-slice-to-int-slice 25 | func BytesToInts(bytes []byte) []int { 26 | ints := []int{} 27 | for _, b := range bytes { 28 | ints = append(ints, int(b)) 29 | } 30 | return ints 31 | } 32 | 33 | // https://stackoverflow.com/questions/48178008/convert-byte-slice-to-int-slice 34 | func BytesToIntsMore(bytes []byte, intLength int) []int { 35 | //ints := make([]int, len(bytes)) 36 | ints := []int{} 37 | curNum := []byte{} 38 | for i, b := range bytes { 39 | curNum = append(curNum, b) 40 | if intLength > 0 { 41 | if mod := mathutil.ModPyInt(i, intLength); mod == intLength-1 { 42 | ints = append(ints, BytesToInt(curNum)) 43 | curNum = []byte{} 44 | } 45 | } 46 | } 47 | if len(curNum) > 0 { 48 | ints = append(ints, BytesToInt(curNum)) 49 | } 50 | return ints 51 | } 52 | -------------------------------------------------------------------------------- /type/slicesutil/index_test.go: -------------------------------------------------------------------------------- 1 | package slicesutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/grokify/mogo/math/mathutil" 7 | ) 8 | 9 | var reverseIndexTests = []struct { 10 | n uint 11 | i uint 12 | r uint 13 | }{ 14 | {n: 5, i: 0, r: 4}, 15 | {n: 5, i: 1, r: 3}, 16 | {n: 5, i: 2, r: 2}, 17 | {n: 5, i: 3, r: 1}, 18 | {n: 5, i: 4, r: 0}, 19 | {n: 5, i: 5, r: 4}, 20 | {n: 5, i: 6, r: 3}, 21 | {n: 5, i: 7, r: 2}, 22 | {n: 5, i: 8, r: 1}, 23 | {n: 5, i: 9, r: 0}, 24 | 25 | {n: 4, i: 0, r: 3}, 26 | {n: 4, i: 1, r: 2}, 27 | {n: 4, i: 2, r: 1}, 28 | {n: 4, i: 3, r: 0}, 29 | } 30 | 31 | func TestReverseIndex(t *testing.T) { 32 | for _, tt := range reverseIndexTests { 33 | r := ReverseIndex(tt.n, tt.i) 34 | if r != tt.r { 35 | t.Errorf("slicesutil.ReverseIndex(%d, %d) (fwd) want (%d), got (%d)", 36 | tt.n, tt.i, tt.r, r) 37 | } 38 | if tt.i < tt.n { 39 | i := ReverseIndex(tt.n, r) 40 | if i != tt.i { 41 | t.Errorf("slicesutil.ReverseIndex(%d, %d) (rev) want (%d), got (%d)", 42 | tt.n, r, tt.i, i) 43 | } 44 | } else { 45 | i := ReverseIndex(tt.n, r) 46 | if i != mathutil.ModPyInt(tt.i, tt.n) { 47 | t.Errorf("slicesutil.ReverseIndex(%d, %d) (rev) want (%d), got (%d)", 48 | tt.n, r, tt.i, i) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /fmt/fmtutil/if_test.go: -------------------------------------------------------------------------------- 1 | package fmtutil 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "testing" 7 | 8 | "github.com/grokify/mogo/pointer" 9 | ) 10 | 11 | var fprintxTests = []struct { 12 | w io.Writer 13 | str *string 14 | args []any 15 | wantN int 16 | }{ 17 | {os.Stdout, pointer.Pointer("testprint1"), []any{}, 10}, 18 | {nil, pointer.Pointer("testprint1"), []any{}, 0}, 19 | {os.Stdout, nil, []any{"testprint2"}, 10}, 20 | {nil, nil, []any{"testprint2"}, 0}, 21 | } 22 | 23 | func TestFprintxIf(t *testing.T) { 24 | for _, tt := range fprintxTests { 25 | if tt.str != nil { 26 | n, err := FprintfIf(tt.w, *tt.str, tt.args...) 27 | if err != nil { 28 | t.Errorf("fmtutil.FprintfIf(%v, %v, %v) Error: (%s)", 29 | tt.w, tt.str, tt.args, err.Error()) 30 | } else if n != tt.wantN { 31 | t.Errorf("fmtutil.FprintfIf(%v, %v, %v) Mismatch: want (%d) got (%d)", 32 | tt.w, tt.str, tt.args, tt.wantN, n) 33 | } 34 | } else { 35 | n, err := FprintIf(tt.w, tt.args...) 36 | if err != nil { 37 | t.Errorf("fmtutil.FprintfIf(%v, %v) Error: (%s)", 38 | tt.w, tt.args, err.Error()) 39 | } else if n != tt.wantN { 40 | t.Errorf("fmtutil.FprintfIf(%v, %v) Mismatch: want (%d) got (%d)", 41 | tt.w, tt.args, tt.wantN, n) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /unicode/unicodeutil/unicodeutil.go: -------------------------------------------------------------------------------- 1 | package unicodeutil 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "unicode" 7 | 8 | "golang.org/x/text/unicode/norm" 9 | ) 10 | 11 | // RemoveDiacritics removes diacritical marks (accents) from a Unicode string. 12 | // A rune map can be used for manual overrides such as converting '™' to "tm". 13 | func RemoveDiacritics(s string, m map[rune][]rune) string { 14 | t := norm.NFD.String(s) 15 | b := make([]rune, 0, len(t)) 16 | 17 | for _, r := range t { 18 | if v, ok := m[r]; ok && len(v) > 0 { 19 | b = append(b, v...) 20 | } else if ok { 21 | continue 22 | } else if unicode.Is(unicode.Mn, r) { 23 | continue // skip mark (Mn) runes 24 | } else { 25 | b = append(b, r) 26 | } 27 | } 28 | 29 | return string(b) 30 | } 31 | 32 | // Unescape wraps the `strconv.Unquote()` function to provide a method of converting 33 | // \u escaped unicode literals. It can take a string like "M\\u00fcnchen" and return "München" 34 | func Unescape(input string) (string, error) { 35 | // Ensure proper escaping (e.g., turn \uXXXX into \\uXXXX if needed) 36 | escaped := input 37 | 38 | // Wrap in double quotes for strconv to parse correctly 39 | quoted := `"` + strings.ReplaceAll(escaped, `"`, `\"`) + `"` 40 | return strconv.Unquote(quoted) 41 | } 42 | -------------------------------------------------------------------------------- /database/datasource/sslmode.go: -------------------------------------------------------------------------------- 1 | package datasource 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | OracleParamConnectTimeout = "connect_timeout" 10 | 11 | PgSSLModeParam = "sslmode" 12 | SSLModeAllow = "allow" 13 | SSLModeDisable = "disable" 14 | SSLModePrefer = "prefer" 15 | SSLModeRequire = "require" 16 | SSLModeVerifyCA = "verify-ca" 17 | SSLModeVerifyFull = "verify-full" 18 | SSLModeDefault = SSLModeDisable 19 | ) 20 | 21 | var ErrSSLModeNotSUpported = errors.New("sslmode not supported") 22 | 23 | // SSLModeParseOrDefault manages Postgres sslmode query param 24 | func SSLModeParseOrDefault(s, d string) string { 25 | m, err := SSLModeParse(s) 26 | if err != nil { 27 | return d 28 | } 29 | return m 30 | } 31 | 32 | func sslModes() map[string]int { 33 | return map[string]int{ 34 | SSLModeAllow: 1, 35 | SSLModeDisable: 1, 36 | SSLModePrefer: 1, 37 | SSLModeRequire: 1, 38 | SSLModeVerifyCA: 1, 39 | SSLModeVerifyFull: 1} 40 | } 41 | 42 | // SSLModeParse parses Postgres sslmode query param 43 | func SSLModeParse(s string) (string, error) { 44 | s = strings.ToLower(strings.TrimSpace(s)) 45 | modes := sslModes() 46 | if _, ok := modes[s]; ok { 47 | return s, nil 48 | } 49 | return "", ErrSSLModeNotSUpported 50 | } 51 | -------------------------------------------------------------------------------- /unicode/unicodeutil/unicodeutil_test.go: -------------------------------------------------------------------------------- 1 | package unicodeutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestRemoveDiacritics ensures timeutil.DateDMYHM2 is parsed to GMT timezone. 8 | func TestRemoveDiacritics(t *testing.T) { 9 | var removeDiacriticsTests = []struct { 10 | v string 11 | vMap map[rune][]rune 12 | want string 13 | }{ 14 | {"João", nil, "Joao"}, 15 | {"München", nil, "Munchen"}, 16 | {"item™", map[rune][]rune{'™': {'t', 'm'}}, "itemtm"}, // mkdocs example 17 | } 18 | 19 | for _, tt := range removeDiacriticsTests { 20 | try := RemoveDiacritics(tt.v, tt.vMap) 21 | if try != tt.want { 22 | t.Errorf("unicodeutil.RemoveDiacritics(\"%s\") Mismatch: want [%s], got [%s]", tt.v, tt.want, try) 23 | } 24 | } 25 | } 26 | 27 | func TestUnescape(t *testing.T) { 28 | var unescapeTests = []struct { 29 | v string 30 | want string 31 | }{ 32 | {`M\u00fcnchen`, "München"}, 33 | {`"M\u00fcnchen"`, `"München"`}, 34 | } 35 | 36 | for _, tt := range unescapeTests { 37 | unescaped, err := Unescape(tt.v) 38 | if err != nil { 39 | t.Errorf("unicodeutil.Unescape(\"%s\") error: (%s)", tt.v, err.Error()) 40 | } 41 | if unescaped != tt.want { 42 | t.Errorf("unicodeutil.Unescape(\"%s\") mismatch: want (%s), got (%s)", tt.v, tt.want, unescaped) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /math/bigint/encode_test.go: -------------------------------------------------------------------------------- 1 | package bigint 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var encodeStringTests = []struct { 8 | data []byte 9 | base int 10 | enc string 11 | }{ 12 | {[]byte(`{"foo":"bar"}`), 62, "3iDIA3jnw58n8DhGln"}, 13 | {[]byte(`{"foo":"bar"}`), 36, "q9txxhmgdvb1hq1gvon1"}, 14 | {[]byte(`{"foo":"bar"}`), 16, "7b22666f6f223a22626172227d"}, 15 | } 16 | 17 | func TestEncodeString(t *testing.T) { 18 | for _, tt := range encodeStringTests { 19 | enc, err := EncodeToString(tt.base, tt.data) 20 | if err != nil { 21 | t.Errorf("bigutil.EncodeToString(%d,%v):err [%v]", tt.base, string(tt.data), err.Error()) 22 | } 23 | if tt.enc != enc { 24 | t.Errorf("bigutil.EncodeToString(%d,%v): want [%v], got [%v]", tt.base, string(tt.data), tt.enc, enc) 25 | } 26 | enc2 := MustEncodeToString(tt.base, tt.data) 27 | if tt.enc != enc2 { 28 | t.Errorf("bigutil.MustEncodeToString(%d,%v): want [%v], got [%v]", tt.base, string(tt.data), tt.enc, enc) 29 | } 30 | dec, err := DecodeString(tt.base, enc) 31 | if err != nil { 32 | t.Errorf("bigutil.DecodeString(%d,%v):err [%v]", tt.base, enc, err.Error()) 33 | } 34 | if string(tt.data) != string(dec) { 35 | t.Errorf("bigutil.DecodeString(%d,%v): want [%v], got [%v]", tt.base, enc, string(tt.data), string(dec)) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /encoding/base62/base62_test.go: -------------------------------------------------------------------------------- 1 | package base62 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var encodeBase36StringTests = []struct { 8 | plaintext string 9 | encoded62 string 10 | }{ 11 | {`{"foo":"bar","baz":1}`, "eyJmb17iOiJiYXIiLCJiYXoiOjF8"}, 12 | {"Hello", "SGVsbG7+"}, 13 | {"Hello World", "SGVsbG7gV18ybGQ+"}} 14 | 15 | func TestEncodeBase62String(t *testing.T) { 16 | levels := []int{9} 17 | for _, tt := range encodeBase36StringTests { 18 | for _, level := range levels { 19 | enc, err := EncodeGzip([]byte(tt.plaintext), level) 20 | if err != nil { 21 | t.Errorf("base62.EncodeGzip(%v): err [%v]", tt.plaintext, err.Error()) 22 | } 23 | 24 | if level == 0 && enc != tt.encoded62 { 25 | t.Errorf("base62.EncodeGzip(%v): want [%v], got [%v]", tt.plaintext, tt.encoded62, enc) 26 | } 27 | 28 | enc = StripPadding(enc) 29 | 30 | if !ValidBase62(enc) { 31 | t.Errorf("base62.EncodeGzip(%v): got [%v], err [%v]", tt.plaintext, enc, "E_NOT_BASE62") 32 | } 33 | 34 | dec, err := DecodeGunzip(enc) 35 | if err != nil { 36 | t.Errorf("base62.DecodeGuzip(%v): want [%v], err [%v]", enc, tt.plaintext, err.Error()) 37 | } 38 | 39 | if string(dec) != tt.plaintext { 40 | t.Errorf("base62.DecodeGuzip(%v): want [%v], err [%v]", enc, tt.plaintext, string(dec)) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /type/maputil/map_comp_comp.go: -------------------------------------------------------------------------------- 1 | package maputil 2 | 3 | type MapCompComp[T, U comparable] map[T]U 4 | 5 | func (m MapCompComp[T, U]) Equal(n map[T]U) bool { 6 | if len(m) != len(n) { 7 | return false 8 | } 9 | for k, vM := range m { 10 | vN, ok := n[k] 11 | if !ok || vM != vN { 12 | return false 13 | } 14 | } 15 | return true 16 | } 17 | 18 | // FilterMergeByMap returns key/values from m where keys exist in n. 19 | // Additionally, add optional default for keys in n that don't exist in m. 20 | func (m MapCompComp[T, U]) FilterMergeByMap(n map[T]U, useNOnlyVal bool, nOnlyDefault *U) map[T]U { 21 | out := make(map[T]U) 22 | for k, v := range m { 23 | if _, ok := n[k]; ok { 24 | out[k] = v 25 | } 26 | } 27 | if useNOnlyVal || nOnlyDefault != nil { 28 | for k, v := range n { 29 | if _, ok := m[k]; !ok { 30 | if useNOnlyVal { 31 | out[k] = v 32 | } else if nOnlyDefault != nil { 33 | out[k] = *nOnlyDefault 34 | } 35 | } 36 | } 37 | } 38 | return out 39 | } 40 | 41 | func (m MapCompComp[T, U]) ValueKeyCounts() map[U]int { 42 | rev := make(map[U]map[T]int) 43 | for k, v := range m { 44 | if _, ok := rev[v]; !ok { 45 | rev[v] = map[T]int{} 46 | } 47 | rev[v][k]++ 48 | } 49 | out := make(map[U]int) 50 | for k, v := range rev { 51 | out[k] = len(v) 52 | } 53 | return out 54 | } 55 | -------------------------------------------------------------------------------- /config/env.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | env "github.com/caarlos0/env/v11" 9 | "github.com/grokify/mogo/os/osutil" 10 | "github.com/joho/godotenv" 11 | ) 12 | 13 | // EnvFileToJSONFile Converts an .env file to a JSON file using the definition 14 | // provided in data. 15 | func EnvFileToJSONFile(data any, filepathENV, filepathJSON string, perm os.FileMode, prefix, indent string) error { 16 | err := godotenv.Load(filepathENV) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | err = env.Parse(data) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | return osutil.WriteFileJSON(filepathJSON, data, perm, prefix, indent) 27 | } 28 | 29 | // Return a merged environment var which is split into multiple 30 | // vars. This is useful when the system has a size limit on 31 | // environment variables. 32 | func JoinEnvNumbered(prefix, delimiter string, startInt uint8, includeBase bool) string { 33 | vals := []string{} 34 | if includeBase { 35 | val := os.Getenv(prefix) 36 | if len(val) > 0 { 37 | vals = append(vals, val) 38 | } 39 | } 40 | i := startInt 41 | for { 42 | val := os.Getenv(fmt.Sprintf("%s_%d", prefix, i)) 43 | if len(val) > 0 { 44 | vals = append(vals, val) 45 | } else { 46 | break 47 | } 48 | i++ 49 | } 50 | return strings.Join(vals, delimiter) 51 | } 52 | -------------------------------------------------------------------------------- /net/http/httputilmore/http_server.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "log/slog" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | // Log is a custom Http handler that will log all requests. 10 | // It can be called using 11 | // http.ListenAndServe(":8080", Log(http.DefaultServeMux)) 12 | // From: https://groups.google.com/forum/#!topic/golang-nuts/s7Xk1q0LSU0 13 | func Log(handler http.Handler) http.Handler { 14 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | slog.Info("log handler func", "remote-addr", r.RemoteAddr, "req-method", r.Method, "req-url", r.URL) 16 | handler.ServeHTTP(w, r) 17 | }) 18 | } 19 | 20 | // NewServerTimeouts returns a `*http.Server` with all timeouts set to a single value provided. 21 | func NewServerTimeouts(addr string, handler http.Handler, timeout time.Duration) *http.Server { 22 | if timeout < 0 { 23 | timeout = 0 24 | } 25 | return &http.Server{ 26 | Addr: addr, 27 | Handler: handler, 28 | IdleTimeout: timeout, 29 | ReadHeaderTimeout: timeout, 30 | ReadTimeout: timeout, 31 | WriteTimeout: timeout, 32 | MaxHeaderBytes: 1 << 20, 33 | } 34 | } 35 | 36 | func ListenAndServeTimeouts(addr string, handler http.Handler, timeout time.Duration) error { 37 | return NewServerTimeouts(addr, handler, timeout).ListenAndServe() 38 | } 39 | -------------------------------------------------------------------------------- /type/maputil/map_string_int_test.go: -------------------------------------------------------------------------------- 1 | package maputil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var msiSortTests = []struct { 8 | data map[string]int 9 | sortBy string 10 | wantNameFirst string 11 | }{ 12 | { 13 | data: map[string]int{ 14 | "ARecord": 2, "BRecord": 1, "CRecord": 4, "DRecord": 3}, 15 | sortBy: SortNameAsc, 16 | wantNameFirst: "ARecord"}, 17 | { 18 | data: map[string]int{ 19 | "ARecord": 2, "BRecord": 1, "CRecord": 4, "DRecord": 3}, 20 | sortBy: SortNameDesc, 21 | wantNameFirst: "DRecord"}, 22 | { 23 | data: map[string]int{ 24 | "ARecord": 2, "BRecord": 1, "CRecord": 4, "DRecord": 3}, 25 | sortBy: SortValueAsc, 26 | wantNameFirst: "BRecord"}, 27 | { 28 | data: map[string]int{ 29 | "ARecord": 2, "BRecord": 1, "CRecord": 4, "DRecord": 3}, 30 | sortBy: SortValueDesc, 31 | wantNameFirst: "CRecord"}, 32 | } 33 | 34 | func TestMapStringIntSort(t *testing.T) { 35 | for _, tt := range msiSortTests { 36 | msi := MapStringInt(tt.data) 37 | sorted := msi.Sorted(tt.sortBy) 38 | if len(sorted) > 0 { 39 | gotNameFirst := sorted[0].Name 40 | if tt.wantNameFirst != gotNameFirst { 41 | t.Errorf("maputil.MapStringInt.Sorted() sort [%s] Error: want [%s], got [%s]", 42 | tt.sortBy, tt.wantNameFirst, gotNameFirst) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /net/urlutil/url_path.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | var leading *regexp.Regexp = regexp.MustCompile(`^/+`) 11 | var trailing *regexp.Regexp = regexp.MustCompile(`/+$`) 12 | 13 | // SplitPath splits a URL path string with optional removal of leading 14 | // and trailing slashes. 15 | func SplitPath(urlPath string, stripLeading, stripTrailing bool) []string { 16 | urlPath = strings.TrimSpace(urlPath) 17 | if stripLeading { 18 | urlPath = leading.ReplaceAllString(urlPath, "") 19 | } 20 | if stripTrailing { 21 | urlPath = trailing.ReplaceAllString(urlPath, "") 22 | } 23 | return strings.Split(urlPath, "/") 24 | } 25 | 26 | func GetPathLeaf(s string) (string, error) { 27 | u, err := url.Parse(s) 28 | if err != nil { 29 | return "", err 30 | } 31 | sep := "/" 32 | p := strings.Trim(u.Path, sep) 33 | parts := strings.Split(p, sep) 34 | if len(parts) == 0 { 35 | return "", errors.New("GetPathLeaf - no path") 36 | } 37 | return parts[len(parts)-1], nil 38 | } 39 | 40 | func ModifyPath(rawurl, newpath string) (string, error) { 41 | u, err := url.Parse(rawurl) 42 | if err != nil { 43 | return "", err 44 | } 45 | newpath = strings.TrimSpace(newpath) 46 | if newpath == "/" { 47 | newpath = "" 48 | } 49 | u.Path = newpath 50 | return CondenseURI(u.String()), nil 51 | } 52 | -------------------------------------------------------------------------------- /image/imageutil/cmd/webp2jpeg/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "log/slog" 7 | "os" 8 | 9 | "github.com/grokify/mogo/image/imageutil" 10 | "github.com/grokify/mogo/log/logutil" 11 | flags "github.com/jessevdk/go-flags" 12 | ) 13 | 14 | type cliOptions struct { 15 | Input string `short:"i" long:"input dir/file" description:"A dir or file" value-name:"FILE" required:"true"` 16 | Output string `short:"o" long:"output dir/filefile" description:"A dir or file" required:"true"` 17 | Quality int `short:"q" long:"quality" description:"Quality"` 18 | WidthMin int `short:"2" long:"width-minimum" description:"Resize to minimum width"` 19 | } 20 | 21 | func main() { 22 | opts := cliOptions{} 23 | _, err := flags.Parse(&opts) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | img, format, err := imageutil.ReadImage(opts.Input) 29 | if err != nil { 30 | slog.Error("error on `imageutil.ReadImageHTTP()`", "msg", err.Error()) 31 | os.Exit(1) 32 | } 33 | 34 | fmt.Printf("GOT: [%s]\n", format) 35 | if opts.WidthMin > 0 { 36 | img = imageutil.ResizeMin(opts.WidthMin, 0, img, imageutil.ScalerBest()) 37 | } 38 | 39 | im := imageutil.Image{Image: img} 40 | err = im.WriteJPEGFileSimple(opts.Output, opts.Quality) 41 | logutil.FatalErr(err) 42 | fmt.Printf("Wrote [%s]\n", opts.Output) 43 | fmt.Println("DONE") 44 | } 45 | -------------------------------------------------------------------------------- /time/duration/durationbin/bins_test.go: -------------------------------------------------------------------------------- 1 | package durationbin 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/grokify/mogo/time/timeutil" 8 | ) 9 | 10 | var binFromDurationTests = []struct { 11 | v time.Duration 12 | binDuration time.Duration 13 | }{ 14 | {0 * timeutil.Day, 7 * timeutil.Day}, 15 | {6 * timeutil.Day, 7 * timeutil.Day}, 16 | {7 * timeutil.Day, 7 * timeutil.Day}, 17 | {7*timeutil.Day + 1, 14 * timeutil.Day}, 18 | {1*timeutil.Year - 1, 1 * timeutil.Year}, 19 | {1*timeutil.Year + 0, 1 * timeutil.Year}, 20 | {1*timeutil.Year + 1, 2 * timeutil.Year}, 21 | {1*timeutil.Year + 180*timeutil.Day, 2 * timeutil.Year}, 22 | {2*timeutil.Year - 1, 2 * timeutil.Year}, 23 | {2*timeutil.Year + 0, 2 * timeutil.Year}, 24 | {2*timeutil.Year + 1, 3 * timeutil.Year}, 25 | {3*timeutil.Year + 0, 3 * timeutil.Year}, 26 | {3*timeutil.Year + 2, 4 * timeutil.Year}, 27 | {5*timeutil.Year + 0, 5 * timeutil.Year}, 28 | {5*timeutil.Year + 1, 6 * timeutil.Year}, 29 | } 30 | 31 | // TestBinForDuration ensures the right bin is returned. 32 | func TestBinFromDuration(t *testing.T) { 33 | for _, tt := range binFromDurationTests { 34 | try := BinFromDuration(tt.v) 35 | if try.Duration != tt.binDuration { 36 | t.Errorf("durationbins.BinForDuration(%d) Mismatch: want (%d) got (%d)", tt.v, 37 | tt.binDuration, try.Duration) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /encoding/csvutil/one_col_list.go: -------------------------------------------------------------------------------- 1 | package csvutil 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | 8 | "github.com/grokify/mogo/type/stringsutil" 9 | ) 10 | 11 | // ReadFileOneColListToGrid parses a file with one value per row. 12 | func ReadFileOneColListToGrid(filename string, colCount int, validateLength bool) ([][]string, error) { 13 | bytes, err := os.ReadFile(filename) 14 | if err != nil { 15 | return [][]string{}, err 16 | } 17 | return ParseOneColListToGrid(stringsutil.SplitLines(string(bytes)), colCount, validateLength) 18 | } 19 | 20 | // ParseOneColListToGrid parses a set of lines with one value per row. 21 | func ParseOneColListToGrid(lines []string, colCount int, validateLength bool) ([][]string, error) { 22 | rows := [][]string{} 23 | curRow := []string{} 24 | colCountFloat := float64(colCount) 25 | 26 | for i, line := range lines { 27 | curRow = append(curRow, line) 28 | if math.Mod(float64(i+1), colCountFloat) == 0 { 29 | if len(curRow) > 0 { 30 | rows = append(rows, curRow) 31 | curRow = []string{} 32 | } 33 | } 34 | } 35 | if len(curRow) > 0 { 36 | rows = append(rows, curRow) 37 | } 38 | if validateLength { 39 | for _, row := range rows { 40 | if len(row) != colCount { 41 | return rows, fmt.Errorf("error row length: want [%d] have [%d]", colCount, len(row)) 42 | } 43 | } 44 | } 45 | return rows, nil 46 | } 47 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grokify/mogo 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/ProtonMail/go-crypto v1.3.0 7 | github.com/btcsuite/btcd/btcutil v1.1.6 8 | github.com/btcsuite/btcutil v1.0.2 9 | github.com/caarlos0/env/v11 v11.3.1 10 | github.com/grokify/base36 v1.0.5 11 | github.com/grokify/bitcoinmath v0.1.0 12 | github.com/huandu/xstrings v1.5.0 13 | github.com/iancoleman/strcase v0.3.0 14 | github.com/jessevdk/go-flags v1.6.1 15 | github.com/joho/godotenv v1.5.1 16 | github.com/json-iterator/go v1.1.12 17 | github.com/lytics/base62 v0.0.0-20180808010106-0ee4de5a5d6d 18 | github.com/martinlindhe/base36 v1.1.1 19 | github.com/microcosm-cc/bluemonday v1.0.27 20 | golang.org/x/crypto v0.46.0 21 | golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 22 | golang.org/x/image v0.34.0 23 | golang.org/x/net v0.48.0 24 | golang.org/x/term v0.38.0 25 | golang.org/x/text v0.32.0 26 | google.golang.org/genproto v0.0.0-20251213004720-97cd9d5aeac2 27 | ) 28 | 29 | require ( 30 | github.com/aymerick/douceur v0.2.0 // indirect 31 | github.com/cloudflare/circl v1.6.1 // indirect 32 | github.com/gorilla/css v1.0.1 // indirect 33 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 34 | github.com/modern-go/reflect2 v1.0.2 // indirect 35 | golang.org/x/sys v0.39.0 // indirect 36 | google.golang.org/protobuf v1.36.11 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /codegen/apps/nestedstruct2pointer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "regexp" 7 | 8 | "github.com/grokify/mogo/codegen" 9 | "github.com/grokify/mogo/fmt/fmtutil" 10 | flags "github.com/jessevdk/go-flags" 11 | ) 12 | 13 | /* 14 | 15 | go get github.com/grokify/mogo/codegen/apps/nestedstruct2pointer 16 | 17 | */ 18 | 19 | type Options struct { 20 | Dir string `short:"d" long:"dir" description:"Directory"` 21 | Pattern string `short:"p" long:"pattern" description:"Pattern"` 22 | } 23 | 24 | func main() { 25 | opts := Options{} 26 | _, err := flags.Parse(&opts) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | if opts.Dir == "" { 31 | opts.Dir = "." 32 | } 33 | err = fmtutil.PrintJSON(opts) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | if len(opts.Pattern) > 0 { 38 | files, err := codegen.ConvertFilesInPlaceNestedstructsToPointers( 39 | opts.Dir, regexp.MustCompile(opts.Pattern)) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | err = fmtutil.PrintJSON(files) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | } else { 48 | files, err := codegen.ConvertFilesInPlaceNestedstructsToPointers( 49 | opts.Dir, nil) 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | err = fmtutil.PrintJSON(files) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | } 58 | fmt.Println("DONE") 59 | } 60 | -------------------------------------------------------------------------------- /text/currencyutil/currencies.go: -------------------------------------------------------------------------------- 1 | package currencyutil 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | "strings" 7 | 8 | "github.com/grokify/mogo/type/maputil" 9 | ) 10 | 11 | type Currencies []Currency 12 | 13 | func (c Currencies) Codes() []string { 14 | codes := []string{} 15 | for _, ci := range c { 16 | codes = append(codes, ci.Code) 17 | } 18 | sort.Strings(codes) 19 | return codes 20 | } 21 | 22 | type Currency struct { 23 | Code string 24 | Symbol string 25 | Country string 26 | Name string 27 | } 28 | 29 | func (c *Currency) TrimSpace() { 30 | c.Code = strings.TrimSpace(c.Code) 31 | c.Symbol = strings.TrimSpace(c.Symbol) 32 | c.Country = strings.TrimSpace(c.Country) 33 | c.Name = strings.TrimSpace(c.Name) 34 | } 35 | 36 | type CurrencySet struct { 37 | Map map[string]Currency 38 | } 39 | 40 | var ErrNoCurrencyCode = errors.New("currency code not set") 41 | 42 | func NewCurrencySet(c ...Currency) (CurrencySet, error) { 43 | set := CurrencySet{Map: map[string]Currency{}} 44 | err := set.Add(c...) 45 | return set, err 46 | } 47 | 48 | func (set *CurrencySet) Add(c ...Currency) error { 49 | for _, ci := range c { 50 | if strings.TrimSpace(ci.Code) == "" { 51 | return ErrNoCurrencyCode 52 | } 53 | set.Map[ci.Code] = ci 54 | } 55 | return nil 56 | } 57 | 58 | func (set CurrencySet) Codes() []string { 59 | return maputil.Keys(set.Map) 60 | } 61 | -------------------------------------------------------------------------------- /time/year/year_test.go: -------------------------------------------------------------------------------- 1 | package year 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/grokify/mogo/time/timeutil" 8 | ) 9 | 10 | var timesStartsYearTests = []struct { 11 | input []string 12 | series []string 13 | }{ 14 | { 15 | []string{ 16 | "2010-01-01T00:00:00Z", 17 | "2000-01-01T00:00:00Z"}, 18 | []string{ 19 | "2000-01-01T00:00:00Z", 20 | "2001-01-01T00:00:00Z", 21 | "2002-01-01T00:00:00Z", 22 | "2003-01-01T00:00:00Z", 23 | "2004-01-01T00:00:00Z", 24 | "2005-01-01T00:00:00Z", 25 | "2006-01-01T00:00:00Z", 26 | "2007-01-01T00:00:00Z", 27 | "2008-01-01T00:00:00Z", 28 | "2009-01-01T00:00:00Z", 29 | "2010-01-01T00:00:00Z"}, 30 | }} 31 | 32 | func TestTimesStartsYear(t *testing.T) { 33 | for _, tt := range timesStartsYearTests { 34 | input, err := timeutil.ParseTimes(time.RFC3339, tt.input) 35 | if err != nil { 36 | t.Errorf("year.TestTimeSeriesYear cannot parse [%v] Error: [%s]", tt.input, err.Error()) 37 | } 38 | seriesWant, err := timeutil.ParseTimes(time.RFC3339, tt.series) 39 | if err != nil { 40 | t.Errorf("year.TestTimeSeriesYear cannot parse [%v] Error: [%s]", tt.series, err.Error()) 41 | } 42 | seriesTry := TimesYearStarts(input...) 43 | if !seriesTry.Equal(seriesWant) { 44 | t.Errorf("year.TestTimeSeriesYear series not equal: want [%v] try [%v]", seriesWant, seriesTry) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /net/http/httputilmore/transport_headers.go: -------------------------------------------------------------------------------- 1 | package httputilmore 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | ) 7 | 8 | // TransportRequestModifier implements http.RoundTripper. 9 | // When set as Transport of http.Client, it adds HTTP headers and or query 10 | // string parameters to requests. 11 | // No field is mandatory. Can be implemented with http.Client as: 12 | // client.Transport = httputilmore.TransportRequestModifier{ 13 | // Transport:client.Transport, Header:myHeader} 14 | type TransportRequestModifier struct { 15 | Transport http.RoundTripper 16 | Header http.Header 17 | Query url.Values 18 | Override bool 19 | } 20 | 21 | // RoundTrip adds the additional headers per request implements http.RoundTripper. 22 | func (t TransportRequestModifier) RoundTrip(req *http.Request) (*http.Response, error) { 23 | req.Header = MergeHeader(req.Header, t.Header, t.Override) 24 | 25 | if len(t.Query) > 0 { 26 | q := req.URL.Query() 27 | for k, vals := range t.Query { 28 | for _, v := range vals { 29 | if t.Override || len(q.Get(k)) == 0 { 30 | q.Add(k, v) 31 | } 32 | } 33 | } 34 | req.URL.RawQuery = q.Encode() 35 | } 36 | 37 | return t.transport().RoundTrip(req) 38 | } 39 | 40 | func (t TransportRequestModifier) transport() http.RoundTripper { 41 | if t.Transport != nil { 42 | return t.Transport 43 | } 44 | 45 | return http.DefaultTransport 46 | } 47 | -------------------------------------------------------------------------------- /net/urlutil/scheme_test.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var uriSchemeTests = []struct { 8 | v string 9 | want string 10 | }{ 11 | {"https://example.com", "https"}, 12 | {"rc6://example", "rc6"}} 13 | 14 | func TestURIScheme(t *testing.T) { 15 | for _, tt := range uriSchemeTests { 16 | try := tt.v 17 | got := URIScheme(try) 18 | if got != tt.want { 19 | t.Errorf("GetScheme(%v) failed want [%v] got [%v]", tt.v, tt.want, got) 20 | } 21 | } 22 | } 23 | 24 | var isHTTPTests = []struct { 25 | v string 26 | inclHTTP bool 27 | inclHTTPS bool 28 | want bool 29 | }{ 30 | {"http://example.com", false, false, false}, 31 | {"http://example.com", true, false, true}, 32 | {"http://example.com", false, true, false}, 33 | {"http://example.com", true, true, true}, 34 | {"https://example.com", false, false, false}, 35 | {"https://example.com", true, false, false}, 36 | {"https://example.com", false, true, true}, 37 | {"https://example.com", true, true, true}, 38 | {"email://example.com", true, true, false}, 39 | {"tel://example.com", true, true, false}, 40 | } 41 | 42 | func TestIsHTTP(t *testing.T) { 43 | for _, tt := range isHTTPTests { 44 | try := tt.v 45 | got := IsHTTP(try, tt.inclHTTP, tt.inclHTTPS) 46 | if got != tt.want { 47 | t.Errorf("func IsHttp(%v) failed want [%v] got [%v]", tt.v, tt.want, got) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /os/executil/executil.go: -------------------------------------------------------------------------------- 1 | package executil 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // ExecSimple provides a simple interface to execute a system command. 11 | func ExecSimple(command string) (bytes.Buffer, bytes.Buffer, error) { 12 | parts := strings.Split(command, " ") 13 | cmd := exec.Command(parts[0], parts[1:]...) // #nosec G204 14 | var stdout, stderr bytes.Buffer 15 | cmd.Stdout = &stdout 16 | cmd.Stderr = &stderr 17 | return stdout, stderr, cmd.Run() 18 | } 19 | 20 | // ExecToFiles provides a simple interface to execute a system command. 21 | // Redirects for STDOUT and STDERR must be passed in as file names, 22 | // not as `>` and `2>` UNIX file descriptors. 23 | func ExecToFiles(command, stdoutFile, stderrFile string, perm os.FileMode) (bytes.Buffer, bytes.Buffer, error) { 24 | stdout, stderr, err := ExecSimple(command) 25 | if err != nil { 26 | return stdout, stderr, err 27 | } 28 | stdoutFile = strings.TrimSpace(stdoutFile) 29 | stderrFile = strings.TrimSpace(stderrFile) 30 | if len(stdoutFile) > 0 { 31 | err := os.WriteFile(stdoutFile, stdout.Bytes(), perm) 32 | if err != nil { 33 | return stdout, stderr, err 34 | } 35 | } 36 | if len(stderrFile) > 0 { 37 | err := os.WriteFile(stderrFile, stdout.Bytes(), perm) 38 | if err != nil { 39 | return stdout, stderr, err 40 | } 41 | } 42 | return stdout, stderr, nil 43 | } 44 | -------------------------------------------------------------------------------- /time/timeutil/time_series.go: -------------------------------------------------------------------------------- 1 | package timeutil 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // TimeSeriesSlice builds a time series based on supplied interval. 9 | func TimeSeriesSlice(interval Interval, times []time.Time) []time.Time { 10 | if len(times) == 0 { 11 | return times 12 | } 13 | min, max := SliceMinMax(times) 14 | return TimeSeriesMinMax(interval, min, max) 15 | } 16 | 17 | // TimeSeriesMinMax builds a time series based on supplied interval. 18 | func TimeSeriesMinMax(interval Interval, min, max time.Time) []time.Time { 19 | min, max = MinMax(min, max) 20 | series := []time.Time{} 21 | tmMin := NewTimeMore(min, 0) 22 | tmMax := NewTimeMore(max, 0) 23 | switch interval { 24 | case IntervalYear: 25 | min = tmMin.YearStart() 26 | max = tmMax.YearStart() 27 | series = append(series, min) 28 | cur := min 29 | for { 30 | cur = cur.AddDate(1, 0, 0) 31 | if cur.After(max) { 32 | break 33 | } 34 | series = append(series, cur) 35 | } 36 | case IntervalMonth: 37 | min = tmMin.MonthStart() 38 | max = tmMin.MonthStart() 39 | series = append(series, min) 40 | cur := min 41 | for { 42 | cur = cur.AddDate(0, 1, 0) 43 | if cur.After(max) { 44 | break 45 | } 46 | series = append(series, cur) 47 | } 48 | default: 49 | panic(fmt.Sprintf("interval not supportedin timeutil.TimeSeriesMinMax [%v]", interval)) 50 | } 51 | return series 52 | } 53 | -------------------------------------------------------------------------------- /text/markdown/table_gfm.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/grokify/mogo/type/slicesutil" 7 | ) 8 | 9 | const ( 10 | GFMTableSep = "|" 11 | GFMTableSepStart = GFMTableSep + " " 12 | GFMTableSepMid = " " + GFMTableSep + " " 13 | GFMTableSepEnd = " " + GFMTableSep 14 | ) 15 | 16 | func TableRowsToMarkdown(rows [][]string, newline string, esc, withHeader bool) string { 17 | var out string 18 | sepLineRowIdx := -1 19 | 20 | for i, row := range rows { 21 | md := TableRowToMarkdown(row, esc) 22 | out += md 23 | if i < len(rows)-1 { 24 | out += newline 25 | } 26 | if i == 0 && withHeader && len(rows) >= 2 { 27 | out += TableSeparator(len(row)) + newline 28 | sepLineRowIdx = i + 1 29 | } 30 | } 31 | 32 | return TableAlign(out, sepLineRowIdx) 33 | } 34 | 35 | func TableRowToMarkdown(cells []string, esc bool) string { 36 | if !esc { 37 | return GFMTableSepStart + strings.Join(cells, GFMTableSepMid) + GFMTableSepEnd 38 | } 39 | new := []string{} 40 | for _, c := range cells { 41 | new = append(new, strings.ReplaceAll(c, `|`, `\|`)) 42 | } 43 | return GFMTableSepStart + strings.Join(new, GFMTableSepMid) + GFMTableSepEnd 44 | } 45 | 46 | func TableSeparator(cellCount int) string { 47 | if cellCount == 0 { 48 | return "" 49 | } 50 | return GFMTableSepStart + strings.Join(slicesutil.NewWithDefault(cellCount, "-"), GFMTableSepMid) + GFMTableSepEnd 51 | } 52 | -------------------------------------------------------------------------------- /strconv/phonenumber/phonenumberletters_test.go: -------------------------------------------------------------------------------- 1 | package phonenumber 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // numbers = "2ABC3DEF4GHI5JKL6MNO7PQRS8TUV9WXYZ" 8 | 9 | var letterToNumberTests = []struct { 10 | v string 11 | want int 12 | }{ 13 | {"A", 2}, {"B", 2}, {"C", 2}, {"D", 3}, {"E", 3}, 14 | {"F", 3}, {"G", 4}, {"H", 4}, {"I", 4}, {"J", 5}, 15 | {"K", 5}, {"L", 5}, {"M", 6}, {"N", 6}, {"O", 6}, 16 | {"P", 7}, {"Q", 7}, {"R", 7}, {"S", 7}, {"T", 8}, 17 | {"U", 8}, {"V", 8}, {"W", 9}, {"X", 9}, {"Y", 9}, {"Z", 9}, 18 | } 19 | 20 | func TestLetterToNumber(t *testing.T) { 21 | l2n := LetterToNumberMap() 22 | 23 | for _, tt := range letterToNumberTests { 24 | if num, ok := l2n[tt.v]; ok { 25 | if num != tt.want { 26 | t.Errorf("phonenumber.LetterToNumberMap() Error: with [%v], want [%v], got [%v]", 27 | tt.v, tt.want, num) 28 | } 29 | } else { 30 | t.Errorf("phonenumber.LetterToNumberMap() Not Found Error: with [%v], want [%v]", 31 | tt.v, tt.want) 32 | } 33 | } 34 | } 35 | 36 | var stringToNumbersTests = []struct { 37 | v string 38 | want string 39 | }{ 40 | {"gotmilk", "4686455"}, 41 | } 42 | 43 | func TestStringToNumbers(t *testing.T) { 44 | for _, tt := range stringToNumbersTests { 45 | nums := StringToNumbers(tt.v) 46 | 47 | if nums != tt.want { 48 | t.Errorf("phonenumber.StringToNumbers() Error: with [%v], want [%v], got [%v]", 49 | tt.v, tt.want, nums) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /database/breadops.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/grokify/mogo/type/stringsutil" 7 | ) 8 | 9 | const ( 10 | FunctionBrowse = "browse" 11 | FunctionRead = "read" 12 | FunctionEdit = "edit" 13 | FunctionAdd = "add" 14 | FunctionDelete = "delete" 15 | FunctionOther = "other" 16 | ) 17 | 18 | // BreadOps uses the BREAD acronym to store identifiers for each 19 | // operation. 20 | type BreadOps struct { 21 | Name string 22 | Browse []string 23 | Read []string 24 | Edit []string 25 | Add []string 26 | Delete []string 27 | } 28 | 29 | func NewBreadOps(name string) BreadOps { 30 | return BreadOps{ 31 | Name: name, 32 | Browse: []string{}, 33 | Read: []string{}, 34 | Edit: []string{}, 35 | Add: []string{}, 36 | Delete: []string{}} 37 | } 38 | 39 | func (bo *BreadOps) TrimSpace(dedupe, sort bool) { 40 | bo.Name = strings.TrimSpace(bo.Name) 41 | if bo.Browse != nil { 42 | bo.Browse = stringsutil.SliceCondenseSpace(bo.Browse, dedupe, sort) 43 | } 44 | if bo.Read != nil { 45 | bo.Read = stringsutil.SliceCondenseSpace(bo.Read, dedupe, sort) 46 | } 47 | if bo.Edit != nil { 48 | bo.Edit = stringsutil.SliceCondenseSpace(bo.Edit, dedupe, sort) 49 | } 50 | if bo.Add != nil { 51 | bo.Add = stringsutil.SliceCondenseSpace(bo.Add, dedupe, sort) 52 | } 53 | if bo.Delete != nil { 54 | bo.Delete = stringsutil.SliceCondenseSpace(bo.Delete, dedupe, sort) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /text/password/generate.go: -------------------------------------------------------------------------------- 1 | package password 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/grokify/mogo/crypto/randutil" 7 | "github.com/grokify/mogo/encoding/basex" 8 | "github.com/grokify/mogo/type/stringsutil" 9 | ) 10 | 11 | // Generate creates passwords using options selected in `GenerateOpts`. 12 | func Generate(opts GenerateOpts) (string, error) { 13 | return randutil.RandString(opts.Alphabet(), opts.Length) 14 | } 15 | 16 | const ( 17 | AlphabetSymbols = "!@#$%&*?" 18 | AlphabetAmbiguous = "{}[]()/\\'\"`,;:.<>" 19 | AlphabetSimilar = "iI1LoO0" 20 | ) 21 | 22 | type GenerateOpts struct { 23 | Length uint 24 | InclLower bool 25 | InclUpper bool 26 | InclNumbers bool 27 | InclSymbols bool 28 | InclAmbiguous bool 29 | ExclSimilar bool 30 | } 31 | 32 | // Alphabet builds an alphabet that's useful for passwords. 33 | func (opts GenerateOpts) Alphabet() string { 34 | var alphabet string 35 | if opts.InclLower { 36 | alphabet += strings.ToLower(basex.AlphabetBase26) 37 | } 38 | if opts.InclNumbers { 39 | alphabet += basex.AlphabetBase10 40 | } 41 | if opts.InclUpper { 42 | alphabet += strings.ToUpper(basex.AlphabetBase26) 43 | } 44 | if opts.InclSymbols { 45 | alphabet += AlphabetSymbols 46 | } 47 | if opts.InclAmbiguous { 48 | alphabet += AlphabetAmbiguous 49 | } 50 | if opts.ExclSimilar { 51 | alphabet = stringsutil.StripChars(alphabet, AlphabetSimilar) 52 | } 53 | return alphabet 54 | } 55 | -------------------------------------------------------------------------------- /sort/sortutil/integersuffix_test.go: -------------------------------------------------------------------------------- 1 | package sortutil 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestIntegerSuffix(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | input []string 12 | expected []string 13 | }{ 14 | { 15 | name: "basic numeric suffix", 16 | input: []string{"ABC-10", "ABC-2", "ABC-1"}, 17 | expected: []string{"ABC-1", "ABC-2", "ABC-10"}, 18 | }, 19 | { 20 | name: "mixed numeric and non-numeric", 21 | input: []string{"ABC-10", "ABC-2", "XYZ", "FOO-bar", "ABC-abc", "ABC-1"}, 22 | expected: []string{"ABC-1", "ABC-2", "ABC-10", "ABC-abc", "FOO-bar", "XYZ"}, 23 | }, 24 | { 25 | name: "no numeric suffixes", 26 | input: []string{"banana", "apple", "cherry"}, 27 | expected: []string{"apple", "banana", "cherry"}, 28 | }, 29 | { 30 | name: "numeric and empty suffix", 31 | input: []string{"Task-3", "Task-A", "Task-2", "Task"}, 32 | expected: []string{"Task-2", "Task-3", "Task", "Task-A"}, 33 | }, 34 | { 35 | name: "stable ordering", 36 | input: []string{"ABC-1", "ABC-1", "ABC-2"}, 37 | expected: []string{"ABC-1", "ABC-1", "ABC-2"}, 38 | }, 39 | } 40 | 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | got := IntegerSuffix(tt.input) 44 | if !reflect.DeepEqual(got, tt.expected) { 45 | t.Errorf("IntegerSuffix() = %v, want %v", got, tt.expected) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /config/examples/dotenv2json/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/grokify/mogo/config" 7 | "github.com/jessevdk/go-flags" 8 | ) 9 | 10 | type Options struct { 11 | InputFile string `short:"i" long:"input" description:"Input file in .env format" required:"true"` 12 | Outputfile string `short:"o" long:"output" description:"Output file in JSON format" required:"true"` 13 | } 14 | 15 | type AppConfig struct { 16 | RingCentralTokenJSON string `env:"RINGCENTRAL_TOKEN_JSON" json:"ringcentralTokenJson"` 17 | RingCentralServerURL string `env:"RINGCENTRAL_SERVER_URL" json:"ringcentralServerUrl"` 18 | RingCentralWebhookURL string `env:"RINGCENTRAL_WEBHOOK_URL" json:"ringcentralWebhookUrl"` 19 | RingCentralBotID string `env:"RINGCENTRAL_BOT_ID" json:"ringcentralBotId"` 20 | GoogleSvcAccountJWT string `env:"GOOGLE_SERVICE_ACCOUNT_JWT" json:"googleServiceAccountJwt"` 21 | GoogleSpreadsheetID string `env:"GOOGLE_SPREADSHEET_ID" json:"googleSpreadsheetId"` 22 | GoogleSheetIndex int64 `env:"GOOGLE_SHEET_INDEX" json:"googleSheetIndex"` 23 | } 24 | 25 | func main() { 26 | opts := &Options{} 27 | _, err := flags.Parse(opts) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | cfg := &AppConfig{} 33 | 34 | err = config.EnvFileToJSONFile(cfg, opts.InputFile, opts.Outputfile, 0600, "", " ") 35 | if err != nil { 36 | panic(err) 37 | } 38 | fmt.Printf("WROTE: %v\n", opts.Outputfile) 39 | fmt.Println("DONE") 40 | } 41 | -------------------------------------------------------------------------------- /net/smtputil/addr_format_test.go: -------------------------------------------------------------------------------- 1 | package smtputil 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var addrFormatTests = []struct { 8 | regular string 9 | swapped string 10 | reverse string 11 | }{ 12 | {"info", "info", "info"}, 13 | {"info@example.com", "example.com@@info", "com.example@@@info"}, 14 | {"info@www.example.com", "www.example.com@@info", "com.example.www@@@info"}, 15 | {"info@blog.www.example.com", "blog.www.example.com@@info", "com.example.www.blog@@@info"}, 16 | } 17 | 18 | func TestAddressFormats(t *testing.T) { 19 | for _, tt := range addrFormatTests { 20 | trySwapped, err := EmailAddrToSwapped(tt.regular) 21 | if err != nil { 22 | t.Errorf("smtputil.EmailAddrToSwapped(\"%s\") Error: input [%v], want [%v], got error [%v]", 23 | tt.regular, tt.regular, tt.swapped, err.Error()) 24 | } 25 | if trySwapped != tt.swapped { 26 | t.Errorf("smtputil.EmailAddrToSwapped(\"%s\") Failure: input [%v], want [%v], got [%v]", 27 | tt.regular, tt.regular, tt.swapped, trySwapped) 28 | } 29 | tryReverse, err := EmailAddrToReverse(tt.regular) 30 | if err != nil { 31 | t.Errorf("smtputil.EmailAddrToReverse(\"%s\") Error: input [%v], want [%v], got error [%v]", 32 | tt.regular, tt.regular, tt.reverse, err.Error()) 33 | } 34 | if tryReverse != tt.reverse { 35 | t.Errorf("smtputil.EmailAddrToReverse(\"%s\") Failure: input [%v], want [%v], got [%v]", 36 | tt.regular, tt.regular, tt.reverse, tryReverse) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /encoding/jsonpointer/parser.go: -------------------------------------------------------------------------------- 1 | package jsonpointer 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | ErrJSONPointerInvalidSyntaxNoAnchorSlash = errors.New("invalid JSON Pointer format - no `#/`") 10 | ErrJSONPointerInvalidSyntaxNonOneAnchorSlash = errors.New("invalid JSON Pointer format - non-1 `#/`") 11 | ) 12 | 13 | type JSONPointer struct { 14 | Document string 15 | String string 16 | PathString string 17 | Path []string 18 | } 19 | 20 | func ParseJSONPointer(s string) (JSONPointer, error) { 21 | anchorSlash := "#/" 22 | slash := "/" 23 | ptr := JSONPointer{String: s} 24 | if strings.Index(s, anchorSlash) == 0 { 25 | ptr.PathString = s 26 | pathTrimmed := strings.TrimLeft(s, anchorSlash) 27 | ptr.Path = strings.Split(pathTrimmed, "/") 28 | return ptr, nil 29 | } else if strings.Index(s, slash) == 0 { 30 | ptr.PathString = s 31 | pathTrimmed := strings.TrimLeft(s, slash) 32 | ptr.Path = strings.Split(pathTrimmed, "/") 33 | return ptr, nil 34 | } 35 | if !strings.Contains(s, anchorSlash) { 36 | return ptr, ErrJSONPointerInvalidSyntaxNoAnchorSlash 37 | } 38 | parts := []string{} 39 | if strings.Contains(s, anchorSlash) { 40 | parts = strings.Split(s, anchorSlash) 41 | } 42 | if len(parts) != 2 { 43 | return ptr, ErrJSONPointerInvalidSyntaxNonOneAnchorSlash 44 | } 45 | ptr.Document = parts[0] 46 | ptr.PathString = parts[1] 47 | ptr.Path = strings.Split(ptr.PathString, "/") 48 | return ptr, nil 49 | } 50 | -------------------------------------------------------------------------------- /net/urlutil/urlvalidator.go: -------------------------------------------------------------------------------- 1 | package urlutil 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | type URLValidator struct { 11 | RequiredSchemes map[string]int 12 | } 13 | 14 | func (uv *URLValidator) SchemesToLower() { 15 | newSchemes := map[string]int{} 16 | for scheme, v := range uv.RequiredSchemes { 17 | newSchemes[strings.ToLower(scheme)] = v 18 | } 19 | uv.RequiredSchemes = newSchemes 20 | } 21 | 22 | func (uv *URLValidator) ValidateURLString(s string) (*url.URL, error) { 23 | if len(strings.TrimSpace(s)) < 1 { 24 | return nil, fmt.Errorf("string URL is empty") 25 | } 26 | u, err := url.ParseRequestURI(s) 27 | if err != nil { 28 | return u, err 29 | } 30 | return uv.ValidateURL(u) 31 | } 32 | 33 | func (uv *URLValidator) ValidateURL(u *url.URL) (*url.URL, error) { 34 | if len(uv.RequiredSchemes) > 0 { 35 | if _, ok := uv.RequiredSchemes[u.Scheme]; !ok { 36 | return u, 37 | fmt.Errorf("scheme `%v` is not in list of required schemes: %v", 38 | u.Scheme, uv.RequiredSchemesSortedString()) 39 | } 40 | } 41 | return u, nil 42 | } 43 | 44 | func (uv *URLValidator) RequiredSchemesSorted() []string { 45 | schemes := []string{} 46 | for scheme := range uv.RequiredSchemes { 47 | schemes = append(schemes, scheme) 48 | } 49 | sort.Strings(schemes) 50 | return schemes 51 | } 52 | 53 | func (uv *URLValidator) RequiredSchemesSortedString() string { 54 | return strings.Join(uv.RequiredSchemesSorted(), ",") 55 | } 56 | -------------------------------------------------------------------------------- /html/raymondhelpers/raymondhelpers.go: -------------------------------------------------------------------------------- 1 | package raymondhelpers 2 | 3 | /* 4 | import ( 5 | "regexp" 6 | "strings" 7 | "time" 8 | 9 | "github.com/aymerick/raymond" 10 | "github.com/grokify/mogo/time/timeutil" 11 | ) 12 | 13 | // RegisterAll registers helpers for the Raymond Handlebars template engine. 14 | func RegisterAll() { 15 | RegisterStringSafe() 16 | RegisterTimeSafe() 17 | } 18 | 19 | func RegisterTimeSafe() { 20 | raymond.RegisterHelper("timeRfc3339", func(t time.Time) raymond.SafeString { 21 | return raymond.SafeString(t.Format(time.RFC3339)) 22 | }) 23 | raymond.RegisterHelper("timeRfc3339ymd", func(t time.Time) raymond.SafeString { 24 | return raymond.SafeString(t.Format(timeutil.RFC3339FullDate)) 25 | }) 26 | } 27 | 28 | func RegisterStringSafe() { 29 | raymond.RegisterHelper("spaceToHyphen", func(s string) raymond.SafeString { 30 | return raymond.SafeString(regexp.MustCompile(`[\s-]+`).ReplaceAllString(s, "-")) 31 | }) 32 | raymond.RegisterHelper("spaceToUnderscore", func(s string) raymond.SafeString { 33 | return raymond.SafeString(regexp.MustCompile(`[\s_]+`).ReplaceAllString(s, "_")) 34 | }) 35 | raymond.RegisterHelper("toLower", func(s string) raymond.SafeString { 36 | return raymond.SafeString(strings.ToLower(s)) 37 | }) 38 | raymond.RegisterHelper("defaultUnknown", func(s string) raymond.SafeString { 39 | if len(strings.TrimSpace(s)) == 0 { 40 | return raymond.SafeString("unknown") 41 | } 42 | return raymond.SafeString(s) 43 | }) 44 | } 45 | */ 46 | --------------------------------------------------------------------------------