Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
chey committed May 8, 2023
0 parents commit e02b98d
Show file tree
Hide file tree
Showing 14 changed files with 671 additions and 0 deletions.
23 changes: 23 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
; https://editorconfig.org/

root = true

[*]
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab
indent_size = 4

[*.md]
indent_size = 4
trim_trailing_whitespace = false

eclint_indent_style = unset

[Dockerfile]
indent_size = 4
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
qr
*.png
.build
45 changes: 45 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
LDFLAGS ?= -s -w
BUILD_DIR ?= .build

ifeq (, $(shell which golangci-lint))
$(warning "could not find golangci-lint in $$(PATH), visit: https://golangci-lint.run/usage/install/ for more information")
endif

.PHONY: lint build install lib test test-lib clean

lint:
golangci-lint run ./...

build: $(BUILD_DIR)/bin
go build -ldflags "$(LDFLAGS)" -o $</

install:
go install -ldflags "$(LDFLAGS)"

test:
go test ./...

lib: $(BUILD_DIR)/lib
export C_INCLUDE_PATH=$<; go build -buildmode=c-shared -ldflags "$(LDFLAGS)" -o $</libqr.so ./internal...

test-lib: $(BUILD_DIR)/bin/main_test
export LD_LIBRARY_PATH=$(BUILD_DIR)/lib; $<

$(BUILD_DIR)/bin/main_test: $(BUILD_DIR)/bin $(BUILD_DIR)/lib/libqr.so $(BUILD_DIR)/lib/libqr.h
gcc test/main.c -o $</main_test -L"$(BUILD_DIR)/lib" -I"$(BUILD_DIR)/lib" -lqr

clean:
rm -rf $(BUILD_DIR)

$(BUILD_DIR)/lib/libqr.so: lib

$(BUILD_DIR)/lib/libqr.h: lib

$(BUILD_DIR):
mkdir -p .build

$(BUILD_DIR)/bin: $(BUILD_DIR)
mkdir -p $@

$(BUILD_DIR)/lib: $(BUILD_DIR)
mkdir -p $@
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# qr

A lightweight QR encoder CLI and library.

* Simple to use
* PNG support
* Terminal support

## Install CLI

### Using GO
```shell
go install github.com/chey/qr

qr https://www.example.com
```

## Library usage
```shell
go get [-u] github.com/chey/qr
```

### Example

This example prints a small QR code to the terminal using the lowest level of error correction.

```go
package main

import (
"github.com/chey/qr/code"
)

func main() {
qr, _ := code.New("https://www.example.com", code.Low)
qr.Print()
}
```
134 changes: 134 additions & 0 deletions code/code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package code

import (
"image"
"image/color"
"image/png"
"io"
"os"

"rsc.io/qr"
)

const (
emptyBlock = "\u0020"
upperHalfBlock = "\u2580"
lowerHalfBlock = "\u2584"
fullBlock = "\u2588"
termBlack = "\033[40m \033[0m"
termWhite = "\033[47m \033[0m"
newline = "\n"
)

type Level = qr.Level

const (
Low = qr.L
Medium = qr.M
High = qr.Q
Highest = qr.H
)

// Code wraps module import
type Code struct {
*qr.Code
}

// New creates a new QR Code.
// Error correction levels are 0, 1, 2, 3.
// 3 being the highest level of correction.
func New(text string, level Level) (*Code, error) {
c, err := qr.Encode(text, level)
if err != nil {
return nil, err
}
return &Code{c}, nil
}

// Small writes Code to io.Writer w.
// Perfect for terminal.
func (c *Code) Small(w io.Writer) {
for y := -1; y <= c.Size; y += 2 {
for x := -1; x <= c.Size; x++ {
next := c.Black(x, y+1)
curr := c.Black(x, y)
if curr && next {
writeString(w, emptyBlock)
} else if curr && !next {
writeString(w, lowerHalfBlock)
} else if !curr && !next {
if y == c.Size {
writeString(w, upperHalfBlock)
} else {
writeString(w, fullBlock)
}
} else {
writeString(w, upperHalfBlock)
}
}
writeString(w, newline)
}
}

