<commit_before>// Command webhook starts a web server which listens on GitHub's POST requests.
// The payload of each request is verified against its signature, unmarshalled
// into corresponding event struct and the applied to the template script provided
// by a user.
//
// Usage
//
//   webhook [-cert file -key file] [-addr address] -secret key script
//
// The struct being passed to the template script is:
//
//   type Event struct {
//   	Name    string
//   	Payload interface{}
//   }
//
// The Name field denotes underlying type for the Payload. Full mapping between
// possible Name values and Payload types is listed in the documentation of
// the webhook package.
//
// Template scripts use template syntax of text/template package. Each template
// script has registered extra control functions:
//
//   env
//   	An alias for os.Getenv.
//   log
//   	An alias for log.Println. Used only for side-effect, returns empty string.
//   logf
//   	An alias for log.Printf. Used only for side-effect, returns empty string.
//   exec
//   	An alias for exec.Command. Returned value is the process' output read
//   	from its os.Stdout.
//
// Example
//
// In order to log an e-mail of each person that pushed to your repository, create
// a template script with the following content:
//
//   $ cat >push.tsc <<EOF
//   > {{if .Name | eq "push"}}
//   >   {{logf "%s pushed to %s" .Payload.Pusher.Email .Payload.Repository.Name}}
//   > {{endif}}
//   > EOF
//
// And start the webhook:
//
//   $ webhook -secret secret123 push.tsc
//   2015/03/13 21:32:15 INFO Listening on [::]:8080 . . .
//
// Webhook listens on 0.0.0.0:8080 by default.
//
// The -cert and -key flags are used to provide paths for the certificate and private
// key files. When specified, webhook serves HTTPS connections by default on 0.0.0.0:8443.
//
// The -addr flag can be used to specify a network address for the webhook to listen on.
//
// The -secret flag sets the secret value to verify the signature of GitHub's payloads.
// The value is required and cannot be empty.
//
// The script argument is a path to the template script file which is used as a handler
// for incoming events.
package main

import (
	"bytes"
	"crypto/rand"
	"crypto/tls"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"text/template"
	"time"

	"github.com/rjeczalik/gh/webhook"
)

const usage = `usage: webhook [-cert file -key file] [-addr address] -secret key script

Starts a web server which listens on GitHub's POST requests. The payload of each
request is verified against its signature, unmarshalled into corresponding event
struct and the applied to the template script provided by a user.

The struct being passed to the template script is:

	type Event struct {
		Name    string
		Payload interface{}
	}

The Name field denotes underlying type for the Payload. Full mapping between
possible Name values and Payload types is listed in the documentation of
the webhook package.

Template scripts use template syntax of text/template package. Each template
script has registered extra control functions:

	env
		An alias for os.Getenv.
	log
		An alias for log.Println. Used only for side-effect, returns empty string.
	logf
		An alias for log.Printf. Used only for side-effect, returns empty string.
	exec
		An alias for exec.Command. Returned value is the process' output read
		from its os.Stdout.

Example

In order to log an e-mail of each person that pushed to your repository, create
a template script with the following content:

	$ cat >push.tsc <EOF
	> {{if .Name eq "push"}}
	>   {{logf "%s pushed to %s" .Payload.Pusher.Email .Payload.Repository.Name}}
	> {{endif}}
	> EOF

And start the webhook:

	$ webhook -secret secret123 push.tsc
	2015/03/13 21:32:15 INFO Listening on [::]:8080 . . .

Webhook listens on 0.0.0.0:8080 by default.

The -cert and -key flags are used to provide paths for certificate and private
key files. When specified, webhook serves HTTPS connection by default on 0.0.0.0:8443.

The -addr flag can be used to specify a network address for the webhook to listen on.

The -secret flag sets the secret value to verify the signature of GitHub's payloads.
The value is required and cannot be empty.

The script argument is a path to the template script file which is used as a handler
for incoming events.`

