<commit_before>package cucumberexpressions

import (
	"fmt"
	"github.com/stretchr/testify/require"
	"reflect"
	"testing"
)

func TestCucumberExpression(t *testing.T) {
	t.Run("documents expression generation", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()

		/// [capture-match-arguments]
		expr := "I have {int} cuke(s)"
		expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
		require.NoError(t, err)
		args, err := expression.Match("I have 7 cukes")
		require.NoError(t, err)
		require.Equal(t, args[0].GetValue(), 7)
		/// [capture-match-arguments]
	})

	t.Run("matches word", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {word} mice", "three blind mice"),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches double quoted string", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "blind" mice`),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches multiple double quoted strings", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} and {string} mice", `three "blind" and "crippled" mice`),
			[]interface{}{"blind", "crippled"},
		)
	})

	t.Run("matches single quoted string", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three 'blind' mice`),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches multiple single quoted strings", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} and {string} mice", `three 'blind' and 'crippled' mice`),
			[]interface{}{"blind", "crippled"},
		)
	})

	t.Run("does not match misquoted string", func(t *testing.T) {
		require.Nil(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "blind' mice`),
		)
	})

	t.Run("matches single quoted strings with double quotes", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three '"blind"' mice`),
			[]interface{}{`"blind"`},
		)
	})

	t.Run("matches double quoted strings with single quotes", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "'blind'" mice`),
			[]interface{}{`'blind'`},
		)
	})

	t.Run("matches double quoted string with escaped double quote", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "bl\"nd" mice`),
			[]interface{}{`bl\"nd`},
		)
	})

	t.Run("matches single quoted string with escaped single quote", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three 'bl\'nd' mice`),
			[]interface{}{`bl\'nd`},
		)
	})

	t.Run("matches escaped parenthesis", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three \\\\(exceptionally) {string} mice", `three (exceptionally) "blind" mice`),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches escaped slash", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "12\\\\/2020", `12/2020`),
			[]interface{}{},
		)
	})

	t.Run("matches int", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "{int}", "22"),
			[]interface{}{22},
		)
	})

	t.Run("doesn't match float as int", func(t *testing.T) {
		require.Nil(
			t,
			MatchCucumberExpression(t, "{int}", "1.22"),
		)
	})

	t.Run("matches float", func(t *testing.T) {
		require.Equal(t, MatchCucumberExpression(t, "{float}", ""), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "."), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", ","), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "E"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", ",1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1."), []interface{}(nil))

		require.Equal(t, MatchCucumberExpression(t, "{float}", "1"), []interface{}{1.0})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-1"), []interface{}{-1.0})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1.1"), []interface{}{1.1})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000,0"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000.1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000,10"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,0.1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000,000.1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-1.1"), []interface{}{-1.1})

		require.Equal(t, MatchCucumberExpression(t, "{float}", ".1"), []interface{}{0.1})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1"), []interface{}{-0.1})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.10000001"), []interface{}{-0.10000001})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1e1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", ".1E1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "E1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E-1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E-2"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E+1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E+2"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.10E2"), []interface{}(nil))
	})

	t.Run("matches float with zero", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "{float}", "0"),
			[]interface{}{0.0},
		)
		require.Equal(
			t,
			MatchCucumberExpression(t, "{float}", "-1"),
			[]interface{}{-1.0},
		)
	})

	t.Run("matches anonymous", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "{}", ".22", reflect.TypeOf(float64(0))),
			[]interface{}{float64(0.22)},
		)
		require.Equal(
			t,
			MatchCucumberExpression(t, "{}", "0.22", reflect.TypeOf(float64(0))),
			[]interface{}{float64(0.22)},
		)
	})

	t.Run("does not allow parameter type with left bracket", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("{[string]}", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, err.Error(), "illegal character '[' in parameter name {[string]}")
	})

	t.Run("does not allow optional parameter types", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("({int})", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, "Parameter types cannot be optional: ({int})", err.Error())
	})

	t.Run("allows escaped optional parameters", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "\\\\({int})", `(3)`),
			[]interface{}{3},
		)
	})

	t.Run("does not allow text/parameter type alternation", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("x/{int}", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, "Parameter types cannot be alternative: x/{int}", err.Error())
	})

	t.Run("does not allow parameter type/text alternation", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("{int}/x", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, "Parameter types cannot be alternative: {int}/x", err.Error())
	})

	t.Run("returns error for unknown parameter", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("{unknown}", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, err.Error(), "Undefined parameter type {unknown}")
	})

	t.Run("exposes source", func(t *testing.T) {
		expr := "I have {int} cuke(s)"
		parameterTypeRegistry := NewParameterTypeRegistry()
		expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
		require.NoError(t, err)
		require.Equal(t, expression.Source(), expr)
	})

	t.Run("escapes special characters", func(t *testing.T) {
		for _, char := range []string{"\\", "[", "]", "^", "$", ".", "|", "?", "*", "+"} {
			t.Run(fmt.Sprintf("escapes %s", char), func(t *testing.T) {
				require.Equal(
					t,
					MatchCucumberExpression(
						t,
						fmt.Sprintf("I have {int} cuke(s) and %s", char),
						fmt.Sprintf("I have 800 cukes and %s", char),
					),
					[]interface{}{800},
				)
			})
		}

		t.Run("escapes .", func(t *testing.T) {
			expr := "I have {int} cuke(s) and ."
			parameterTypeRegistry := NewParameterTypeRegistry()
			expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
			require.NoError(t, err)
			args, err := expression.Match("I have 800 cukes and 3")
			require.NoError(t, err)
			require.Nil(t, args)
			args, err = expression.Match("I have 800 cukes and .")
			require.NoError(t, err)
			require.NotNil(t, args)
		})

		t.Run("escapes |", func(t *testing.T) {
			expr := "I have {int} cuke(s) and a|b"
			parameterTypeRegistry := NewParameterTypeRegistry()
			expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
			require.NoError(t, err)
			args, err := expression.Match("I have 800 cukes and a")
			require.NoError(t, err)
			require.Nil(t, args)
			args, err = expression.Match("I have 800 cukes and b")
			require.NoError(t, err)
			require.Nil(t, args)
			args, err = expression.Match("I have 800 cukes and a|b")
			require.NoError(t, err)
			require.NotNil(t, args)
		})
	})
}

func MatchCucumberExpression(t *testing.T, expr string, text string, typeHints ...reflect.Type) []interface{} {
	parameterTypeRegistry := NewParameterTypeRegistry()
	expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
	require.NoError(t, err)
	args, err := expression.Match(text, typeHints...)
	require.NoError(t, err)
	if args == nil {
		return nil
	}
	result := make([]interface{}, len(args))
	for i, arg := range args {
		result[i] = arg.GetValue()
	}
	return result
}
<commit_msg>add tests to prove empty strings work in go impl<commit_after>package cucumberexpressions

import (
	"fmt"
	"github.com/stretchr/testify/require"
	"reflect"
	"testing"
)

