Skip to content

Commit

Permalink
Merge pull request #15 from andrysds/from-front-opt
Browse files Browse the repository at this point in the history
Shorten from the front option (--from-front)
  • Loading branch information
dnnrly authored Mar 17, 2020
2 parents 25207b0 + 96db3b0 commit 75a829d
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 20 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Available Commands:
Flags:
-c, --custom string Custom abbreviation set
--from-front Shorten from the front
-h, --help help for abbreviate
-l, --language string Language to select (default "en-us")
--list List all abbreviate sets by language
Expand All @@ -68,9 +69,12 @@ Examples:
$ abbreviate original strategy-limited
stg-ltd
$ abbreviate original strategy-limited --max 11
$ abbreviate original --max 11 strategy-limited
strategy-ltd
$ abbreviate original --max 11 --from-front strategy-limited
stg-limited
$ abbreviate camel --max 99 strategy-limited
strategyLimited
```
Expand Down
2 changes: 1 addition & 1 deletion cmd/camel.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var camelCmd = &cobra.Command{
Long: `Abbreviate a string and convert it to camel case.`,
Args: validateArgPresent,
Run: func(cmd *cobra.Command, args []string) {
abbr := domain.AsPascal(matcher, args[0], optMax)
abbr := domain.AsPascal(matcher, args[0], optMax, optFrmFront)

ch := string(abbr[0])

Expand Down
2 changes: 1 addition & 1 deletion cmd/original.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var originalCmd = &cobra.Command{
Short: "Abbreviate the string using the original word boundary separators",
Args: validateArgPresent,
Run: func(cmd *cobra.Command, args []string) {
abbr := domain.AsOriginal(matcher, args[0], optMax)
abbr := domain.AsOriginal(matcher, args[0], optMax, optFrmFront)

fmt.Printf("%s", abbr)
if optNewline {
Expand Down
2 changes: 1 addition & 1 deletion cmd/pascal.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var pascalCmd = &cobra.Command{
Long: `Abbreviate a string and convert it to pascal case.`,
Args: validateArgPresent,
Run: func(cmd *cobra.Command, args []string) {
abbr := domain.AsPascal(matcher, args[0], optMax)
abbr := domain.AsPascal(matcher, args[0], optMax, optFrmFront)

fmt.Printf("%s", abbr)
if optNewline {
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
optSet = "common"
optCustom = ""
optMax = 0
optFrmFront = false

data = packr.New("abbreviate", "../data")
matcher *domain.Matcher
Expand Down Expand Up @@ -119,6 +120,7 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&optSet, "set", "s", optSet, "Abbreviation set")
rootCmd.PersistentFlags().StringVarP(&optCustom, "custom", "c", optCustom, "Custom abbreviation set")
rootCmd.PersistentFlags().IntVarP(&optMax, "max", "m", optMax, "Maximum length of string, keep on abbreviating while the string is longer than this limit")
rootCmd.PersistentFlags().BoolVarP(&optFrmFront, "from-front", "", optFrmFront, "Shorten from the front")
}

func validateArgPresent(cmd *cobra.Command, args []string) error {
Expand Down
2 changes: 1 addition & 1 deletion cmd/snake.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Where a string is not shortened, it will be converted to snake case anyway, even
if this means that the string will end up longer.`,
Args: validateArgPresent,
Run: func(cmd *cobra.Command, args []string) {
abbr := domain.AsSnake(matcher, args[0], optSnakeSeperator, optMax)
abbr := domain.AsSnake(matcher, args[0], optSnakeSeperator, optMax, optFrmFront)

fmt.Printf("%s", abbr)
if optNewline {
Expand Down
37 changes: 25 additions & 12 deletions domain/shorteners.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,31 +96,31 @@ func (all Sequences) Len() int {
type Shortener func(matcher Matcher, original string, max int) string

// AsOriginal discovers words using camel case and non letter characters,
// starting from the back until the string has less than 'max' characters
// starting from the back or the front until the string has less than 'max' characters
// or it can't shorten any more.
func AsOriginal(matcher *Matcher, original string, max int) string {
func AsOriginal(matcher *Matcher, original string, max int, frmFront bool) string {
if len(original) < max {
return original
}

shortened := NewSequences(original)
for pos := len(shortened) - 1; pos >= 0 && shortened.Len() > max; pos-- {
shorten(shortened, max, frmFront, func(pos int) {
str := shortened[pos]
abbr := matcher.Match(strings.ToLower(str))
if isTitleCase(str) {
abbr = makeTitle(abbr)
}
shortened[pos] = abbr
}
})

return shortened.String()
}

// AsSnake discovers words using camel case and non letter characters,
// starting from the back until the string has less than 'max' characters
// starting from the back or the front until the string has less than 'max' characters
// or it can't shorten any more. This inserts the specified separator
// where a sequence is not alpha-numeric
func AsSnake(matcher *Matcher, original, separator string, max int) string {
func AsSnake(matcher *Matcher, original, separator string, max int, frmFront bool) string {
if original == "" {
return ""
}
Expand All @@ -142,20 +142,20 @@ func AsSnake(matcher *Matcher, original, separator string, max int) string {
return shortened.String()
}

for pos := len(shortened) - 1; pos >= 0 && shortened.Len() > max; pos-- {
shorten(shortened, max, frmFront, func(pos int) {
str := shortened[pos]
abbr := matcher.Match(str)
shortened[pos] = abbr
}
})

return shortened.String()
}

// AsPascal discovers words using camel case and non letter characters,
// starting from the back until the string has less than 'max' characters
// starting from the back or the front until the string has less than 'max' characters
// or it can't shorten any more. Word boundaries are a capital letter at
// the start of each word
func AsPascal(matcher *Matcher, original string, max int) string {
func AsPascal(matcher *Matcher, original string, max int, frmFront bool) string {
if original == "" {
return ""
}
Expand All @@ -177,12 +177,12 @@ func AsPascal(matcher *Matcher, original string, max int) string {
return shortened.String()
}

for pos := len(shortened) - 1; pos >= 0 && shortened.Len() > max; pos-- {
shorten(shortened, max, frmFront, func(pos int) {
str := strings.ToLower(shortened[pos])
abbr := matcher.Match(str)
abbr = makeTitle(abbr)
shortened[pos] = abbr
}
})

return shortened.String()
}
Expand Down Expand Up @@ -228,3 +228,16 @@ func lastChar(str string) (string, rune) {

return str[0 : l-1], []rune(str)[l-1:][0]
}

func shorten(sequences Sequences, max int, frmFront bool, shorten func(int)) Sequences {
if frmFront {
for pos := 0; pos < len(sequences) && sequences.Len() > max; pos++ {
shorten(pos)
}
} else {
for pos := len(sequences) - 1; pos >= 0 && sequences.Len() > max; pos-- {
shorten(pos)
}
}
return sequences
}
28 changes: 25 additions & 3 deletions domain/shorteners_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,37 @@ ltd=limited`)
name string
original string
max int
frmFront bool
want string
}{
{name: "Length longer than origin with '-'", original: "aaa-bbb-ccc", max: 99, want: "aaa-bbb-ccc"},
{name: "Length is 0 with '-'", original: "aaa-bbb-ccc", max: 0, want: "a-b-c"},
{name: "Partial abbreviation with '-'", original: "aaa-bbb-ccc", max: 10, want: "aaa-bbb-c"},
{name: "Partial abbreviation with '-', start from the front", original: "aaa-bbb-ccc", max: 10, frmFront: true, want: "a-bbb-ccc"},
{name: "Length longer than origin with camel case", original: "AaaBbbCcc", max: 99, want: "AaaBbbCcc"},
{name: "Length is 0 with camel case", original: "AaaBbbCcc", max: 0, want: "ABC"},
{name: "Length is 0 with camel case, matching case", original: "aaaBbbCcc", max: 0, want: "aBC"},
{name: "Partial abbreviation with camel case", original: "AaaBbbCcc", max: 8, want: "AaaBbbC"},
{name: "Partial abbreviation with camel case, start from the front", original: "AaaBbbCcc", max: 8, frmFront: true, want: "ABbbCcc"},
{name: "Doesn't match wrong casing", original: "AaaBBbCcc", max: 0, want: "ABBbC"},
{name: "Mixed camel case and non word separators", original: "AaaBbb-ccc", max: 0, want: "AB-c"},
{name: "Mixed camel case and non word separators with same borders", original: "Aaa-Bbb-Ccc", max: 0, want: "A-B-C"},
{name: "Real example, full short", original: "strategy-limited", max: 0, want: "stg-ltd"},
{name: "Real example, shorter than total", original: "strategy-limited", max: 13, want: "strategy-ltd"},
{name: "Real example, shorter than total, start from the front", original: "strategy-limited", max: 13, frmFront: true, want: "stg-limited"},
{name: "Real example, max same as shorted", original: "strategy-limited", max: 12, want: "strategy-ltd"},
{name: "Real example, max same as shorted, start from the front", original: "strategy-limited", max: 12, frmFront: true, want: "stg-limited"},
{name: "Real example, max on separator", original: "strategy-limited", max: 9, want: "stg-ltd"},
{name: "Real example, max shorter than first word", original: "strategy-limited", max: 6, want: "stg-ltd"},
{name: "Real example, no short", original: "strategy-limited", max: 99, want: "strategy-limited"},
{name: "Real example, with numbers #1", original: "strategy-limited99", max: 15, want: "strategy-ltd99"},
{name: "Real example, with numbers #2", original: "strategy-limited-99", max: 15, want: "strategy-ltd-99"},
{name: "Real example, with numbers, start from the front #1", original: "strategy-limited99", max: 15, frmFront: true, want: "stg-limited99"},
{name: "Real example, with numbers, start from the front #2", original: "strategy-limited-99", max: 15, frmFront: true, want: "stg-limited-99"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := AsOriginal(matcher, tt.original, tt.max); got != tt.want {
if got := AsOriginal(matcher, tt.original, tt.max, tt.frmFront); got != tt.want {
t.Errorf("AsOriginal('%s', %d) = '%v', want '%v'", tt.original, tt.max, got, tt.want)
}
})
Expand Down Expand Up @@ -162,6 +169,7 @@ ltd=limited`)
original string
separator string
max int
frmFront bool
want string
}{
{name: "Length is 0 with '-'", original: "aaa-bbb-ccc", separator: "_", max: 0, want: "a_b_c"},
Expand All @@ -170,24 +178,31 @@ ltd=limited`)
{name: "Length is 0 with camel case", original: "AaaBbbCcc", separator: "_", max: 0, want: "a_b_c"},
{name: "Length is 0 with camel case, matching case", original: "aaaBbbCcc", separator: "_", max: 0, want: "a_b_c"},
{name: "Partial abbreviation with camel case", original: "AaaBbbCcc", separator: "_", max: 8, want: "aaa_b_c"},
{name: "Partial abbreviation with camel case, start from the front", original: "AaaBbbCcc", separator: "_", max: 8, frmFront: true, want: "a_b_ccc"},
{name: "Doesn't match wrong casing", original: "AaaBBbCcc", separator: "_", max: 0, want: "a_b_bb_c"},
{name: "Mixed camel case and non word separators", original: "AaaBbb-ccc", separator: "_", max: 0, want: "a_b_c"},
{name: "Mixed camel case and non word separators with same borders", separator: "_", original: "Aaa-Bbb-Ccc", max: 0, want: "a_b_c"},
{name: "Real example, full short", original: "strategy-limited", separator: "_", max: 0, want: "stg_ltd"},
{name: "Real example, shorter than total", original: "strategy-limited", separator: "_", max: 13, want: "strategy_ltd"},
{name: "Real example, shorter than total, start from the front", original: "strategy-limited", separator: "_", max: 13, frmFront: true, want: "stg_limited"},
{name: "Real example, max same as shorted", original: "strategy-limited", separator: "_", max: 12, want: "strategy_ltd"},
{name: "Real example, max same as shorted, start from the front", original: "strategy-limited", separator: "_", max: 12, frmFront: true, want: "stg_limited"},
{name: "Real example, max on separator", original: "strategy-limited", separator: "_", max: 9, want: "stg_ltd"},
{name: "Real example, max shorter than first word", original: "strategy-limited", separator: "_", max: 6, want: "stg_ltd"},
{name: "Real example, no short", original: "strategy-limited", separator: "_", max: 99, want: "strategy_limited"},
{name: "Real example, with numbers #1", original: "strategy-limited99", separator: "_", max: 15, want: "strategy_ltd_99"},
{name: "Real example, with numbers #2", original: "strategy-limited-99", separator: "_", max: 15, want: "strategy_ltd_99"},
{name: "Real example, with numbers, start from the front #1", original: "strategy-limited99", separator: "_", max: 15, frmFront: true, want: "stg_limited_99"},
{name: "Real example, with numbers, start from the front #2", original: "strategy-limited-99", separator: "_", max: 15, frmFront: true, want: "stg_limited_99"},
{name: "Multiple separators", original: "strategy---limited--99", separator: "_", max: 15, want: "strategy_ltd_99"},
{name: "Multiple separators, start from the front", original: "strategy---limited--99", separator: "_", max: 15, frmFront: true, want: "stg_limited_99"},
{name: "Other separator", original: "strategy-limited-99", separator: "+", max: 15, want: "strategy+ltd+99"},
{name: "Other separator, start from the front", original: "strategy-limited-99", separator: "+", max: 15, frmFront: true, want: "stg+limited+99"},
{name: "Empty string", original: "", separator: "+", max: 15, want: ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := AsSnake(matcher, tt.original, tt.separator, tt.max); got != tt.want {
if got := AsSnake(matcher, tt.original, tt.separator, tt.max, tt.frmFront); got != tt.want {
t.Errorf("AsSnake() = %v, want %v", got, tt.want)
}
})
Expand All @@ -205,30 +220,37 @@ ltd=limited`)
name string
original string
max int
frmFront bool
want string
}{
{name: "Length longer than origin with '-'", original: "aaa-bbb-ccc", max: 99, want: "AaaBbbCcc"},
{name: "Length is 0 with '-'", original: "aaa-bbb-ccc", max: 0, want: "ABC"},
{name: "Partial abbreviation with '-'", original: "aaa-bbb-ccc", max: 8, want: "AaaBbbC"},
{name: "Partial abbreviation with '-', start from the front", original: "aaa-bbb-ccc", max: 8, frmFront: true, want: "ABbbCcc"},
{name: "Length longer than origin with camel case", original: "AaaBbbCcc", max: 99, want: "AaaBbbCcc"},
{name: "Length is 0 with camel case", original: "AaaBbbCcc", max: 0, want: "ABC"},
{name: "Length is 0 with camel case, matching case", original: "aaaBbbCcc", max: 0, want: "ABC"},
{name: "Partial abbreviation with camel case", original: "AaaBbbCcc", max: 8, want: "AaaBbbC"},
{name: "Partial abbreviation with camel case, start from the front", original: "AaaBbbCcc", max: 8, frmFront: true, want: "ABbbCcc"},
{name: "Doesn't match wrong casing", original: "AaaBBbCcc", max: 0, want: "ABBbC"},
{name: "Mixed camel case and non word separators", original: "AaaBbb-ccc", max: 0, want: "ABC"},
{name: "Mixed camel case and non word separators with same borders", original: "Aaa-Bbb-Ccc", max: 0, want: "ABC"},
{name: "Real example, full short", original: "strategy-limited", max: 0, want: "StgLtd"},
{name: "Real example, shorter than total", original: "strategy-limited", max: 13, want: "StrategyLtd"},
{name: "Real example, shorter than total, start from the front", original: "strategy-limited", max: 13, frmFront: true, want: "StgLimited"},
{name: "Real example, max same as shorted", original: "strategy-limited", max: 12, want: "StrategyLtd"},
{name: "Real example, max same as shorted", original: "strategy-limited", max: 12, frmFront: true, want: "StgLimited"},
{name: "Real example, max on separator", original: "strategy-limited", max: 9, want: "StgLtd"},
{name: "Real example, max shorter than first word", original: "strategy-limited", max: 6, want: "StgLtd"},
{name: "Real example, no short", original: "strategy-limited", max: 99, want: "StrategyLimited"},
{name: "Real example, with numbers #1", original: "strategy-limited99", max: 15, want: "StrategyLtd99"},
{name: "Real example, with numbers #2", original: "strategy-limited-99", max: 15, want: "StrategyLtd99"},
{name: "Real example, with numbers #1, start from the front", original: "strategy-limited99", max: 15, frmFront: true, want: "StgLimited99"},
{name: "Real example, with numbers #2, start from the front", original: "strategy-limited-99", max: 15, frmFront: true, want: "StgLimited99"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := AsPascal(matcher, tt.original, tt.max); got != tt.want {
if got := AsPascal(matcher, tt.original, tt.max, tt.frmFront); got != tt.want {
t.Errorf("AsPascal('%s', %d) = '%v', want '%v'", tt.original, tt.max, got, tt.want)
}
})
Expand Down

0 comments on commit 75a829d

Please sign in to comment.