<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

var (
	githubToken       string
	githubUser        string
	githubRepo        string
	githubAPIEndpoint string
	// Version gets initialized in compilation time.
	Version string
	debug   bool
)

// Release represents a Github Release.
type Release struct {
	UploadURL  string `json:"upload_url,omitempty"`
	TagName    string `json:"tag_name"`
	Branch     string `json:"target_commitish"`
	Name       string `json:"name"`
	Body       string `json:"body"`
	Draft      bool   `json:"draft"`
	Prerelease bool   `json:"prerelease"`
}

var verFlag bool

func init() {
	log.SetFlags(0)

	debug, _ = strconv.ParseBool(os.Getenv("DEBUG"))

	githubToken = os.Getenv("GITHUB_TOKEN")
	githubUser = os.Getenv("GITHUB_USER")
	githubRepo = os.Getenv("GITHUB_REPO")
	githubAPIEndpoint = os.Getenv("GITHUB_API")

	flag.BoolVar(&verFlag, "version", false, "-version")
	flag.Parse()
}

var usage = `Github command line release tool.

Usage:
	github-release <user/repo> <tag> <branch> <description> "<files>"

Parameters:
	<user/repo>: Github user and repository
	<tag>: Used to created the release. It is also used as the release's name
	<branch>: Reference from where to create the provided <tag>, if it does not exist
	<description>: The release description
	<files>: Glob pattern describing the list of files to include in the release.
			Make sure you enclose it in quotes to avoid the shell expanding the glob pattern.

Options:
	-version: Displays version

Environment variables:
  DEBUG: Allows you to run github-release in debugging mode. DO NOT do this if you are attempting to upload big files.
  GITHUB_TOKEN: Must be set in order to interact with Github's API
  GITHUB_USER: Just in case you want an alternative way of providing your github user
  GITHUB_REPO: Just in case you want an alternative way of providing your github repo
  GITHUB_API: Github API endpoint. Set to https://api.github.com/repos/:github-user/:github-repo" by default

Before using this tool make sure you set the environment variable GITHUB_TOKEN
with a valid Github token and correct authorization scopes to allow you to create releases
in your project. For more information about creating Github tokens please read the
official Github documentation at https://help.github.com/articles/creating-an-access-token-for-command-line-use/

Author: https://github.com/c4milo
License: http://mozilla.org/MPL/2.0/
`

func main() {
	if verFlag {
		log.Println(Version)
		return
	}

	if len(os.Args) < 6 {
		log.Fatal(usage)
	}

	userRepo := strings.Split(os.Args[1], "/")
	if len(userRepo) != 2 {
		log.Printf("Error: Invalid format used for username and repository: %s\n\n", os.Args[1])
		log.Fatal(usage)
	}

	if githubToken == "" {
		log.Fatal(`Error: GITHUB_TOKEN environment variable is not set.
Please refer to https://help.github.com/articles/creating-an-access-token-for-command-line-use/ for more help\n`)
	}

	githubUser = userRepo[0]
	githubRepo = userRepo[1]
	githubAPIEndpoint = fmt.Sprintf("https://api.github.com/repos/%s/%s", githubUser, githubRepo)

	if debug {
		log.Println("Glob pattern received: ")
		log.Println(os.Args[5])
	}

	filepaths, err := filepath.Glob(os.Args[5])
	if err != nil {
		log.Fatalf("Error: Invalid glob pattern: %s\n", os.Args[5])
	}

	if debug {
		log.Println("Expanded glob pattern: ")
		log.Printf("%v\n", filepaths)
	}

	tag := os.Args[2]
	branch := os.Args[3]
	desc := os.Args[4]

	CreateRelease(tag, branch, desc, filepaths)
	log.Println("Done")
}

func uploadFile(uploadURL, path string) {
	file, err := os.Open(path)
	if err != nil {
		log.Printf("Error: %s\n", err.Error())
		return
	}
	defer file.Close()

	size, err := fileSize(file)
	if err != nil {
		log.Printf("Error: %s\n", err.Error())
		return
	}

	filename := filepath.Base(file.Name())
	log.Printf("Uploading %s...\n", filename)
	body, err := doRequest("POST", uploadURL+"?name="+filename, "application/octet-stream", file, size)
	if err != nil {
		log.Printf("Error: %s\n", err.Error())
	}

	if debug {
		log.Println("========= UPLOAD RESPONSE ===========")
		log.Println(string(body[:]))
	}
}

// CreateRelease creates a Github Release, attaching the given files as release assets
// If a release already exist, up in Github, this function will attempt to attach the given files to it.
func CreateRelease(tag, branch, desc string, filepaths []string) {
	endpoint := fmt.Sprintf("%s/releases", githubAPIEndpoint)

	release := Release{
		TagName:    tag,
		Name:       tag,
		Prerelease: false,
		Draft:      false,
		Branch:     branch,
		Body:       desc,
	}

	releaseData, err := json.Marshal(release)
	if err != nil {
		log.Fatalln(err)
	}

	releaseBuffer := bytes.NewBuffer(releaseData)

	data, err := doRequest("POST", endpoint, "application/json", releaseBuffer, int64(releaseBuffer.Len()))
	if err != nil {
		log.Fatalln(err)
	}

	// Gets the release Upload URL from the returned JSON data
	err = json.Unmarshal(data, &release)
	if err != nil {
		log.Fatalln(err)
	}

	// Upload URL comes like this https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name}
	// So we need to remove the {?name} part
	uploadURL := strings.Split(release.UploadURL, "{")[0]

	var wg sync.WaitGroup
	for i := range filepaths {
		wg.Add(1)
		func(index int) {
			uploadFile(uploadURL, filepaths[index])
			wg.Done()
		}(i)
	}
	wg.Wait()
}

func fileSize(file *os.File) (int64, error) {
	stat, err := file.Stat()
	if err != nil {
		return 0, err
	}
	return stat.Size(), nil
}

// Sends HTTP request to Github API
func doRequest(method, url, contentType string, reqBody io.Reader, bodySize int64) ([]byte, error) {
	req, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", fmt.Sprintf("token %s", githubToken))
	req.Header.Set("Content-type", contentType)
	req.Header.Set("Accept", "application/vnd.github.v3+json")
	req.ContentLength = bodySize

	if debug {
		log.Println("================ REQUEST DUMP ==================")
		dump, err := httputil.DumpRequestOut(req, true)
		if err != nil {
			log.Println(err.Error())
		}
		log.Println(string(dump[:]))
	}

	resp, err := http.DefaultClient.Do(req)

	if debug {
		log.Println("================ RESPONSE DUMP ==================")
		dump, err := httputil.DumpResponse(resp, true)
		if err != nil {
			log.Println(err.Error())
		}
		log.Println(string(dump[:]))
	}

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("Github returned an error:\n Code: %s. \n Body: %s", resp.Status, respBody)
	}

	return respBody, nil
}
<commit_msg>Fixes usage text formatting<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

var (
	githubToken       string
	githubUser        string
	githubRepo        string
	githubAPIEndpoint string
	// Version gets initialized in compilation time.
	Version string
	debug   bool
)

// Release represents a Github Release.
type Release struct {
	UploadURL  string `json:"upload_url,omitempty"`
	TagName    string `json:"tag_name"`
	Branch     string `json:"target_commitish"`
	Name       string `json:"name"`
	Body       string `json:"body"`
	Draft      bool   `json:"draft"`
	Prerelease bool   `json:"prerelease"`
}

var verFlag bool

func init() {
	log.SetFlags(0)

	debug, _ = strconv.ParseBool(os.Getenv("DEBUG"))

	githubToken = os.Getenv("GITHUB_TOKEN")
	githubUser = os.Getenv("GITHUB_USER")
	githubRepo = os.Getenv("GITHUB_REPO")
	githubAPIEndpoint = os.Getenv("GITHUB_API")

	flag.BoolVar(&verFlag, "version", false, "-version")
	flag.Parse()
}

var usage = `Github command line release tool.

Usage:
	github-release <user/repo> <tag> <branch> <description> "<files>"

Parameters:
	<user/repo>: Github user and repository
	<tag>: Used to created the release. It is also used as the release's name
	<branch>: Reference from where to create the provided <tag>, if it does not exist
	<description>: The release description
	<files>: Glob pattern describing the list of files to include in the release.
	Make sure you enclose it in quotes to avoid the shell expanding the glob pattern.

Options:
	-version: Displays version

Environment variables:
  DEBUG: Allows you to run github-release in debugging mode. DO NOT do this if you are attempting to upload big files.
  GITHUB_TOKEN: Must be set in order to interact with Github's API
  GITHUB_USER: Just in case you want an alternative way of providing your github user
  GITHUB_REPO: Just in case you want an alternative way of providing your github repo
  GITHUB_API: Github API endpoint. Set to https://api.github.com/repos/:github-user/:github-repo" by default

Before using this tool make sure you set the environment variable GITHUB_TOKEN
with a valid Github token and correct authorization scopes to allow you to create releases
in your project. For more information about creating Github tokens please read the
official Github documentation at https://help.github.com/articles/creating-an-access-token-for-command-line-use/

Author: https://github.com/c4milo
License: http://mozilla.org/MPL/2.0/
`

func main() {
	if verFlag {
		log.Println(Version)
		return
	}

	if len(os.Args) < 6 {
		log.Fatal(usage)
	}

	userRepo := strings.Split(os.Args[1], "/")
	if len(userRepo) != 2 {
		log.Printf("Error: Invalid format used for username and repository: %s\n\n", os.Args[1])
		log.Fatal(usage)
	}

	if githubToken == "" {
		log.Fatal(`Error: GITHUB_TOKEN environment variable is not set.
Please refer to https://help.github.com/articles/creating-an-access-token-for-command-line-use/ for more help\n`)
	}

	githubUser = userRepo[0]
	githubRepo = userRepo[1]
	githubAPIEndpoint = fmt.Sprintf("https://api.github.com/repos/%s/%s", githubUser, githubRepo)

	if debug {
		log.Println("Glob pattern received: ")
		log.Println(os.Args[5])
	}

	filepaths, err := filepath.Glob(os.Args[5])
	if err != nil {
		log.Fatalf("Error: Invalid glob pattern: %s\n", os.Args[5])
	}

	if debug {
		log.Println("Expanded glob pattern: ")
		log.Printf("%v\n", filepaths)
	}

	tag := os.Args[2]
	branch := os.Args[3]
	desc := os.Args[4]

	CreateRelease(tag, branch, desc, filepaths)
	log.Println("Done")
}