// Big writes a larger Code to io.Writer w.
// Great for terminal.
func (c *Code) Big(w io.Writer) {
for y := -1; y <= c.Size; y++ {
for x := -1; x <= c.Size; x++ {
if c.Black(x, y) {
writeString(w, termBlack)
} else {
writeString(w, termWhite)
}
}
writeString(w, newline)
}
}

// Print writes Code to os.Stdout
func (c *Code) Print() {
c.Small(os.Stdout)
}

// Image returns an image.Image
func (c *Code) Image() image.Image {
return &codeImage{c.Code}
}

// PNG writes a PNG image to io.Writer w
func (c *Code) PNG(w io.Writer) error {
return png.Encode(w, c.Image())
}

// writeString writes string s to io.Writer w
func writeString(w io.Writer, s string) {
if _, err := io.WriteString(w, s); err != nil {
panic(err)
}
}

// codeImage implements image.Image
type codeImage struct {
*qr.Code
}

var (
whiteColor color.Color = color.Gray{0xFF}
blackColor color.Color = color.Gray{0x00}
)

func (c *codeImage) Bounds() image.Rectangle {
d := (c.Size + 8) * c.Scale
return image.Rect(0, 0, d, d)
}

func (c *codeImage) At(x, y int) color.Color {
if c.Black(x/c.Scale-4, y/c.Scale-4) {
return blackColor
}
return whiteColor
}

func (c *codeImage) ColorModel() color.Model {
return color.GrayModel
}
94 changes: 94 additions & 0 deletions code/code_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package code

import (
"bytes"
"fmt"
"os"
"strings"
"testing"
)

func TestCode(t *testing.T) {
_, err := New("chey", 0)
if err != nil {
t.Error(err)
}
}

// max is 7089 for numeric characters so this should fail
// http://en.wikipedia.org/wiki/QR_code
func TestCodeError(t *testing.T) {
if _, err := New(strings.Repeat("1", 7090), 0); err == nil {
t.Error("expeced error")
}
}

func TestCodeSmall(t *testing.T) {
c, _ := New("chey", 0)

var buf bytes.Buffer

c.Small(&buf)

if buf.String() == "" {
t.Error("got empty code")
}
}

func TestCodeBig(t *testing.T) {
c, _ := New("chey", 0)

var buf bytes.Buffer

c.Big(&buf)

if buf.String() == "" {
t.Error("got empty code")
}
}

func TestCodePNG(t *testing.T) {
c, _ := New("chey", 0)

var buf bytes.Buffer

err := c.PNG(&buf)
if err != nil {
t.Error(err)
}

if buf.String() == "" {
t.Error("got empty png")
}
}

func TestCodePrint(t *testing.T) {
c, _ := New("chey", 0)

c.Print()
}

func TestCodeWriteStringPanic(t *testing.T) {
file := fmt.Sprintf("%s%c%s", t.TempDir(), os.PathSeparator, "qrcode.txt")
fmt.Println(file)
f, _ := os.Create(file)
if err := f.Chmod(0400); err != nil {
t.Error(err)
}
f.Close()
f, _ = os.Open(file)

recovered := false

defer func() {
if r := recover(); r != nil {
recovered = true
fmt.Println("Recovered in TestCodeWriteStringPanic", r)
}
}()
writeString(f, "hi")

if !recovered {
t.Error("expected we could handle a write panic but we didn't")
}
}
8 changes: 8 additions & 0 deletions code/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package code

var version = "DEV"

// Version returns version
func Version() string {
return version
}
9 changes: 9 additions & 0 deletions code/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package code

import "testing"

func TestVersion(t *testing.T) {
if Version() == "" {
t.Errorf("missing version")
}
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/chey/qr

go 1.20

require rsc.io/qr v0.2.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
Loading

0 comments on commit e02b98d

Please sign in to comment.