<commit_before>package command

import (
	"fmt"
	"strings"
	"testing"

	"github.com/mitchellh/cli"
	"github.com/stretchr/testify/require"
)

func TestCommand_Ui(t *testing.T) {
	t.Parallel()

	type testCaseSetupFn func(*testing.T)

	cases := []struct {
		Name        string
		SetupFn     testCaseSetupFn
		Args        []string
		ExpectedURL string
	}{
		{
			Name:        "default values",
			ExpectedURL: "http://127.0.0.1:4646",
		},
		{
			Name:        "set namespace via flag",
			Args:        []string{"-namespace=dev"},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev",
		},
		{
			Name:        "set region via flag",
			Args:        []string{"-region=earth"},
			ExpectedURL: "http://127.0.0.1:4646?region=earth",
		},
		{
			Name:        "set region and namespace via flag",
			Args:        []string{"-region=earth", "-namespace=dev"},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev&region=earth",
		},
		{
			Name: "set namespace via env var",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev",
		},
		{
			Name: "set region via flag",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
			},
			ExpectedURL: "http://127.0.0.1:4646?region=earth",
		},
		{
			Name: "set region and namespace via flag",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev&region=earth",
		},
		{
			Name: "set region and namespace via flag",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev&region=earth",
		},
		{
			Name: "flags have higher precedence",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			Args: []string{
				"-region=mars",
				"-namespace=prod",
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=prod&region=mars",
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			// Make sure environment variables are clean.
			setEnv(t, "NOMAD_NAMESPACE", "")
			setEnv(t, "NOMAD_REGION", "")

			// Setup fake CLI UI and test case
			ui := cli.NewMockUi()
			cmd := &UiCommand{Meta: Meta{Ui: ui}}

			if tc.SetupFn != nil {
				tc.SetupFn(t)
			}

			// Don't try to open a browser.
			args := append(tc.Args, "-show-url")

			if code := cmd.Run(args); code != 0 {
				require.Equal(t, 0, code, "expected exit code 0, got %d", code)
			}

			got := ui.OutputWriter.String()
			expected := fmt.Sprintf("URL for web UI: %s", tc.ExpectedURL)
			require.Equal(t, expected, strings.TrimSpace(got))
		})
	}
}
<commit_msg>fix test names (#11374)<commit_after>package command

import (
	"fmt"
	"strings"
	"testing"

	"github.com/mitchellh/cli"
	"github.com/stretchr/testify/require"
)

func TestCommand_Ui(t *testing.T) {
	t.Parallel()

	type testCaseSetupFn func(*testing.T)

	cases := []struct {
		Name        string
		SetupFn     testCaseSetupFn
		Args        []string
		ExpectedURL string
	}{
		{
			Name:        "default values",
			ExpectedURL: "http://127.0.0.1:4646",
		},
		{
			Name:        "set namespace via flag",
			Args:        []string{"-namespace=dev"},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev",
		},
		{
			Name:        "set region via flag",
			Args:        []string{"-region=earth"},
			ExpectedURL: "http://127.0.0.1:4646?region=earth",
		},
		{
			Name:        "set region and namespace via flag",
			Args:        []string{"-region=earth", "-namespace=dev"},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev&region=earth",
		},
		{
			Name: "set namespace via env var",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev",
		},
		{
			Name: "set region via env var",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
			},
			ExpectedURL: "http://127.0.0.1:4646?region=earth",
		},
		{
			Name: "set region and namespace via env var",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev&region=earth",
		},
		{
			Name: "set region and namespace via env var",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=dev&region=earth",
		},
		{
			Name: "flags have higher precedence",
			SetupFn: func(t *testing.T) {
				setEnv(t, "NOMAD_REGION", "earth")
				setEnv(t, "NOMAD_NAMESPACE", "dev")
			},
			Args: []string{
				"-region=mars",
				"-namespace=prod",
			},
			ExpectedURL: "http://127.0.0.1:4646?namespace=prod&region=mars",
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			// Make sure environment variables are clean.
			setEnv(t, "NOMAD_NAMESPACE", "")
			setEnv(t, "NOMAD_REGION", "")

			// Setup fake CLI UI and test case
			ui := cli.NewMockUi()
			cmd := &UiCommand{Meta: Meta{Ui: ui}}

			if tc.SetupFn != nil {
				tc.SetupFn(t)
			}

			// Don't try to open a browser.
			args := append(tc.Args, "-show-url")

			if code := cmd.Run(args); code != 0 {
				require.Equal(t, 0, code, "expected exit code 0, got %d", code)
			}

			got := ui.OutputWriter.String()
			expected := fmt.Sprintf("URL for web UI: %s", tc.ExpectedURL)
			require.Equal(t, expected, strings.TrimSpace(got))
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package network

import (
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"strings"

	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/crypto/ssh"
	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/crypto/ssh/agent"
)

const (
	defaultPort = 22
	defaultUser = "core"
	rsaKeySize  = 2048
)

// Dialer is an interface for anything compatible with net.Dialer
type Dialer interface {
	Dial(network, address string) (net.Conn, error)
}

// SSHAgent can manage keys, updates cloud config, and loves ponies.
// The embedded dialer is used for establishing new SSH connections.
type SSHAgent struct {
	agent.Agent
	Dialer
	User     string
	Socket   string
	sockDir  string
	listener *net.UnixListener
}

// NewSSHAgent constructs a new SSHAgent using dialer to create ssh
// connections.
func NewSSHAgent(dialer Dialer) (*SSHAgent, error) {
	key, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
	if err != nil {
		return nil, err
	}

	keyring := agent.NewKeyring()
	err = keyring.Add(key, nil, "core@default")
	if err != nil {
		return nil, err
	}

	sockDir, err := ioutil.TempDir("", "mantle-ssh-")
	if err != nil {
		return nil, err
	}

	// Use a similar naming scheme to ssh-agent
	sockPath := fmt.Sprintf("%s/agent.%d", sockDir, os.Getpid())
	sockAddr := &net.UnixAddr{Name: sockPath, Net: "unix"}
	listener, err := net.ListenUnix("unix", sockAddr)
	if err != nil {
		os.RemoveAll(sockDir)
		return nil, err
	}

	a := &SSHAgent{
		Agent:    keyring,
		Dialer:   dialer,
		User:     defaultUser,
		Socket:   sockPath,
		sockDir:  sockDir,
		listener: listener,
	}

	go func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				return
			}
			go agent.ServeAgent(a, conn)
		}
	}()

	return a, nil
}

// Close closes the unix socket of the agent.
func (a *SSHAgent) Close() error {
	a.listener.Close()
	return os.RemoveAll(a.sockDir)
}

// Add port to host if not already set.
func ensurePortSuffix(host string, port int) string {
	switch {
	case !strings.Contains(host, ":"):
		return fmt.Sprintf("%s:%d", host, port)
	case strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]"):
		return fmt.Sprintf("%s:%d", host, port)
	case strings.HasPrefix(host, "[") && strings.Contains(host, "]:"):
		return host
	case strings.Count(host, ":") > 1:
		return fmt.Sprintf("[%s]:%d", host, port)
	default:
		return host
	}
}

// NewClient connects to the given host via SSH, the client will support
// agent forwarding but it must also be enabled per-session.
func (a *SSHAgent) NewClient(host string) (*ssh.Client, error) {
	sshcfg := ssh.ClientConfig{
		User: a.User,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeysCallback(a.Signers),
		},
	}

	addr := ensurePortSuffix(host, defaultPort)
	tcpconn, err := a.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}

	sshconn, chans, reqs, err := ssh.NewClientConn(tcpconn, addr, &sshcfg)
	if err != nil {
		return nil, err
	}

	client := ssh.NewClient(sshconn, chans, reqs)
	err = agent.ForwardToAgent(client, a)
	if err != nil {
		client.Close()
		return nil, err
	}

	return client, nil
}

// Enable SSH Agent forwarding for the given session.
// This is just for convenience.
func (a *SSHAgent) RequestAgentForwarding(session *ssh.Session) error {
	return agent.RequestAgentForwarding(session)
}
<commit_msg>network: remove RequestAgentForwarding, it has no callers<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package network

import (
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"strings"

	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/crypto/ssh"
	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/crypto/ssh/agent"
)

const (
	defaultPort = 22
	defaultUser = "core"
	rsaKeySize  = 2048
)

// Dialer is an interface for anything compatible with net.Dialer
type Dialer interface {
	Dial(network, address string) (net.Conn, error)
}

// SSHAgent can manage keys, updates cloud config, and loves ponies.
// The embedded dialer is used for establishing new SSH connections.
type SSHAgent struct {
	agent.Agent
	Dialer
	User     string
	Socket   string
	sockDir  string
	listener *net.UnixListener
}

// NewSSHAgent constructs a new SSHAgent using dialer to create ssh
// connections.
func NewSSHAgent(dialer Dialer) (*SSHAgent, error) {
	key, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
	if err != nil {
		return nil, err
	}

	keyring := agent.NewKeyring()
	err = keyring.Add(key, nil, "core@default")
	if err != nil {
		return nil, err
	}

	sockDir, err := ioutil.TempDir("", "mantle-ssh-")
	if err != nil {
		return nil, err
	}

	// Use a similar naming scheme to ssh-agent
	sockPath := fmt.Sprintf("%s/agent.%d", sockDir, os.Getpid())
	sockAddr := &net.UnixAddr{Name: sockPath, Net: "unix"}
	listener, err := net.ListenUnix("unix", sockAddr)
	if err != nil {
		os.RemoveAll(sockDir)
		return nil, err
	}

	a := &SSHAgent{
		Agent:    keyring,
		Dialer:   dialer,
		User:     defaultUser,
		Socket:   sockPath,
		sockDir:  sockDir,
		listener: listener,
	}

	go func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				return
			}
			go agent.ServeAgent(a, conn)
		}
	}()

	return a, nil
}

// Close closes the unix socket of the agent.
func (a *SSHAgent) Close() error {
	a.listener.Close()
	return os.RemoveAll(a.sockDir)
}

// Add port to host if not already set.
func ensurePortSuffix(host string, port int) string {
	switch {
	case !strings.Contains(host, ":"):
		return fmt.Sprintf("%s:%d", host, port)
	case strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]"):
		return fmt.Sprintf("%s:%d", host, port)
	case strings.HasPrefix(host, "[") && strings.Contains(host, "]:"):
		return host
	case strings.Count(host, ":") > 1:
		return fmt.Sprintf("[%s]:%d", host, port)
	default:
		return host
	}
}

// NewClient connects to the given host via SSH, the client will support
// agent forwarding but it must also be enabled per-session.
func (a *SSHAgent) NewClient(host string) (*ssh.Client, error) {
	sshcfg := ssh.ClientConfig{
		User: a.User,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeysCallback(a.Signers),
		},
	}

	addr := ensurePortSuffix(host, defaultPort)
	tcpconn, err := a.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}

	sshconn, chans, reqs, err := ssh.NewClientConn(tcpconn, addr, &sshcfg)
	if err != nil {
		return nil, err
	}

	client := ssh.NewClient(sshconn, chans, reqs)
	err = agent.ForwardToAgent(client, a)
	if err != nil {
		client.Close()
		return nil, err
	}

	return client, nil
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"

	yaml "gopkg.in/yaml.v2"

	stks "github.com/daidokoro/qaz/stacks"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/daidokoro/hcl"
)