func uploadFile(uploadURL, path string) {
	file, err := os.Open(path)
	if err != nil {
		log.Printf("Error: %s\n", err.Error())
		return
	}
	defer file.Close()

	size, err := fileSize(file)
	if err != nil {
		log.Printf("Error: %s\n", err.Error())
		return
	}

	filename := filepath.Base(file.Name())
	log.Printf("Uploading %s...\n", filename)
	body, err := doRequest("POST", uploadURL+"?name="+filename, "application/octet-stream", file, size)
	if err != nil {
		log.Printf("Error: %s\n", err.Error())
	}

	if debug {
		log.Println("========= UPLOAD RESPONSE ===========")
		log.Println(string(body[:]))
	}
}

// CreateRelease creates a Github Release, attaching the given files as release assets
// If a release already exist, up in Github, this function will attempt to attach the given files to it.
func CreateRelease(tag, branch, desc string, filepaths []string) {
	endpoint := fmt.Sprintf("%s/releases", githubAPIEndpoint)

	release := Release{
		TagName:    tag,
		Name:       tag,
		Prerelease: false,
		Draft:      false,
		Branch:     branch,
		Body:       desc,
	}

	releaseData, err := json.Marshal(release)
	if err != nil {
		log.Fatalln(err)
	}

	releaseBuffer := bytes.NewBuffer(releaseData)

	data, err := doRequest("POST", endpoint, "application/json", releaseBuffer, int64(releaseBuffer.Len()))
	if err != nil {
		log.Fatalln(err)
	}

	// Gets the release Upload URL from the returned JSON data
	err = json.Unmarshal(data, &release)
	if err != nil {
		log.Fatalln(err)
	}

	// Upload URL comes like this https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name}
	// So we need to remove the {?name} part
	uploadURL := strings.Split(release.UploadURL, "{")[0]

	var wg sync.WaitGroup
	for i := range filepaths {
		wg.Add(1)
		func(index int) {
			uploadFile(uploadURL, filepaths[index])
			wg.Done()
		}(i)
	}
	wg.Wait()
}

func fileSize(file *os.File) (int64, error) {
	stat, err := file.Stat()
	if err != nil {
		return 0, err
	}
	return stat.Size(), nil
}

// Sends HTTP request to Github API
func doRequest(method, url, contentType string, reqBody io.Reader, bodySize int64) ([]byte, error) {
	req, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", fmt.Sprintf("token %s", githubToken))
	req.Header.Set("Content-type", contentType)
	req.Header.Set("Accept", "application/vnd.github.v3+json")
	req.ContentLength = bodySize

	if debug {
		log.Println("================ REQUEST DUMP ==================")
		dump, err := httputil.DumpRequestOut(req, true)
		if err != nil {
			log.Println(err.Error())
		}
		log.Println(string(dump[:]))
	}

	resp, err := http.DefaultClient.Do(req)

	if debug {
		log.Println("================ RESPONSE DUMP ==================")
		dump, err := httputil.DumpResponse(resp, true)
		if err != nil {
			log.Println(err.Error())
		}
		log.Println(string(dump[:]))
	}

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("Github returned an error:\n Code: %s. \n Body: %s", resp.Status, respBody)
	}

	return respBody, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"golang.org/x/net/html/charset"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"regexp"
	"syscall"
)

type songInfo struct {
	artist, title string
}

// TODO: probably should use string instead of []byte everywhere

func main() {
	home := os.Getenv("HOME")
	if home == "" {
		log.Fatal("HOME not found")
	}

	songinfo, err := getSongInfo()
	if err != nil {
		log.Fatal(err)
	}

	// TODO
	// artist and title can contain slashes
	dotDir := path.Join(home, ".show-lyrics")
	cacheDir := path.Join(dotDir, "cache")
	cacheArtistDir := path.Join(cacheDir, songinfo.artist)
	songFile := path.Join(cacheArtistDir, songinfo.title + ".txt")

	for _, dir := range []string{dotDir, cacheDir, cacheArtistDir} {
		err := mkdirUnlessExists(dir)
		if err != nil {
			log.Fatal(err)
		}
	}

	client := &http.Client{}

	lyrics, err := fetchLyrics(client, songinfo)
	if err != nil {
		log.Fatal(err)
	}

	lyrics = prepareLyrics(songinfo, lyrics)

	err = ioutil.WriteFile(songFile, lyrics, 0644)
	if err != nil {
		log.Fatal(err)
	}

	err = execLess(songFile)
	if err != nil {
		log.Fatal(err)
	}
}

func prepareLyrics(si *songInfo, lyrics []byte) []byte {
	title := si.prettyTitle()
	return []byte(title + "\n\n" + string(lyrics) + "\n")
}

func (si *songInfo) prettyTitle() string {
	return si.artist + " - " + si.title
}

func execLess(file string) error {
	lessBin, err := exec.LookPath("less")
	if err != nil {
		return err
	}
	err = syscall.Exec(lessBin, []string{"less", "-c", file}, os.Environ())
	if err != nil {
		return err
	}
	return nil
}

func getSongInfo() (*songInfo, error) {
	cmusStatus, err := getCmusStatus()
	if err != nil {
		return nil, err
	}
	songinfo, err := parseCmusStatus(cmusStatus)
	if err != nil {
		return nil, err
	}
	return songinfo, nil
}

func mkdirUnlessExists(dir string) error {
	_, err := os.Stat(dir)
	if err != nil {
		err = os.Mkdir(dir, 0755)
		if err != nil {
			return err
		}
	}
	return nil
}

func getCmusStatus() ([]byte, error) {
	cmd := exec.Command("cmus-remote", "-Q")
	return cmd.Output()
}

var artistRe = regexp.MustCompile(`(?m)^tag\s+artist\s+(.+)\s*$`)
var titleRe = regexp.MustCompile(`(?m)^tag\s+title\s+(.+)\s*$`)

func regexpMatch(re *regexp.Regexp, buf []byte) []byte {
	match := re.FindAllSubmatch(buf, 1)
	if len(match) > 0 {
		return match[0][1]
	}
	return nil
}

func parseCmusStatus(cmusStatus []byte) (*songInfo, error) {
	artist := regexpMatch(artistRe, cmusStatus)
	title := regexpMatch(titleRe, cmusStatus)

	if artist == nil || title == nil {
		return nil, errors.New("Failed to parse cmus status")
	}

	si := songInfo{
		artist: string(artist),
		title:  string(title),
	}

	return &si, nil
}

func makeURL(si *songInfo) string {
	artist := []byte(si.artist)
	title := []byte(si.title)

	theRe := regexp.MustCompile(`(?i)^the `)
	weirdRe := regexp.MustCompile(`(?i)[^a-z0-9]`)

	artist = theRe.ReplaceAll(artist, []byte{})

	artist = bytes.ToLower(artist)
	title = bytes.ToLower(title)

	for _, str := range []*[]byte{&artist, &title} {
		*str = bytes.ToLower(*str)
		*str = weirdRe.ReplaceAll(*str, []byte{})
	}

	url := "https://www.azlyrics.com/lyrics/"
	url += string(artist) + "/" + string(title) + ".html"

	return url
}

func fetchLyrics(client *http.Client, si *songInfo) ([]byte, error) {
	reqUrl := makeURL(si)

	req, err := http.NewRequest("GET", reqUrl, nil)
	if err != nil {
		return []byte{}, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return []byte{}, err
	}

	if resp.StatusCode != 200 {
		return []byte{}, errors.New(resp.Status)
	}

	utf8, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
	if err != nil {
		return []byte{}, err
	}
	body, err := ioutil.ReadAll(utf8)
	if err != nil {
		return []byte{}, err
	}

	lyrics, err := parseLyrics(body)
	if err != nil {
		return []byte{}, err
	}

	return lyrics, nil
}

func htmlStrip(html []byte) []byte {
	commentsRe := regexp.MustCompile(`(?s)<!--.*?-->`)
	brRe := regexp.MustCompile(`<br/?>`)

	html = commentsRe.ReplaceAll(html, []byte{})
	html = brRe.ReplaceAll(html, []byte{})
	html = bytes.TrimSpace(html)

	return html
}

func parseLyrics(lyricsHtml []byte) ([]byte, error) {
	re := regexp.MustCompile(
		`(?s)<div[^<>]*?class="lyricsh"[^<>]*?>.*?</div>\s*?` +
			`<div[^<>]*?>.*?</div>\s*` +
			`.*?` +
			`<div[^<>]*?>(.*?)</div>`)

	match := re.FindAllSubmatch(lyricsHtml, 1)
	if match == nil {
		return []byte{}, errors.New("Failed to parse html")
	}

	lyrics := htmlStrip(match[0][1])
	return lyrics, nil
}
<commit_msg>Read from cache<commit_after>package main

import (
	"bytes"
	"errors"
	"golang.org/x/net/html/charset"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"regexp"
	"syscall"
)

type songInfo struct {
	artist, title string
}

// TODO: probably should use string instead of []byte everywhere

func main() {
	home := os.Getenv("HOME")
	if home == "" {
		log.Fatal("HOME not found")
	}

	songinfo, err := getSongInfo()
	if err != nil {
		log.Fatal(err)
	}

	// TODO
	// artist and title can contain slashes
	dotDir := path.Join(home, ".show-lyrics")
	cacheDir := path.Join(dotDir, "cache")
	cacheArtistDir := path.Join(cacheDir, songinfo.artist)
	songFile := path.Join(cacheArtistDir, songinfo.title + ".txt")

	_, err = os.Stat(songFile)
	if err == nil {
		err := execLess(songFile)
		if err != nil {
			log.Fatal(err)
		}
	}

	for _, dir := range []string{dotDir, cacheDir, cacheArtistDir} {
		err := mkdirUnlessExists(dir)
		if err != nil {
			log.Fatal(err)
		}
	}

	client := &http.Client{}

	lyrics, err := fetchLyrics(client, songinfo)
	if err != nil {
		log.Fatal(err)
	}

	lyrics = prepareLyrics(songinfo, lyrics)

	err = ioutil.WriteFile(songFile, lyrics, 0644)
	if err != nil {
		log.Fatal(err)
	}

	err = execLess(songFile)
	if err != nil {
		log.Fatal(err)
	}
}

func prepareLyrics(si *songInfo, lyrics []byte) []byte {
	title := si.prettyTitle()
	return []byte(title + "\n\n" + string(lyrics) + "\n")
}

func (si *songInfo) prettyTitle() string {
	return si.artist + " - " + si.title
}