func TestCucumberExpression(t *testing.T) {
	t.Run("documents expression generation", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()

		/// [capture-match-arguments]
		expr := "I have {int} cuke(s)"
		expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
		require.NoError(t, err)
		args, err := expression.Match("I have 7 cukes")
		require.NoError(t, err)
		require.Equal(t, args[0].GetValue(), 7)
		/// [capture-match-arguments]
	})

	t.Run("matches word", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {word} mice", "three blind mice"),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches double quoted string", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "blind" mice`),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches multiple double quoted strings", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} and {string} mice", `three "blind" and "crippled" mice`),
			[]interface{}{"blind", "crippled"},
		)
	})

	t.Run("matches single quoted string", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three 'blind' mice`),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches multiple single quoted strings", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} and {string} mice", `three 'blind' and 'crippled' mice`),
			[]interface{}{"blind", "crippled"},
		)
	})

	t.Run("does not match misquoted string", func(t *testing.T) {
		require.Nil(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "blind' mice`),
		)
	})

	t.Run("matches single quoted strings with double quotes", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three '"blind"' mice`),
			[]interface{}{`"blind"`},
		)
	})

	t.Run("matches double quoted strings with single quotes", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "'blind'" mice`),
			[]interface{}{`'blind'`},
		)
	})

	t.Run("matches double quoted string with escaped double quote", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "bl\"nd" mice`),
			[]interface{}{`bl\"nd`},
		)
	})

	t.Run("matches single quoted string with escaped single quote", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three 'bl\'nd' mice`),
			[]interface{}{`bl\'nd`},
		)
	})

	t.Run("matches single quoted empty string as empty string", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three '' mice`),
			[]interface{}{""},
		)
	})

	t.Run("matches double quoted empty string as empty string", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} mice", `three "" mice`),
			[]interface{}{""},
		)
	})

	t.Run("matches single quoted empty string as empty string along with other strings", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} and {string} mice", `three '' and 'handsome' mice`),
			[]interface{}{"", "handsome"},
		)
	})

	t.Run("matches double quoted empty string as empty string along with other strings", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three {string} and {string} mice", `three "" and "handsome" mice`),
			[]interface{}{"", "handsome"},
		)
	})

	t.Run("matches escaped parenthesis", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "three \\\\(exceptionally) {string} mice", `three (exceptionally) "blind" mice`),
			[]interface{}{"blind"},
		)
	})

	t.Run("matches escaped slash", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "12\\\\/2020", `12/2020`),
			[]interface{}{},
		)
	})

	t.Run("matches int", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "{int}", "22"),
			[]interface{}{22},
		)
	})

	t.Run("doesn't match float as int", func(t *testing.T) {
		require.Nil(
			t,
			MatchCucumberExpression(t, "{int}", "1.22"),
		)
	})

	t.Run("matches float", func(t *testing.T) {
		require.Equal(t, MatchCucumberExpression(t, "{float}", ""), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "."), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", ","), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "E"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", ",1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1."), []interface{}(nil))

		require.Equal(t, MatchCucumberExpression(t, "{float}", "1"), []interface{}{1.0})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-1"), []interface{}{-1.0})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1.1"), []interface{}{1.1})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000,0"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000.1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000,10"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,0.1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1,000,000.1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-1.1"), []interface{}{-1.1})

		require.Equal(t, MatchCucumberExpression(t, "{float}", ".1"), []interface{}{0.1})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1"), []interface{}{-0.1})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.10000001"), []interface{}{-0.10000001})
		require.Equal(t, MatchCucumberExpression(t, "{float}", "1e1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", ".1E1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "E1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E-1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E-2"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E+1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E+2"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.1E1"), []interface{}(nil))
		require.Equal(t, MatchCucumberExpression(t, "{float}", "-.10E2"), []interface{}(nil))
	})

	t.Run("matches float with zero", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "{float}", "0"),
			[]interface{}{0.0},
		)
		require.Equal(
			t,
			MatchCucumberExpression(t, "{float}", "-1"),
			[]interface{}{-1.0},
		)
	})

	t.Run("matches anonymous", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "{}", ".22", reflect.TypeOf(float64(0))),
			[]interface{}{float64(0.22)},
		)
		require.Equal(
			t,
			MatchCucumberExpression(t, "{}", "0.22", reflect.TypeOf(float64(0))),
			[]interface{}{float64(0.22)},
		)
	})

	t.Run("does not allow parameter type with left bracket", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("{[string]}", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, err.Error(), "illegal character '[' in parameter name {[string]}")
	})

	t.Run("does not allow optional parameter types", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("({int})", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, "Parameter types cannot be optional: ({int})", err.Error())
	})

	t.Run("allows escaped optional parameters", func(t *testing.T) {
		require.Equal(
			t,
			MatchCucumberExpression(t, "\\\\({int})", `(3)`),
			[]interface{}{3},
		)
	})

	t.Run("does not allow text/parameter type alternation", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("x/{int}", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, "Parameter types cannot be alternative: x/{int}", err.Error())
	})

	t.Run("does not allow parameter type/text alternation", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("{int}/x", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, "Parameter types cannot be alternative: {int}/x", err.Error())
	})

	t.Run("returns error for unknown parameter", func(t *testing.T) {
		parameterTypeRegistry := NewParameterTypeRegistry()
		_, err := NewCucumberExpression("{unknown}", parameterTypeRegistry)
		require.Error(t, err)
		require.Equal(t, err.Error(), "Undefined parameter type {unknown}")
	})

	t.Run("exposes source", func(t *testing.T) {
		expr := "I have {int} cuke(s)"
		parameterTypeRegistry := NewParameterTypeRegistry()
		expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
		require.NoError(t, err)
		require.Equal(t, expression.Source(), expr)
	})

	t.Run("escapes special characters", func(t *testing.T) {
		for _, char := range []string{"\\", "[", "]", "^", "$", ".", "|", "?", "*", "+"} {
			t.Run(fmt.Sprintf("escapes %s", char), func(t *testing.T) {
				require.Equal(
					t,
					MatchCucumberExpression(
						t,
						fmt.Sprintf("I have {int} cuke(s) and %s", char),
						fmt.Sprintf("I have 800 cukes and %s", char),
					),
					[]interface{}{800},
				)
			})
		}

		t.Run("escapes .", func(t *testing.T) {
			expr := "I have {int} cuke(s) and ."
			parameterTypeRegistry := NewParameterTypeRegistry()
			expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
			require.NoError(t, err)
			args, err := expression.Match("I have 800 cukes and 3")
			require.NoError(t, err)
			require.Nil(t, args)
			args, err = expression.Match("I have 800 cukes and .")
			require.NoError(t, err)
			require.NotNil(t, args)
		})

		t.Run("escapes |", func(t *testing.T) {
			expr := "I have {int} cuke(s) and a|b"
			parameterTypeRegistry := NewParameterTypeRegistry()
			expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
			require.NoError(t, err)
			args, err := expression.Match("I have 800 cukes and a")
			require.NoError(t, err)
			require.Nil(t, args)
			args, err = expression.Match("I have 800 cukes and b")
			require.NoError(t, err)
			require.Nil(t, args)
			args, err = expression.Match("I have 800 cukes and a|b")
			require.NoError(t, err)
			require.NotNil(t, args)
		})
	})
}

func MatchCucumberExpression(t *testing.T, expr string, text string, typeHints ...reflect.Type) []interface{} {
	parameterTypeRegistry := NewParameterTypeRegistry()
	expression, err := NewCucumberExpression(expr, parameterTypeRegistry)
	require.NoError(t, err)
	args, err := expression.Match(text, typeHints...)
	require.NoError(t, err)
	if args == nil {
		return nil
	}
	result := make([]interface{}, len(args))
	for i, arg := range args {
		result[i] = arg.GetValue()
	}
	return result
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"./terminal"
)

const VERSION = "0.0.1"

const (
	CTRL_Q      = 0x11
	ARROW_LEFT  = 1000
	ARROW_RIGHT = 1001
	ARROW_UP    = 1002
	ARROW_DOWN  = 1003
	PAGE_UP     = 1004
	PAGE_DOWN   = 1005
	HOME        = 1006
	END         = 1007
	DELETE      = 1008
)

type Row struct {
	Text string
}

type Editor struct {
	Reader     *bufio.Reader
	ScreenRows int
	ScreenCols int
	EditRows   int // actual number of rows used for text editing
	EditCols   int
	CursorRow  int
	CursorCol  int
	Message    string // status message
	Rows       []Row
	RowOffset  int
	ColOffset  int
}

func NewEditor() *Editor {
	e := &Editor{}
	e.Reader = bufio.NewReader(os.Stdin)
	e.Rows = make([]Row, 0)
	return e
}

func (e *Editor) ReadFile(path string) error {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	s := string(b)
	lines := strings.Split(s, "\n")
	e.Rows = make([]Row, 0)
	for _, line := range lines {
		e.Rows = append(e.Rows, Row{Text: line})
	}
	return nil
}

func (e *Editor) ReadKey() int {
	var err error
	b := make([]byte, 10)
	n := 0
	for n == 0 {
		n, _ = os.Stdin.Read(b)
		if n == 0 {
			time.Sleep(time.Microsecond)
		}
	}
	//e.Message = fmt.Sprintf(" code=%02x", b[0:n])
	if err != nil {
		fmt.Print(err)
	}
	switch b[0] {
	case 0x1b:
		switch b[1] {
		case 0x5b:
			switch b[2] {
			case 'A':
				return ARROW_UP
			case 'B':
				return ARROW_DOWN
			case 'C':
				return ARROW_RIGHT
			case 'D':
				return ARROW_LEFT
			case 0x31:
				switch b[3] {
				case 0x7e:
					return HOME
				}
			case 0x33:
				switch b[3] {
				case 0x7e:
					return DELETE
				}
			case 0x34:
				switch b[3] {
				case 0x7e:
					return END
				}
			case 0x35:
				switch b[3] {
				case 0x7e:
					return PAGE_UP
				}
			case 0x36:
				switch b[3] {
				case 0x7e:
					return PAGE_DOWN
				}
			}
		}
	}
	return int(b[0])
}

func (e *Editor) ProcessKeyPress() error {
	key := e.ReadKey()
	//e.Message += fmt.Sprintf(" key=%d", key)

	switch key {
	case CTRL_Q:
		e.Exit()
		return errors.New("quit")
	case PAGE_UP:
		for times := e.EditRows; times > 0; times-- {
			e.MoveCursor(ARROW_UP)
		}
	case PAGE_DOWN:
		for times := e.EditRows; times > 0; times-- {
			e.MoveCursor(ARROW_DOWN)
		}
	case HOME:
		e.CursorCol = 0
	case END:
		e.CursorCol = e.ScreenCols - 1
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		e.MoveCursor(key)
	}
	return nil
}

func (e *Editor) Scroll() {
	if e.CursorRow < e.RowOffset {
		e.RowOffset = e.CursorRow
	}
	if e.CursorRow-e.RowOffset >= e.EditRows {
		e.RowOffset = e.CursorRow - e.EditRows + 1
	}
	if e.CursorCol < e.ColOffset {
		e.ColOffset = e.CursorCol
	}
	if e.CursorCol-e.ColOffset >= e.EditCols {
		e.ColOffset = e.CursorCol - e.EditCols + 1
	}

	e.Message = fmt.Sprintf("(%d,%d)", e.CursorRow, e.CursorCol)
}

func (e *Editor) RefreshScreen() {
	w, h, err := terminal.GetSize(0)
	if err != nil {
		fmt.Print(err)
	}
	e.ScreenRows = h
	e.ScreenCols = w
	e.EditRows = e.ScreenRows - 1
	e.EditCols = e.ScreenCols

	e.Scroll()
	buffer := make([]byte, 0)
	buffer = append(buffer, []byte("\x1b[?25l")...) // hide cursor
	buffer = append(buffer, []byte("\x1b[1;1H")...) // move cursor to row 1, col 1
	buffer = e.DrawRows(buffer)

	buffer = append(buffer, []byte(fmt.Sprintf("\x1b[%d;%dH", e.CursorRow+1-e.RowOffset, e.CursorCol+1-e.ColOffset))...)

	buffer = append(buffer, []byte("\x1b[?25h")...) // show cursor
	os.Stdout.Write(buffer)
}

func (e *Editor) Exit() {
	buffer := make([]byte, 0)
	buffer = append(buffer, []byte("\x1b[2J")...)   // clear screen
	buffer = append(buffer, []byte("\x1b[1;1H")...) // move cursor to row 1, col 1
	os.Stdout.Write(buffer)
}

func (e *Editor) DrawRows(buffer []byte) []byte {
	for y := 0; y < e.ScreenRows; y++ {
		if y == e.ScreenRows-1 {
			buffer = append(buffer, []byte(e.Message)...)
			buffer = append(buffer, []byte("\x1b[K")...)
		} else if (y + e.RowOffset) < len(e.Rows) {
			line := e.Rows[y+e.RowOffset].Text
			line = strings.Replace(line, "\t", "        ", -1)

			if len(line) > e.ColOffset {
				line = line[e.ColOffset:]
			} else {
				line = ""
			}
			if len(line) > e.ScreenCols {
				line = line[0:e.ScreenCols]
			}
			buffer = append(buffer, []byte(line)...)
			buffer = append(buffer, []byte("\x1b[K")...)
			buffer = append(buffer, []byte("\r\n")...)
		} else {
			if y == e.ScreenRows/3 {
				welcome := fmt.Sprintf("goed editor -- version %s", VERSION)
				padding := (e.ScreenCols - len(welcome)) / 2
				buffer = append(buffer, []byte("~")...)
				for i := 1; i <= padding; i++ {
					buffer = append(buffer, []byte(" ")...)
				}
				buffer = append(buffer, []byte(welcome)...)
			} else {
				buffer = append(buffer, []byte("~")...)
			}
			buffer = append(buffer, []byte("\x1b[K")...)
			buffer = append(buffer, []byte("\r\n")...)
		}
	}
	return buffer
}

func (e *Editor) MoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if e.CursorCol > 0 {
			e.CursorCol--
		}
	case ARROW_RIGHT:
		//	if e.CursorCol < e.ScreenCols-1 {
		e.CursorCol++
	//	}
	case ARROW_UP:
		if e.CursorRow > 0 {
			e.CursorRow--
		}
	case ARROW_DOWN:
		if e.CursorRow < len(e.Rows)-1 {
			e.CursorRow++
		}
	}
}

func main() {
	// put the terminal into raw mode
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		panic(err)
	}
	// restore terminal however we exit
	defer terminal.Restore(0, oldState)

	e := NewEditor()
	e.ReadFile("goed.go")
	// input loop
	for {
		e.RefreshScreen()
		err = e.ProcessKeyPress()
		if err != nil {
			break
		}
	}
}
<commit_msg>fixed terminal reference path<commit_after>package main

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"xmachine.net/go/goed/terminal"
)

const VERSION = "0.0.1"

const (
	CTRL_Q      = 0x11
	ARROW_LEFT  = 1000
	ARROW_RIGHT = 1001
	ARROW_UP    = 1002
	ARROW_DOWN  = 1003
	PAGE_UP     = 1004
	PAGE_DOWN   = 1005
	HOME        = 1006
	END         = 1007
	DELETE      = 1008
)

type Row struct {
	Text string
}

type Editor struct {
	Reader     *bufio.Reader
	ScreenRows int
	ScreenCols int
	EditRows   int // actual number of rows used for text editing
	EditCols   int
	CursorRow  int
	CursorCol  int
	Message    string // status message
	Rows       []Row
	RowOffset  int
	ColOffset  int
}

func NewEditor() *Editor {
	e := &Editor{}
	e.Reader = bufio.NewReader(os.Stdin)
	e.Rows = make([]Row, 0)
	return e
}

func (e *Editor) ReadFile(path string) error {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	s := string(b)
	lines := strings.Split(s, "\n")
	e.Rows = make([]Row, 0)
	for _, line := range lines {
		e.Rows = append(e.Rows, Row{Text: line})
	}
	return nil
}

func (e *Editor) ReadKey() int {
	var err error
	b := make([]byte, 10)
	n := 0
	for n == 0 {
		n, _ = os.Stdin.Read(b)
		if n == 0 {
			time.Sleep(time.Microsecond)
		}
	}
	//e.Message = fmt.Sprintf(" code=%02x", b[0:n])
	if err != nil {
		fmt.Print(err)
	}
	switch b[0] {
	case 0x1b:
		switch b[1] {
		case 0x5b:
			switch b[2] {
			case 'A':
				return ARROW_UP
			case 'B':
				return ARROW_DOWN
			case 'C':
				return ARROW_RIGHT
			case 'D':
				return ARROW_LEFT
			case 0x31:
				switch b[3] {
				case 0x7e:
					return HOME
				}
			case 0x33:
				switch b[3] {
				case 0x7e:
					return DELETE
				}
			case 0x34:
				switch b[3] {
				case 0x7e:
					return END
				}
			case 0x35:
				switch b[3] {
				case 0x7e:
					return PAGE_UP
				}
			case 0x36:
				switch b[3] {
				case 0x7e:
					return PAGE_DOWN
				}
			}
		}
	}
	return int(b[0])
}

func (e *Editor) ProcessKeyPress() error {
	key := e.ReadKey()
	//e.Message += fmt.Sprintf(" key=%d", key)

	switch key {
	case CTRL_Q:
		e.Exit()
		return errors.New("quit")
	case PAGE_UP:
		for times := e.EditRows; times > 0; times-- {
			e.MoveCursor(ARROW_UP)
		}
	case PAGE_DOWN:
		for times := e.EditRows; times > 0; times-- {
			e.MoveCursor(ARROW_DOWN)
		}
	case HOME:
		e.CursorCol = 0
	case END:
		e.CursorCol = e.ScreenCols - 1
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		e.MoveCursor(key)
	}
	return nil
}

func (e *Editor) Scroll() {
	if e.CursorRow < e.RowOffset {
		e.RowOffset = e.CursorRow
	}
	if e.CursorRow-e.RowOffset >= e.EditRows {
		e.RowOffset = e.CursorRow - e.EditRows + 1
	}
	if e.CursorCol < e.ColOffset {
		e.ColOffset = e.CursorCol
	}
	if e.CursorCol-e.ColOffset >= e.EditCols {
		e.ColOffset = e.CursorCol - e.EditCols + 1
	}

	e.Message = fmt.Sprintf("(%d,%d)", e.CursorRow, e.CursorCol)
}

func (e *Editor) RefreshScreen() {
	w, h, err := terminal.GetSize(0)
	if err != nil {
		fmt.Print(err)
	}
	e.ScreenRows = h
	e.ScreenCols = w
	e.EditRows = e.ScreenRows - 1
	e.EditCols = e.ScreenCols

	e.Scroll()
	buffer := make([]byte, 0)
	buffer = append(buffer, []byte("\x1b[?25l")...) // hide cursor
	buffer = append(buffer, []byte("\x1b[1;1H")...) // move cursor to row 1, col 1
	buffer = e.DrawRows(buffer)

	buffer = append(buffer, []byte(fmt.Sprintf("\x1b[%d;%dH", e.CursorRow+1-e.RowOffset, e.CursorCol+1-e.ColOffset))...)

	buffer = append(buffer, []byte("\x1b[?25h")...) // show cursor
	os.Stdout.Write(buffer)
}

func (e *Editor) Exit() {
	buffer := make([]byte, 0)
	buffer = append(buffer, []byte("\x1b[2J")...)   // clear screen
	buffer = append(buffer, []byte("\x1b[1;1H")...) // move cursor to row 1, col 1
	os.Stdout.Write(buffer)
}

func (e *Editor) DrawRows(buffer []byte) []byte {
	for y := 0; y < e.ScreenRows; y++ {
		if y == e.ScreenRows-1 {
			buffer = append(buffer, []byte(e.Message)...)
			buffer = append(buffer, []byte("\x1b[K")...)
		} else if (y + e.RowOffset) < len(e.Rows) {
			line := e.Rows[y+e.RowOffset].Text
			line = strings.Replace(line, "\t", "        ", -1)

			if len(line) > e.ColOffset {
				line = line[e.ColOffset:]
			} else {
				line = ""
			}
			if len(line) > e.ScreenCols {
				line = line[0:e.ScreenCols]
			}
			buffer = append(buffer, []byte(line)...)
			buffer = append(buffer, []byte("\x1b[K")...)
			buffer = append(buffer, []byte("\r\n")...)
		} else {
			if y == e.ScreenRows/3 {
				welcome := fmt.Sprintf("goed editor -- version %s", VERSION)
				padding := (e.ScreenCols - len(welcome)) / 2
				buffer = append(buffer, []byte("~")...)
				for i := 1; i <= padding; i++ {
					buffer = append(buffer, []byte(" ")...)
				}
				buffer = append(buffer, []byte(welcome)...)
			} else {
				buffer = append(buffer, []byte("~")...)
			}
			buffer = append(buffer, []byte("\x1b[K")...)
			buffer = append(buffer, []byte("\r\n")...)
		}
	}
	return buffer
}

func (e *Editor) MoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if e.CursorCol > 0 {
			e.CursorCol--
		}
	case ARROW_RIGHT:
		//	if e.CursorCol < e.ScreenCols-1 {
		e.CursorCol++
	//	}
	case ARROW_UP:
		if e.CursorRow > 0 {
			e.CursorRow--
		}
	case ARROW_DOWN:
		if e.CursorRow < len(e.Rows)-1 {
			e.CursorRow++
		}
	}
}

func main() {
	// put the terminal into raw mode
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		panic(err)
	}
	// restore terminal however we exit
	defer terminal.Restore(0, oldState)

	e := NewEditor()
	e.ReadFile("goed.go")
	// input loop
	for {
		e.RefreshScreen()
		err = e.ProcessKeyPress()
		if err != nil {
			break
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"
)

type Any interface{}

type Env struct {
	m     map[string]Any
	outer *Env
}

func (e *Env) find(k Any) (*Env, bool) {
	if _, ok := e.m[k.(string)]; ok {
		return e, true
	} else if e.outer != nil {
		return e.outer.find(k)
	}
	return nil, false
}

func (e *Env) get(k Any) Any {
	return e.m[k.(string)]
}

func (e *Env) merge(m map[string]Any) {
	for k, v := range m {
		e.m[k] = v
	}
}

func (e *Env) set(k Any, v Any) {
	e.m[k.(string)] = v
}

func NewEnv(keys []Any, vals []Any, outer *Env) *Env {
	zipped := map[string]Any{}
	vlen := len(vals)
	for i, key := range keys {
		if i < vlen {
			zipped[key.(string)] = vals[i]
		}
	}
	return &Env{zipped, outer}
}

// Evaluate an expression
func eval(e Any, env *Env) Any {
	switch e := e.(type) {
	case string:
		if environ, ok := env.find(e); ok {
			return environ.get(e)
		}
		return nil
	case bool, int64, float64:
		return e
	case []Any:
		if len(e) > 0 {
			switch e[0] {
			case "quote": // (quote exp)
				return e[1]
			case "if": // (if test then else?)
				if eval(e[1], env).(bool) {
					return eval(e[2], env)
				} else {
					return eval(e[3], env)
				}
			case "set!": // (set! var exp)
				if environ, ok := env.find(e[1]); ok {
					environ.set(e[1], eval(e[2], env))
				}
			case "define", "def": // (define var exp)
				env.set(e[1], eval(e[2], env))
			case "lambda", "fn": // (lambda (var*) exp)
				vars := e[1].([]Any)
				exp := e[2]
				return func(args ...Any) Any {
					return eval(exp, NewEnv(vars, args, env))
				}
			case "begin": // (begin exp*)
				var val Any
				for _, exp := range e[1:] {
					val = eval(exp, env)
				}
				return val
			default: // (proc exp*)
				exprs := make([]Any, len(e))
				for i, exp := range e {
					exprs[i] = eval(exp, env)
				}
				fn := exprs[0].(func(...Any) Any)
				return fn(exprs[1:]...)
			}
		}
	}
	return e
}

// Read a Scheme expression from a string.
func read(s string) (Any, error) {
	parsed, _, err := read_from(tokenize(s))
	return parsed, err
}

// Converts a string into an array of tokens.
func tokenize(s string) []string {
	return regexp.MustCompile(`\s+`).Split(strings.TrimSpace(
		strings.Replace(strings.Replace(s, "(", " ( ", -1), ")", " ) ", -1)), -1)
}

// Read an expression from a sequence of tokens.
func read_from(tokens []string) (Any, []string, error) {
	if len(tokens) == 0 {
		return nil, nil, errors.New("unexpected EOF while reading")
	}
	token := tokens[0]
	tokens = tokens[1:]
	switch token {
	case "(":
		L := []Any{}
		for len(tokens) > 0 && tokens[0] != ")" {
			token, remaining, _ := read_from(tokens) // TODO handle err
			L = append(L, token)
			tokens = remaining
		}
		if len(tokens) > 0 {
			tokens = tokens[1:] // pop off ')'
		}
		return L, tokens, nil
	case ")":
		return token, tokens, errors.New("unexpected )")
	}
	return atom(token), tokens, nil
}

// Bools, ints, and floats are converted; every other token is a symbol.
func atom(s string) Any {
	if b, err := strconv.ParseBool(s); (s == "true" || s == "false") && err == nil {
		return b
	}
	if i, err := strconv.ParseInt(s, 0, 64); err == nil {
		return i
	}
	if f, err := strconv.ParseFloat(s, 64); err == nil {
		return f
	}
	return s
}

func prompt() {
	fmt.Print(">>> ")
}

func main() {
	env := NewEnv(nil, nil, nil)
	env.merge(map[string]Any{
		"+": func(xs ...Any) Any {
			sum := int64(0)
			for _, x := range xs {
				sum += x.(int64)
			}
			return sum
		},
		"-": func(xs ...Any) Any {
			switch len(xs) {
			case 0:
				return 0
			case 1:
				return -1 * xs[0].(int64)
			case 2:
				return xs[0].(int64) - xs[1].(int64)
			default:
				sum := xs[0].(int64) - xs[1].(int64)
				for _, x := range xs[2:] {
					sum -= x.(int64)
				}
				return sum
			}
		},
		"*": func(xs ...Any) Any {
			sum := int64(1)
			for _, x := range xs {
				sum *= x.(int64)
			}
			return sum
		},
		"<=": func(xs ...Any) Any {
			last := xs[0].(int64)
			for _, x := range xs {
				if x.(int64) < last {
					return false
				}
				last = x.(int64)
			}
			return true
		},
	})

	scanner := bufio.NewScanner(os.Stdin)
	for prompt(); scanner.Scan(); prompt() {
		in := scanner.Text()
		parsed, _ := read(in) // TODO handle err
		evaled := eval(parsed, env)
		fmt.Printf("%v (%T)\n", evaled, evaled)
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}
}
<commit_msg>refactored operator fns and added others<commit_after>package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"
)

type Any interface{}

type Env struct {
	m     map[string]Any
	outer *Env
}

func (e *Env) find(k Any) (*Env, bool) {
	if _, ok := e.m[k.(string)]; ok {
		return e, true
	} else if e.outer != nil {
		return e.outer.find(k)
	}
	return nil, false
}

func (e *Env) get(k Any) Any {
	return e.m[k.(string)]
}

func (e *Env) merge(m map[string]Any) {
	for k, v := range m {
		e.m[k] = v
	}
}

func (e *Env) set(k Any, v Any) {
	e.m[k.(string)] = v
}

func NewEnv(keys []Any, vals []Any, outer *Env) *Env {
	zipped := map[string]Any{}
	vlen := len(vals)
	for i, key := range keys {
		if i < vlen {
			zipped[key.(string)] = vals[i]
		}
	}
	return &Env{zipped, outer}
}

// Evaluate an expression
func eval(e Any, env *Env) Any {
	switch e := e.(type) {
	case string:
		if environ, ok := env.find(e); ok {
			return environ.get(e)
		}
		return nil
	case bool, int64, float64:
		return e
	case []Any:
		if len(e) > 0 {
			switch e[0] {
			case "quote": // (quote exp)
				return e[1]
			case "if": // (if test then else?)
				if eval(e[1], env).(bool) {
					return eval(e[2], env)
				} else {
					return eval(e[3], env)
				}
			case "set!": // (set! var exp)
				if environ, ok := env.find(e[1]); ok {
					environ.set(e[1], eval(e[2], env))
				}
			case "define", "def": // (define var exp)
				env.set(e[1], eval(e[2], env))
			case "lambda", "fn": // (lambda (var*) exp)
				vars := e[1].([]Any)
				exp := e[2]
				return func(args ...Any) Any {
					return eval(exp, NewEnv(vars, args, env))
				}
			case "begin": // (begin exp*)
				var val Any
				for _, exp := range e[1:] {
					val = eval(exp, env)
				}
				return val
			default: // (proc exp*)
				exprs := make([]Any, len(e))
				for i, exp := range e {
					exprs[i] = eval(exp, env)
				}
				fn := exprs[0].(func(...Any) Any)
				return fn(exprs[1:]...)
			}
		}
	}
	return e
}

// Read a Scheme expression from a string.
func read(s string) (Any, error) {
	parsed, _, err := read_from(tokenize(s))
	return parsed, err
}

// Converts a string into an array of tokens.
func tokenize(s string) []string {
	return regexp.MustCompile(`\s+`).Split(strings.TrimSpace(
		strings.Replace(strings.Replace(s, "(", " ( ", -1), ")", " ) ", -1)), -1)
}

// Read an expression from a sequence of tokens.
func read_from(tokens []string) (Any, []string, error) {
	if len(tokens) == 0 {
		return nil, nil, errors.New("unexpected EOF while reading")
	}
	token := tokens[0]
	tokens = tokens[1:]
	switch token {
	case "(":
		L := []Any{}
		for len(tokens) > 0 && tokens[0] != ")" {
			token, remaining, _ := read_from(tokens) // TODO handle err
			L = append(L, token)
			tokens = remaining
		}
		if len(tokens) > 0 {
			tokens = tokens[1:] // pop off ')'
		}
		return L, tokens, nil
	case ")":
		return token, tokens, errors.New("unexpected )")
	}
	return atom(token), tokens, nil
}

// Bools, ints, and floats are converted; every other token is a symbol.
func atom(s string) Any {
	if b, err := strconv.ParseBool(s); (s == "true" || s == "false") && err == nil {
		return b
	}
	if i, err := strconv.ParseInt(s, 0, 64); err == nil {
		return i
	}
	if f, err := strconv.ParseFloat(s, 64); err == nil {
		return f
	}
	return s
}

func prompt() {
	fmt.Print(">>> ")
}

func main() {
	env := NewEnv(nil, nil, nil)
	env.merge(map[string]Any{
		"+": Add, "-": Sub, "<": Lt, "*": Mul,
		"=": Eq, ">": Gt, ">=": Ge, "<=": Le,
		"not": Not,
		"car": Car, "cdr": Cdr, "cons": Cons,
	})

	scanner := bufio.NewScanner(os.Stdin)
	for prompt(); scanner.Scan(); prompt() {
		in := scanner.Text()
		parsed, _ := read(in) // TODO handle err
		evaled := eval(parsed, env)
		fmt.Printf("%v (%T)\n", evaled, evaled)
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}
}

func Add(xs ...Any) Any {
	sum := int64(0)
	for _, x := range xs {
		sum += x.(int64)
	}
	return sum
}

func Car(xs ...Any) Any {
	return xs[0].([]Any)[0]
}

func Cdr(xs ...Any) Any {
	return xs[0].([]Any)[1:]
}

func Cons(xs ...Any) Any {
	return append(xs[1].([]Any), xs[0].(Any))
}

func Eq(xs ...Any) Any {
	switch len(xs) {
	case 0:
		return nil
	case 1:
		return true
	default:
		val := xs[0].(int64)
		for _, x := range xs[1:] {
			if (x != val) {
				return false
			}
		}
		return true
	}
}

func Ge(xs ...Any) Any {
	last := xs[0].(int64)
	for _, x := range xs {
		if last < x.(int64) {
			return false
		}
		last = x.(int64)
	}
	return true
}

func Gt(xs ...Any) Any {
	last := xs[0].(int64)
	for _, x := range xs {
		if last <= x.(int64) {
			return false
		}
		last = x.(int64)
	}
	return true
}

func Lt(xs ...Any) Any {
	last := xs[0].(int64)
	for _, x := range xs {
		if x.(int64) <= last {
			return false
		}
		last = x.(int64)
	}
	return true
}

func Le(xs ...Any) Any {
	last := xs[0].(int64)
	for _, x := range xs {
		if x.(int64) < last {
			return false
		}
		last = x.(int64)
	}
	return true
}

func Mul(xs ...Any) Any {
	prod := int64(1)
	for _, x := range xs {
		prod *= x.(int64)
	}
	return prod
}

func Not(xs ...Any) Any {
	return xs[0].(bool) == false
}

func Sub(xs ...Any) Any {
	switch len(xs) {
	case 0:
		return 0
	case 1:
		return -1 * xs[0].(int64)
	default:
		diff := xs[0].(int64)
		for _, x := range xs[1:] {
			diff -= x.(int64)
		}
		return diff
	}
}

<|endoftext|>


<commit_before>package main

import (
	"archive/tar"
	"compress/gzip"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

var (
	ip       = flag.String("i", "127.0.0.1", "The IP Address the server should run on")
	port     = flag.Int("p", 8086, "The port on which the server listens")
	root     = flag.String("f", "", "The name of the file/folder to be shared")
	count    = flag.Int("c", 1, "The number of times the file/folder should be shared")
	duration = flag.Int("t", 0, "Server timeout")
)

type fileHandler struct {
	root  string
	count int
}

func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println("Serving " + path.Base(f.root) + " to " + strings.Split(r.RemoteAddr, ":")[0])
	w.Header().Set("Content-Disposition", "attachment;filename=\""+path.Base(f.root)+"\"")
	http.ServeFile(w, r, f.root)
	f.count = f.count - 1
	if f.count == 0 {
		log.Fatal("Finished serving. Server exiting.")
	}
}

func exitafter(minutes int) {
	if minutes == 0 {
		return
	}
	delay := fmt.Sprintf("%dm", minutes)
	duration, _ := time.ParseDuration(delay)
	log.Println("Will exit automatically after", duration)
	<-time.After(duration)
	log.Fatal("Server timed out.")
}

func serveFile(handler http.Handler, endpoint string) {
	http.Handle("/", handler)
	log.Fatal(http.ListenAndServe(endpoint, nil))
}

func serveFolder(root string, count, duration int, endpoint string) {
	tarfile, err := archiveDir(root)
	if err != nil {
		log.Fatal(err)
	}
	go exitafter(duration)
	// log.Println("Serving", tarfile, "at", endpoint)
	serveFile(&fileHandler{tarfile, count}, endpoint)
}

func newArchWriter(dirname string) (*tar.Writer, error) {
	w, err := os.Create(dirname + ".tar")
	if err != nil {
		return new(tar.Writer), err
	}
	cw := gzip.NewWriter(w)
	return tar.NewWriter(cw), nil
}

func archiveDir(root string) (string, error) {
	dir := filepath.Dir(root)
	tw, err := newArchWriter(root)
	if err != nil {
		return "", err
	}
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		header, _ := tar.FileInfoHeader(info, "")
		header.Name = path[len(dir):]
		tw.WriteHeader(header)
		if info.IsDir() == false {
			data, _ := ioutil.ReadFile(path)
			tw.Write(data)
			tw.Flush()
		}
		return nil
	})
	tw.Close()
	return root + ".tar", nil
}

func main() {
	flag.Parse()
	endpoint := fmt.Sprintf("%s:%d", *ip, *port)
	fi, err := os.Stat(*root)
	if err != nil {
		log.Fatal("Path is invalid")
	}
	if fi.IsDir() == true {
		serveFolder(*root, *count, *duration, endpoint)
	} else {
		// is a file
		go exitafter(*duration)
		// log.Println("Serving", *root, "at", endpoint)
		serveFile(&fileHandler{*root, *count}, endpoint)
	}
}
<commit_msg>Add extra logs<commit_after>package main

import (
	"archive/tar"
	"compress/gzip"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

var (
	ip       = flag.String("i", "127.0.0.1", "The IP Address the server should run on")
	port     = flag.Int("p", 8086, "The port on which the server listens")
	root     = flag.String("f", "", "The name of the file/folder to be shared")
	count    = flag.Int("c", 1, "The number of times the file/folder should be shared")
	duration = flag.Int("t", 0, "Server timeout")
)

type fileHandler struct {
	root  string
	count int
}

func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println("Serving " + path.Base(f.root) + " to " + strings.Split(r.RemoteAddr, ":")[0])
	w.Header().Set("Content-Disposition", "attachment;filename=\""+path.Base(f.root)+"\"")
	http.ServeFile(w, r, f.root)
	f.count = f.count - 1
	if f.count == 0 {
		log.Fatal("Finished serving. Server exiting.")
	}
}

func exitafter(minutes int) {
	if minutes == 0 {
		return
	}
	delay := fmt.Sprintf("%dm", minutes)
	duration, _ := time.ParseDuration(delay)
	log.Println("Will exit automatically after", duration)
	<-time.After(duration)
	log.Fatal("Server timed out.")
}

func serveFile(handler http.Handler, endpoint string) {
	http.Handle("/", handler)
	log.Fatal(http.ListenAndServe(endpoint, nil))
}

func serveFolder(root string, count, duration int, endpoint string) {
	tarfile, err := archiveDir(root)
	if err != nil {
		log.Fatal(err)
	}
	go exitafter(duration)
	log.Println("Serving", tarfile, "at", endpoint)
	serveFile(&fileHandler{tarfile, count}, endpoint)
}

func newArchWriter(dirname string) (*tar.Writer, error) {
	w, err := os.Create(dirname + ".tar")
	if err != nil {
		return new(tar.Writer), err
	}
	cw := gzip.NewWriter(w)
	return tar.NewWriter(cw), nil
}

func archiveDir(root string) (string, error) {
	log.Println("Creating archive of", root)
	dir := filepath.Dir(root)
	tw, err := newArchWriter(root)
	if err != nil {
		return "", err
	}
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		header, _ := tar.FileInfoHeader(info, "")
		header.Name = path[len(dir):]
		tw.WriteHeader(header)
		if info.IsDir() == false {
			data, _ := ioutil.ReadFile(path)
			tw.Write(data)
			tw.Flush()
		}
		return nil
	})
	tw.Close()
	log.Println("Created", root+".tar")
	return root + ".tar", nil
}

func main() {
	flag.Parse()
	endpoint := fmt.Sprintf("%s:%d", *ip, *port)
	fi, err := os.Stat(*root)
	if err != nil {
		log.Fatal("Path is invalid")
	}
	if fi.IsDir() == true {
		serveFolder(*root, *count, *duration, endpoint)
	} else {
		// is a file
		go exitafter(*duration)
		log.Println("Serving", *root, "at", endpoint)
		serveFile(&fileHandler{*root, *count}, endpoint)
	}
}
<|endoftext|>


<commit_before><commit_msg>"by" -> "uploaded by"<commit_after><|endoftext|>


<commit_before>// GPIO support.

package embd

import "time"

// The Direction type indicates the direction of a GPIO pin.
type Direction int

// The Edge trigger for the GPIO Interrupt
type Edge string

const (
	// In represents read mode.
	In Direction = iota

	// Out represents write mode.
	Out
)

const (
	// Low represents 0.
	Low int = iota

	// High represents 1.
	High
)

const (
	EdgeNone    Edge = "none"
	EdgeRising  Edge = "rising"
	EdgeFalling Edge = "falling"
	EdgeBoth    Edge = "both"
)

// InterruptPin implements access to a Interruptable capable GPIO pin.
type InterruptPin interface {

	// Start watching this pin for interrupt
	Watch(edge Edge, handler func(DigitalPin)) error

	// Stop watching this pin for interrupt
	StopWatching() error
}

// DigitalPin implements access to a digital IO capable GPIO pin.
type DigitalPin interface {
	InterruptPin

	// N returns the logical GPIO number.
	N() int

	// Write writes the provided value to the pin.
	Write(val int) error

	// Read reads the value from the pin.
	Read() (int, error)

	// TimePulse measures the duration of a pulse on the pin.
	TimePulse(state int) (time.Duration, error)

	// SetDirection sets the direction of the pin (in/out).
	SetDirection(dir Direction) error

	// ActiveLow makes the pin active low. A low logical state is represented by
	// a high state on the physical pin, and vice-versa.
	ActiveLow(b bool) error

	// PullUp pulls the pin up.
	PullUp() error

	// PullDown pulls the pin down.
	PullDown() error

	// Close releases the resources associated with the pin.
	Close() error
}

// AnalogPin implements access to a analog IO capable GPIO pin.
type AnalogPin interface {
	// N returns the logical GPIO number.
	N() int

	// Read reads the value from the pin.
	Read() (int, error)

	// Close releases the resources associated with the pin.
	Close() error
}

// The Polarity type indicates the polarity of a pwm pin.
type Polarity int

const (
	// Positive represents (default) positive polarity.
	Positive Polarity = iota

	// Negative represents negative polarity.
	Negative
)

// PWMPin implements access to a pwm capable GPIO pin.
type PWMPin interface {
	// N returns the logical PWM id.
	N() string

	// SetPeriod sets the period of a pwm pin.
	SetPeriod(ns int) error

	// SetDuty sets the duty of a pwm pin.
	SetDuty(ns int) error

	// SetPolarity sets the polarity of a pwm pin.
	SetPolarity(pol Polarity) error

	// SetMicroseconds sends a command to the PWM driver to generate a us wide pulse.
	SetMicroseconds(us int) error

	// SetAnalog allows easy manipulation of the PWM based on a (0-255) range value.
	SetAnalog(value byte) error

	// Close releases the resources associated with the pin.
	Close() error
}

// GPIODriver implements a generic GPIO driver.
type GPIODriver interface {
	// PinMap returns the pinmap for this driver.
	PinMap() PinMap

	// Unregister unregisters the pin from the driver. Should be called when the pin is closed.
	Unregister(string) error

	// DigitalPin returns a pin capable of doing digital IO.
	DigitalPin(key interface{}) (DigitalPin, error)

	// AnalogPin returns a pin capable of doing analog IO.
	AnalogPin(key interface{}) (AnalogPin, error)

	// PWMPin returns a pin capable of generating PWM.
	PWMPin(key interface{}) (PWMPin, error)

	// Close releases the resources associated with the driver.
	Close() error
}

var gpioDriverInitialized bool
var gpioDriverInstance GPIODriver

// InitGPIO initializes the GPIO driver.
func InitGPIO() error {
	if gpioDriverInitialized {
		return nil
	}

	desc, err := DescribeHost()
	if err != nil {
		return err
	}

	if desc.GPIODriver == nil {
		return ErrFeatureNotSupported
	}

	gpioDriverInstance = desc.GPIODriver()
	gpioDriverInitialized = true

	return nil
}

// CloseGPIO releases resources associated with the GPIO driver.
func CloseGPIO() error {
	return gpioDriverInstance.Close()
}

// NewDigitalPin returns a DigitalPin interface which allows control over
// the digital GPIO pin.
func NewDigitalPin(key interface{}) (DigitalPin, error) {
	if err := InitGPIO(); err != nil {
		return nil, err
	}

	return gpioDriverInstance.DigitalPin(key)
}

// DigitalWrite writes val to the pin.
func DigitalWrite(key interface{}, val int) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.Write(val)
}

// DigitalRead reads a value from the pin.
func DigitalRead(key interface{}) (int, error) {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return 0, err
	}

	return pin.Read()
}

// SetDirection sets the direction of the pin (in/out).
func SetDirection(key interface{}, dir Direction) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.SetDirection(dir)
}

// ActiveLow makes the pin active low. A low logical state is represented by
// a high state on the physical pin, and vice-versa.
func ActiveLow(key interface{}, b bool) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.ActiveLow(b)
}

// PullUp pulls the pin up.
func PullUp(key interface{}) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.PullUp()
}

// PullDown pulls the pin down.
func PullDown(key interface{}) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.PullDown()
}

// NewAnalogPin returns a AnalogPin interface which allows control over
// the analog GPIO pin.
func NewAnalogPin(key interface{}) (AnalogPin, error) {
	if err := InitGPIO(); err != nil {
		return nil, err
	}

	return gpioDriverInstance.AnalogPin(key)
}

// AnalogWrite reads a value from the pin.
func AnalogRead(key interface{}) (int, error) {
	pin, err := NewAnalogPin(key)
	if err != nil {
		return 0, err
	}

	return pin.Read()
}

// NewPWMPin returns a PWMPin interface which allows PWM signal
// generation over a the PWM pin.
func NewPWMPin(key interface{}) (PWMPin, error) {
	if err := InitGPIO(); err != nil {
		return nil, err
	}

	return gpioDriverInstance.PWMPin(key)
}
<commit_msg>minor documentation addition<commit_after>// GPIO support.

package embd

import "time"

// The Direction type indicates the direction of a GPIO pin.
type Direction int

// The Edge trigger for the GPIO Interrupt
type Edge string

const (
	// In represents read mode.
	In Direction = iota

	// Out represents write mode.
	Out
)

const (
	// Low represents 0.
	Low int = iota

	// High represents 1.
	High
)

const (
	EdgeNone    Edge = "none"
	EdgeRising  Edge = "rising"
	EdgeFalling Edge = "falling"
	EdgeBoth    Edge = "both"
)

// InterruptPin implements access to an interrupt capable GPIO pin.
// The basic capability provided is to watch for a transition on the pin and
// generate a callback to a handler when a transition occurs.
// On Linux the underlying implementation generally uses epoll to receive the
// interrupts at user-level.
type InterruptPin interface {

	// Start watching this pin for interrupt
	Watch(edge Edge, handler func(DigitalPin)) error

	// Stop watching this pin for interrupt
	StopWatching() error
}

// DigitalPin implements access to a digital IO capable GPIO pin.
type DigitalPin interface {
	InterruptPin

	// N returns the logical GPIO number.
	N() int

	// Write writes the provided value to the pin.
	Write(val int) error

	// Read reads the value from the pin.
	Read() (int, error)

	// TimePulse measures the duration of a pulse on the pin.
	TimePulse(state int) (time.Duration, error)

	// SetDirection sets the direction of the pin (in/out).
	SetDirection(dir Direction) error

	// ActiveLow makes the pin active low. A low logical state is represented by
	// a high state on the physical pin, and vice-versa.
	ActiveLow(b bool) error

	// PullUp pulls the pin up.
	PullUp() error

	// PullDown pulls the pin down.
	PullDown() error

	// Close releases the resources associated with the pin.
	Close() error
}

// AnalogPin implements access to a analog IO capable GPIO pin.
type AnalogPin interface {
	// N returns the logical GPIO number.
	N() int

	// Read reads the value from the pin.
	Read() (int, error)

	// Close releases the resources associated with the pin.
	Close() error
}

// The Polarity type indicates the polarity of a pwm pin.
type Polarity int

const (
	// Positive represents (default) positive polarity.
	Positive Polarity = iota

	// Negative represents negative polarity.
	Negative
)

// PWMPin implements access to a pwm capable GPIO pin.
type PWMPin interface {
	// N returns the logical PWM id.
	N() string

	// SetPeriod sets the period of a pwm pin.
	SetPeriod(ns int) error

	// SetDuty sets the duty of a pwm pin.
	SetDuty(ns int) error

	// SetPolarity sets the polarity of a pwm pin.
	SetPolarity(pol Polarity) error

	// SetMicroseconds sends a command to the PWM driver to generate a us wide pulse.
	SetMicroseconds(us int) error

	// SetAnalog allows easy manipulation of the PWM based on a (0-255) range value.
	SetAnalog(value byte) error

	// Close releases the resources associated with the pin.
	Close() error
}

// GPIODriver implements a generic GPIO driver.
type GPIODriver interface {
	// PinMap returns the pinmap for this driver.
	PinMap() PinMap

	// Unregister unregisters the pin from the driver. Should be called when the pin is closed.
	Unregister(string) error

	// DigitalPin returns a pin capable of doing digital IO.
	DigitalPin(key interface{}) (DigitalPin, error)

	// AnalogPin returns a pin capable of doing analog IO.
	AnalogPin(key interface{}) (AnalogPin, error)

	// PWMPin returns a pin capable of generating PWM.
	PWMPin(key interface{}) (PWMPin, error)

	// Close releases the resources associated with the driver.
	Close() error
}

var gpioDriverInitialized bool
var gpioDriverInstance GPIODriver

// InitGPIO initializes the GPIO driver.
func InitGPIO() error {
	if gpioDriverInitialized {
		return nil
	}

	desc, err := DescribeHost()
	if err != nil {
		return err
	}

	if desc.GPIODriver == nil {
		return ErrFeatureNotSupported
	}

	gpioDriverInstance = desc.GPIODriver()
	gpioDriverInitialized = true

	return nil
}

// CloseGPIO releases resources associated with the GPIO driver.
func CloseGPIO() error {
	return gpioDriverInstance.Close()
}

// NewDigitalPin returns a DigitalPin interface which allows control over
// the digital GPIO pin.
func NewDigitalPin(key interface{}) (DigitalPin, error) {
	if err := InitGPIO(); err != nil {
		return nil, err
	}

	return gpioDriverInstance.DigitalPin(key)
}

// DigitalWrite writes val to the pin.
func DigitalWrite(key interface{}, val int) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.Write(val)
}

// DigitalRead reads a value from the pin.
func DigitalRead(key interface{}) (int, error) {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return 0, err
	}

	return pin.Read()
}

// SetDirection sets the direction of the pin (in/out).
func SetDirection(key interface{}, dir Direction) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.SetDirection(dir)
}

// ActiveLow makes the pin active low. A low logical state is represented by
// a high state on the physical pin, and vice-versa.
func ActiveLow(key interface{}, b bool) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.ActiveLow(b)
}

// PullUp pulls the pin up.
func PullUp(key interface{}) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.PullUp()
}

// PullDown pulls the pin down.
func PullDown(key interface{}) error {
	pin, err := NewDigitalPin(key)
	if err != nil {
		return err
	}

	return pin.PullDown()
}

// NewAnalogPin returns a AnalogPin interface which allows control over
// the analog GPIO pin.
func NewAnalogPin(key interface{}) (AnalogPin, error) {
	if err := InitGPIO(); err != nil {
		return nil, err
	}

	return gpioDriverInstance.AnalogPin(key)
}

// AnalogWrite reads a value from the pin.
func AnalogRead(key interface{}) (int, error) {
	pin, err := NewAnalogPin(key)
	if err != nil {
		return 0, err
	}

	return pin.Read()
}

// NewPWMPin returns a PWMPin interface which allows PWM signal
// generation over a the PWM pin.
func NewPWMPin(key interface{}) (PWMPin, error) {
	if err := InitGPIO(); err != nil {
		return nil, err
	}

	return gpioDriverInstance.PWMPin(key)
}
<|endoftext|>


<commit_before>// Copyright 2014 Eric Holmes.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package hookshot is a router that de-multiplexes and authorizes github webhooks.
package hookshot

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"crypto/subtle"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	// HeaderEvent is the name of the header that contains the type of event.
	HeaderEvent = "X-GitHub-Event"

	// HeaderSignature is the name of the header that contains the signature.
	HeaderSignature = "X-Hub-Signature"
)

var (
	// DefaultNotFoundHandler is the default NotFoundHandler for a Router instance.
	DefaultNotFoundHandler = http.HandlerFunc(http.NotFound)

	// DefaultUnauthorizedHandler is the default UnauthorizedHandler for a Router
	// instance, which responds with a 403 status and a plain text body.
	DefaultUnauthorizedHandler = http.HandlerFunc(unauthorized)
)

// Router demultiplexes github hooks.
type Router struct {
	// NotFoundHandler is called when a handler is not found for a given GitHub event.
	// The nil value for NotFoundHandler
	NotFoundHandler http.Handler

	// UnauthorizedHandler is called when the calculated signature does not match the
	// provided signature in the X-Hub-Signature header.
	UnauthorizedHandler http.Handler

	routes routes
}

// NewRouter returns a new Router.
func NewRouter() *Router {
	return &Router{
		routes: make(routes),
	}
}

// Handle maps a github event to an http.Handler.
func (r *Router) Handle(event string, h http.Handler) {
	r.routes[event] = h
}

// HandleFunc maps a github event to an http.HandlerFunc.
func (r *Router) HandleFunc(event string, fn func(http.ResponseWriter, *http.Request)) {
	r.Handle(event, http.HandlerFunc(fn))
}

// ServeHTTP implements the http.Handler interface to route a request to an
// appropriate http.Handler, based on the value of the X-GitHub-Event header.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	event := req.Header.Get(HeaderEvent)

	route := r.routes[event]
	if route == nil {
		r.notFound(w, req)
		return
	}

	route.ServeHTTP(w, req)
}

func (r *Router) notFound(w http.ResponseWriter, req *http.Request) {
	if r.NotFoundHandler == nil {
		r.NotFoundHandler = DefaultNotFoundHandler
	}

	r.NotFoundHandler.ServeHTTP(w, req)
}

// routes maps a github event to an http.Handler.
type routes map[string]http.Handler

// SecretHandler is an http.Handler that will verify the authenticity of the
// request.
type SecretHandler struct {
	// The secret to use to verify the request.
	Secret string

	// SetHeader controls what happens when the X-Hub-Signature header value does
	// not match the calculated signature. Setting this value to true will set
	// the X-Calculated-Signature header in the response.
	//
	// It's recommended that you only enable this for debugging purposes.
	SetHeader bool

	// Handler is the http.Handler that will be called if the request is
	// authorized.
	Handler http.Handler

	// Unauthorized is the http.Handler that will be called if the request
	// is not authorized.
	Unauthorized http.Handler
}

// Authorize wraps an http.Handler to verify the authenticity of the request
// using the provided secret.
func Authorize(h http.Handler, secret string) *SecretHandler {
	return &SecretHandler{Handler: h, Secret: secret}
}

// ServeHTTP implements the http.Handler interface.
func (h *SecretHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if h.Unauthorized == nil {
		h.Unauthorized = DefaultUnauthorizedHandler
	}

	// If a secret is provided, ensure that the request is verified.
	if h.Secret != "" {
		sig, ok := IsAuthorized(req, h.Secret)

		if h.SetHeader {
			w.Header().Set("X-Calculated-Signature", sig)
		}

		if !ok {
			h.Unauthorized.ServeHTTP(w, req)
			return
		}
	}

	h.Handler.ServeHTTP(w, req)
}

// Signature calculates the SHA1 HMAC signature of body, signed by the secret.
//
// When github-services makes a POST request, it includes a SHA1 HMAC signature
// of the request body, signed with the secret provided in the webhook configuration.
// See http://goo.gl/Oe4WwR.
func Signature(body []byte, secret string) string {
	mac := hmac.New(sha1.New, []byte(secret))
	mac.Write(body)
	return fmt.Sprintf("%x", mac.Sum(nil))
}

// IsAuthorized checks that the calculated signature for the request matches the provided signature in
// the request headers. Returns the calculated signature, and a boolean value
// indicating whether or not the calculated signature matches the
// X-Hub-Signature value.
func IsAuthorized(r *http.Request, secret string) (string, bool) {
	raw, er := ioutil.ReadAll(r.Body)
	if er != nil {
		return "", false
	}

	// Since we're reading the request from the network, r.Body will return EOF if any
	// downstream http.Handler attempts to read it. We set it to a new io.ReadCloser
	// that will read from the bytes in memory.
	r.Body = ioutil.NopCloser(bytes.NewReader(raw))

	sig := "sha1=" + Signature(raw, secret)
	return sig, compareStrings(r.Header.Get(HeaderSignature), sig)
}

// compareStrings compares two strings in constant time.
func compareStrings(a, b string) bool {
	if len(a) != len(b) {
		return false
	}

	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

// unauthorized is the default UnauthorizedHandler.
func unauthorized(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "The provided signature in the "+HeaderSignature+" header does not match.", 403)
}
<commit_msg>Cleanup.<commit_after>// Copyright 2014 Eric Holmes.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package hookshot is a router that de-multiplexes and authorizes github webhooks.
package hookshot

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"crypto/subtle"
	"fmt"
	"io/ioutil"
	"net/http"
)

const (
	// HeaderEvent is the name of the header that contains the type of event.
	HeaderEvent = "X-GitHub-Event"

	// HeaderSignature is the name of the header that contains the signature.
	HeaderSignature = "X-Hub-Signature"
)

var (
	// DefaultNotFoundHandler is the default NotFoundHandler for a Router instance.
	DefaultNotFoundHandler = http.HandlerFunc(http.NotFound)

	// DefaultUnauthorizedHandler is the default UnauthorizedHandler for a Router
	// instance, which responds with a 403 status and a plain text body.
	DefaultUnauthorizedHandler = http.HandlerFunc(unauthorized)
)

// Router demultiplexes github hooks.
type Router struct {
	// NotFoundHandler is called when a handler is not found for a given GitHub event.
	// The nil value for NotFoundHandler
	NotFoundHandler http.Handler

	routes routes
}

// NewRouter returns a new Router.
func NewRouter() *Router {
	return &Router{
		routes: make(routes),
	}
}

// Handle maps a github event to an http.Handler.
func (r *Router) Handle(event string, h http.Handler) {
	r.routes[event] = h
}

// HandleFunc maps a github event to an http.HandlerFunc.
func (r *Router) HandleFunc(event string, fn func(http.ResponseWriter, *http.Request)) {
	r.Handle(event, http.HandlerFunc(fn))
}

// ServeHTTP implements the http.Handler interface to route a request to an
// appropriate http.Handler, based on the value of the X-GitHub-Event header.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	event := req.Header.Get(HeaderEvent)

	route := r.routes[event]
	if route == nil {
		r.notFound(w, req)
		return
	}

	route.ServeHTTP(w, req)
}

func (r *Router) notFound(w http.ResponseWriter, req *http.Request) {
	if r.NotFoundHandler == nil {
		r.NotFoundHandler = DefaultNotFoundHandler
	}

	r.NotFoundHandler.ServeHTTP(w, req)
}

// routes maps a github event to an http.Handler.
type routes map[string]http.Handler

// SecretHandler is an http.Handler that will verify the authenticity of the
// request.
type SecretHandler struct {
	// The secret to use to verify the request.
	Secret string

	// SetHeader controls what happens when the X-Hub-Signature header value does
	// not match the calculated signature. Setting this value to true will set
	// the X-Calculated-Signature header in the response.
	//
	// It's recommended that you only enable this for debugging purposes.
	SetHeader bool

	// Handler is the http.Handler that will be called if the request is
	// authorized.
	Handler http.Handler

	// Unauthorized is the http.Handler that will be called if the request
	// is not authorized.
	Unauthorized http.Handler
}

// Authorize wraps an http.Handler to verify the authenticity of the request
// using the provided secret.
func Authorize(h http.Handler, secret string) *SecretHandler {
	return &SecretHandler{Handler: h, Secret: secret}
}

// ServeHTTP implements the http.Handler interface.
func (h *SecretHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if h.Unauthorized == nil {
		h.Unauthorized = DefaultUnauthorizedHandler
	}

	// If a secret is provided, ensure that the request is verified.
	if h.Secret != "" {
		sig, ok := IsAuthorized(req, h.Secret)

		if h.SetHeader {
			w.Header().Set("X-Calculated-Signature", sig)
		}

		if !ok {
			h.Unauthorized.ServeHTTP(w, req)
			return
		}
	}

	h.Handler.ServeHTTP(w, req)
}

// Signature calculates the SHA1 HMAC signature of body, signed by the secret.
//
// When github-services makes a POST request, it includes a SHA1 HMAC signature
// of the request body, signed with the secret provided in the webhook configuration.
// See http://goo.gl/Oe4WwR.
func Signature(body []byte, secret string) string {
	mac := hmac.New(sha1.New, []byte(secret))
	mac.Write(body)
	return fmt.Sprintf("%x", mac.Sum(nil))
}

// IsAuthorized checks that the calculated signature for the request matches the provided signature in
// the request headers. Returns the calculated signature, and a boolean value
// indicating whether or not the calculated signature matches the
// X-Hub-Signature value.
func IsAuthorized(r *http.Request, secret string) (string, bool) {
	raw, er := ioutil.ReadAll(r.Body)
	if er != nil {
		return "", false
	}

	// Since we're reading the request from the network, r.Body will return EOF if any
	// downstream http.Handler attempts to read it. We set it to a new io.ReadCloser
	// that will read from the bytes in memory.
	r.Body = ioutil.NopCloser(bytes.NewReader(raw))

	sig := "sha1=" + Signature(raw, secret)
	return sig, compareStrings(r.Header.Get(HeaderSignature), sig)
}

// compareStrings compares two strings in constant time.
func compareStrings(a, b string) bool {
	if len(a) != len(b) {
		return false
	}

	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

// unauthorized is the default UnauthorizedHandler.
func unauthorized(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "The provided signature in the "+HeaderSignature+" header does not match.", 403)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"strings"
	"time"
	"os"
	"fmt"
	"net"
	"net/http"
	"io/ioutil"
	"encoding/json"
	"github.com/miekg/dns"
	_ "strconv"
)

func main(){
	// load the API key
	api_key := os.Getenv("LINODE_API_KEY")

	client := &http.Client{}

	// verify that it works
	req, _ := http.NewRequest("GET", "https://api.linode.com", nil)
	q := req.URL.Query()
	q.Add("api_key", api_key)
	q.Add("api_action", "test.echo")
	q.Add("now", fmt.Sprintf("%v", time.Now()))
	req.URL.RawQuery = q.Encode()

	resp, _ := client.Do(req)
	defer resp.Body.Close()
	data, _ := ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		fmt.Printf("Invalid API key ("+api_key+"): "+resp.Status)
		return
	}

	// fine, that worked: get a list of all the domains
	req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
	q = req.URL.Query()
	q.Add("api_key", api_key)
	q.Add("api_action", "domain.list")
	req.URL.RawQuery = q.Encode()

	resp, _ = client.Do(req)
	defer resp.Body.Close()
	data, _ = ioutil.ReadAll(resp.Body)

	var input interface{}
	d := json.NewDecoder(bytes.NewReader(data))
	d.UseNumber()
	_ = d.Decode(&input)

	//output, _ := json.MarshalIndent(domains, "", "  ")
	// fmt.Printf("Contents: %v", string(output))
	handler := os.Args[1]
	domain := os.Args[2]
	// filename := os.Args[3]
	token := os.Args[4]
	//fmt.Printf("Handler: %s\n", handler)
	domains := input.(map[string]interface{})["DATA"].([]interface{})
	var zone map[string]interface{}
	for _, v := range domains {
		zone = v.(map[string]interface{})
		if !strings.HasSuffix(domain, zone["DOMAIN"].(string)) {
			zone = nil
			continue
		}else{
			break
		}
	}

	// output, _ := json.MarshalIndent(zone, "", "  ")
	// fmt.Printf("Contents: %v\n",string(output))

	// get the zone ID and root
	domainid := zone["DOMAINID"].(json.Number)
	root := zone["DOMAIN"].(string)

	// process the requested action
	if handler == "deploy_challenge" {
		target := "_acme-challenge."+domain

		// see if there's already a resource
		req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
		q = req.URL.Query()
		q.Add("api_key", api_key)
		q.Add("api_action", "domain.resource.list")
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()
		data, _ = ioutil.ReadAll(resp.Body)
		d = json.NewDecoder(bytes.NewReader(data))
		d.UseNumber()
		_ = d.Decode(&input)
		// resourcelist, _ := json.MarshalIndent(input, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resourcelist))
		resourcelist := input.(map[string]interface{})["DATA"].([]interface{})
		var resource map[string]interface{}
		for _, rv := range resourcelist {
			resource = rv.(map[string]interface{})
			 // fmt.Printf("Looking For: [%s]; Name: [%s]; Target: [%s]; Type: %s\n", target, resource["NAME"].(string)+"."+root, resource["TARGET"].(string), resource["TYPE"].(string))
			if resource["TYPE"].(string) == "TXT" && resource["NAME"].(string)+"."+root == target {
				break
			}else{
				resource = nil
				continue
			}
		}

		// resoutput, _ := json.MarshalIndent(resource, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resoutput))
		req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
		q = req.URL.Query()
		q.Add("api_key", api_key)
		if resource == nil {
			// create
			q.Add("api_action", "domain.resource.create")
			q.Add("Type", "TXT")
			q.Add("Name", target)
		}else{
			// update
			q.Add("api_action", "domain.resource.update")
			q.Add("ResourceID", fmt.Sprintf("%v", resource["RESOURCEID"].(json.Number)))
		}
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		q.Add("Target", token)
		q.Add("TTL_sec", "300")
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			data, _ = ioutil.ReadAll(resp.Body)
			fmt.Printf("Error: %s\n", string(data))
		}else{
			// need to loop: check every 10 seconds to see if the entry has propagated
			var resolver dns.Client
			resolver_host := os.Getenv("DEHYDRATED_RESOLVER")
			if resolver_host != "" {
				resolver = dns.Client{}
			}
			for {
				time.Sleep(10 * time.Second)
				if resolver_host != "" {
					msg := new(dns.Msg)
					msg.Id = dns.Id()
					msg.RecursionDesired = false
					msg.Question = make([]dns.Question, 1)
					msg.Question[0] = dns.Question{target+".", dns.TypeTXT, dns.ClassINET}
					in, _, err1 := resolver.Exchange(msg, resolver_host+":53")
					if err1 == nil {
						if len(in.Answer) > 0 {
							txt := in.Answer[0].(*dns.TXT)
							if txt.Txt[0] == token {
								break
							}
						}
					}
				}else{
					challenges, err1 := net.LookupTXT(target)
					if err1 == nil && len(challenges) > 0 {
						if challenges[0] == token {
							break
						}
					}
				}
			}
		}
	}else if handler == "clean_challenge" {
		target := "_acme-challenge."+domain

		// see if there's already a resource
		req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
		q = req.URL.Query()
		q.Add("api_key", api_key)
		q.Add("api_action", "domain.resource.list")
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()
		data, _ = ioutil.ReadAll(resp.Body)
		d = json.NewDecoder(bytes.NewReader(data))
		d.UseNumber()
		_ = d.Decode(&input)
		// resourcelist, _ := json.MarshalIndent(input, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resourcelist))
		resourcelist := input.(map[string]interface{})["DATA"].([]interface{})
		var resource map[string]interface{}
		for _, rv := range resourcelist {
			resource = rv.(map[string]interface{})
			 // fmt.Printf("Looking For: [%s]; Name: [%s]; Target: [%s]; Type: %s\n", target, resource["NAME"].(string)+"."+root, resource["TARGET"].(string), resource["TYPE"].(string))
			if resource["TYPE"].(string) == "TXT" && resource["NAME"].(string)+"."+root == target {
				break
			}else{
				resource = nil
				continue
			}
		}

		// resoutput, _ := json.MarshalIndent(resource, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resoutput))
		if resource != nil {
			// flush it
			req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
			q = req.URL.Query()
			q.Add("api_key", api_key)
			q.Add("api_action", "domain.resource.delete")
			q.Add("ResourceID", fmt.Sprintf("%v", resource["RESOURCEID"].(json.Number)))
		}
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		q.Add("Target", token)
		q.Add("TTL_sec", "60")
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			data, _ = ioutil.ReadAll(resp.Body)
			fmt.Printf("Error: %s\n", string(data))
		}
	}else if handler == "deploy_cert" {
		fmt.Printf("Deploying cert [%s] (full chain %s) and key [%s] for domain [%s]\n", os.Args[4], os.Args[5], os.Args[3], domain)
	}
}
<commit_msg>Updated to only retrieve variables from os.Args when we're expecting them from dehydrated<commit_after>package main

import (
	"bytes"
	"strings"
	"time"
	"os"
	"fmt"
	"net"
	"net/http"
	"io/ioutil"
	"encoding/json"
	"github.com/miekg/dns"
	_ "strconv"
)

func main(){
	// load the API key
	api_key := os.Getenv("LINODE_API_KEY")

	client := &http.Client{}

	// verify that it works
	req, _ := http.NewRequest("GET", "https://api.linode.com", nil)
	q := req.URL.Query()
	q.Add("api_key", api_key)
	q.Add("api_action", "test.echo")
	q.Add("now", fmt.Sprintf("%v", time.Now()))
	req.URL.RawQuery = q.Encode()

	resp, _ := client.Do(req)
	defer resp.Body.Close()
	data, _ := ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		fmt.Printf("Invalid API key ("+api_key+"): "+resp.Status)
		return
	}

	// fine, that worked: get a list of all the domains
	req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
	q = req.URL.Query()
	q.Add("api_key", api_key)
	q.Add("api_action", "domain.list")
	req.URL.RawQuery = q.Encode()

	resp, _ = client.Do(req)
	defer resp.Body.Close()
	data, _ = ioutil.ReadAll(resp.Body)

	var input interface{}
	d := json.NewDecoder(bytes.NewReader(data))
	d.UseNumber()
	_ = d.Decode(&input)

	//output, _ := json.MarshalIndent(domains, "", "  ")
	// fmt.Printf("Contents: %v", string(output))
	handler := os.Args[1]

	// process the requested action
	if handler == "deploy_challenge" {
		domain := os.Args[2]
		// filename := os.Args[3]

		//fmt.Printf("Handler: %s\n", handler)
		domains := input.(map[string]interface{})["DATA"].([]interface{})
		var zone map[string]interface{}
		for _, v := range domains {
			zone = v.(map[string]interface{})
			if !strings.HasSuffix(domain, zone["DOMAIN"].(string)) {
				zone = nil
				continue
			}else{
				break
			}
		}

		// output, _ := json.MarshalIndent(zone, "", "  ")
		// fmt.Printf("Contents: %v\n",string(output))

		// get the zone ID and root
		domainid := zone["DOMAINID"].(json.Number)
		root := zone["DOMAIN"].(string)

		token := os.Args[4]
		target := "_acme-challenge."+domain

		// see if there's already a resource
		req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
		q = req.URL.Query()
		q.Add("api_key", api_key)
		q.Add("api_action", "domain.resource.list")
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()
		data, _ = ioutil.ReadAll(resp.Body)
		d = json.NewDecoder(bytes.NewReader(data))
		d.UseNumber()
		_ = d.Decode(&input)
		// resourcelist, _ := json.MarshalIndent(input, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resourcelist))
		resourcelist := input.(map[string]interface{})["DATA"].([]interface{})
		var resource map[string]interface{}
		for _, rv := range resourcelist {
			resource = rv.(map[string]interface{})
			 // fmt.Printf("Looking For: [%s]; Name: [%s]; Target: [%s]; Type: %s\n", target, resource["NAME"].(string)+"."+root, resource["TARGET"].(string), resource["TYPE"].(string))
			if resource["TYPE"].(string) == "TXT" && resource["NAME"].(string)+"."+root == target {
				break
			}else{
				resource = nil
				continue
			}
		}

		// resoutput, _ := json.MarshalIndent(resource, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resoutput))
		req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
		q = req.URL.Query()
		q.Add("api_key", api_key)
		if resource == nil {
			// create
			q.Add("api_action", "domain.resource.create")
			q.Add("Type", "TXT")
			q.Add("Name", target)
		}else{
			// update
			q.Add("api_action", "domain.resource.update")
			q.Add("ResourceID", fmt.Sprintf("%v", resource["RESOURCEID"].(json.Number)))
		}
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		q.Add("Target", token)
		q.Add("TTL_sec", "300")
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			data, _ = ioutil.ReadAll(resp.Body)
			fmt.Printf("Error: %s\n", string(data))
		}else{
			// need to loop: check every 10 seconds to see if the entry has propagated
			var resolver dns.Client
			resolver_host := os.Getenv("DEHYDRATED_RESOLVER")
			if resolver_host != "" {
				resolver = dns.Client{}
			}
			for {
				time.Sleep(10 * time.Second)
				if resolver_host != "" {
					msg := new(dns.Msg)
					msg.Id = dns.Id()
					msg.RecursionDesired = false
					msg.Question = make([]dns.Question, 1)
					msg.Question[0] = dns.Question{target+".", dns.TypeTXT, dns.ClassINET}
					in, _, err1 := resolver.Exchange(msg, resolver_host+":53")
					if err1 == nil {
						if len(in.Answer) > 0 {
							txt := in.Answer[0].(*dns.TXT)
							if txt.Txt[0] == token {
								break
							}
						}
					}
				}else{
					challenges, err1 := net.LookupTXT(target)
					if err1 == nil && len(challenges) > 0 {
						if challenges[0] == token {
							break
						}
					}
				}
			}
		}
	}else if handler == "clean_challenge" {
		domain := os.Args[2]
		// filename := os.Args[3]

		//fmt.Printf("Handler: %s\n", handler)
		domains := input.(map[string]interface{})["DATA"].([]interface{})
		var zone map[string]interface{}
		for _, v := range domains {
			zone = v.(map[string]interface{})
			if !strings.HasSuffix(domain, zone["DOMAIN"].(string)) {
				zone = nil
				continue
			}else{
				break
			}
		}

		// output, _ := json.MarshalIndent(zone, "", "  ")
		// fmt.Printf("Contents: %v\n",string(output))

		// get the zone ID and root
		domainid := zone["DOMAINID"].(json.Number)
		root := zone["DOMAIN"].(string)

		target := "_acme-challenge."+domain
		token := os.Args[4]

		// see if there's already a resource
		req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
		q = req.URL.Query()
		q.Add("api_key", api_key)
		q.Add("api_action", "domain.resource.list")
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()
		data, _ = ioutil.ReadAll(resp.Body)
		d = json.NewDecoder(bytes.NewReader(data))
		d.UseNumber()
		_ = d.Decode(&input)
		// resourcelist, _ := json.MarshalIndent(input, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resourcelist))
		resourcelist := input.(map[string]interface{})["DATA"].([]interface{})
		var resource map[string]interface{}
		for _, rv := range resourcelist {
			resource = rv.(map[string]interface{})
			 // fmt.Printf("Looking For: [%s]; Name: [%s]; Target: [%s]; Type: %s\n", target, resource["NAME"].(string)+"."+root, resource["TARGET"].(string), resource["TYPE"].(string))
			if resource["TYPE"].(string) == "TXT" && resource["NAME"].(string)+"."+root == target {
				break
			}else{
				resource = nil
				continue
			}
		}

		// resoutput, _ := json.MarshalIndent(resource, "", "  ")
		// fmt.Printf("Contents: %v\n",string(resoutput))
		if resource != nil {
			// flush it
			req, _ = http.NewRequest("GET", "https://api.linode.com", nil)
			q = req.URL.Query()
			q.Add("api_key", api_key)
			q.Add("api_action", "domain.resource.delete")
			q.Add("ResourceID", fmt.Sprintf("%v", resource["RESOURCEID"].(json.Number)))
		}
		q.Add("DomainID", fmt.Sprintf("%v", domainid))
		q.Add("Target", token)
		q.Add("TTL_sec", "60")
		req.URL.RawQuery = q.Encode()

		resp, _ = client.Do(req)
		defer resp.Body.Close()

		if resp.StatusCode != 200 {
			data, _ = ioutil.ReadAll(resp.Body)
			fmt.Printf("Error: %s\n", string(data))
		}
	}else if handler == "deploy_cert" {
		domain := os.Args[2]
		fmt.Printf("Deploying cert [%s] (full chain %s) and key [%s] for domain [%s]\n", os.Args[4], os.Args[5], os.Args[3], domain)
	}else if handler == "invalid_challenge" {
	}else if handler == "exit_hook" {
	}else if handler == "unchanged_cert" {
	}else if handler == "request_failure" {
	}
}
<|endoftext|>