// Config type for handling yaml config files
type Config struct {
	Region            string                 `yaml:"region,omitempty" json:"region,omitempty" hcl:"region,omitempty"`
	Project           string                 `yaml:"project" json:"project" hcl:"project"`
	GenerateDelimiter string                 `yaml:"gen_time,omitempty" json:"gen_time,omitempty" hcl:"gen_time,omitempty"`
	DeployDelimiter   string                 `yaml:"deploy_time,omitempty" json:"deploy_time,omitempty" hcl:"deploy_time,omitempty"`
	Global            map[string]interface{} `yaml:"global,omitempty" json:"global,omitempty" hcl:"global,omitempty"`
	Stacks            map[string]struct {
		DependsOn  []string               `yaml:"depends_on,omitempty" json:"depends_on,omitempty" hcl:"depends_on,omitempty"`
		Parameters []map[string]string    `yaml:"parameters,omitempty" json:"parameters,omitempty" hcl:"parameters,omitempty"`
		Policy     string                 `yaml:"policy,omitempty" json:"policy,omitempty" hcl:"policy,omitempty"`
		Profile    string                 `yaml:"profile,omitempty" json:"profile,omitempty" hcl:"profile,omitempty"`
		Source     string                 `yaml:"source,omitempty" json:"source,omitempty" hcl:"source,omitempty"`
		Bucket     string                 `yaml:"bucket,omitempty" json:"bucket,omitempty" hcl:"bucket,omitempty"`
		Role       string                 `yaml:"role,omitempty" json:"role,omitempty" hcl:"role,omitempty"`
		Tags       []map[string]string    `yaml:"tags,omitempty" json:"tags,omitempty" hcl:"tags,omitempty"`
		Timeout    int64                  `yaml:"timeout,omitempty" json:"timeout,omitempty" hcl:"timeout,omitempty"`
		CF         map[string]interface{} `yaml:"cf,omitempty" json:"cf,omitempty" hcl:"cf,omitempty"`
	} `yaml:"stacks" json:"stacks" hcl:"stacks"`
}

// Vars Returns map string of config values
func (c *Config) Vars() map[string]interface{} {
	m := make(map[string]interface{})
	m["global"] = c.Global
	m["region"] = c.Region
	m["project"] = c.Project

	for s, v := range c.Stacks {
		m[s] = v.CF
	}

	return m
}

// Adds parameters to given stack based on config
func (c *Config) parameters(s *stks.Stack) {

	for stk, val := range c.Stacks {
		if s.Name == stk {
			for _, param := range val.Parameters {
				for k, v := range param {
					s.Parameters = append(s.Parameters, &cloudformation.Parameter{
						ParameterKey:   aws.String(k),
						ParameterValue: aws.String(v),
					})
				}

			}
		}
	}
}

// Adds stack tags to given stack based on config
func (c *Config) tags(s *stks.Stack) {

	for stk, val := range c.Stacks {
		if s.Name == stk {
			for _, param := range val.Tags {
				for k, v := range param {
					s.Tags = append(s.Tags, &cloudformation.Tag{
						Key:   aws.String(k),
						Value: aws.String(v),
					})
				}

			}
		}
	}
}

// Configure parses the config file abd setos stacjs abd ebv
func Configure(confSource string, conf string) error {

	if conf == "" {
		cfg, err := fetchContent(confSource)
		if err != nil {
			return err
		}

		conf = cfg
	}

	log.Debug("checking Config for HCL format...")
	if err := hcl.Unmarshal([]byte(conf), &config); err != nil {
		// fmt.Println(err)
		log.Debug(fmt.Sprintln("failed to parse hcl... moving to JSON/YAML...", err.Error()))
		if err := yaml.Unmarshal([]byte(conf), &config); err != nil {
			return err
		}
	}

	log.Debug(fmt.Sprintln("Config File Read:", config))

	stacks = make(map[string]*stks.Stack)

	// Get Stack Values
	for s, v := range config.Stacks {
		stacks[s] = &stks.Stack{
			Name:           s,
			Profile:        v.Profile,
			DependsOn:      v.DependsOn,
			Policy:         v.Policy,
			Source:         v.Source,
			Bucket:         v.Bucket,
			Role:           v.Role,
			DeployDelims:   &config.DeployDelimiter,
			GenDelims:      &config.GenerateDelimiter,
			TemplateValues: config.Vars(),
			GenTimeFunc:    &GenTimeFunctions,
			DeployTimeFunc: &DeployTimeFunctions,
			Project:        &config.Project,
			Timeout:        v.Timeout,
		}

		stacks[s].SetStackName()

		// set session
		sess, err := manager.GetSess(stacks[s].Profile)
		if err != nil {
			return err
		}

		stacks[s].Session = sess

		// set parameters and tags, if any
		config.parameters(stacks[s])
		config.tags(stacks[s])

	}

	return nil
}
<commit_msg>added supported for executing gentime functions in config files<commit_after>package commands

import (
	"bytes"
	"fmt"
	"text/template"

	yaml "gopkg.in/yaml.v2"

	stks "github.com/daidokoro/qaz/stacks"
	"github.com/daidokoro/qaz/utils"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudformation"
	"github.com/daidokoro/hcl"
)

// Config type for handling yaml config files
type Config struct {
	String            string                 `yaml:"-" json:"-" hcl:"-"`
	Region            string                 `yaml:"region,omitempty" json:"region,omitempty" hcl:"region,omitempty"`
	Project           string                 `yaml:"project" json:"project" hcl:"project"`
	GenerateDelimiter string                 `yaml:"gen_time,omitempty" json:"gen_time,omitempty" hcl:"gen_time,omitempty"`
	DeployDelimiter   string                 `yaml:"deploy_time,omitempty" json:"deploy_time,omitempty" hcl:"deploy_time,omitempty"`
	Global            map[string]interface{} `yaml:"global,omitempty" json:"global,omitempty" hcl:"global,omitempty"`
	Stacks            map[string]struct {
		DependsOn  []string               `yaml:"depends_on,omitempty" json:"depends_on,omitempty" hcl:"depends_on,omitempty"`
		Parameters []map[string]string    `yaml:"parameters,omitempty" json:"parameters,omitempty" hcl:"parameters,omitempty"`
		Policy     string                 `yaml:"policy,omitempty" json:"policy,omitempty" hcl:"policy,omitempty"`
		Profile    string                 `yaml:"profile,omitempty" json:"profile,omitempty" hcl:"profile,omitempty"`
		Source     string                 `yaml:"source,omitempty" json:"source,omitempty" hcl:"source,omitempty"`
		Bucket     string                 `yaml:"bucket,omitempty" json:"bucket,omitempty" hcl:"bucket,omitempty"`
		Role       string                 `yaml:"role,omitempty" json:"role,omitempty" hcl:"role,omitempty"`
		Tags       []map[string]string    `yaml:"tags,omitempty" json:"tags,omitempty" hcl:"tags,omitempty"`
		Timeout    int64                  `yaml:"timeout,omitempty" json:"timeout,omitempty" hcl:"timeout,omitempty"`
		CF         map[string]interface{} `yaml:"cf,omitempty" json:"cf,omitempty" hcl:"cf,omitempty"`
	} `yaml:"stacks" json:"stacks" hcl:"stacks"`
}

// Vars Returns map string of config values
func (c *Config) Vars() map[string]interface{} {
	m := make(map[string]interface{})
	m["global"] = c.Global
	m["region"] = c.Region
	m["project"] = c.Project

	for s, v := range c.Stacks {
		m[s] = v.CF
	}

	return m
}

// Adds parameters to given stack based on config
func (c *Config) parameters(s *stks.Stack) {

	for stk, val := range c.Stacks {
		if s.Name == stk {
			for _, param := range val.Parameters {
				for k, v := range param {
					s.Parameters = append(s.Parameters, &cloudformation.Parameter{
						ParameterKey:   aws.String(k),
						ParameterValue: aws.String(v),
					})
				}

			}
		}
	}
}

// Adds stack tags to given stack based on config
func (c *Config) tags(s *stks.Stack) {

	for stk, val := range c.Stacks {
		if s.Name == stk {
			for _, param := range val.Tags {
				for k, v := range param {
					s.Tags = append(s.Tags, &cloudformation.Tag{
						Key:   aws.String(k),
						Value: aws.String(v),
					})
				}

			}
		}
	}
}

// execute gentime/deploytime functions in config
func (c *Config) callFunctions() error {

	log.Debug("calling functions in config file")
	// define Delims
	left, right := func() (string, string) {
		if utils.IsJSON(c.String) || utils.IsHCL(c.String) {
			return "${", "}"
		}
		return "!", "\n"
	}()

	// create template
	t, err := template.New("config-template").Delims(left, right).Funcs(GenTimeFunctions).Parse(c.String)
	if err != nil {
		return err
	}

	// so that we can write to string
	var doc bytes.Buffer

	t.Execute(&doc, nil)
	c.String = doc.String()
	log.Debug(fmt.Sprintln("config:", c.String))
	return nil
}

