├── 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 |
--------------------------------------------------------------------------------