Skip to content
This repository was archived by the owner on Sep 26, 2021. It is now read-only.

Commit

Permalink
TLDR client written in Go
Browse files Browse the repository at this point in the history
Soon to be distributable as a static binary, through the Github Releases
API.
  • Loading branch information
Pranav Raja committed Mar 8, 2014
0 parents commit f77a5d0
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/tldr
*.swp
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

A work-in-progress [Go](http://golang.org/) client for [tldr](https://github.com/rprieto/tldr/).

# Setup

If you have Go installed, grab the latest version:

go get github.com/pranavraja/tldr

Binary releases for common platforms will be added soon, once I add a few more features.

# Prerequisites

If you installed using `go get`, make sure `$GOPATH/bin` is in your `$PATH`.

# Usage

tldr <command>

Fetch the docs for `command` and render them to the terminal.

# Hacking

Once you have cloned the repo, build using `go build`, run the tests using `go test`.

# TODO (contributions welcome)

- Improve rendering of command placeholders, like in `sed 's/a/b/' {{filename}}`
- Caching of commands
- Add a command-line flag to override the platform (currently only "common" is supported)
- Improve multi-line command rendering
20 changes: 20 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"os"
)

func main() {
if len(os.Args) <= 1 {
println("Usage: tldr <command>")
os.Exit(1)
}
cmd := os.Args[1]
platform := "common"
page, err := GetPageForPlatform(cmd, platform)
if err != nil {
println(err.Error())
os.Exit(1)
}
println(Render(page))
}
24 changes: 24 additions & 0 deletions page.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"fmt"
"io"
"net/http"
)

var remote string = "http://raw.github.com/rprieto/tldr/master/pages"

// Caller must close the response body after reading.
func GetPageForPlatform(page, platform string) (io.ReadCloser, error) {
resp, err := http.Get(remote + "/" + platform + "/" + page + ".md")
if err != nil {
return nil, err
}
if resp.StatusCode == 404 {
return nil, fmt.Errorf("Not found.\nTo add this command, send Romain a pull request at:\n https://github.com/rprieto/tldr")
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Unexpected status code: %d", resp.StatusCode)
}
return resp.Body, nil
}
62 changes: 62 additions & 0 deletions page_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)

type testServer struct {
originalRequest *http.Request
statusCode int
response string
}

func (t *testServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t.originalRequest = r
w.WriteHeader(t.statusCode)
io.WriteString(w, t.response)
}

func (t *testServer) Intercept(test func()) {
server := httptest.NewServer(t)
defer server.Close()
remote = server.URL
test()
}

func TestGetPageForPlatform_404(t *testing.T) {
server := testServer{statusCode: 404, response: "NOT FOUND BRO"}
var resp io.ReadCloser
var err error
server.Intercept(func() {
resp, err = GetPageForPlatform("tldr", "osx")
})
if resp != nil {
t.Errorf("Expected a nil response but got a non-nil response")
}
if err == nil {
t.Errorf("Expected an error for an invalid status code, but got none")
}
}

func TestGetPageForPlatform(t *testing.T) {
server := testServer{statusCode: 200, response: "DO IT BRO"}
var resp io.ReadCloser
var err error
server.Intercept(func() {
resp, err = GetPageForPlatform("tldr", "osx")
})
defer resp.Close()
if err != nil {
t.Error(err)
}
if expected := "/osx/tldr.md"; server.originalRequest.URL.Path != expected {
t.Errorf("Page requested from wrong url: %s", server.originalRequest.URL.Path)
}
if body, _ := ioutil.ReadAll(resp); string(body) != "DO IT BRO" {
t.Errorf("Read wrong body: %s")
}
}
49 changes: 49 additions & 0 deletions render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"bufio"
"io"
"strings"
)

const (
TermEscapePrefix = "\033["
TermEscapeSuffix = "m"

Reset = TermEscapePrefix + "0" + TermEscapeSuffix

Bold = TermEscapePrefix + "1" + TermEscapeSuffix

Green = TermEscapePrefix + "32" + TermEscapeSuffix
Cyan = TermEscapePrefix + "36" + TermEscapeSuffix
White = TermEscapePrefix + "37" + TermEscapeSuffix

OnBlue = TermEscapePrefix + "44" + TermEscapeSuffix
OnGrey = TermEscapePrefix + "40" + TermEscapeSuffix
)

// Render pretties up some markdown for terminal display.
// Only a small subset of markdown is actually supported,
// as described in https://github.com/rprieto/tldr/blob/master/CONTRIBUTING.md#markdown-format
func Render(markdown io.Reader) (rendered string) {
scanner := bufio.NewScanner(markdown)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "#") {
// Heading
rendered += OnBlue + Bold + Cyan + strings.TrimLeft(line, "# ") + Reset + "\n"
} else if strings.HasPrefix(line, ">") {
// Quotation
rendered += " " + White + strings.TrimLeft(line, "> ") + Reset + "\n"
} else if strings.HasPrefix(line, "-") {
// Inline list
rendered += Green + line + Reset + "\n"
} else if strings.HasPrefix(line, "`") {
// Code
rendered += " " + OnGrey + White + strings.Trim(line, "`") + Reset + "\n"
} else {
rendered += line + "\n"
}
}
return rendered
}
27 changes: 27 additions & 0 deletions render_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import (
"strings"
"testing"
)

func TestRender(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"simple string", "asd\nsdfghi\njk", "asd\nsdfghi\njk\n"},
{"simple utf8 string", "Hello, 世界\n\ni like chips", "Hello, 世界\n\ni like chips\n"},
{"headings", "# Hello, 世界\n\ni like chips", "\033[44m\033[1m\033[36mHello, 世界\033[0m\n\ni like chips\n"},
{"quotation", "Title\n> Raja", "Title\n \033[37mRaja\033[0m\n"},
{"inline list", "Title\n- Raja\n- Pranav", "Title\n\033[32m- Raja\033[0m\n\033[32m- Pranav\033[0m\n"},
{"code", "Title\n`go build`\n`go test`", "Title\n \033[40m\033[37mgo build\033[0m\n \033[40m\033[37mgo test\033[0m\n"},
}
for _, test := range tests {
rendered := Render(strings.NewReader(test.input))
if rendered != test.expected {
t.Errorf("Incorrect render of %s: got '%s', want '%s'", test.name, rendered, test.expected)
}
}
}

0 comments on commit f77a5d0

Please sign in to comment.