// Configure parses the config file abd setos stacjs abd ebv
func Configure(confSource string, conf string) error {

	if conf == "" {
		cfg, err := fetchContent(confSource)
		if err != nil {
			return err
		}

		config.String = cfg
	}

	// execute Functions
	if err := config.callFunctions(); err != nil {
		return fmt.Errorf("failed to run template functions in config: %s", err)
	}

	fmt.Println(config.String)

	log.Debug("checking Config for HCL format...")
	if err := hcl.Unmarshal([]byte(config.String), &config); err != nil {
		// fmt.Println(err)
		log.Debug(fmt.Sprintln("failed to parse hcl... moving to JSON/YAML...", err.Error()))
		if err := yaml.Unmarshal([]byte(conf), &config); err != nil {
			return err
		}
	}

	log.Debug(fmt.Sprintln("Config File Read:", config))

	stacks = make(map[string]*stks.Stack)

	// Get Stack Values
	for s, v := range config.Stacks {
		stacks[s] = &stks.Stack{
			Name:           s,
			Profile:        v.Profile,
			DependsOn:      v.DependsOn,
			Policy:         v.Policy,
			Source:         v.Source,
			Bucket:         v.Bucket,
			Role:           v.Role,
			DeployDelims:   &config.DeployDelimiter,
			GenDelims:      &config.GenerateDelimiter,
			TemplateValues: config.Vars(),
			GenTimeFunc:    &GenTimeFunctions,
			DeployTimeFunc: &DeployTimeFunctions,
			Project:        &config.Project,
			Timeout:        v.Timeout,
		}

		stacks[s].SetStackName()

		// set session
		sess, err := manager.GetSess(stacks[s].Profile)
		if err != nil {
			return err
		}

		stacks[s].Session = sess

		// set parameters and tags, if any
		config.parameters(stacks[s])
		config.tags(stacks[s])

	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 Mathew Robinson <mrobinson@praelatus.io>. All rights reserved.
// Use of this source code is governed by the AGPLv3 license that can be found in
// the LICENSE file.

package commands

import (
	"fmt"
	"os"

	"github.com/urfave/cli"

	"github.com/praelatus/praelatus/config"
)

// ShowConfig will print the current configuration.
func ShowConfig(c *cli.Context) error {
	fmt.Println(config.Cfg)
	return nil
}

// GenConfig will generate a config.json based on environment variables
func GenConfig(c *cli.Context) error {
	f, err := os.Open("config.json")
	if err != nil && !os.IsNotExist(err) {
		fmt.Println(err)
		os.Exit(1)
	}

	defer f.Close()

	fmt.Println(config.Cfg)

	if os.IsNotExist(err) {
		f, _ = os.Create("config.json")
		_, err = f.Write([]byte(config.Cfg.String()))
		return err
	}

	fmt.Println("config.json already exists exiting...")
	return nil
}
<commit_msg>Rewrite config commands<commit_after>// Copyright 2017 Mathew Robinson <mrobinson@praelatus.io>. All rights reserved.
// Use of this source code is governed by the AGPLv3 license that can be found in
// the LICENSE file.

package commands

import (
	"fmt"
	"os"

	"github.com/praelatus/praelatus/config"
	"github.com/spf13/cobra"
)

func init() {
	configCmd.AddCommand(show)
}

var configCmd = &cobra.Command{
	Use:   "config",
	Short: "Commands for interacting with the config file.",
}

var show = &cobra.Command{
	Use:   "show",
	Short: "Show the current configuration.",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println(config.Cfg)
	},
}

var gen = &cobra.Command{
	Use:   "gen",
	Short: "Generate a config file based on environment variables and defaults.",
	Run: func(cmd *cobra.Command, args []string) {
		f, err := os.Open("config.json")
		if err != nil && !os.IsNotExist(err) {
			fmt.Println(err)
			os.Exit(1)
		}

		defer f.Close()

		fmt.Println(config.Cfg)

		f, _ = os.Create("config.json")
		_, err = f.Write([]byte(config.Cfg.String()))
		if err != nil {
			fmt.Println("ERROR:", err)
		}
	},
}
<|endoftext|>


<commit_before>package commands

import (
	"log"
	"regexp"
	"strings"
)

// Command is a struct which separates the user's input for easier handling of commands
type Command struct {
	Raw       string   // Raw is full string passed to the command
	IsCommand bool     // Confirmation if this is a command or just a regular message
	Message   string   // Full string without the prefix
	Command   string   // Command is the first argument passed to the bot
	FullArg   string   // Full argument as a single string
	Args      []string // Arguments as array
}

// Parse the arguments returning the Command to execute and the arguments passed to it
func Parse(c string, prefix string) *Command {
	cmd := &Command{Raw: c}
	c = strings.TrimSpace(c)
	cmd.IsCommand = strings.HasPrefix(c, prefix)

	// we can stop here if no prefix is detected
	if !cmd.IsCommand {
		cmd.Message = c
		return cmd
	}

	// Trim the prefix and extra spaces
	cmd.Message = strings.TrimPrefix(c, prefix)
	cmd.Message = strings.TrimSpace(cmd.Message)

	// check if we have the command and not only the prefix
	cmd.IsCommand = cmd.Message != ""
	if !cmd.IsCommand {
		return cmd
	}

	// get the command
	pieces := strings.SplitN(cmd.Message, " ", 2)
	cmd.Command = pieces[0]

	// get the arguments and remove extra spaces
	if len(pieces) > 1 {
		reg, err := regexp.Compile("\\s+")
		if err != nil {
			log.Fatal(err)
		}
		cmd.FullArg = reg.ReplaceAllString(strings.TrimSpace(pieces[1]), " ")
		cmd.Args = strings.Split(cmd.FullArg, " ")
	}

	return cmd
}
<commit_msg>Refactoring, extracted removeExtraSpaces method<commit_after>package commands

import (
	"regexp"
	"strings"
)

// Command is a struct which separates the user's input for easier handling of commands
type Command struct {
	Raw       string   // Raw is full string passed to the command
	IsCommand bool     // Confirmation if this is a command or just a regular message
	Message   string   // Full string without the prefix
	Command   string   // Command is the first argument passed to the bot
	FullArg   string   // Full argument as a single string
	Args      []string // Arguments as array
}

// Parse the arguments returning the Command to execute and the arguments passed to it
func Parse(c string, prefix string) *Command {
	cmd := &Command{Raw: c}
	c = strings.TrimSpace(c)
	cmd.IsCommand = strings.HasPrefix(c, prefix)

	// we can stop here if no prefix is detected
	if !cmd.IsCommand {
		cmd.Message = c
		return cmd
	}

	// Trim the prefix and extra spaces
	cmd.Message = strings.TrimPrefix(c, prefix)
	cmd.Message = strings.TrimSpace(cmd.Message)

	// check if we have the command and not only the prefix
	cmd.IsCommand = cmd.Message != ""
	if !cmd.IsCommand {
		return cmd
	}

	// get the command
	pieces := strings.SplitN(cmd.Message, " ", 2)
	cmd.Command = pieces[0]

	if len(pieces) > 1 {
		// get the arguments and remove extra spaces
		cmd.FullArg = removeExtraSpaces(pieces[1])
		cmd.Args = strings.Split(cmd.FullArg, " ")
	}

	return cmd
}

func removeExtraSpaces(args string) string {
	reg := regexp.MustCompile("\\s+") // Matches one or more spaces
	return reg.ReplaceAllString(strings.TrimSpace(args), " ")
}
<|endoftext|>


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

package commands

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"regexp"
	"strings"
)

type cmdQuotes struct {
	name        string
	description string
	syntax      string
	re          *regexp.Regexp
	config      QuotesConfig
}

type QuotesConfig struct {
	Enabled  bool
	Endpoint string
	User     string
	Password string
}

func NewCmdQuotes(config QuotesConfig) Command {
	return &cmdQuotes{
		syntax:      "!q [message]",
		description: "If message, add a quote. Otherwise, return a random one",
		re:          regexp.MustCompile(`^!q($| .+$)`),
		config:      config,
	}
}

func (cmd *cmdQuotes) Enabled() bool {
	return cmd.config.Enabled
}

func (cmd *cmdQuotes) Syntax() string {
	return cmd.syntax
}

func (cmd *cmdQuotes) Description() string {
	return cmd.description
}

func (cmd *cmdQuotes) Match(text string) bool {
	return cmd.re.MatchString(text)
}

func (cmd *cmdQuotes) Run(w io.Writer, title, from, text string) error {
	var err error

	quoteText := strings.TrimSpace(strings.TrimPrefix(text, "!q"))
	if quoteText == "" {
		err = cmd.randomQuote(w, title)
	} else {
		err = cmd.addQuote(w, title, quoteText)
	}
	return err
}

func (cmd *cmdQuotes) Shutdown() error {
	return nil
}

func (cmd *cmdQuotes) randomQuote(w io.Writer, title string) error {
	req, err := http.NewRequest("GET", cmd.config.Endpoint, nil)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot get quote\n", title)
		return err
	}
	req.SetBasicAuth(cmd.config.User, cmd.config.Password)
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Do(req)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot get quote\n", title)
		return err
	}

	quotes, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot get quote\n", title)
		return err
	}
	lines := strings.Split(string(quotes), "\n")
	if len(lines) <= 1 { // If there aren't quotes, lines == []string{""}
		fmt.Fprintf(w, "msg %v error: no quotes\n", title)
		return errors.New("no quotes")
	}

	rndInt := rand.Intn(len(lines) - 1)
	rndQuote := lines[rndInt]

	fmt.Fprintf(w, "msg %v Random quote: %v\n", title, rndQuote)
	return nil
}

func (cmd *cmdQuotes) addQuote(w io.Writer, title string, text string) error {
	r := strings.NewReader(text)
	req, err := http.NewRequest("POST", cmd.config.Endpoint, r)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot add quote\n", title)
		return err
	}
	req.SetBasicAuth(cmd.config.User, cmd.config.Password)
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Do(req)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot add quote\n", title)
		return err
	}
	res.Body.Close()

	if res.StatusCode != http.StatusOK {
		fmt.Fprintf(w, "msg %v error (%v): Cannot add quote\n", title, res.StatusCode)
		return fmt.Errorf("cannot add quote (%v - %v: %v)", res.StatusCode, title, text)
	}

	fmt.Fprintf(w, "msg %v New quote added: %v\n", title, text)
	return nil
}
<commit_msg>commands/quotes: Handle all HTTP errors<commit_after>// Copyright 2015 The tgbot Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package commands

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"regexp"
	"strings"
)

type cmdQuotes struct {
	name        string
	description string
	syntax      string
	re          *regexp.Regexp
	config      QuotesConfig
}

type QuotesConfig struct {
	Enabled  bool
	Endpoint string
	User     string
	Password string
}

func NewCmdQuotes(config QuotesConfig) Command {
	return &cmdQuotes{
		syntax:      "!q [message]",
		description: "If message, add a quote. Otherwise, return a random one",
		re:          regexp.MustCompile(`^!q($| .+$)`),
		config:      config,
	}
}

func (cmd *cmdQuotes) Enabled() bool {
	return cmd.config.Enabled
}

func (cmd *cmdQuotes) Syntax() string {
	return cmd.syntax
}

func (cmd *cmdQuotes) Description() string {
	return cmd.description
}

func (cmd *cmdQuotes) Match(text string) bool {
	return cmd.re.MatchString(text)
}

func (cmd *cmdQuotes) Run(w io.Writer, title, from, text string) error {
	var err error

	quoteText := strings.TrimSpace(strings.TrimPrefix(text, "!q"))
	if quoteText == "" {
		err = cmd.randomQuote(w, title)
	} else {
		err = cmd.addQuote(w, title, quoteText)
	}
	return err
}

func (cmd *cmdQuotes) Shutdown() error {
	return nil
}

func (cmd *cmdQuotes) randomQuote(w io.Writer, title string) error {
	req, err := http.NewRequest("GET", cmd.config.Endpoint, nil)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot get quote\n", title)
		return err
	}
	req.SetBasicAuth(cmd.config.User, cmd.config.Password)
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Do(req)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot get quote\n", title)
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		fmt.Fprintf(w, "msg %v error (%v): Cannot get quote\n", title, res.StatusCode)
		return fmt.Errorf("cannot get quote (%v)", res.StatusCode)
	}

	quotes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot get quote\n", title)
		return err
	}
	lines := strings.Split(string(quotes), "\n")
	if len(lines) <= 1 { // If there aren't quotes, lines == []string{""}
		fmt.Fprintf(w, "msg %v error: no quotes\n", title)
		return errors.New("no quotes")
	}

	rndInt := rand.Intn(len(lines) - 1)
	rndQuote := lines[rndInt]

	fmt.Fprintf(w, "msg %v Random quote: %v\n", title, rndQuote)
	return nil
}

