Skip to content

Commit

Permalink
feat(spaced): Add spaced utility (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
72636c authored Dec 2, 2018
1 parent f5050b8 commit f32450f
Show file tree
Hide file tree
Showing 15 changed files with 541 additions and 0 deletions.
56 changes: 56 additions & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
kind: pipeline
name: default

platform:
os: linux
arch: amd64

steps:
- name: lint + test
image: golang:1.11.2
commands:
- go get -u golang.org/x/lint/golint
- ./scripts/lint-test.sh
when:
event:
exclude:
- pull_request
- tag

- name: build + package
image: golang:1.11.2
commands:
- apt-get update && apt-get install --assume-yes zip
- ./scripts/build-package.sh
environment:
OUTDIR: /tmp/dist
volumes:
- name: dist
path: /tmp/dist
when:
event:
exclude:
- pull_request
- tag

- name: publish
image: node:10.14.0
commands:
- npx semantic-release
environment:
GITHUB_TOKEN:
from_secret: github/public_repo
# https://github.com/npm/uid-number/issues/7
NPM_CONFIG_UNSAFE_PERM: true
volumes:
- name: dist
path: /tmp/dist
when:
branch:
- master
event:
- push

volumes:
- name: dist
temp: {}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.temp/
.vscode/
dist/
vendor/

.DS_Store
Expand Down
16 changes: 16 additions & 0 deletions .releaserc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
repositoryUrl: [email protected]:72636c/hyperspaced
branch: master

ci: true
debug: true

plugins:
- '@semantic-release/commit-analyzer'
- '@semantic-release/release-notes-generator'
- - '@semantic-release/github'
- assets: '/tmp/dist/**'
failComment: false
failTitle: false
labels: false
releasedLabels: false
successComment: false
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
# H Y P E R S P A C E D

[![Build Status](https://cloud.drone.io/api/badges/72636c/hyperspaced/status.svg)](https://cloud.drone.io/72636c/hyperspaced)

Command line utilities to improve the aesthetics of your favourite phrases with
automatic space insertion (ASI).

## Usage

### CLI

Prerequisites:

- Go 1.11+

```shell
go install ./cmd/spaced
```

```shell
echo 'gofmt urself' | spaced
```

### Go

```go
import (
"github.com/72636c/hyperspaced"
)

hyperspaced.Spaced("gofmt urself")
```

## Meta

### CI/CD pipeline

Builds and releases are automated with [Drone](https://drone.io/), which is
impressively _"powered by blazing fast bare-metal servers"_, and more
importantly _"written in Go"_.

### Local scripts

```shell
./scripts/build-package.sh
```

```shell
./scripts/lint-test.sh
```

### Mouseprint

_This is a shameless rip-off of <https://www.npmjs.com/package/letter-spacing>._
17 changes: 17 additions & 0 deletions cmd/spaced/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"os"

"github.com/72636c/hyperspaced/internal/text"
"github.com/72636c/hyperspaced/internal/text/transform"
)

func main() {
err := text.LineFilter(transform.Spaced)
if err != nil {
fmt.Fprintln(os.Stderr, "error: ", err)
os.Exit(1)
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/72636c/hyperspaced

require golang.org/x/text v0.3.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
15 changes: 15 additions & 0 deletions hyperspaced.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package hyperspaced

import (
"github.com/72636c/hyperspaced/internal/text/transform"
)

// Spaced inserts a space between each character in a string.
func Spaced(str string) string {
return transform.Spaced(str)
}

// SpacedN inserts n spaces between each character in a string.
func SpacedN(str string, n int) string {
return transform.SpacedN(str, n)
}
81 changes: 81 additions & 0 deletions internal/text/char.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package text

import (
"unicode"
"unicode/utf8"

"golang.org/x/text/unicode/norm"
)

// Char represents a character. Whatever that means.
type Char []rune

func (char Char) String() (str string) {
for _, r := range char {
str += string(r)
}

return
}

// SplitString splits a string into a slice of characters.
//
// It's obviously not perfect; don't @ me.
func SplitString(str string) []Char {
var iter norm.Iter
iter.InitString(norm.NFC, str)

chars := make([]Char, 0)
runes := make([]rune, 0)

for !iter.Done() {
r, _ := utf8.DecodeRune(iter.Next())

if areSeparate(runes, r) {
chars = append(chars, Char(runes))
runes = []rune{r}
} else {
runes = append(runes, r)
}
}

if len(runes) == 0 {
return chars
}

return append(chars, Char(runes))
}

func areSeparate(runes []rune, next rune) bool {
if len(runes) == 0 {
return false
}

previous := runes[len(runes)-1]

return !isNull(previous) &&
!isJoinControl(previous) &&
!isJoinControl(next) &&
!isModifierSymbol(next) &&
!isVariationSelector(next)
}

// https://www.fileformat.info/info/unicode/char/0000/index.htm
func isNull(r rune) bool {
return r == '\u0000'
}

// http://unicode.org/reports/tr44/#Join_Control
func isJoinControl(r rune) bool {
return unicode.In(r, unicode.Join_Control)
}

// http://www.fileformat.info/info/unicode/category/Sk/list.htm
func isModifierSymbol(r rune) bool {
return unicode.In(r, unicode.Sk)
}

// http://www.unicode.org/charts/PDF/UFE00.pdf
func isVariationSelector(r rune) bool {
return unicode.In(r, unicode.Variation_Selector)
}
75 changes: 75 additions & 0 deletions internal/text/char_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package text_test

import (
"reflect"
"testing"

"github.com/72636c/hyperspaced/internal/text"
)

func Test_SplitString(t *testing.T) {
testCases := []struct {
description string
input string
expected []text.Char
}{
{
description: "Empty String",
input: "",
expected: []text.Char{},
},
{
description: "One Letter",
input: "f",
expected: []text.Char{text.Char{'f'}},
},
{
description: "Two Letters",
input: "hi",
expected: []text.Char{text.Char{'h'}, text.Char{'i'}},
},
{
description: "Accent Normalisation",
input: "e\u0301",
expected: []text.Char{text.Char{'Γ©'}},
},
{
description: "Separate Emoji",
input: "\U0001f30f\u2696",
expected: []text.Char{text.Char{'🌏'}, text.Char{'βš–'}},
},
{
// https://emojipedia.org/family-man-light-skin-tone-woman-light-skin-tone-girl-light-skin-tone-baby-light-skin-tone/
description: "Sequenced Emoji",
input: "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f476\U0001f3fb",
expected: []text.Char{text.Char{'πŸ‘¨', '🏻', '\u200d', 'πŸ‘©', '🏻', '\u200d', 'πŸ‘§', '🏻', '\u200d', 'πŸ‘Ά', '🏻'}},
},
{
// https://emojipedia.org/female-sleuth/
description: "Female Variant Selector",
input: "\U0001f575\ufe0f\u200d\u2640\ufe0f",
expected: []text.Char{text.Char{'πŸ•΅', '\ufe0f', '\u200d', '♀', '\ufe0f'}},
},
{
// https://emojipedia.org/female-technologist/
description: "Join Control",
input: "\U0001f469\u200d\U0001f4bb",
expected: []text.Char{text.Char{'πŸ‘©', '\u200d', 'πŸ’»'}},
},
{
// https://emojipedia.org/man-type-6/
description: "Skin Tone Modifier",
input: "\U0001f468\U0001f3ff",
expected: []text.Char{text.Char{'πŸ‘¨', '🏿'}},
},
}

for _, testCase := range testCases {
t.Run(testCase.description, func(t *testing.T) {
actual := text.SplitString(testCase.input)
if !reflect.DeepEqual(actual, testCase.expected) {
t.Errorf("expected %+v, received %+v", testCase.expected, actual)
}
})
}
}
26 changes: 26 additions & 0 deletions internal/text/lineFilter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package text

import (
"bufio"
"fmt"
"os"
)

// TransformLine is a function that transforms a line of text.
type TransformLine func(string) string

// LineFilter transforms lines of text as they pass from stdin to stdout.
func LineFilter(transform TransformLine) error {
scanner := bufio.NewScanner(os.Stdin)

for scanner.Scan() {
output := transform(scanner.Text())

_, err := fmt.Println(output)
if err != nil {
return err
}
}

return scanner.Err()
}
Loading

0 comments on commit f32450f

Please sign in to comment.