var (
	cert   = flag.String("cert", "", "Certificate file.")
	key    = flag.String("key", "", "Private key file.")
	addr   = flag.String("addr", "", "Network address to listen on. Default is :8080 for HTTP and :8443 for HTTPS.")
	secret = flag.String("secret", "", "GitHub secret value used for signing payloads.")
	debug  = flag.Bool("debug", false, "Dumps verified payloads into testdata directory.")
)

type Event struct {
	Name    string      // https://developer.github.com/webhooks/#events
	Payload interface{} // https://developer.github.com/v3/activity/events/types/
}

var scriptFuncs = template.FuncMap{
	"env": func(s string) string {
		return os.Getenv(s)
	},
	"exec": func(cmd string, args ...string) (string, error) {
		out, err := exec.Command(cmd, args...).Output()
		if *debug {
			log.Printf("[DEBUG] exec cmd=%s args=%q err=%v", cmd, args, err)
		}
		return string(bytes.TrimSpace(out)), err
	},
	"log": func(v ...interface{}) string {
		log.Println(v...)
		return ""
	},
	"logf": func(format string, v ...interface{}) string {
		if len(v) == 0 {
			log.Printf("%s", format)
		} else {
			log.Printf(format, v...)
		}
		return ""
	},
}

type templater struct {
	tmpl *template.Template
}

func newTemplater(file string) (templater, error) {
	tmpl := template.New(filepath.Base(file)).Funcs(scriptFuncs)
	tmpl, err := tmpl.ParseFiles(flag.Arg(0))
	if err != nil {
		return templater{}, err
	}
	return templater{tmpl: tmpl}, nil
}