func (cmd *cmdQuotes) addQuote(w io.Writer, title string, text string) error {
	r := strings.NewReader(text)
	req, err := http.NewRequest("POST", cmd.config.Endpoint, r)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot add quote\n", title)
		return err
	}
	req.SetBasicAuth(cmd.config.User, cmd.config.Password)
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Do(req)
	if err != nil {
		fmt.Fprintf(w, "msg %v error: Cannot add quote\n", title)
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		fmt.Fprintf(w, "msg %v error (%v): Cannot add quote\n", title, res.StatusCode)
		return fmt.Errorf("cannot add quote (%v - %v: %v)", res.StatusCode, title, text)
	}

	fmt.Fprintf(w, "msg %v New quote added: %v\n", title, text)
	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.

package commands

import (
	"fmt"
	"html"
	"html/template"
	"log"
	"net/http"
	"os"
	"strings"

	"labix.org/v2/mgo/bson"

	"github.com/GeertJohan/go.rice"
	"github.com/gin-gonic/gin"
	"github.com/pilu/fresh/runner/runnerutils"
	"github.com/spf13/cast"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const pLimit = 10

var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "Server for feeds",
	Long:  `Dagobah will serve all feeds listed in the config file.`,
	Run:   serverRun,
}

func init() {
	serverCmd.Flags().Int("port", 1138, "Port to run Dagobah server on")
	viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
}

func serverRun(cmd *cobra.Command, args []string) {
	Server()
}

func Server() {
	port := viper.GetString("port")

	r := gin.Default()

	if os.Getenv("DEV") != "" {
		r.Use(RunnerMiddleware())
	}

	templates := loadTemplates("home.html", "channels.html", "items.html", "main.html")
	r.HTMLTemplates = templates

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	r.GET("/", homeRoute)
	r.GET("/post/*key", postRoute)
	r.GET("/search/*query", searchRoute)
	r.GET("/static/*filepath", staticServe)
	r.GET("/channel/*key", channelRoute)
	fmt.Println("Running on port:", port)
	r.Run(":" + port)
}

func staticServe(c *gin.Context) {
	static, err := rice.FindBox("static")
	if err != nil {
		log.Fatal(err)
	}
	original := c.Req.URL.Path
	c.Req.URL.Path = c.Params.ByName("filepath")
	http.FileServer(static.HTTPBox()).ServeHTTP(c.Writer, c.Req)
	c.Req.URL.Path = original
}

func RunnerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		if runnerutils.HasErrors() {
			runnerutils.RenderError(c.Writer)
			c.Abort(500)
		}
	}
}

func loadTemplates(list ...string) *template.Template {
	templateBox, err := rice.FindBox("templates")
	if err != nil {
		log.Fatal(err)
	}

	templates := template.New("")

	for _, x := range list {
		templateString, err := templateBox.String(x)
		if err != nil {
			log.Fatal(err)
		}

		// get file contents as string
		_, err = templates.New(x).Parse(templateString)
		if err != nil {
			log.Fatal(err)
		}
	}

	funcMap := template.FuncMap{
		"html":  ProperHtml,
		"title": func(a string) string { return strings.Title(a) },
	}

	templates.Funcs(funcMap)

	return templates
}

func ProperHtml(text string) template.HTML {
	if strings.Contains(text, "content:encoded>") || strings.Contains(text, "content/:encoded>") {
		text = html.UnescapeString(text)
	}
	return template.HTML(html.UnescapeString(template.HTMLEscapeString(text)))
}