func execLess(file string) error {
	lessBin, err := exec.LookPath("less")
	if err != nil {
		return err
	}
	err = syscall.Exec(lessBin, []string{"less", "-c", file}, os.Environ())
	if err != nil {
		return err
	}
	return nil
}

func getSongInfo() (*songInfo, error) {
	cmusStatus, err := getCmusStatus()
	if err != nil {
		return nil, err
	}
	songinfo, err := parseCmusStatus(cmusStatus)
	if err != nil {
		return nil, err
	}
	return songinfo, nil
}

func mkdirUnlessExists(dir string) error {
	_, err := os.Stat(dir)
	if err != nil {
		err = os.Mkdir(dir, 0755)
		if err != nil {
			return err
		}
	}
	return nil
}

func getCmusStatus() ([]byte, error) {
	cmd := exec.Command("cmus-remote", "-Q")
	return cmd.Output()
}

var artistRe = regexp.MustCompile(`(?m)^tag\s+artist\s+(.+)\s*$`)
var titleRe = regexp.MustCompile(`(?m)^tag\s+title\s+(.+)\s*$`)

func regexpMatch(re *regexp.Regexp, buf []byte) []byte {
	match := re.FindAllSubmatch(buf, 1)
	if len(match) > 0 {
		return match[0][1]
	}
	return nil
}

func parseCmusStatus(cmusStatus []byte) (*songInfo, error) {
	artist := regexpMatch(artistRe, cmusStatus)
	title := regexpMatch(titleRe, cmusStatus)

	if artist == nil || title == nil {
		return nil, errors.New("Failed to parse cmus status")
	}

	si := songInfo{
		artist: string(artist),
		title:  string(title),
	}

	return &si, nil
}

func makeURL(si *songInfo) string {
	artist := []byte(si.artist)
	title := []byte(si.title)

	theRe := regexp.MustCompile(`(?i)^the `)
	weirdRe := regexp.MustCompile(`(?i)[^a-z0-9]`)

	artist = theRe.ReplaceAll(artist, []byte{})

	artist = bytes.ToLower(artist)
	title = bytes.ToLower(title)

	for _, str := range []*[]byte{&artist, &title} {
		*str = bytes.ToLower(*str)
		*str = weirdRe.ReplaceAll(*str, []byte{})
	}

	url := "https://www.azlyrics.com/lyrics/"
	url += string(artist) + "/" + string(title) + ".html"

	return url
}

func fetchLyrics(client *http.Client, si *songInfo) ([]byte, error) {
	reqUrl := makeURL(si)

	req, err := http.NewRequest("GET", reqUrl, nil)
	if err != nil {
		return []byte{}, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return []byte{}, err
	}

	if resp.StatusCode != 200 {
		return []byte{}, errors.New(resp.Status)
	}

	utf8, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
	if err != nil {
		return []byte{}, err
	}
	body, err := ioutil.ReadAll(utf8)
	if err != nil {
		return []byte{}, err
	}

	lyrics, err := parseLyrics(body)
	if err != nil {
		return []byte{}, err
	}

	return lyrics, nil
}

func htmlStrip(html []byte) []byte {
	commentsRe := regexp.MustCompile(`(?s)<!--.*?-->`)
	brRe := regexp.MustCompile(`<br/?>`)

	html = commentsRe.ReplaceAll(html, []byte{})
	html = brRe.ReplaceAll(html, []byte{})
	html = bytes.TrimSpace(html)

	return html
}

func parseLyrics(lyricsHtml []byte) ([]byte, error) {
	re := regexp.MustCompile(
		`(?s)<div[^<>]*?class="lyricsh"[^<>]*?>.*?</div>\s*?` +
			`<div[^<>]*?>.*?</div>\s*` +
			`.*?` +
			`<div[^<>]*?>(.*?)</div>`)

	match := re.FindAllSubmatch(lyricsHtml, 1)
	if match == nil {
		return []byte{}, errors.New("Failed to parse html")
	}

	lyrics := htmlStrip(match[0][1])
	return lyrics, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"net"
	"net/http"
	"os"
	"sync"
	"text/template"
	"time"

	"github.com/go-recaptcha/recaptcha"
	"github.com/gorilla/handlers"
	"github.com/nlopes/slack"
	"github.com/oxtoacart/bpool"
)

var captcha *recaptcha.Recaptcha
var api *slack.Client
var bufpool *bpool.BufferPool
var indexTemplate = template.Must(template.New("index.tmpl").ParseFiles("templates/index.tmpl"))

var captchaSitekey = flag.String("captchaSitekey", "REPLACEME", "reCaptcha Sitekey")
var captchaSecret = flag.String("captchaSecret", "REPLACEME", "reCaptcha Secret")
var slackToken = flag.String("slackToken", "REPLACEME", "Slack API Token")
var listenAddr = flag.String("listenAddr", ":8887", "Address to listen on")

// Slack statistics
var userCount int
var activeUserCount int
var statsMutex sync.RWMutex // Guards slack statistics variables

func init() {
	flag.Parse()
	// Init stuff
	captcha = recaptcha.New(*captchaSecret)
	api = slack.New(*slackToken)
	bufpool = bpool.NewBufferPool(64)
	go pollSlack()
}
func pollSlack() {
	for {
		users, err := api.GetUsers()
		if err != nil {
			log.Println("error polling slack for users:", err)
			continue
		}
		uCount := 0 // users
		aCount := 0 // active users
		for _, u := range users {
			if u.ID != "USLACKBOT" && !u.IsBot && !u.Deleted {
				uCount += 1
				if u.Presence == "active" {
					aCount += 1
				}
			}
		}
		statsMutex.Lock()
		userCount = uCount
		activeUserCount = aCount
		statsMutex.Unlock()
		time.Sleep(10 * time.Minute)
	}
}

// Homepage renders the homepage
func homepage(w http.ResponseWriter, r *http.Request) {
	statsMutex.RLock()
	data := map[string]interface{}{"SiteKey": *captchaSitekey, "UserCount": userCount, "ActiveCount": activeUserCount}
	statsMutex.RUnlock()
	buf := bufpool.Get()
	defer bufpool.Put(buf)
	err := indexTemplate.Execute(buf, data)
	if err != nil {
		log.Println("error rendering template:", err)
		http.Error(w, "error rendering template :-(", http.StatusInternalServerError)
		return
	}
	// Set the header and write the buffer to the http.ResponseWriter
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	buf.WriteTo(w)
}

// ShowPost renders a single post
func handleInvite(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}
	captchaResponse := r.FormValue("g-recaptcha-response")
	remoteIP, _, _ := net.SplitHostPort(r.RemoteAddr)

	valid, err := captcha.Verify(captchaResponse, remoteIP)
	if err != nil {
		http.Error(w, "Error validating recaptcha.. Did you click it?", http.StatusPreconditionFailed)
		return
	}
	if !valid {
		http.Error(w, "Invalid recaptcha", http.StatusInternalServerError)
		return

	}
	fname := r.FormValue("fname")
	lname := r.FormValue("lname")
	email := r.FormValue("email")
	coc := r.FormValue("coc")
	if fname == "" {
		http.Error(w, "Missing first name", http.StatusPreconditionFailed)
		return
	}
	if lname == "" {
		http.Error(w, "Missing last name", http.StatusPreconditionFailed)
		return
	}
	if email == "" {
		http.Error(w, "Missing email", http.StatusPreconditionFailed)
		return
	}
	if coc != "1" {
		http.Error(w, "You need to accept the code of conduct", http.StatusPreconditionFailed)
		return
	}
	err = api.InviteToTeam("Gophers", fname, lname, email)
	if err != nil {
		log.Println("InviteToTeam error:", err)
		http.Error(w, "Error inviting you :-(", http.StatusInternalServerError)
		return
	}
}

func main() {
	// Check if we are missing vital config values
	if *captchaSitekey == "REPLACEME" || *captchaSecret == "REPLACEME" || *slackToken == "REPLACEME" {
		log.Fatalln("Missing required input values")
	}
	log.SetPrefix("slackinvite:")
	mux := http.NewServeMux()
	mux.HandleFunc("/invite/", handleInvite)
	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
	mux.HandleFunc("/", homepage)
	log.Println("listening on port", *listenAddr)
	err := http.ListenAndServe(":"+*listenAddr, handlers.CombinedLoggingHandler(os.Stdout, mux))
	if err != nil {
		panic(err)
	}
}
<commit_msg>Add some expvars to start<commit_after>package main

import (
	"expvar"
	"flag"
	"log"
	"net"
	"net/http"
	"os"
	"sync"
	"text/template"
	"time"

	"github.com/go-recaptcha/recaptcha"
	"github.com/gorilla/handlers"
	"github.com/nlopes/slack"
	"github.com/oxtoacart/bpool"
)

var captcha *recaptcha.Recaptcha
var api *slack.Client
var bufpool *bpool.BufferPool
var indexTemplate = template.Must(template.New("index.tmpl").ParseFiles("templates/index.tmpl"))

var captchaSitekey = flag.String("captchaSitekey", "REPLACEME", "reCaptcha Sitekey")
var captchaSecret = flag.String("captchaSecret", "REPLACEME", "reCaptcha Secret")
var slackToken = flag.String("slackToken", "REPLACEME", "Slack API Token")
var listenAddr = flag.String("listenAddr", ":8887", "Address to listen on")

// Slack statistics
var userCount int
var activeUserCount int
var statsMutex sync.RWMutex // Guards slack statistics variables

var requests = expvar.NewInt("requests")
var inviteErrors = expvar.NewInt("invite_errors")
var missingFirstName = expvar.NewInt("missing_first_name")
var missingLastName = expvar.NewInt("missing_last_name")
var missingEmail = expvar.NewInt("missing_email")
var missingCoC = expvar.NewInt("missing_code_of_conduct")
var successfulCaptcha = expvar.NewInt("successful_captchas")
var failedCaptcha = expvar.NewInt("failed_captchas")
var invalidCaptcha = expvar.NewInt("invalid_captchas")

func init() {
	flag.Parse()
	// Init stuff
	captcha = recaptcha.New(*captchaSecret)
	api = slack.New(*slackToken)
	bufpool = bpool.NewBufferPool(64)
	go pollSlack()
}
func pollSlack() {
	for {
		users, err := api.GetUsers()
		if err != nil {
			log.Println("error polling slack for users:", err)
			continue
		}
		uCount := 0 // users
		aCount := 0 // active users
		for _, u := range users {
			if u.ID != "USLACKBOT" && !u.IsBot && !u.Deleted {
				uCount += 1
				if u.Presence == "active" {
					aCount += 1
				}
			}
		}
		statsMutex.Lock()
		userCount = uCount
		activeUserCount = aCount
		statsMutex.Unlock()
		time.Sleep(10 * time.Minute)
	}
}