func (h templater) All(event string, payload interface{}) {
	if err := h.tmpl.Execute(ioutil.Discard, Event{Name: event, Payload: payload}); err != nil {
		log.Println("ERROR template error:", err)
		return<commit_msg>cmd/webhook: don't log empty lines<commit_after>// Command webhook starts a web server which listens on GitHub's POST requests.
// The payload of each request is verified against its signature, unmarshalled
// into corresponding event struct and the applied to the template script provided
// by a user.
//
// Usage
//
//   webhook [-cert file -key file] [-addr address] -secret key script
//
// The struct being passed to the template script is:
//
//   type Event struct {
//   	Name    string
//   	Payload interface{}
//   }
//
// The Name field denotes underlying type for the Payload. Full mapping between
// possible Name values and Payload types is listed in the documentation of
// the webhook package.
//
// Template scripts use template syntax of text/template package. Each template
// script has registered extra control functions:
//
//   env
//   	An alias for os.Getenv.
//   log
//   	An alias for log.Println. Used only for side-effect, returns empty string.
//   logf
//   	An alias for log.Printf. Used only for side-effect, returns empty string.
//   exec
//   	An alias for exec.Command. Returned value is the process' output read
//   	from its os.Stdout.
//
// Example
//
// In order to log an e-mail of each person that pushed to your repository, create
// a template script with the following content:
//
//   $ cat >push.tsc <<EOF
//   > {{if .Name | eq "push"}}
//   >   {{logf "%s pushed to %s" .Payload.Pusher.Email .Payload.Repository.Name}}
//   > {{endif}}
//   > EOF
//
// And start the webhook:
//
//   $ webhook -secret secret123 push.tsc
//   2015/03/13 21:32:15 INFO Listening on [::]:8080 . . .
//
// Webhook listens on 0.0.0.0:8080 by default.
//
// The -cert and -key flags are used to provide paths for the certificate and private
// key files. When specified, webhook serves HTTPS connections by default on 0.0.0.0:8443.
//
// The -addr flag can be used to specify a network address for the webhook to listen on.
//
// The -secret flag sets the secret value to verify the signature of GitHub's payloads.
// The value is required and cannot be empty.
//
// The script argument is a path to the template script file which is used as a handler
// for incoming events.
package main

import (
	"bytes"
	"crypto/rand"
	"crypto/tls"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"text/template"
	"time"

	"github.com/rjeczalik/gh/webhook"
)

const usage = `usage: webhook [-cert file -key file] [-addr address] -secret key script

Starts a web server which listens on GitHub's POST requests. The payload of each
request is verified against its signature, unmarshalled into corresponding event
struct and the applied to the template script provided by a user.

The struct being passed to the template script is:

	type Event struct {
		Name    string
		Payload interface{}
	}

The Name field denotes underlying type for the Payload. Full mapping between
possible Name values and Payload types is listed in the documentation of
the webhook package.

Template scripts use template syntax of text/template package. Each template
script has registered extra control functions:

	env
		An alias for os.Getenv.
	log
		An alias for log.Println. Used only for side-effect, returns empty string.
	logf
		An alias for log.Printf. Used only for side-effect, returns empty string.
	exec
		An alias for exec.Command. Returned value is the process' output read
		from its os.Stdout.

Example

In order to log an e-mail of each person that pushed to your repository, create
a template script with the following content:

	$ cat >push.tsc <EOF
	> {{if .Name eq "push"}}
	>   {{logf "%s pushed to %s" .Payload.Pusher.Email .Payload.Repository.Name}}
	> {{endif}}
	> EOF

And start the webhook:

	$ webhook -secret secret123 push.tsc
	2015/03/13 21:32:15 INFO Listening on [::]:8080 . . .

Webhook listens on 0.0.0.0:8080 by default.

The -cert and -key flags are used to provide paths for certificate and private
key files. When specified, webhook serves HTTPS connection by default on 0.0.0.0:8443.

The -addr flag can be used to specify a network address for the webhook to listen on.

The -secret flag sets the secret value to verify the signature of GitHub's payloads.
The value is required and cannot be empty.

The script argument is a path to the template script file which is used as a handler
for incoming events.`

var (
	cert   = flag.String("cert", "", "Certificate file.")
	key    = flag.String("key", "", "Private key file.")
	addr   = flag.String("addr", "", "Network address to listen on. Default is :8080 for HTTP and :8443 for HTTPS.")
	secret = flag.String("secret", "", "GitHub secret value used for signing payloads.")
	debug  = flag.Bool("debug", false, "Dumps verified payloads into testdata directory.")
)

type Event struct {
	Name    string      // https://developer.github.com/webhooks/#events
	Payload interface{} // https://developer.github.com/v3/activity/events/types/
}

var scriptFuncs = template.FuncMap{
	"env": func(s string) string {
		return os.Getenv(s)
	},
	"exec": func(cmd string, args ...string) (string, error) {
		out, err := exec.Command(cmd, args...).Output()
		if *debug {
			log.Printf("[DEBUG] exec cmd=%s args=%q err=%v", cmd, args, err)
		}
		return string(bytes.TrimSpace(out)), err
	},
	"log": func(v ...interface{}) string {
		if len(v) != 0 {
			log.Println(v...)
		}
		return ""
	},
	"logf": func(format string, v ...interface{}) string {
		if format == "" {
			return ""
		}
		if len(v) == 0 {
			log.Printf("%s", format)
		} else {
			log.Printf(format, v...)
		}
		return ""
	},
}

type templater struct {
	tmpl *template.Template
}

func newTemplater(file string) (templater, error) {
	tmpl := template.New(filepath.Base(file)).Funcs(scriptFuncs)
	tmpl, err := tmpl.ParseFiles(flag.Arg(0))
	if err != nil {
		return templater{}, err
	}
	return templater{tmpl: tmpl}, nil
}

func (h templater) All(event string, payload interface{}) {
	if err := h.tmpl.Execute(ioutil.Discard, Event{Name: event, Payload: payload}); err != nil {
		log.Println("ERROR template error:", err)
		return
{'fraction_non_alphanumeric': 0.11595516729850008, 'fraction_numerical': 0.014092632272952035, 'mean_word_length': 3.240041928721174, 'pattern_counts': {'":': 8, '<': 9, '<?xml version=': 0, '>': 23, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 8, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5141467', 'n_tokens_mistral': 3896, 'n_tokens_neox': 3718, 'n_words': 1545}