func postRoute(c *gin.Context) {

	key := c.Params.ByName("key")

	if len(key) < 2 {
		four04(c, "Invalid Channel")
		return
	}

	key = key[1:]

	// TODO Need to find posts before and after this... not just the first ones
	var posts []Itm
	results := Items().Find(bson.M{}).Sort("-date").Limit(pLimit)
	results.All(&posts)

	var post Itm
	Items().Find(bson.M{"key": key}).Sort("-date").One(&post)

	channels := AllChannels()

	obj := gin.H{"title": post.Title, "post": post, "items": posts, "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "main.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}

func Offset(c *gin.Context) int {
	curPage := cast.ToInt(c.Req.FormValue("p")) - 1
	if curPage < 1 {
		return 0
	}
	return pLimit * curPage
}

func four04(c *gin.Context, message string) {
	c.HTML(404, "home.html", gin.H{"message": message, "title": viper.GetString("title")})
}

func homeRoute(c *gin.Context) {

	channels := AllChannels()

	var posts []Itm
	results := Items().Find(bson.M{}).Skip(Offset(c)).Sort("-date").Limit(pLimit)
	results.All(&posts)

	if len(posts) == 0 {
		four04(c, "No Articles")
		return
	}

	obj := gin.H{"title": viper.GetString("title"), "items": posts, "post": posts[0], "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "items.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}

func searchRoute(c *gin.Context) {
	q := c.Params.ByName("query")
	if len(q) < 2 {
		four04(c, "Query is too short. Please try a longer query.")
		return
	}

	q = q[1:]

	channels := AllChannels()

	var posts []Itm
	// TODO need to send a PR to Gustavo with support for textscore sorting
	results := Items().Find(bson.M{"$text": bson.M{"$search": q}}).Skip(Offset(c)).Limit(pLimit)

	results.All(&posts)

	if len(posts) == 0 {
		four04(c, "No Articles for query '"+q+"'")
		return
	}

	obj := gin.H{"title": q, "header": q, "items": posts, "post": posts[0], "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "items.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}

func channelRoute(c *gin.Context) {
	key := c.Params.ByName("key")
	if len(key) < 2 {
		four04(c, "Channel Not Found")
		return
	}

	key = key[1:]

	var posts []Itm
	results := Items().Find(bson.M{"channelkey": key}).Skip(Offset(c)).Sort("-date").Limit(pLimit)
	results.All(&posts)

	if len(posts) == 0 {
		four04(c, "No Articles")
		return
	}

	channels := AllChannels()

	var currentChannel Chnl
	err := Channels().Find(bson.M{"key": key}).One(&currentChannel)
	if err != nil {
		if string(err.Error()) == "not found" {
			four04(c, "Channel not found")
			return
		} else {
			fmt.Println(err)
		}
	}

	obj := gin.H{"title": currentChannel.Title, "header": currentChannel.Title, "post": posts[0], "items": posts, "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "items.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}
<commit_msg>move to gpkg.in again<commit_after>// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.

package commands

import (
	"fmt"
	"html"
	"html/template"
	"log"
	"net/http"
	"os"
	"strings"

	"gopkg.in/mgo.v2/bson"

	"github.com/GeertJohan/go.rice"
	"github.com/gin-gonic/gin"
	"github.com/pilu/fresh/runner/runnerutils"
	"github.com/spf13/cast"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const pLimit = 10

var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "Server for feeds",
	Long:  `Dagobah will serve all feeds listed in the config file.`,
	Run:   serverRun,
}

func init() {
	serverCmd.Flags().Int("port", 1138, "Port to run Dagobah server on")
	viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
}

func serverRun(cmd *cobra.Command, args []string) {
	Server()
}

func Server() {
	port := viper.GetString("port")

	r := gin.Default()

	if os.Getenv("DEV") != "" {
		r.Use(RunnerMiddleware())
	}

	templates := loadTemplates("home.html", "channels.html", "items.html", "main.html")
	r.HTMLTemplates = templates

	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	r.GET("/", homeRoute)
	r.GET("/post/*key", postRoute)
	r.GET("/search/*query", searchRoute)
	r.GET("/static/*filepath", staticServe)
	r.GET("/channel/*key", channelRoute)
	fmt.Println("Running on port:", port)
	r.Run(":" + port)
}

func staticServe(c *gin.Context) {
	static, err := rice.FindBox("static")
	if err != nil {
		log.Fatal(err)
	}
	original := c.Req.URL.Path
	c.Req.URL.Path = c.Params.ByName("filepath")
	http.FileServer(static.HTTPBox()).ServeHTTP(c.Writer, c.Req)
	c.Req.URL.Path = original
}

func RunnerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		if runnerutils.HasErrors() {
			runnerutils.RenderError(c.Writer)
			c.Abort(500)
		}
	}
}

func loadTemplates(list ...string) *template.Template {
	templateBox, err := rice.FindBox("templates")
	if err != nil {
		log.Fatal(err)
	}

	templates := template.New("")

	for _, x := range list {
		templateString, err := templateBox.String(x)
		if err != nil {
			log.Fatal(err)
		}

		// get file contents as string
		_, err = templates.New(x).Parse(templateString)
		if err != nil {
			log.Fatal(err)
		}
	}

	funcMap := template.FuncMap{
		"html":  ProperHtml,
		"title": func(a string) string { return strings.Title(a) },
	}

	templates.Funcs(funcMap)

	return templates
}

func ProperHtml(text string) template.HTML {
	if strings.Contains(text, "content:encoded>") || strings.Contains(text, "content/:encoded>") {
		text = html.UnescapeString(text)
	}
	return template.HTML(html.UnescapeString(template.HTMLEscapeString(text)))
}

func postRoute(c *gin.Context) {

	key := c.Params.ByName("key")

	if len(key) < 2 {
		four04(c, "Invalid Channel")
		return
	}

	key = key[1:]

	// TODO Need to find posts before and after this... not just the first ones
	var posts []Itm
	results := Items().Find(bson.M{}).Sort("-date").Limit(pLimit)
	results.All(&posts)

	var post Itm
	Items().Find(bson.M{"key": key}).Sort("-date").One(&post)

	channels := AllChannels()

	obj := gin.H{"title": post.Title, "post": post, "items": posts, "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "main.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}

func Offset(c *gin.Context) int {
	curPage := cast.ToInt(c.Req.FormValue("p")) - 1
	if curPage < 1 {
		return 0
	}
	return pLimit * curPage
}

func four04(c *gin.Context, message string) {
	c.HTML(404, "home.html", gin.H{"message": message, "title": viper.GetString("title")})
}

func homeRoute(c *gin.Context) {

	channels := AllChannels()

	var posts []Itm
	results := Items().Find(bson.M{}).Skip(Offset(c)).Sort("-date").Limit(pLimit)
	results.All(&posts)

	if len(posts) == 0 {
		four04(c, "No Articles")
		return
	}

	obj := gin.H{"title": viper.GetString("title"), "items": posts, "post": posts[0], "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "items.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}

func searchRoute(c *gin.Context) {
	q := c.Params.ByName("query")
	if len(q) < 2 {
		four04(c, "Query is too short. Please try a longer query.")
		return
	}

	q = q[1:]

	channels := AllChannels()

	var posts []Itm
	// TODO need to send a PR to Gustavo with support for textscore sorting
	results := Items().Find(bson.M{"$text": bson.M{"$search": q}}).Skip(Offset(c)).Limit(pLimit)

	results.All(&posts)

	if len(posts) == 0 {
		four04(c, "No Articles for query '"+q+"'")
		return
	}

	obj := gin.H{"title": q, "header": q, "items": posts, "post": posts[0], "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "items.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}

func channelRoute(c *gin.Context) {
	key := c.Params.ByName("key")
	if len(key) < 2 {
		four04(c, "Channel Not Found")
		return
	}

	key = key[1:]

	var posts []Itm
	results := Items().Find(bson.M{"channelkey": key}).Skip(Offset(c)).Sort("-date").Limit(pLimit)
	results.All(&posts)

	if len(posts) == 0 {
		four04(c, "No Articles")
		return
	}

	channels := AllChannels()

	var currentChannel Chnl
	err := Channels().Find(bson.M{"key": key}).One(&currentChannel)
	if err != nil {
		if string(err.Error()) == "not found" {
			four04(c, "Channel not found")
			return
		} else {
			fmt.Println(err)
		}
	}

	obj := gin.H{"title": currentChannel.Title, "header": currentChannel.Title, "post": posts[0], "items": posts, "channels": channels}

	if strings.ToLower(c.Req.Header.Get("X-Requested-With")) == "xmlhttprequest" {
		c.HTML(200, "items.html", obj)
	} else {
		c.HTML(200, "home.html", obj)
	}
}
<|endoftext|>


<commit_before>package mesh

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/emersion/go-upnp-igd"
)

var (
	ErrAllAddressesFailed = errors.New("all addresses failed to dial")
)

type Net struct {
	registry Registry
	accepted chan net.Conn
	reusable map[string]*reusableConn
	handlers map[string]Handler
	close    chan bool
}

func New(registry Registry) *Net {
	n := &Net{
		registry: registry,
		close:    make(chan bool),
		accepted: make(chan net.Conn),
		reusable: map[string]*reusableConn{},
		handlers: map[string]Handler{},
	}
	n.RegisterHandler("id", &ID{})
	n.RegisterHandler("yamux", &Yamux{})
	n.RegisterHandler("relay", &Relay{n})
	return n
}

func (n *Net) RegisterHandler(protocol string, handler Handler) error {
	n.handlers[protocol] = handler
	return nil
}

func (n *Net) Dial(ctx context.Context, peerID string, commands ...string) (net.Conn, error) {
	// fmt.Println("Dial()ing ", peerID)
	peerInfo, err := n.registry.GetPeerInfo(peerID)
	if err != nil {
		return nil, err
	}
	var conn net.Conn
	if reusableConn, ok := n.reusable[peerID]; ok {
		newConn, err := reusableConn.NewConn()
		if err != nil {
			// TODO remove reusable conn
		} else {
			// fmt.Println("dial reusing conn")
			conn = newConn
		}
	}
	if conn == nil {
		for _, addr := range peerInfo.Addresses {
			if strings.HasPrefix(addr, "relay:") {
				relayID := strings.Replace(addr, "relay:", "", 1)
				// fmt.Println("dialing the relay")
				relayConn, err := n.Dial(ctx, relayID)
				if err != nil {
					// TODO blacklist address for a bit
					// TODO hold error maybe?
					// return nil, err
					continue
				}
				localAddress := peerAddress{
					network: "tcp",
					peerID:  n.registry.GetLocalPeerInfo().ID,
				}
				remoteAddress := peerAddress{
					network: "tcp",
					peerID:  peerID,
				}
				conn = NewAddressableConn(relayConn, localAddress, remoteAddress)
				commands = append([]string{"relay"}, commands...)
			} else {
				addr = strings.Replace(addr, "tcp:", "", 1)
				// fmt.Println("dial dialing new conn to", addr)
				dialer := net.Dialer{Timeout: time.Second * 5}
				newConn, err := dialer.DialContext(ctx, "tcp", addr)
				if err != nil {
					// TODO blacklist address for a bit
					// TODO hold error maybe?
					// return nil, err
					continue
				}
				localAddress := peerAddress{
					network: "tcp",
					peerID:  n.registry.GetLocalPeerInfo().ID,
				}
				remoteAddress := peerAddress{
					network: "tcp",
					peerID:  peerID,
				}
				conn = NewAddressableConn(newConn, localAddress, remoteAddress)
				commands = append([]string{"id", "yamux"}, commands...)
			}
			break
		}
	}
	if conn == nil {
		return nil, ErrAllAddressesFailed
	}

	finalConn, err := n.Select(conn, commands...)
	if err != nil {
		if err := conn.Close(); err != nil {
			fmt.Println("could not close connection after failure to select")
		}
		fmt.Println("error selecting", err)
		return nil, err
	}

	return finalConn, nil
}

func (n *Net) Select(conn net.Conn, commands ...string) (net.Conn, error) {
	for _, command := range commands {
		if err := WriteToken(conn, []byte(command)); err != nil {
			return nil, err
		}
		token, err := ReadToken(conn)
		if err != nil {
			return nil, err
		}
		// fmt.Printf("Dialer got token %s for command %s\n", string(token), command)
		if string(token) != "ok" {
			return nil, errors.New("unexpected token response")
		}
		handler, ok := n.handlers[command]
		if !ok {
			return nil, errors.New("no such handler")
		}
		newConn, err := handler.Initiate(conn)
		if err != nil {
			return nil, err
		}
		// TODO maybe use a switch
		if reusableConn, ok := newConn.(*reusableConn); ok {
			go reusableConn.Accepted(n.accepted)
			// TODO lock
			// fmt.Println("client storing reusable")
			n.reusable[conn.RemoteAddr().String()] = reusableConn
		}
		conn = newConn
	}
	return conn, nil
}

// TODO do we need to return a listener?
func (n *Net) Listen(addr string) (Listener, string, error) {
	tcpListener, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, "", err
	}
	port := tcpListener.Addr().(*net.TCPAddr).Port
	addresses := GetAddresses(tcpListener)

	devices := make(chan igd.Device)
	go func() {

		for device := range devices {
			upnp := true
			upnpFlag := os.Getenv("UPNP")
			if upnpFlag != "" {
				upnp, _ = strconv.ParseBool(upnpFlag)
			}
			if !upnp {
				continue
			}
			externalAddress, err := device.GetExternalIPAddress()
			if err != nil {
				fmt.Println("could not get external ip")
				continue
			}
			desc := "nimona"
			ttl := time.Hour * 24 * 365
			if _, err := device.AddPortMapping(igd.TCP, port, port, desc, ttl); err != nil {
				fmt.Println("could not add port mapping", err)
			} else {
				addresses = append(addresses, fmt.Sprintf("tcp:%s:%d", externalAddress.String(), port))
			}
		}
	}()

	if err := igd.Discover(devices, 5*time.Second); err != nil {
		log.Println("could not discover devices")
	}

	addresses = append(addresses, "relay:andromeda.nimona.io")

	n.registry.PutLocalPeerInfo(&PeerInfo{
		ID:        n.registry.GetLocalPeerInfo().ID,
		Addresses: addresses,
	})

	go func() {
		for {
			conn := <-n.accepted
			go n.HandleSelection(conn)
		}
	}()

	closed := false

	go func() {
		closed = true
		<-n.close
		// fmt.Println("Closing")
		tcpListener.Close()
	}()

	go func() {
		for {
			conn, err := tcpListener.Accept()
			if err != nil {
				if closed {
					return
				}
				fmt.Println("Error accepting: ", err.Error())
				// TODO check conn is still alive and return
				return
			}
			localAddress := peerAddress{
				network: "tcp",
				peerID:  n.registry.GetLocalPeerInfo().ID,
			}
			remoteAddress := peerAddress{
				network: "tcp",
				peerID:  conn.RemoteAddr().String(),
			}
			conn = NewAddressableConn(conn, localAddress, remoteAddress)
			n.accepted <- conn
		}
	}()

	return nil, tcpListener.Addr().String(), nil
}

func (n *Net) Close() error {
	n.close <- true
	return nil
}

func (n *Net) HandleSelection(conn net.Conn) (net.Conn, error) {
	for {
		token, err := ReadToken(conn)
		if err != nil {
			return nil, err
		}
		// fmt.Println("selection handler got token", string(token))
		handler, ok := n.handlers[string(token)]
		if !ok {
			if err := WriteToken(conn, []byte("error")); err != nil {
				return nil, err
			}
			return nil, errors.New("no such handler")
		}
		if err := WriteToken(conn, []byte("ok")); err != nil {
			return nil, err
		}
		newConn, err := handler.Handle(conn)
		if err != nil {
			return nil, err
		}
		// TODO maybe use a switch
		if reusableConn, ok := newConn.(*reusableConn); ok {
			go reusableConn.Accepted(n.accepted)
			// TODO lock
			// TODO check if remote addr is indeed a peer id
			// fmt.Println("server storing reusable")
			n.reusable[conn.RemoteAddr().String()] = reusableConn
		}
		conn = newConn
	}
}
<commit_msg>Fix races<commit_after>package mesh

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/emersion/go-upnp-igd"
)

var (
	ErrAllAddressesFailed = errors.New("all addresses failed to dial")
)

type Net struct {
	registry     Registry
	accepted     chan net.Conn
	reusableLock sync.RWMutex
	reusable     map[string]*reusableConn
	handlers     map[string]Handler
	close        chan bool
}

func New(registry Registry) *Net {
	n := &Net{
		registry: registry,
		close:    make(chan bool),
		accepted: make(chan net.Conn),
		reusable: map[string]*reusableConn{},
		handlers: map[string]Handler{},
	}
	n.RegisterHandler("id", &ID{})
	n.RegisterHandler("yamux", &Yamux{})
	n.RegisterHandler("relay", &Relay{n})
	return n
}

func (n *Net) RegisterHandler(protocol string, handler Handler) error {
	n.handlers[protocol] = handler
	return nil
}

func (n *Net) Dial(ctx context.Context, peerID string, commands ...string) (net.Conn, error) {
	// fmt.Println("Dial()ing ", peerID)
	peerInfo, err := n.registry.GetPeerInfo(peerID)
	if err != nil {
		return nil, err
	}
	var conn net.Conn

	n.reusableLock.RLock()
	if reusableConn, ok := n.reusable[peerID]; ok {
		newConn, err := reusableConn.NewConn()
		if err != nil {
			// TODO remove reusable conn
		} else {
			// fmt.Println("dial reusing conn")
			conn = newConn
		}
	}
	n.reusableLock.RUnlock()
	if conn == nil {
		for _, addr := range peerInfo.Addresses {
			if strings.HasPrefix(addr, "relay:") {
				relayID := strings.Replace(addr, "relay:", "", 1)
				// fmt.Println("dialing the relay")
				relayConn, err := n.Dial(ctx, relayID)
				if err != nil {
					// TODO blacklist address for a bit
					// TODO hold error maybe?
					// return nil, err
					continue
				}
				localAddress := peerAddress{
					network: "tcp",
					peerID:  n.registry.GetLocalPeerInfo().ID,
				}
				remoteAddress := peerAddress{
					network: "tcp",
					peerID:  peerID,
				}
				conn = NewAddressableConn(relayConn, localAddress, remoteAddress)
				commands = append([]string{"relay"}, commands...)
			} else {
				addr = strings.Replace(addr, "tcp:", "", 1)
				// fmt.Println("dial dialing new conn to", addr)
				dialer := net.Dialer{Timeout: time.Second * 5}
				newConn, err := dialer.DialContext(ctx, "tcp", addr)
				if err != nil {
					// TODO blacklist address for a bit
					// TODO hold error maybe?
					// return nil, err
					continue
				}
				localAddress := peerAddress{
					network: "tcp",
					peerID:  n.registry.GetLocalPeerInfo().ID,
				}
				remoteAddress := peerAddress{
					network: "tcp",
					peerID:  peerID,
				}
				conn = NewAddressableConn(newConn, localAddress, remoteAddress)
				commands = append([]string{"id", "yamux"}, commands...)
			}
			break
		}
	}
	if conn == nil {
		return nil, ErrAllAddressesFailed
	}

	finalConn, err := n.Select(conn, commands...)
	if err != nil {
		if err := conn.Close(); err != nil {
			fmt.Println("could not close connection after failure to select")
		}
		fmt.Println("error selecting", err)
		return nil, err
	}

	return finalConn, nil
}

func (n *Net) Select(conn net.Conn, commands ...string) (net.Conn, error) {
	for _, command := range commands {
		if err := WriteToken(conn, []byte(command)); err != nil {
			return nil, err
		}
		token, err := ReadToken(conn)
		if err != nil {
			return nil, err
		}
		// fmt.Printf("Dialer got token %s for command %s\n", string(token), command)
		if string(token) != "ok" {
			return nil, errors.New("unexpected token response")
		}
		handler, ok := n.handlers[command]
		if !ok {
			return nil, errors.New("no such handler")
		}
		newConn, err := handler.Initiate(conn)
		if err != nil {
			return nil, err
		}
		// TODO maybe use a switch
		if reusableConn, ok := newConn.(*reusableConn); ok {
			go reusableConn.Accepted(n.accepted)
			// TODO lock
			// fmt.Println("client storing reusable")
			n.reusableLock.Lock()
			n.reusable[conn.RemoteAddr().String()] = reusableConn
			n.reusableLock.Unlock()
		}
		conn = newConn
	}
	return conn, nil
}

// TODO do we need to return a listener?
func (n *Net) Listen(addr string) (Listener, string, error) {
	tcpListener, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, "", err
	}
	port := tcpListener.Addr().(*net.TCPAddr).Port

	lock := sync.Mutex{}
	addresses := GetAddresses(tcpListener)

	devices := make(chan igd.Device)
	go func() {
		for device := range devices {
			upnp := true
			upnpFlag := os.Getenv("UPNP")
			if upnpFlag != "" {
				upnp, _ = strconv.ParseBool(upnpFlag)
			}
			if !upnp {
				continue
			}
			externalAddress, err := device.GetExternalIPAddress()
			if err != nil {
				fmt.Println("could not get external ip")
				continue
			}
			desc := "nimona"
			ttl := time.Hour * 24 * 365
			if _, err := device.AddPortMapping(igd.TCP, port, port, desc, ttl); err != nil {
				fmt.Println("could not add port mapping", err)
			} else {
				lock.Lock()
				addresses = append(addresses, fmt.Sprintf("tcp:%s:%d", externalAddress.String(), port))
				lock.Unlock()
			}
		}
	}()

	if err := igd.Discover(devices, 5*time.Second); err != nil {
		log.Println("could not discover devices")
	}

	lock.Lock()
	addresses = append(addresses, "relay:andromeda.nimona.io")
	lock.Unlock()

	n.registry.PutLocalPeerInfo(&PeerInfo{
		ID:        n.registry.GetLocalPeerInfo().ID,
		Addresses: addresses,
	})

	go func() {
		for {
			conn := <-n.accepted
			go n.HandleSelection(conn)
		}
	}()

	closed := false

	go func() {
		closed = true
		<-n.close
		// fmt.Println("Closing")
		tcpListener.Close()
	}()

	go func() {
		for {
			conn, err := tcpListener.Accept()
			if err != nil {
				if closed {
					return
				}
				fmt.Println("Error accepting: ", err.Error())
				// TODO check conn is still alive and return
				return
			}
			localAddress := peerAddress{
				network: "tcp",
				peerID:  n.registry.GetLocalPeerInfo().ID,
			}
			remoteAddress := peerAddress{
				network: "tcp",
				peerID:  conn.RemoteAddr().String(),
			}
			conn = NewAddressableConn(conn, localAddress, remoteAddress)
			n.accepted <- conn
		}
	}()

	return nil, tcpListener.Addr().String(), nil
}

func (n *Net) Close() error {
	n.close <- true
	return nil
}

func (n *Net) HandleSelection(conn net.Conn) (net.Conn, error) {
	for {
		token, err := ReadToken(conn)
		if err != nil {
			return nil, err
		}
		// fmt.Println("selection handler got token", string(token))
		handler, ok := n.handlers[string(token)]
		if !ok {
			if err := WriteToken(conn, []byte("error")); err != nil {
				return nil, err
			}
			return nil, errors.New("no such handler")
		}
		if err := WriteToken(conn, []byte("ok")); err != nil {
			return nil, err
		}
		newConn, err := handler.Handle(conn)
		if err != nil {
			return nil, err
		}
		// TODO maybe use a switch
		if reusableConn, ok := newConn.(*reusableConn); ok {
			go reusableConn.Accepted(n.accepted)
			// TODO lock
			// TODO check if remote addr is indeed a peer id
			// fmt.Println("server storing reusable")
			n.reusableLock.Lock()
			n.reusable[conn.RemoteAddr().String()] = reusableConn
			n.reusableLock.Unlock()
		}
		conn = newConn
	}
}
<|endoftext|>


<commit_before>package tests

import (
	"github.com/bozaro/tech-db-forum/generated/client"
	"github.com/bozaro/tech-db-forum/generated/client/operations"
)

func init() {
	Register(Checker{
		Name:        "forum_get_one_simple",
		Description: "",
		FnCheck:     CheckForumGetOneSimple,
		Deps: []string{
			"forum_create_simple",
		},
	})
	Register(Checker{
		Name:        "forum_get_one_notfound",
		Description: "",
		FnCheck:     CheckForumGetOneNotFound,
		Deps: []string{
			"forum_get_one_simple",
		},
	})
	Register(Checker{
		Name:        "forum_get_one_nocase",
		Description: "",
		FnCheck:     Modifications(CheckForumGetOneNocase),
		Deps: []string{
			"forum_get_one_simple",
		},
	})
	Register(Checker{
		Name:        "forum_get_one_counter",
		Description: "",
		FnCheck:     CheckForumGetOneCounter,
		Deps: []string{
			"posts_create_simple",
		},
	})
}

func CheckForumGetOneSimple(c *client.Forum) {
	forum := CreateForum(c, nil, nil)
	CheckForum(c, forum)
}

func CheckForumGetOneNotFound(c *client.Forum) {
	forum := RandomForum()
	_, err := c.Operations.ForumGetOne(operations.NewForumGetOneParams().
		WithSlug(forum.Slug).
		WithContext(Expected(404, nil, nil)))
	CheckIsType(operations.NewForumGetOneNotFound(), err)
}

func CheckForumGetOneNocase(c *client.Forum, m *Modify) {
	forum := CreateForum(c, nil, nil)
	slug := m.Case(forum.Slug)
	_, err := c.Operations.ForumGetOne(operations.NewForumGetOneParams().
		WithSlug(slug).
		WithContext(Expected(200, forum, nil)))
	CheckNil(err)
}

func CheckForumGetOneCounter(c *client.Forum) {
	forum := CreateForum(c, nil, nil)

	t1 := CreateThread(c, nil, forum, nil)
	CreatePosts(c, RandomPosts(3), t1)
	t2 := CreateThread(c, nil, forum, nil)
	CreatePosts(c, RandomPosts(5), t2)
	CreatePosts(c, RandomPosts(2), t1)

	forum.Threads = 2
	forum.Posts = 10
	CheckForum(c, forum)
}
<commit_msg>Добавил в тест forum_get_one_counter второй форум<commit_after>package tests

import (
	"github.com/bozaro/tech-db-forum/generated/client"
	"github.com/bozaro/tech-db-forum/generated/client/operations"
)

func init() {
	Register(Checker{
		Name:        "forum_get_one_simple",
		Description: "",
		FnCheck:     CheckForumGetOneSimple,
		Deps: []string{
			"forum_create_simple",
		},
	})
	Register(Checker{
		Name:        "forum_get_one_notfound",
		Description: "",
		FnCheck:     CheckForumGetOneNotFound,
		Deps: []string{
			"forum_get_one_simple",
		},
	})
	Register(Checker{
		Name:        "forum_get_one_nocase",
		Description: "",
		FnCheck:     Modifications(CheckForumGetOneNocase),
		Deps: []string{
			"forum_get_one_simple",
		},
	})
	Register(Checker{
		Name:        "forum_get_one_counter",
		Description: "",
		FnCheck:     CheckForumGetOneCounter,
		Deps: []string{
			"posts_create_simple",
		},
	})
}

func CheckForumGetOneSimple(c *client.Forum) {
	forum := CreateForum(c, nil, nil)
	CheckForum(c, forum)
}

func CheckForumGetOneNotFound(c *client.Forum) {
	forum := RandomForum()
	_, err := c.Operations.ForumGetOne(operations.NewForumGetOneParams().
		WithSlug(forum.Slug).
		WithContext(Expected(404, nil, nil)))
	CheckIsType(operations.NewForumGetOneNotFound(), err)
}

func CheckForumGetOneNocase(c *client.Forum, m *Modify) {
	forum := CreateForum(c, nil, nil)
	slug := m.Case(forum.Slug)
	_, err := c.Operations.ForumGetOne(operations.NewForumGetOneParams().
		WithSlug(slug).
		WithContext(Expected(200, forum, nil)))
	CheckNil(err)
}

func CheckForumGetOneCounter(c *client.Forum) {
	f1 := CreateForum(c, nil, nil)
	f2 := CreateForum(c, nil, nil)

	t1 := CreateThread(c, nil, f1, nil)
	CreatePosts(c, RandomPosts(3), t1)
	t2 := CreateThread(c, nil, f1, nil)
	CreatePosts(c, RandomPosts(5), t2)
	CreatePosts(c, RandomPosts(2), t1)
	t3 := CreateThread(c, nil, f2, nil)
	CreatePosts(c, RandomPosts(4), t3)

	f1.Threads = 2
	f1.Posts = 10
	CheckForum(c, f1)

	f2.Threads = 1
	f2.Posts = 4
	CheckForum(c, f2)
}
<|endoftext|>


<commit_before>package net

import (
	"errors"
	"strconv"

	"github.com/v2ray/v2ray-core/common/serial"
)

var (
	// ErrorInvalidPortRage indicates an error during port range parsing.
	ErrorInvalidPortRange = errors.New("Invalid port range.")
)

type Port serial.Uint16Literal

func PortFromBytes(port []byte) Port {
	return Port(serial.BytesLiteral(port).Uint16Value())
}

func PortFromInt(v int) (Port, error) {
	if v <= 0 || v > 65535 {
		return Port(0), ErrorInvalidPortRange
	}
	return Port(v), nil
}

func PortFromString(s string) (Port, error) {
	v, err := strconv.Atoi(s)
	if err != nil {
		return Port(0), ErrorInvalidPortRange
	}
	return PortFromInt(v)
}

func (this Port) Value() uint16 {
	return uint16(this)
}

func (this Port) Bytes() []byte {
	return serial.Uint16Literal(this).Bytes()
}

func (this Port) String() string {
	return serial.Uint16Literal(this).String()
}

type PortRange struct {
	From Port
	To   Port
}

func (this PortRange) Contains(port Port) bool {
	return this.From <= port && port <= this.To
}
<commit_msg>doc for port<commit_after>package net

import (
	"errors"
	"strconv"

	"github.com/v2ray/v2ray-core/common/serial"
)

var (
	// ErrorInvalidPortRage indicates an error during port range parsing.
	ErrorInvalidPortRange = errors.New("Invalid port range.")
)

// Port represents a network port in TCP and UDP protocol.
type Port serial.Uint16Literal

// PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order.
// @unsafe Caller must ensure that the byte array has at least 2 elements.
func PortFromBytes(port []byte) Port {
	return Port(serial.BytesLiteral(port).Uint16Value())
}

// PortFromInt converts an integer to a Port.
// @error when the integer is not positive or larger then 65535
func PortFromInt(v int) (Port, error) {
	if v <= 0 || v > 65535 {
		return Port(0), ErrorInvalidPortRange
	}
	return Port(v), nil
}

// PortFromString converts a string to a Port.
// @error when the string is not an integer or the integral value is a not a valid Port.
func PortFromString(s string) (Port, error) {
	v, err := strconv.Atoi(s)
	if err != nil {
		return Port(0), ErrorInvalidPortRange
	}
	return PortFromInt(v)
}

// Value return the correspoding uint16 value of this Port.
func (this Port) Value() uint16 {
	return uint16(this)
}

// Bytes returns the correspoding bytes of this Port, in big endian order.
func (this Port) Bytes() []byte {
	return serial.Uint16Literal(this).Bytes()
}

// String returns the string presentation of this Port.
func (this Port) String() string {
	return serial.Uint16Literal(this).String()
}

// PortRange represents a range of ports.
type PortRange struct {
	From Port
	To   Port
}

// Contains returns true if the given port is within the range of this PortRange.
func (this PortRange) Contains(port Port) bool {
	return this.From <= port && port <= this.To
}
<|endoftext|>


<commit_before>// this file contains a pre-processor to pull some stuff out of the markdown file before parsing it

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/mail"
	"os"
	"sort"
	"strings"

	"github.com/JackKnifed/blackfriday"
	tocRenderer "github.com/JackKnifed/goki/tocRenderer"
)