// Homepage renders the homepage
func homepage(w http.ResponseWriter, r *http.Request) {
	statsMutex.RLock()
	data := map[string]interface{}{"SiteKey": *captchaSitekey, "UserCount": userCount, "ActiveCount": activeUserCount}
	statsMutex.RUnlock()
	buf := bufpool.Get()
	defer bufpool.Put(buf)
	err := indexTemplate.Execute(buf, data)
	if err != nil {
		log.Println("error rendering template:", err)
		http.Error(w, "error rendering template :-(", http.StatusInternalServerError)
		return
	}
	// Set the header and write the buffer to the http.ResponseWriter
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	buf.WriteTo(w)
}

// ShowPost renders a single post
func handleInvite(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}
	captchaResponse := r.FormValue("g-recaptcha-response")
	remoteIP, _, _ := net.SplitHostPort(r.RemoteAddr)

	valid, err := captcha.Verify(captchaResponse, remoteIP)
	if err != nil {
		http.Error(w, "Error validating recaptcha.. Did you click it?", http.StatusPreconditionFailed)
		failedCaptcha.Add(1)
		return
	}
	if !valid {
		http.Error(w, "Invalid recaptcha", http.StatusInternalServerError)
		invalidCaptcha.Add(1)
		return

	}
	fname := r.FormValue("fname")
	lname := r.FormValue("lname")
	email := r.FormValue("email")
	coc := r.FormValue("coc")
	if fname == "" {
		http.Error(w, "Missing first name", http.StatusPreconditionFailed)
		missingFirstName.Add(1)
		return
	}
	if lname == "" {
		http.Error(w, "Missing last name", http.StatusPreconditionFailed)
		missingLastName.Add(1)
		return
	}
	if email == "" {
		http.Error(w, "Missing email", http.StatusPreconditionFailed)
		missingEmail.Add(1)
		return
	}
	if coc != "1" {
		http.Error(w, "You need to accept the code of conduct", http.StatusPreconditionFailed)
		missingCoC.Add(1)
		return
	}
	err = api.InviteToTeam("Gophers", fname, lname, email)
	if err != nil {
		log.Println("InviteToTeam error:", err)
		inviteErrors.Add(1)
		http.Error(w, "Error inviting you :-(", http.StatusInternalServerError)
		return
	}
}