type PageMetadata struct {
	Keywords  map[string]bool
	Topics    map[string]bool
	Authors   map[string]bool
	Page      []byte
	Title     string
	FileStats os.FileInfo
}

const (
	bodyHtmlFlags = 0 |
		blackfriday.HTML_USE_XHTML |
		blackfriday.HTML_USE_SMARTYPANTS |
		blackfriday.HTML_SMARTYPANTS_FRACTIONS |
		blackfriday.HTML_SMARTYPANTS_LATEX_DASHES |
		blackfriday.HTML_ALERT_BOXES

	bodyExtensions = 0 |
		blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
		blackfriday.EXTENSION_TABLES |
		blackfriday.EXTENSION_FENCED_CODE |
		blackfriday.EXTENSION_AUTOLINK |
		blackfriday.EXTENSION_STRIKETHROUGH |
		blackfriday.EXTENSION_SPACE_HEADERS |
		blackfriday.EXTENSION_AUTO_HEADER_IDS |
		blackfriday.EXTENSION_TITLEBLOCK |
		blackfriday.EXTENSION_ALERT_BOXES

	tocHtmlFlags = 0 |
		blackfriday.HTML_USE_XHTML |
		blackfriday.HTML_SMARTYPANTS_FRACTIONS |
		blackfriday.HTML_SMARTYPANTS_LATEX_DASHES |
		blackfriday.HTML_TOC |
		blackfriday.HTML_OMIT_CONTENTS |
		blackfriday.HTML_FLAT_TOC

	tocExtensions = 0 |
		blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
		blackfriday.EXTENSION_TABLES |
		blackfriday.EXTENSION_FENCED_CODE |
		blackfriday.EXTENSION_AUTOLINK |
		blackfriday.EXTENSION_STRIKETHROUGH |
		blackfriday.EXTENSION_SPACE_HEADERS |
		blackfriday.EXTENSION_AUTO_HEADER_IDS |
		blackfriday.EXTENSION_TITLEBLOCK
)