func main() {
	// Check if we are missing vital config values
	if *captchaSitekey == "REPLACEME" || *captchaSecret == "REPLACEME" || *slackToken == "REPLACEME" {
		log.Fatalln("Missing required input values")
	}
	log.SetPrefix("slackinvite:")
	mux := http.NewServeMux()
	mux.HandleFunc("/invite/", handleInvite)
	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
	mux.HandleFunc("/", homepage)
	log.Println("listening on port", *listenAddr)
	err := http.ListenAndServe(":"+*listenAddr, handlers.CombinedLoggingHandler(os.Stdout, mux))
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>// The MIT License (MIT)
//
// Copyright (c) 2015, Alexandr Emelin

// Package gocent is a Go language API client for Centrifugo real-time messaging server.
//
// Usage example
//
// In example below we initialize new client with server URL address, project key, project
// secret and request timeout. Then publish data into channel, call presence and history
// for channel and finally show how to publish several messages in one POST request to API
// endpoint using internal command buffer.
//
//  c := NewClient("http://localhost:8000", "development", "secret", 5*time.Second)
//  ok, err := c.Publish("$public:chat", []byte(`{"input": "test"}`))
//  if err != nil {
//  	println(err.Error())
//  	return
//  }
//  println(ok)
//  presence, _ := c.Presence("$public:chat")
//  fmt.Printf("%v", presence)
//  history, _ := c.History("$public:chat")
//  fmt.Printf("%v", history)
//  _ = c.AddPublish("$public:chat", []byte(`{"input": "test1"}`))
//  _ = c.AddPublish("$public:chat", []byte(`{"input": "test2"}`))
//  _ = c.AddPublish("$public:chat", []byte(`{"input": "test3"}`))
//  result, err := c.Send()
//  println(len(result))
//
package gocent // import "github.com/centrifugal/gocent"

import (
	"bytes"
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/centrifugal/centrifugo/libcentrifugo"
	"github.com/centrifugal/centrifugo/libcentrifugo/auth"
)

var (
	// ErrClientNotEmpty can be returned when client with non empty commands buffer
	// is used for single command send.
	ErrClientNotEmpty = errors.New("client command buffer not empty, send commands or reset client")
	// ErrMalformedResponse can be returned when server replied with invalid response.
	ErrMalformedResponse = errors.New("malformed response returned from server")
)

// Client is API client for project registered in server.
type Client struct {
	sync.RWMutex

	Endpoint string
	Key      string
	Secret   string
	Timeout  time.Duration
	cmds     []Command
}

// Command represents API command to send.
type Command struct {
	Method string                 `json:"method"`
	Params map[string]interface{} `json:"params"`
}

// Response is a response of server on command sent.
type Response struct {
	Method string
	Error  string
	Body   json.RawMessage
}

// Result is a slice of responses.
type Result []Response

// NewClient returns initialized client instance based on provided server address,
//project key, project secret and timeout.
func NewClient(addr, key, secret string, timeout time.Duration) *Client {

	addr = strings.TrimRight(addr, "/")
	if !strings.HasSuffix(addr, "/api") {
		addr = addr + "/api"
	}

	apiEndpoint := addr + "/" + key

	return &Client{
		Endpoint: apiEndpoint,
		Key:      key,
		Secret:   secret,
		Timeout:  timeout,
		cmds:     []Command{},
	}
}

func (c *Client) empty() bool {
	c.RLock()
	defer c.RUnlock()
	return len(c.cmds) == 0
}

// Reset allows to clear client command buffer.
func (c *Client) Reset() {
	c.Lock()
	defer c.Unlock()
	c.cmds = []Command{}
}

// AddPublish adds publish command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddPublish(channel string, data []byte) error {
	c.Lock()
	defer c.Unlock()
	var raw json.RawMessage
	raw = json.RawMessage(data)
	cmd := Command{
		Method: "publish",
		Params: map[string]interface{}{
			"channel": channel,
			"data":    &raw,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddUnsubscribe adds unsubscribe command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddUnsubscribe(channel string, user string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "unsubscribe",
		Params: map[string]interface{}{
			"channel": channel,
			"user":    user,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddDisconnect adds disconnect command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddDisconnect(user string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "disconnect",
		Params: map[string]interface{}{
			"user": user,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddPresence adds presence command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddPresence(channel string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "presence",
		Params: map[string]interface{}{
			"channel": channel,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddHistory adds history command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddHistory(channel string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "history",
		Params: map[string]interface{}{
			"channel": channel,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// Publish sends publish command to server and returns boolean indicator of success and
// any error occurred in process.
func (c *Client) Publish(channel string, data []byte) (bool, error) {
	if !c.empty() {
		return false, ErrClientNotEmpty
	}
	c.AddPublish(channel, data)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return false, err
	}
	resp := result[0]
	if resp.Error != "" {
		return false, errors.New(resp.Error)
	}
	return DecodePublish(resp.Body)
}

// Unsubscribe sends unsubscribe command to server and returns boolean indicator of success and
// any error occurred in process.
func (c *Client) Unsubscribe(channel, user string) (bool, error) {
	if !c.empty() {
		return false, ErrClientNotEmpty
	}
	c.AddUnsubscribe(channel, user)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return false, err
	}
	resp := result[0]
	if resp.Error != "" {
		return false, errors.New(resp.Error)
	}
	return DecodeUnsubscribe(resp.Body)
}

// Disconnect sends disconnect command to server and returns boolean indicator of success and
// any error occurred in process.
func (c *Client) Disconnect(user string) (bool, error) {
	if !c.empty() {
		return false, ErrClientNotEmpty
	}
	c.AddDisconnect(user)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return false, err
	}
	resp := result[0]
	if resp.Error != "" {
		return false, errors.New(resp.Error)
	}
	return DecodeDisconnect(resp.Body)
}

// Presence sends presence command for channel to server and returns map with client
// information and any error occurred in process.
func (c *Client) Presence(channel string) (map[libcentrifugo.ConnID]libcentrifugo.ClientInfo, error) {
	if !c.empty() {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, ErrClientNotEmpty
	}
	c.AddPresence(channel)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, err
	}
	resp := result[0]
	if resp.Error != "" {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, errors.New(resp.Error)
	}
	return DecodePresence(resp.Body)
}

// History sends history command for channel to server and returns slice with
// messages and any error occurred in process.
func (c *Client) History(channel string) ([]libcentrifugo.Message, error) {
	if !c.empty() {
		return []libcentrifugo.Message{}, ErrClientNotEmpty
	}
	c.AddHistory(channel)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return []libcentrifugo.Message{}, err
	}
	resp := result[0]
	if resp.Error != "" {
		return []libcentrifugo.Message{}, errors.New(resp.Error)
	}
	return DecodeHistory(resp.Body)
}

// DecodePublish allows to decode response body of publish command to get
// success flag from it. Currently no error in response means success - so nothing
// to do here yet.
func DecodePublish(body []byte) (bool, error) {
	return true, nil
}

// DecodeUnsubscribe allows to decode response body of unsubscribe command to get
// success flag from it. Currently no error in response means success - so nothing
// to do here yet.
func DecodeUnsubscribe(body []byte) (bool, error) {
	return true, nil
}

// DecodeDisconnect allows to decode response body of disconnect command to get
// success flag from it. Currently no error in response means success - so nothing
// to do here yet.
func DecodeDisconnect(body []byte) (bool, error) {
	return true, nil
}

// DecodeHistory allows to decode history response body to get a slice of messages.
func DecodeHistory(body []byte) ([]libcentrifugo.Message, error) {
	var d libcentrifugo.HistoryBody
	err := json.Unmarshal(body, &d)
	if err != nil {
		return []libcentrifugo.Message{}, err
	}
	return d.Data, nil
}

// DecodePresence allows to decode presence response body to get a map of clients.
func DecodePresence(body []byte) (map[libcentrifugo.ConnID]libcentrifugo.ClientInfo, error) {
	var d libcentrifugo.PresenceBody
	err := json.Unmarshal(body, &d)
	if err != nil {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, err
	}
	return d.Data, nil
}

// Send actually makes API POST request to server sending all buffered commands in
// one request. Using this method you should manually decode all responses in
// returned Result.
func (c *Client) Send() (Result, error) {
	cmds := c.cmds
	c.cmds = []Command{}
	result, err := c.send(cmds)
	if err != nil {
		return Result{}, err
	}
	if len(result) != len(cmds) {
		return Result{}, ErrMalformedResponse
	}
	return result, nil
}

func (c *Client) send(cmds []Command) (Result, error) {
	data, err := json.Marshal(cmds)
	if err != nil {
		return Result{}, err
	}

	client := &http.Client{}
	client.Timeout = c.Timeout
	r, err := http.NewRequest("POST", c.Endpoint, bytes.NewBuffer(data))
	if err != nil {
		return Result{}, err
	}

	r.Header.Set("X-API-Sign", auth.GenerateApiSign(c.Secret, c.Key, data))
	r.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(r)
	if err != nil {
		return Result{}, err
	}
	if resp.StatusCode != http.StatusOK {
		return Result{}, errors.New("wrong status code: " + resp.Status)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)

	var result Result
	err = json.Unmarshal(body, &result)
	return result, err
}
<commit_msg>channels command<commit_after>// The MIT License (MIT)
//
// Copyright (c) 2015, Alexandr Emelin

// Package gocent is a Go language API client for Centrifugo real-time messaging server.
//
// Usage example
//
// In example below we initialize new client with server URL address, project key, project
// secret and request timeout. Then publish data into channel, call presence and history
// for channel and finally show how to publish several messages in one POST request to API
// endpoint using internal command buffer.
//
//  c := NewClient("http://localhost:8000", "development", "secret", 5*time.Second)
//  ok, err := c.Publish("$public:chat", []byte(`{"input": "test"}`))
//  if err != nil {
//  	println(err.Error())
//  	return
//  }
//  println(ok)
//  presence, _ := c.Presence("$public:chat")
//  fmt.Printf("%v", presence)
//  history, _ := c.History("$public:chat")
//  fmt.Printf("%v", history)
//  _ = c.AddPublish("$public:chat", []byte(`{"input": "test1"}`))
//  _ = c.AddPublish("$public:chat", []byte(`{"input": "test2"}`))
//  _ = c.AddPublish("$public:chat", []byte(`{"input": "test3"}`))
//  result, err := c.Send()
//  println(len(result))
//
package gocent // import "github.com/centrifugal/gocent"

import (
	"bytes"
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/centrifugal/centrifugo/libcentrifugo"
	"github.com/centrifugal/centrifugo/libcentrifugo/auth"
)

var (
	// ErrClientNotEmpty can be returned when client with non empty commands buffer
	// is used for single command send.
	ErrClientNotEmpty = errors.New("client command buffer not empty, send commands or reset client")
	// ErrMalformedResponse can be returned when server replied with invalid response.
	ErrMalformedResponse = errors.New("malformed response returned from server")
)

// Client is API client for project registered in server.
type Client struct {
	sync.RWMutex

	Endpoint string
	Key      string
	Secret   string
	Timeout  time.Duration
	cmds     []Command
}

// Command represents API command to send.
type Command struct {
	Method string                 `json:"method"`
	Params map[string]interface{} `json:"params"`
}

// Response is a response of server on command sent.
type Response struct {
	Method string
	Error  string
	Body   json.RawMessage
}

// Result is a slice of responses.
type Result []Response

// NewClient returns initialized client instance based on provided server address,
//project key, project secret and timeout.
func NewClient(addr, key, secret string, timeout time.Duration) *Client {

	addr = strings.TrimRight(addr, "/")
	if !strings.HasSuffix(addr, "/api") {
		addr = addr + "/api"
	}

	apiEndpoint := addr + "/" + key

	return &Client{
		Endpoint: apiEndpoint,
		Key:      key,
		Secret:   secret,
		Timeout:  timeout,
		cmds:     []Command{},
	}
}

func (c *Client) empty() bool {
	c.RLock()
	defer c.RUnlock()
	return len(c.cmds) == 0
}

// Reset allows to clear client command buffer.
func (c *Client) Reset() {
	c.Lock()
	defer c.Unlock()
	c.cmds = []Command{}
}

// AddPublish adds publish command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddPublish(channel string, data []byte) error {
	c.Lock()
	defer c.Unlock()
	var raw json.RawMessage
	raw = json.RawMessage(data)
	cmd := Command{
		Method: "publish",
		Params: map[string]interface{}{
			"channel": channel,
			"data":    &raw,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddUnsubscribe adds unsubscribe command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddUnsubscribe(channel string, user string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "unsubscribe",
		Params: map[string]interface{}{
			"channel": channel,
			"user":    user,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddDisconnect adds disconnect command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddDisconnect(user string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "disconnect",
		Params: map[string]interface{}{
			"user": user,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddPresence adds presence command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddPresence(channel string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "presence",
		Params: map[string]interface{}{
			"channel": channel,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddHistory adds history command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddHistory(channel string) error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "history",
		Params: map[string]interface{}{
			"channel": channel,
		},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// AddChannels adds channels command to client command buffer but not actually
// send it until Send method explicitly called.
func (c *Client) AddChannels() error {
	c.Lock()
	defer c.Unlock()
	cmd := Command{
		Method: "channels",
		Params: map[string]string{},
	}
	c.cmds = append(c.cmds, cmd)
	return nil
}

// Publish sends publish command to server and returns boolean indicator of success and
// any error occurred in process.
func (c *Client) Publish(channel string, data []byte) (bool, error) {
	if !c.empty() {
		return false, ErrClientNotEmpty
	}
	c.AddPublish(channel, data)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return false, err
	}
	resp := result[0]
	if resp.Error != "" {
		return false, errors.New(resp.Error)
	}
	return DecodePublish(resp.Body)
}

// Unsubscribe sends unsubscribe command to server and returns boolean indicator of success and
// any error occurred in process.
func (c *Client) Unsubscribe(channel, user string) (bool, error) {
	if !c.empty() {
		return false, ErrClientNotEmpty
	}
	c.AddUnsubscribe(channel, user)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return false, err
	}
	resp := result[0]
	if resp.Error != "" {
		return false, errors.New(resp.Error)
	}
	return DecodeUnsubscribe(resp.Body)
}

// Disconnect sends disconnect command to server and returns boolean indicator of success and
// any error occurred in process.
func (c *Client) Disconnect(user string) (bool, error) {
	if !c.empty() {
		return false, ErrClientNotEmpty
	}
	c.AddDisconnect(user)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return false, err
	}
	resp := result[0]
	if resp.Error != "" {
		return false, errors.New(resp.Error)
	}
	return DecodeDisconnect(resp.Body)
}

// Presence sends presence command for channel to server and returns map with client
// information and any error occurred in process.
func (c *Client) Presence(channel string) (map[libcentrifugo.ConnID]libcentrifugo.ClientInfo, error) {
	if !c.empty() {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, ErrClientNotEmpty
	}
	c.AddPresence(channel)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, err
	}
	resp := result[0]
	if resp.Error != "" {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, errors.New(resp.Error)
	}
	return DecodePresence(resp.Body)
}

// History sends history command for channel to server and returns slice with
// messages and any error occurred in process.
func (c *Client) History(channel string) ([]libcentrifugo.Message, error) {
	if !c.empty() {
		return []libcentrifugo.Message{}, ErrClientNotEmpty
	}
	c.AddHistory(channel)
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return []libcentrifugo.Message{}, err
	}
	resp := result[0]
	if resp.Error != "" {
		return []libcentrifugo.Message{}, errors.New(resp.Error)
	}
	return DecodeHistory(resp.Body)
}

// Channels sends channels command to server and returns slice with
// active channels (with one or more subscribers) in project.
func (c *Client) Channels() ([]libcentrifugo.Channel, error) {
	if !c.empty() {
		return []libcentrifugo.Channel{}, ErrClientNotEmpty
	}
	c.AddChannels()
	c.Lock()
	defer c.Unlock()
	result, err := c.Send()
	if err != nil {
		return []libcentrifugo.Channel{}, err
	}
	resp := result[0]
	if resp.Error != "" {
		return []libcentrifugo.Channel{}, errors.New(resp.Error)
	}
	return DecodeChannels(resp.Body)
}

// DecodePublish allows to decode response body of publish command to get
// success flag from it. Currently no error in response means success - so nothing
// to do here yet.
func DecodePublish(body []byte) (bool, error) {
	return true, nil
}

// DecodeUnsubscribe allows to decode response body of unsubscribe command to get
// success flag from it. Currently no error in response means success - so nothing
// to do here yet.
func DecodeUnsubscribe(body []byte) (bool, error) {
	return true, nil
}

// DecodeDisconnect allows to decode response body of disconnect command to get
// success flag from it. Currently no error in response means success - so nothing
// to do here yet.
func DecodeDisconnect(body []byte) (bool, error) {
	return true, nil
}

// DecodeHistory allows to decode history response body to get a slice of messages.
func DecodeHistory(body []byte) ([]libcentrifugo.Message, error) {
	var d libcentrifugo.HistoryBody
	err := json.Unmarshal(body, &d)
	if err != nil {
		return []libcentrifugo.Message{}, err
	}
	return d.Data, nil
}

// DecodeChannels allows to decode channels command response body to get a slice of channels.
func DecodeChannels(body []byte) ([]libcentrifugo.Channel, error) {
	var d libcentrifugo.ChannelsBody
	err := json.Unmarshal(body, &d)
	if err != nil {
		return []libcentrifugo.Channel{}, err
	}
	return d.Data, nil
}

// DecodePresence allows to decode presence response body to get a map of clients.
func DecodePresence(body []byte) (map[libcentrifugo.ConnID]libcentrifugo.ClientInfo, error) {
	var d libcentrifugo.PresenceBody
	err := json.Unmarshal(body, &d)
	if err != nil {
		return map[libcentrifugo.ConnID]libcentrifugo.ClientInfo{}, err
	}
	return d.Data, nil
}

// Send actually makes API POST request to server sending all buffered commands in
// one request. Using this method you should manually decode all responses in
// returned Result.
func (c *Client) Send() (Result, error) {
	cmds := c.cmds
	c.cmds = []Command{}
	result, err := c.send(cmds)
	if err != nil {
		return Result{}, err
	}
	if len(result) != len(cmds) {
		return Result{}, ErrMalformedResponse
	}
	return result, nil
}

func (c *Client) send(cmds []Command) (Result, error) {
	data, err := json.Marshal(cmds)
	if err != nil {
		return Result{}, err
	}

	client := &http.Client{}
	client.Timeout = c.Timeout
	r, err := http.NewRequest("POST", c.Endpoint, bytes.NewBuffer(data))
	if err != nil {
		return Result{}, err
	}

	r.Header.Set("X-API-Sign", auth.GenerateApiSign(c.Secret, c.Key, data))
	r.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(r)
	if err != nil {
		return Result{}, err
	}
	if resp.StatusCode != http.StatusOK {
		return Result{}, errors.New("wrong status code: " + resp.Status)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)

	var result Result
	err = json.Unmarshal(body, &result)
	return result, err
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

const (
	HTTPS_TEMPLATE = `` +
		`  DNS Lookup   TCP Connection   SSL Handshake   Server Processing   Content Transfer` +
		`[   {a0000}  |     {a0001}    |    {a0002}    |      {a0003}      |      {a0004}     ]` +
		`             |                |               |                   |                  |` +
		`    namelookup:{b0000}        |               |                   |                  |` +
		`                        connect:{b0001}       |                   |                  |` +
		`                                    pretransfer:{b0002}           |                  |` +
		`                                                      starttransfer:{b0003}          |` +
		`                                                                                total:{b0004}`

	HTTP_TEMPLATE = `` +
		`   DNS Lookup   TCP Connection   Server Processing   Content Transfer` + "\n" +
		`[ %s  |     %s  |        %s  |       %s  ]` + "\n" +
		`             |                |                   |                  |` + "\n" +
		`    namelookup:%s      |                   |                  |` + "\n" +
		`                        connect:%s         |                  |` + "\n" +
		`                                      starttransfer:%s        |` + "\n" +
		`                                                                 total:%s` + "\n"
)

const ISATTY = true // TODO(dfc) make this respect the actual pty state

func makeColor(code int) func(string) string {
	if !ISATTY {
		return func(s string) string { return s }
	}
	return func(s string) string {
		return fmt.Sprintf("\x1b[%dm%s\x1b[0m", code, s)
	}
}

var (
	red       = makeColor(31)
	green     = makeColor(32)
	yellow    = makeColor(33)
	blue      = makeColor(34)
	magenta   = makeColor(35)
	cyan      = makeColor(36)
	bold      = makeColor(1)
	underline = makeColor(4)

	grayscale = func(code int) func(string) string {
		if !ISATTY {
			return func(s string) string { return s }
		}
		return func(s string) string {
			return fmt.Sprintf("\x1b[;38;5;%dm%s\x1b[0m", code+232, s)
		}
	}
)

func main() {
	flag.Parse()

	args := flag.Args()
	if len(args) != 1 {
		log.Fatalf("usage: %s URL", os.Args[0])
	}

	url, err := url.Parse(args[0])
	if err != nil {
		log.Fatalf("could not parse url %q: %v", args[0], err)
	}

	scheme := url.Scheme
	hostport := url.Host
	host, port := func() (string, int) {
		switch scheme {
		case "https":
			return hostport, 443
		case "http":
			return hostport, 80
		default:
			log.Fatalf("unsupported url scheme %q", scheme)
			return "", 0 // not reached
		}
	}()

	t0 := time.Now() // before dns resolution
	raddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		log.Fatalf("unable to resolve host: %v", err)
	}

	t1 := time.Now() // after dns resolution, before connect
	conn, err := net.DialTCP("tcp", nil, raddr)
	if err != nil {
		log.Fatalf("unable to connect to host %vv %v", raddr, err)
	}

	t2 := time.Now() // after connect, before request
	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		log.Fatalf("unable to create request: %v", err)
	}

	if err := req.Write(conn); err != nil {
		log.Fatalf("failed to write request: %v", err)
	}

	t3 := time.Now() // after request, before read response
	resp, err := http.ReadResponse(bufio.NewReader(conn), req)
	if err != nil {
		log.Fatalf("failed to read response: %v", err)
	}

	t4 := time.Now() // after read request, before read body
	if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
		log.Fatalf("failed to read response body: %v", err)
	}

	t5 := time.Now() // after read body
	resp.Body.Close()

	_ = t3

	// print status line and headers
	fmt.Printf("\n%s%s%s\n", green("HTTP"), grayscale(14)("/"), cyan(fmt.Sprintf("%d.%d", resp.ProtoMajor, resp.ProtoMinor)))

	for k, v := range resp.Header {
		fmt.Println(k+":", strings.Join(v, ","))
	}

	fmt.Println("\nBody discarded\n")

	fmta := func(d time.Duration) string {
		return cyan(fmt.Sprintf("%7dms", int(d/time.Millisecond)))
	}

	fmtb := func(d time.Duration) string {
		return cyan(fmt.Sprintf("%7dms", int(d/time.Millisecond)))
	}

	switch scheme {
	case "https":
	case "http":
		fmt.Printf(HTTP_TEMPLATE,
			fmta(t1.Sub(t0)), // dns lookup
			fmta(t2.Sub(t1)), // tcp connection
			fmta(t4.Sub(t2)), // server processing
			fmta(t5.Sub(t4)), // content transfer
			fmtb(t1.Sub(t0)), // namelookup
			fmtb(t2.Sub(t0)), // connect
			fmtb(t4.Sub(t0)), // starttransfer
			fmtb(t5.Sub(t0)), // total
		)

	}
}
<commit_msg>now with some colors<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

const (
	HTTPS_TEMPLATE = `` +
		`  DNS Lookup   TCP Connection   SSL Handshake   Server Processing   Content Transfer` +
		`[   {a0000}  |     {a0001}    |    {a0002}    |      {a0003}      |      {a0004}     ]` +
		`             |                |               |                   |                  |` +
		`    namelookup:{b0000}        |               |                   |                  |` +
		`                        connect:{b0001}       |                   |                  |` +
		`                                    pretransfer:{b0002}           |                  |` +
		`                                                      starttransfer:{b0003}          |` +
		`                                                                                total:{b0004}`

	HTTP_TEMPLATE = `` +
		`   DNS Lookup   TCP Connection   Server Processing   Content Transfer` + "\n" +
		`[ %s  |     %s  |        %s  |       %s  ]` + "\n" +
		`             |                |                   |                  |` + "\n" +
		`    namelookup:%s      |                   |                  |` + "\n" +
		`                        connect:%s         |                  |` + "\n" +
		`                                      starttransfer:%s        |` + "\n" +
		`                                                                 total:%s` + "\n"
)

const ISATTY = true // TODO(dfc) make this respect the actual pty state

func makeColor(code int) func(string) string {
	if !ISATTY {
		return func(s string) string { return s }
	}
	return func(s string) string {
		return fmt.Sprintf("\x1b[%dm%s\x1b[0m", code, s)
	}
}

var (
	red       = makeColor(31)
	green     = makeColor(32)
	yellow    = makeColor(33)
	blue      = makeColor(34)
	magenta   = makeColor(35)
	cyan      = makeColor(36)
	bold      = makeColor(1)
	underline = makeColor(4)

	grayscale = func(code int) func(string) string {
		if !ISATTY {
			return func(s string) string { return s }
		}
		return func(s string) string {
			return fmt.Sprintf("\x1b[;38;5;%dm%s\x1b[0m", code+232, s)
		}
	}
)

func main() {
	flag.Parse()

	args := flag.Args()
	if len(args) != 1 {
		log.Fatalf("usage: %s URL", os.Args[0])
	}

	url, err := url.Parse(args[0])
	if err != nil {
		log.Fatalf("could not parse url %q: %v", args[0], err)
	}

	scheme := url.Scheme
	hostport := url.Host
	host, port := func() (string, int) {
		switch scheme {
		case "https":
			return hostport, 443
		case "http":
			return hostport, 80
		default:
			log.Fatalf("unsupported url scheme %q", scheme)
			return "", 0 // not reached
		}
	}()

	t0 := time.Now() // before dns resolution
	raddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		log.Fatalf("unable to resolve host: %v", err)
	}

	t1 := time.Now() // after dns resolution, before connect
	conn, err := net.DialTCP("tcp", nil, raddr)
	if err != nil {
		log.Fatalf("unable to connect to host %vv %v", raddr, err)
	}

	t2 := time.Now() // after connect, before request
	req, err := http.NewRequest("GET", url.String(), nil)
	if err != nil {
		log.Fatalf("unable to create request: %v", err)
	}

	if err := req.Write(conn); err != nil {
		log.Fatalf("failed to write request: %v", err)
	}

	t3 := time.Now() // after request, before read response
	resp, err := http.ReadResponse(bufio.NewReader(conn), req)
	if err != nil {
		log.Fatalf("failed to read response: %v", err)
	}

	t4 := time.Now() // after read request, before read body
	if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
		log.Fatalf("failed to read response body: %v", err)
	}

	t5 := time.Now() // after read body
	resp.Body.Close()

	_ = t3

	// print status line and headers
	fmt.Printf("\n%s%s%s\n", green("HTTP"), grayscale(14)("/"), cyan(fmt.Sprintf("%d.%d %s", resp.ProtoMajor, resp.ProtoMinor, resp.Status)))

	for k, v := range resp.Header {
		fmt.Println(grayscale(14)(k+":"), cyan(strings.Join(v, ",")))
	}

	fmt.Println("\nBody discarded\n")

	fmta := func(d time.Duration) string {
		return cyan(fmt.Sprintf("%7dms", int(d/time.Millisecond)))
	}

	fmtb := func(d time.Duration) string {
		return cyan(fmt.Sprintf("%7dms", int(d/time.Millisecond)))
	}

	switch scheme {
	case "https":
	case "http":
		fmt.Printf(HTTP_TEMPLATE,
			fmta(t1.Sub(t0)), // dns lookup
			fmta(t2.Sub(t1)), // tcp connection
			fmta(t4.Sub(t2)), // server processing
			fmta(t5.Sub(t4)), // content transfer
			fmtb(t1.Sub(t0)), // namelookup
			fmtb(t2.Sub(t0)), // connect
			fmtb(t4.Sub(t0)), // starttransfer
			fmtb(t5.Sub(t0)), // total
		)

	}
}
<|endoftext|>


<commit_before>// elvish is an experimental Unix shell. It tries to incorporate a powerful
// programming language with an extensible, friendly user interface.
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"os/user"
	"syscall"

	"github.com/elves/elvish/edit"
	"github.com/elves/elvish/errutil"
	"github.com/elves/elvish/eval"
	"github.com/elves/elvish/logutil"
	"github.com/elves/elvish/osutil"
	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/store"
	"github.com/elves/elvish/sys"
)

const (
	sigchSize     = 32
	outChanSize   = 32
	outChanLeader = "▶ "
)

var Logger = logutil.Discard

func usage() {
	fmt.Println("usage: elvish [flags] [script]")
	fmt.Println("flags:")
	flag.PrintDefaults()
}

var (
	debuglog = flag.String("debuglog", "", "a file to write debug log to")
	help     = flag.Bool("help", false, "show usage help and quit")
)

func main() {
	defer rescue()

	flag.Usage = usage
	flag.Parse()

	if *help {
		usage()
		os.Exit(0)
	}

	if *debuglog != "" {
		f, err := os.OpenFile(*debuglog, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()

		Logger = log.New(f, "[main]", log.LstdFlags)
		eval.Logger = log.New(f, "[eval] ", log.LstdFlags)
		edit.Logger = log.New(f, "[edit] ", log.LstdFlags)
	}

	go dumpstackOnQuit()
	go logSignals()

	args := flag.Args()
	switch len(args) {
	case 0:
		interact()
	case 1:
		script(args[0])
	default:
		usage()
		os.Exit(2)
	}
}

func rescue() {
	r := recover()
	if r != nil {
		print(sys.DumpStack())
		println("\nexecing recovery shell /bin/sh")
		syscall.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ())
	}
}

// TODO(xiaq): Currently only the editor deals with signals.
func interact() {
	ev, st := newEvalerAndStore()
	datadir, err := store.EnsureDataDir()
	printError(err)
	if err == nil {
		// XXX
		err := ev.Source(datadir + "/rc.elv")
		if err != nil && !os.IsNotExist(err) {
			printError(err)
		}
	}

	cmdNum := 0

	username := "???"
	user, err := user.Current()
	if err == nil {
		username = user.Username
	}
	hostname, err := os.Hostname()
	if err != nil {
		hostname = "???"
	}
	rpromptStr := username + "@" + hostname

	sigch := make(chan os.Signal, sigchSize)
	signal.Notify(sigch)

	ed := edit.NewEditor(os.Stdin, sigch, ev, st)

	for {
		cmdNum++
		// name := fmt.Sprintf("<tty %d>", cmdNum)

		prompt := func() string {
			return osutil.Getwd() + "> "
		}
		rprompt := func() string {
			return rpromptStr
		}

		lr := ed.ReadLine(prompt, rprompt)
		// signal.Stop(sigch)

		if lr.EOF {
			break
		} else if lr.Err != nil {
			fmt.Println("Editor error:", lr.Err)
			fmt.Println("My pid is", os.Getpid())
		}

		n, err := parse.Parse(lr.Line)
		printError(err)

		if err == nil {
			err := ev.EvalInteractive(lr.Line, n)
			printError(err)
		}
	}
}

func logSignals() {
	sigs := make(chan os.Signal)
	signal.Notify(sigs)
	for sig := range sigs {
		Logger.Println("signal", sig)
	}
}

func dumpstackOnQuit() {
	quitSigs := make(chan os.Signal)
	signal.Notify(quitSigs, syscall.SIGQUIT)
	for range quitSigs {
		fmt.Print(sys.DumpStack())
	}
}

func script(fname string) {
	ev, _ := newEvalerAndStore()
	err := ev.Source(fname)
	if err != nil {
		printError(err)
		os.Exit(1)
	}
}

func newEvalerAndStore() (*eval.Evaler, *store.Store) {
	dataDir, err := store.EnsureDataDir()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Warning: cannot create data dir ~/.elvish")
	}

	var st *store.Store
	if err == nil {
		st, err = store.NewStore(dataDir)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Warning: cannot connect to store:", err)
		}
	}

	return eval.NewEvaler(st), st
}

func printError(err error) {
	if err == nil {
		return
	}
	switch err := err.(type) {
	case *errutil.ContextualError:
		fmt.Print(err.Pprint())
	case *errutil.Errors:
		for _, e := range err.Errors {
			printError(e)
		}
	default:
		eval.PprintError(err)
		fmt.Println()
	}
}
<commit_msg>Initial editor beforce load rc.elv<commit_after>// elvish is an experimental Unix shell. It tries to incorporate a powerful
// programming language with an extensible, friendly user interface.
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"os/user"
	"syscall"

	"github.com/elves/elvish/edit"
	"github.com/elves/elvish/errutil"
	"github.com/elves/elvish/eval"
	"github.com/elves/elvish/logutil"
	"github.com/elves/elvish/osutil"
	"github.com/elves/elvish/parse"
	"github.com/elves/elvish/store"
	"github.com/elves/elvish/sys"
)

const (
	sigchSize     = 32
	outChanSize   = 32
	outChanLeader = "▶ "
)

var Logger = logutil.Discard

func usage() {
	fmt.Println("usage: elvish [flags] [script]")
	fmt.Println("flags:")
	flag.PrintDefaults()
}

var (
	debuglog = flag.String("debuglog", "", "a file to write debug log to")
	help     = flag.Bool("help", false, "show usage help and quit")
)

func main() {
	defer rescue()

	flag.Usage = usage
	flag.Parse()

	if *help {
		usage()
		os.Exit(0)
	}

	if *debuglog != "" {
		f, err := os.OpenFile(*debuglog, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()

		Logger = log.New(f, "[main]", log.LstdFlags)
		eval.Logger = log.New(f, "[eval] ", log.LstdFlags)
		edit.Logger = log.New(f, "[edit] ", log.LstdFlags)
	}

	go dumpstackOnQuit()
	go logSignals()

	args := flag.Args()
	switch len(args) {
	case 0:
		interact()
	case 1:
		script(args[0])
	default:
		usage()
		os.Exit(2)
	}
}

func rescue() {
	r := recover()
	if r != nil {
		print(sys.DumpStack())
		println("\nexecing recovery shell /bin/sh")
		syscall.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ())
	}
}

// TODO(xiaq): Currently only the editor deals with signals.
func interact() {
	ev, st := newEvalerAndStore()

	sigch := make(chan os.Signal, sigchSize)
	signal.Notify(sigch)

	ed := edit.NewEditor(os.Stdin, sigch, ev, st)

	datadir, err := store.EnsureDataDir()
	printError(err)
	if err == nil {
		// XXX
		err := ev.Source(datadir + "/rc.elv")
		if err != nil && !os.IsNotExist(err) {
			printError(err)
		}
	}

	cmdNum := 0

	username := "???"
	user, err := user.Current()
	if err == nil {
		username = user.Username
	}
	hostname, err := os.Hostname()
	if err != nil {
		hostname = "???"
	}
	rpromptStr := username + "@" + hostname

	for {
		cmdNum++
		// name := fmt.Sprintf("<tty %d>", cmdNum)

		prompt := func() string {
			return osutil.Getwd() + "> "
		}
		rprompt := func() string {
			return rpromptStr
		}

		lr := ed.ReadLine(prompt, rprompt)
		// signal.Stop(sigch)

		if lr.EOF {
			break
		} else if lr.Err != nil {
			fmt.Println("Editor error:", lr.Err)
			fmt.Println("My pid is", os.Getpid())
		}

		n, err := parse.Parse(lr.Line)
		printError(err)

		if err == nil {
			err := ev.EvalInteractive(lr.Line, n)
			printError(err)
		}
	}
}

func logSignals() {
	sigs := make(chan os.Signal)
	signal.Notify(sigs)
	for sig := range sigs {
		Logger.Println("signal", sig)
	}
}

func dumpstackOnQuit() {
	quitSigs := make(chan os.Signal)
	signal.Notify(quitSigs, syscall.SIGQUIT)
	for range quitSigs {
		fmt.Print(sys.DumpStack())
	}
}

func script(fname string) {
	ev, _ := newEvalerAndStore()
	err := ev.Source(fname)
	if err != nil {
		printError(err)
		os.Exit(1)
	}
}

func newEvalerAndStore() (*eval.Evaler, *store.Store) {
	dataDir, err := store.EnsureDataDir()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Warning: cannot create data dir ~/.elvish")
	}

	var st *store.Store
	if err == nil {
		st, err = store.NewStore(dataDir)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Warning: cannot connect to store:", err)
		}
	}

	return eval.NewEvaler(st), st
}