// // take a given line, and check it against every possible type of tag
// func (pdata *PageMetadata) processMetadata(line []byte) {
// 	pdata.checkMatch(line, []byte("tag"), &pdata.Topics)
// 	pdata.checkMatch(line, []byte("topic"), &pdata.Topics)
// 	pdata.checkMatch(line, []byte("category"), &pdata.Topics)

// 	pdata.checkMatch(line, []byte("keyword"), &pdata.Keywords)
// 	pdata.checkMatch(line, []byte("meta"), &pdata.Keywords)

// 	pdata.checkMatch(line, []byte("author"), &pdata.Authors)
// 	pdata.checkMatch(line, []byte("maintainer"), &pdata.Authors)
// }

func (pdata *PageMetadata) checkMatch(
	input []byte, looking []byte, tracker *map[string]bool) {
	// trim off any blank spaces at the start of the line
	input = bytes.ToLower(bytes.TrimSpace(input))
	looking = bytes.ToLower(bytes.TrimSpace(looking))

	if !bytes.HasPrefix(input, looking) {
		return
	}

	input = bytes.TrimSpace(bytes.TrimPrefix(input, looking))

	if input[0] != '=' && input[0] != ':' {
		return
	}

	input = bytes.TrimSpace(input[1:])

	if input[0] == '=' || input[0] == ':' {
		return
	}

	input = bytes.Replace(input, []byte("\t"), []byte(" "), -1)

	parts := bytes.Split(input, []byte(" "))
	var cleanParts [][]byte

	for _, piece := range parts {
		if len(piece) > 0 {
			cleanParts = append(cleanParts[:], piece)
		}
	}

	key := bytes.Join(cleanParts, []byte("-"))

	if *tracker != nil {
		(*tracker)[string(key)] = true
	} else {
		*tracker = map[string]bool{string(key): true}
	}
}

func convertArr(in []string) map[string]bool {
	out := make(map[string]bool)
	for _, each := range in {
		out[strings.ToLower(each)] = true
	}
	return out
}

func (pdata *PageMetadata) LoadPage(pageName string) error {
	// open the file
	f, err := os.Open(pageName)
	if err != nil {
		return fmt.Errorf("problem opening file [%q] - %v", pageName, err)
	}
	defer f.Close()
	pdata.FileStats, err = os.Stat(pageName)

	parsed, err := mail.ReadMessage(f)
	if err != nil {
		return fmt.Errorf("problem parsing page [%q] - %v", pageName, err)
	}

	pdata.Topics = convertArr(parsed.Header["Topic"])
	pdata.Authors = convertArr(parsed.Header["Author"])
	pdata.Keywords = convertArr(parsed.Header["Keyword"])
	if len(parsed.Header["title"]) > 0 {
		pdata.Title = parsed.Header["Title"][0]
	} else {
		pdata.Title = pageName
	}

	tempPage, err := ioutil.ReadAll(parsed.Body)
	if err != nil {
		return fmt.Errorf("problem reading page from parsed version - %v", err)
	}

	pdata.Page = tempPage

	return nil
}

// given input, find where the next line starts
func (pdata *PageMetadata) findNextLine(input []byte) int {
	nextLine := 0
	for nextLine < len(input) && input[nextLine] != '\n' {
		nextLine++
	}
	if nextLine == len(input) {
		return -1
	} else {
		return nextLine
	}
}

// runs through all restricted tags, and looks for a match
// if matched, returns true, otherwise false
func (pdata *PageMetadata) MatchedTopic(checkTags []string) bool {
	for _, tag := range checkTags {
		if pdata.Topics[tag] == true {
			return true
		}
	}
	return false
}

// returns all the tags within a list as an array of strings
func (pdata *PageMetadata) ListMeta() (
	topics []string, keywords []string, authors []string) {
	for oneTag, _ := range pdata.Topics {
		topics = append(topics[:], oneTag)
	}
	sort.Strings(topics)

	for oneKeyword, _ := range pdata.Keywords {
		keywords = append(keywords[:], oneKeyword)
	}
	sort.Strings(keywords)

	for oneAuthor, _ := range pdata.Authors {
		authors = append(authors[:], oneAuthor)
	}
	sort.Strings(topics)
	return
}

func bodyParseMarkdown(input []byte) []byte {
	// set up the HTML renderer
	renderer := blackfriday.HtmlRenderer(bodyHtmlFlags, "", "")
	return blackfriday.Markdown(input, renderer, bodyExtensions)
}

func tocParseMarkdown(input []byte) []byte {
	// set up the HTML renderer
	renderer := tocRenderer.HtmlRenderer(tocHtmlFlags, "", "")
	return blackfriday.Markdown(input, renderer, tocExtensions)
}
<commit_msg>fixed the page titles<commit_after>// this file contains a pre-processor to pull some stuff out of the markdown file before parsing it

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/mail"
	"os"
	"sort"
	"strings"

	"github.com/JackKnifed/blackfriday"
	tocRenderer "github.com/JackKnifed/goki/tocRenderer"
)

type PageMetadata struct {
	Keywords  map[string]bool
	Topics    map[string]bool
	Authors   map[string]bool
	Page      []byte
	Title     string
	FileStats os.FileInfo
}

const (
	bodyHtmlFlags = 0 |
		blackfriday.HTML_USE_XHTML |
		blackfriday.HTML_USE_SMARTYPANTS |
		blackfriday.HTML_SMARTYPANTS_FRACTIONS |
		blackfriday.HTML_SMARTYPANTS_LATEX_DASHES |
		blackfriday.HTML_ALERT_BOXES

	bodyExtensions = 0 |
		blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
		blackfriday.EXTENSION_TABLES |
		blackfriday.EXTENSION_FENCED_CODE |
		blackfriday.EXTENSION_AUTOLINK |
		blackfriday.EXTENSION_STRIKETHROUGH |
		blackfriday.EXTENSION_SPACE_HEADERS |
		blackfriday.EXTENSION_AUTO_HEADER_IDS |
		blackfriday.EXTENSION_TITLEBLOCK |
		blackfriday.EXTENSION_ALERT_BOXES

	tocHtmlFlags = 0 |
		blackfriday.HTML_USE_XHTML |
		blackfriday.HTML_SMARTYPANTS_FRACTIONS |
		blackfriday.HTML_SMARTYPANTS_LATEX_DASHES |
		blackfriday.HTML_TOC |
		blackfriday.HTML_OMIT_CONTENTS |
		blackfriday.HTML_FLAT_TOC

	tocExtensions = 0 |
		blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
		blackfriday.EXTENSION_TABLES |
		blackfriday.EXTENSION_FENCED_CODE |
		blackfriday.EXTENSION_AUTOLINK |
		blackfriday.EXTENSION_STRIKETHROUGH |
		blackfriday.EXTENSION_SPACE_HEADERS |
		blackfriday.EXTENSION_AUTO_HEADER_IDS |
		blackfriday.EXTENSION_TITLEBLOCK
)

// // take a given line, and check it against every possible type of tag
// func (pdata *PageMetadata) processMetadata(line []byte) {
// 	pdata.checkMatch(line, []byte("tag"), &pdata.Topics)
// 	pdata.checkMatch(line, []byte("topic"), &pdata.Topics)
// 	pdata.checkMatch(line, []byte("category"), &pdata.Topics)

// 	pdata.checkMatch(line, []byte("keyword"), &pdata.Keywords)
// 	pdata.checkMatch(line, []byte("meta"), &pdata.Keywords)

// 	pdata.checkMatch(line, []byte("author"), &pdata.Authors)
// 	pdata.checkMatch(line, []byte("maintainer"), &pdata.Authors)
// }

func (pdata *PageMetadata) checkMatch(
	input []byte, looking []byte, tracker *map[string]bool) {
	// trim off any blank spaces at the start of the line
	input = bytes.ToLower(bytes.TrimSpace(input))
	looking = bytes.ToLower(bytes.TrimSpace(looking))

	if !bytes.HasPrefix(input, looking) {
		return
	}

	input = bytes.TrimSpace(bytes.TrimPrefix(input, looking))

	if input[0] != '=' && input[0] != ':' {
		return
	}

	input = bytes.TrimSpace(input[1:])

	if input[0] == '=' || input[0] == ':' {
		return
	}

	input = bytes.Replace(input, []byte("\t"), []byte(" "), -1)

	parts := bytes.Split(input, []byte(" "))
	var cleanParts [][]byte

	for _, piece := range parts {
		if len(piece) > 0 {
			cleanParts = append(cleanParts[:], piece)
		}
	}

	key := bytes.Join(cleanParts, []byte("-"))

	if *tracker != nil {
		(*tracker)[string(key)] = true
	} else {
		*tracker = map[string]bool{string(key): true}
	}
}

func convertArr(in []string) map[string]bool {
	out := make(map[string]bool)
	for _, each := range in {
		out[strings.ToLower(each)] = true
	}
	return out
}

func (pdata *PageMetadata) LoadPage(pageName string) error {
	// open the file
	f, err := os.Open(pageName)
	if err != nil {
		return fmt.Errorf("problem opening file [%q] - %v", pageName, err)
	}
	defer f.Close()
	pdata.FileStats, err = os.Stat(pageName)

	parsed, err := mail.ReadMessage(f)
	if err != nil {
		return fmt.Errorf("problem parsing page [%q] - %v", pageName, err)
	}

	pdata.Topics = convertArr(parsed.Header["Topic"])
	pdata.Authors = convertArr(parsed.Header["Author"])
	pdata.Keywords = convertArr(parsed.Header["Keyword"])
	if len(parsed.Header["Title"]) > 0 {
		pdata.Title = parsed.Header["Title"][0]
	} else {
		pdata.Title = pageName
	}

	tempPage, err := ioutil.ReadAll(parsed.Body)
	if err != nil {
		return fmt.Errorf("problem reading page from parsed version - %v", err)
	}

	pdata.Page = tempPage

	return nil
}

// given input, find where the next line starts
func (pdata *PageMetadata) findNextLine(input []byte) int {
	nextLine := 0
	for nextLine < len(input) && input[nextLine] != '\n' {
		nextLine++
	}
	if nextLine == len(input) {
		return -1
	} else {
		return nextLine
	}
}

// runs through all restricted tags, and looks for a match
// if matched, returns true, otherwise false
func (pdata *PageMetadata) MatchedTopic(checkTags []string) bool {
	for _, tag := range checkTags {
		if pdata.Topics[tag] == true {
			return true
		}
	}
	return false
}

// returns all the tags within a list as an array of strings
func (pdata *PageMetadata) ListMeta() (
	topics []string, keywords []string, authors []string) {
	for oneTag, _ := range pdata.Topics {
		topics = append(topics[:], oneTag)
	}
	sort.Strings(topics)

	for oneKeyword, _ := range pdata.Keywords {
		keywords = append(keywords[:], oneKeyword)
	}
	sort.Strings(keywords)

	for oneAuthor, _ := range pdata.Authors {
		authors = append(authors[:], oneAuthor)
	}
	sort.Strings(topics)
	return
}

func bodyParseMarkdown(input []byte) []byte {
	// set up the HTML renderer
	renderer := blackfriday.HtmlRenderer(bodyHtmlFlags, "", "")
	return blackfriday.Markdown(input, renderer, bodyExtensions)
}

func tocParseMarkdown(input []byte) []byte {
	// set up the HTML renderer
	renderer := tocRenderer.HtmlRenderer(tocHtmlFlags, "", "")
	return blackfriday.Markdown(input, renderer, tocExtensions)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/peteretelej/nasa"
)

var (
	interval = flag.Duration("interval", time.Second, "interval to change the random image")
	listen   = flag.String("listen", ":8080", "http server listening address")
)

func main() {
	flag.Parse()
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	if err := serve(*listen); err != nil {
		log.Fatalf("server crashed: %v", err)
	}
}

func serve(listenAddr string) error {
	tmpl, err := template.New("tmpl").Parse(tmpl)
	if err != nil {
		return fmt.Errorf("unable to parse template: %v", err)
	}
	apod, err := nasa.RandomAPOD()
	if err != nil {
		return fmt.Errorf("unable to fetch random apod: %v", err)
	}
	h := &handler{
		lastUpdate: time.Now(),
		cachedApod: apod,
		tmpl:       tmpl,
	}
	http.Handle("/", h)
	svr := &http.Server{
		Addr:           listenAddr,
		ReadTimeout:    15 * time.Second,
		WriteTimeout:   30 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	fmt.Printf("launching http server at %s\n", listenAddr)
	return svr.ListenAndServe()
}

type handler struct {
	tmpl *template.Template

	mu         sync.RWMutex // protects the values below
	lastUpdate time.Time
	cachedApod *nasa.Image
}

func (h *handler) last() time.Time {
	h.mu.RLock()
	last := h.lastUpdate
	h.mu.RUnlock()
	return last
}
func (h *handler) apod() nasa.Image {
	h.mu.RLock()
	apod := *h.cachedApod
	h.mu.RUnlock()
	return apod
}
func (h *handler) update(apod nasa.Image, t time.Time) {
	h.mu.Lock()
	h.cachedApod = &apod
	h.lastUpdate = time.Now()
	h.mu.Unlock()

}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if time.Now().Sub(h.last()) < *interval {
		h.render(w, r)
		return
	}
	apod := h.apod()
	if newApod, err := nasa.RandomAPOD(); err == nil {
		apod = *newApod
	}
	h.update(apod, time.Now())
	h.render(w, r)
}
func (h *handler) render(w http.ResponseWriter, r *http.Request) {
	d := struct {
		Apod               nasa.Image
		HD                 bool
		AutoReload         bool
		AutoReloadInterval int // reload interval in seconds
	}{
		Apod:       h.apod(),
		HD:         r.URL.Query().Get("sd") == "",
		AutoReload: r.URL.Query().Get("auto") != "",
	}
	if d.AutoReload {
		i := r.URL.Query().Get("interval")
		if i != "" {
			if n, err := strconv.Atoi(i); err == nil {
				d.AutoReloadInterval = n
			}
		}
		if d.AutoReloadInterval < 1 {
			d.AutoReloadInterval = 5 * 60 // default reload every 5 minutes
		}
	}
	if err := h.tmpl.Execute(w, d); err != nil {
		log.Print(err)
	}
}

const tmpl = `<!DOCTYPE html>

<meta charset="UTF-8">
<title>Random NASA APOD</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
{{if .AutoReload -}}
<meta http-equiv="refresh" content="{{.AutoReloadInterval}}" >
{{end -}}

{{if not .Apod}}

Unable to generate random APOD :(.

{{end}}
{{if .Apod}}

{{.Apod.Title}}

{{.Apod.Explanation}}

NASA Astronomy Picture of the Day {{.Apod.Date}} View in HD

View in fullscreen (F11) for best experience ☺.
Random NASA Astronomy Picture of the Day.
This project is on Github
Star

{{end}}

`
<commit_msg>add autoreload link to twitter<commit_after>package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/peteretelej/nasa"
)

var (
	interval = flag.Duration("interval", time.Second, "interval to change the random image")
	listen   = flag.String("listen", ":8080", "http server listening address")
)

func main() {
	flag.Parse()
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	if err := serve(*listen); err != nil {
		log.Fatalf("server crashed: %v", err)
	}
}

func serve(listenAddr string) error {
	tmpl, err := template.New("tmpl").Parse(tmpl)
	if err != nil {
		return fmt.Errorf("unable to parse template: %v", err)
	}
	apod, err := nasa.RandomAPOD()
	if err != nil {
		return fmt.Errorf("unable to fetch random apod: %v", err)
	}
	h := &handler{
		lastUpdate: time.Now(),
		cachedApod: apod,
		tmpl:       tmpl,
	}
	http.Handle("/", h)
	svr := &http.Server{
		Addr:           listenAddr,
		ReadTimeout:    15 * time.Second,
		WriteTimeout:   30 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	fmt.Printf("launching http server at %s\n", listenAddr)
	return svr.ListenAndServe()
}

type handler struct {
	tmpl *template.Template

	mu         sync.RWMutex // protects the values below
	lastUpdate time.Time
	cachedApod *nasa.Image
}

func (h *handler) last() time.Time {
	h.mu.RLock()
	last := h.lastUpdate
	h.mu.RUnlock()
	return last
}
func (h *handler) apod() nasa.Image {
	h.mu.RLock()
	apod := *h.cachedApod
	h.mu.RUnlock()
	return apod
}
func (h *handler) update(apod nasa.Image, t time.Time) {
	h.mu.Lock()
	h.cachedApod = &apod
	h.lastUpdate = time.Now()
	h.mu.Unlock()

}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if time.Now().Sub(h.last()) < *interval {
		h.render(w, r)
		return
	}
	apod := h.apod()
	if newApod, err := nasa.RandomAPOD(); err == nil {
		apod = *newApod
	}
	h.update(apod, time.Now())
	h.render(w, r)
}
func (h *handler) render(w http.ResponseWriter, r *http.Request) {
	d := struct {
		Apod               nasa.Image
		HD                 bool
		AutoReload         bool
		AutoReloadInterval int // reload interval in seconds
	}{
		Apod:       h.apod(),
		HD:         r.URL.Query().Get("sd") == "",
		AutoReload: r.URL.Query().Get("auto") != "",
	}
	if d.AutoReload {
		i := r.URL.Query().Get("interval")
		if i != "" {
			if n, err := strconv.Atoi(i); err == nil {
				d.AutoReloadInterval = n
			}
		}
		if d.AutoReloadInterval < 1 {
			d.AutoReloadInterval = 5 * 60 // default reload every 5 minutes
		}
	}
	if err := h.tmpl.Execute(w, d); err != nil {
		log.Print(err)
	}
}

const tmpl = `<!DOCTYPE html>

<meta charset="UTF-8">
<title>Random NASA APOD</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
{{if .AutoReload -}}
<meta http-equiv="refresh" content="{{.AutoReloadInterval}}" >
{{end -}}

{{if not .Apod}}

Unable to generate random APOD :(.

{{end}}
{{if .Apod}}

{{.Apod.Title}}

{{.Apod.Explanation}}

NASA Astronomy Picture of the Day {{.Apod.Date}} View in HD

View in fullscreen (F11) for best experience ☺.
Click here to Auto-reload.
Random NASA Astronomy Picture of the Day.
This project is on Github.
Star

{{end}}

`
<|endoftext|>