func printError(err error) {
	if err == nil {
		return
	}
	switch err := err.(type) {
	case *errutil.ContextualError:
		fmt.Print(err.Pprint())
	case *errutil.Errors:
		for _, e := range err.Errors {
			printError(e)
		}
	default:
		eval.PprintError(err)
		fmt.Println()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"golang.org/x/crypto/ssh"
	"io/ioutil"
	"net"
	"net/http"
	"os"
)

const (
	sshPortEnv  = "SSH_PORT"
	httpPortEnv = "PORT"

	defaultSshPort  = "2022"
	defaultHttpPort = "3000"
)

func handler(conn net.Conn, gm *GameManager, config *ssh.ServerConfig) {
	// Before use, a handshake must be performed on the incoming
	// net.Conn.
	_, chans, reqs, err := ssh.NewServerConn(conn, config)
	if err != nil {
		panic("failed to handshake")
	}
	// The incoming Request channel must be serviced.
	go ssh.DiscardRequests(reqs)

	// Service the incoming Channel channel.
	for newChannel := range chans {
		// Channels have a type, depending on the application level
		// protocol intended. In the case of a shell, the type is
		// "session" and ServerShell may be used to present a simple
		// terminal interface.
		if newChannel.ChannelType() != "session" {
			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
			continue
		}
		channel, requests, err := newChannel.Accept()
		if err != nil {
			panic("could not accept channel.")
		}

		// Reject all out of band requests accept for the unix defaults, pty-req and
		// shell.
		go func(in <-chan *ssh.Request) {
			for req := range in {
				fmt.Println("New request:", req.Type)
				switch req.Type {
				case "pty-req":
					req.Reply(true, nil)
					continue
				case "shell":
					req.Reply(true, nil)
					continue
				}
				req.Reply(false, nil)
			}
		}(requests)

		fmt.Println("Received new connection")

		gm.HandleChannel <- channel
	}
}

func port(env, def string) string {
	var port string
	if os.Getenv(env) == "" {
		port = def
	}

	return fmt.Sprintf(":%s", port)
}

func main() {
	sshPort := port(sshPortEnv, defaultSshPort)
	httpPort := port(httpPortEnv, defaultHttpPort)

	// Everyone can login!
	config := &ssh.ServerConfig{
		NoClientAuth: true,
	}

	privateBytes, err := ioutil.ReadFile("id_rsa")
	if err != nil {
		panic("Failed to load private key")
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		panic("Failed to parse private key")
	}

	config.AddHostKey(private)

	// Create the GameManager
	gm := NewGameManager()
	go gm.Run()

	fmt.Printf(
		"Listening on port %s for SSH and port %s for HTTP...\n",
		sshPort,
		httpPort,
	)

	go func() {
		panic(http.ListenAndServe(httpPort, http.FileServer(http.Dir("./static/"))))
	}()

	// Once a ServerConfig has been configured, connections can be
	// accepted.
	listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0%s", sshPort))
	if err != nil {
		panic("failed to listen for connection")
	}

	for {
		nConn, err := listener.Accept()
		if err != nil {
			panic("failed to accept incoming connection")
		}

		go handler(nConn, gm, config)
	}
}
<commit_msg>Fix ports<commit_after>package main

import (
	"fmt"
	"golang.org/x/crypto/ssh"
	"io/ioutil"
	"net"
	"net/http"
	"os"
)

const (
	sshPortEnv  = "SSH_PORT"
	httpPortEnv = "PORT"

	defaultSshPort  = "2022"
	defaultHttpPort = "3000"
)

func handler(conn net.Conn, gm *GameManager, config *ssh.ServerConfig) {
	// Before use, a handshake must be performed on the incoming
	// net.Conn.
	_, chans, reqs, err := ssh.NewServerConn(conn, config)
	if err != nil {
		panic("failed to handshake")
	}
	// The incoming Request channel must be serviced.
	go ssh.DiscardRequests(reqs)

	// Service the incoming Channel channel.
	for newChannel := range chans {
		// Channels have a type, depending on the application level
		// protocol intended. In the case of a shell, the type is
		// "session" and ServerShell may be used to present a simple
		// terminal interface.
		if newChannel.ChannelType() != "session" {
			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
			continue
		}
		channel, requests, err := newChannel.Accept()
		if err != nil {
			panic("could not accept channel.")
		}

		// Reject all out of band requests accept for the unix defaults, pty-req and
		// shell.
		go func(in <-chan *ssh.Request) {
			for req := range in {
				fmt.Println("New request:", req.Type)
				switch req.Type {
				case "pty-req":
					req.Reply(true, nil)
					continue
				case "shell":
					req.Reply(true, nil)
					continue
				}
				req.Reply(false, nil)
			}
		}(requests)

		fmt.Println("Received new connection")

		gm.HandleChannel <- channel
	}
}

func port(env, def string) string {
	port := os.Getenv(env)
	if port == "" {
		port = def
	}

	return fmt.Sprintf(":%s", port)
}

func main() {
	sshPort := port(sshPortEnv, defaultSshPort)
	httpPort := port(httpPortEnv, defaultHttpPort)

	// Everyone can login!
	config := &ssh.ServerConfig{
		NoClientAuth: true,
	}

	privateBytes, err := ioutil.ReadFile("id_rsa")
	if err != nil {
		panic("Failed to load private key")
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		panic("Failed to parse private key")
	}

	config.AddHostKey(private)

	// Create the GameManager
	gm := NewGameManager()
	go gm.Run()

	fmt.Printf(
		"Listening on port %s for SSH and port %s for HTTP...\n",
		sshPort,
		httpPort,
	)

	go func() {
		panic(http.ListenAndServe(httpPort, http.FileServer(http.Dir("./static/"))))
	}()

	// Once a ServerConfig has been configured, connections can be
	// accepted.
	listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0%s", sshPort))
	if err != nil {
		panic("failed to listen for connection")
	}

	for {
		nConn, err := listener.Accept()
		if err != nil {
			panic("failed to accept incoming connection")
		}

		go handler(nConn, gm, config)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"io"
	"log"
	"net"
	"os"
	"strings"

	"github.com/armon/go-radix"
)

var bind = flag.String("bind", ":70", "Interface/port to bind to")
var root = flag.String("root", "/srv/gopher", "Root directory of server")

// This should be a pretty decent mapping of mimetypes to Gopher file types. I
// just gave /etc/mime.types a quick review on my machine and used my best
// judgement to map them to gopher's types. If nothing matches this list, it's
// treated as type '9'. This should probably be supplemented by something that
// checks the file extension.
var filetypes = map[string]string{
	"text/":                               "0",
	"application/gzip":                    "5",
	"application/java-archive":            "5",
	"application/rar":                     "5",
	"application/zip":                     "5",
	"application/x-cab":                   "5",
	"application/x-cbr":                   "5",
	"application/x-cbt":                   "5",
	"application/x-cbz":                   "5",
	"application/x-cpio":                  "5",
	"application/x-gtar":                  "5",
	"application/x-tar":                   "5",
	"application/x-xz":                    "5",
	"image/gif":                           "g",
	"text/html":                           "h",
	"application/xhtml+xml":               "h",
	"image/":                              "I",
	"application/msaccess":                "d",
	"application/msword":                  "d",
	"application/pdf":                     "d",
	"application/postscript":              "d",
	"application/rtf":                     "d",
	"application/vnd.ms-excel":            "d",
	"application/vnd.ms-powerpoint":       "d",
	"application/vnd.ms-word":             "d",
	"application/vnd.oasis.opendocument.": "d",
	"application/vnd.openxmlformats-officedocument.": "d",
	"application/x-dvi": "d",
	"text/rtf":          "d",
	"audio/":            "s",
	"video/":            ";",
	"text/calendar":     "c",
	"text/x-vcalendar":  "c",
	"application/mbox":  "M",
	"message/":          "M",
	"multipart/":        "M",
}

var ftPrefixes *radix.Tree

func init() {
	// I need to do this rather than just passing `filetypes` into
	// radix.NewFromMap, because the type checker fails with 'cannot use
	// map[string]string literal (type map[string]string) as type
	// map[string]interface {} in argument to radix.NewFromMap', which is
	// unfortunate.
	for prefix, ft := range filetypes {
		ftPrefixes.Insert(prefix, ft)
	}
}

func main() {
	flag.Parse()

	if _, err := os.Stat(*root); os.IsNotExist(err) {
		log.Fatalf("Root directory '%v' not found", *root)
	}

	ln, err := net.Listen("tcp", *bind)
	if err != nil {
		log.Fatal(err)
	}
	defer ln.Close()

	for {
		if conn, err := ln.Accept(); err != nil {
			log.Println(err)
		} else {
			go handleConnection(conn)
		}
	}
}

func handleConnection(conn net.Conn) {
	defer conn.Close()
	scanner := bufio.NewScanner(conn)
	if scanner.Scan() {
		// Format is <selector>TAB<query>CRLF
		parts := strings.SplitN(strings.TrimRight(scanner.Text(), "\r\n"), "\t", 2)
		log.Printf("%q", parts)
		conn.Write([]byte("iMessage\t\t\t\r\n"))
	} else if err := scanner.Err(); err != nil {
		log.Print(err)
	}
}
<commit_msg>Filetype mapping<commit_after>package main

import (
	"bufio"
	"flag"
	"io"
	"log"
	"mime"
	"net"
	"os"
	"path/filepath"
	"strings"

	"github.com/armon/go-radix"
)

var bind = flag.String("bind", ":70", "Interface/port to bind to")
var root = flag.String("root", "/srv/gopher", "Root directory of server")

// This should be a pretty decent mapping of mimetypes to Gopher file types. I
// just gave /etc/mime.types a quick review on my machine and used my best
// judgement to map them to gopher's types. If nothing matches this list, it's
// treated as type '9'. This should probably be supplemented by something that
// checks the file extension.
var filetypes = map[string]string{
	"text/":                               "0",
	"application/gzip":                    "5",
	"application/java-archive":            "5",
	"application/rar":                     "5",
	"application/zip":                     "5",
	"application/x-cab":                   "5",
	"application/x-cbr":                   "5",
	"application/x-cbt":                   "5",
	"application/x-cbz":                   "5",
	"application/x-cpio":                  "5",
	"application/x-gtar":                  "5",
	"application/x-tar":                   "5",
	"application/x-xz":                    "5",
	"image/gif":                           "g",
	"text/html":                           "h",
	"application/xhtml+xml":               "h",
	"image/":                              "I",
	"application/msaccess":                "d",
	"application/msword":                  "d",
	"application/pdf":                     "d",
	"application/postscript":              "d",
	"application/rtf":                     "d",
	"application/vnd.ms-excel":            "d",
	"application/vnd.ms-powerpoint":       "d",
	"application/vnd.ms-word":             "d",
	"application/vnd.oasis.opendocument.": "d",
	"application/vnd.openxmlformats-officedocument.": "d",
	"application/x-dvi": "d",
	"text/rtf":          "d",
	"audio/":            "s",
	"video/":            ";",
	"text/calendar":     "c",
	"text/x-vcalendar":  "c",
	"application/mbox":  "M",
	"message/":          "M",
	"multipart/":        "M",
}

var ftPrefixes *radix.Tree

func init() {
	// I need to do this rather than just passing `filetypes` into
	// radix.NewFromMap, because the type checker fails with 'cannot use
	// map[string]string literal (type map[string]string) as type
	// map[string]interface {} in argument to radix.NewFromMap', which is
	// unfortunate.
	for prefix, ft := range filetypes {
		ftPrefixes.Insert(prefix, ft)
	}
}

func main() {
	flag.Parse()

	if _, err := os.Stat(*root); os.IsNotExist(err) {
		log.Fatalf("Root directory '%v' not found", *root)
	}

	ln, err := net.Listen("tcp", *bind)
	if err != nil {
		log.Fatal(err)
	}
	defer ln.Close()

	for {
		if conn, err := ln.Accept(); err != nil {
			log.Println(err)
		} else {
			go handleConnection(conn)
		}
	}
}

func filenameToGopherType(filename string) string {
	mimetype := mime.TypeByExtension(filepath.Ext(filename))
	if ft, ok := ftPrefixes.Get(mimetype); ok {
		return ft.(string)
	} else {
		return "9"
	}
}

func handleConnection(conn net.Conn) {
	defer conn.Close()
	scanner := bufio.NewScanner(conn)
	if scanner.Scan() {
		// Format is <selector>TAB<query>CRLF
		parts := strings.SplitN(strings.TrimRight(scanner.Text(), "\r\n"), "\t", 2)
		log.Printf("%q", parts)
		conn.Write([]byte("iMessage\t\t\t\r\n"))
	} else if err := scanner.Err(); err != nil {
		log.Print(err)
	}
}
<|endoftext|>