<commit_before>// Copyright 2018 <NAME>. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package main

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	yaml "gopkg.in/yaml.v2"
)

// serverConfig is the configuration for the web server.
type serverConfig interface {
	getName() string
	getPort() int
	getWebHookSecret() []byte
	getProject(org, repo string) project
}

// project is the configuration for this project.
type project interface {
	getOrg() string
	getRepo() string
	// getPath returns the path to checkout the repository into. It may be
	// different than "github.com/<org>/<repo>".
	getPath() string
	isSuperUser(user string) bool
	getChecks() []check
}

// getID returns the "org/repo" identifier for a project.
func getID(p project) string {
	return p.getOrg() + "/" + p.getRepo()
}

// check is a single command to run.
type check struct {
	Cmd []string // Command to run.
	Env []string // Optional environment variables to use.
}

// Worker configuration via gohci.yml.

// projectDef defines a github repository being tested in the worker gohci.yml
// configuration file.
type projectDef struct {
	Org        string   // Organisation name (e.g. a user)
	Repo       string   // Project name
	AltPath    string   // Alternative package path to use. Defaults to the actual path.
	SuperUsers []string // List of github accounts that can trigger a run. In practice any user with write access is a super user but OAuth2 tokens with limited scopes cannot get this information.
	Checks     []check  // Commands to run to test the repository. They are run one after the other from the repository's root.
}

// getPath implements project.
func (p *projectDef) getPath() string {
	if len(p.AltPath) != 0 {
		return strings.Replace(p.AltPath, "/", string(os.PathSeparator), -1)
	}
	return filepath.Join("github.com", p.Org, p.Repo)
}

// isSuperUser returns true if the user can trigger tasks.
func (p *projectDef) isSuperUser(u string) bool {
	for _, s := range p.SuperUsers {
		if s == u {
			return true
		}
	}
	return false
}

func (p *projectDef) getOrg() string {
	return p.Org
}

func (p *projectDef) getRepo() string {
	return p.Repo
}

func (p *projectDef) getChecks() []check {
	return p.Checks
}

// workerConfig is a worker configuration.
type workerConfig struct {
	Port              int          // TCP port number for HTTP server.
	WebHookSecret     string       // https://developer.github.com/webhooks/
	Oauth2AccessToken string       // https://github.com/settings/tokens, check "repo:status" and "gist"
	Name              string       // Display name to use in the status report on Github.
	Projects          []projectDef // All the projects this workre handles.
}

// loadConfig loads the current config or returns the default one.
//
// It saves a reformatted version on disk if it was not in the canonical format.
func loadConfig(fileName string) (*workerConfig, error) {
	hostname, err := os.Hostname()
	if err != nil {
		hostname = "gohci"
	}
	// Create a dummy config file to make it easier to edit.
	c := &workerConfig{
		Port:              8080,
		WebHookSecret:     "",
		Oauth2AccessToken: "Get one at https://github.com/settings/tokens",
		Name:              hostname,
		Projects: []projectDef{
			{
				Org:     "the user",
				Repo:    "the project",
				AltPath: "",
				// Since github user names cannot have a space in it, we know it
				// doesn't open a security hole.
				SuperUsers: []string{"admin user1", "admin user2"},
				Checks: []check{
					{Cmd: []string{"go", "test", "./..."}},
				},
			},
		},
	}
	b, err := ioutil.ReadFile(fileName)
	if err != nil || c.WebHookSecret == "" {
		if c.WebHookSecret == "" {
			var b [32]byte
			if _, err := rand.Read(b[:]); err != nil {
				return nil, err
			}
			c.WebHookSecret = base64.RawURLEncoding.EncodeToString(b[:])
		}
		b, err = yaml.Marshal(c)
		if err != nil {
			return nil, err
		}
		// Makes it editable in notepad.exe.<commit_msg>Defer calling os.Hostname() unless necessary<commit_after>// Copyright 2018 <NAME>. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package main

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	yaml "gopkg.in/yaml.v2"
)

// serverConfig is the configuration for the web server.
type serverConfig interface {
	getName() string
	getPort() int
	getWebHookSecret() []byte
	getProject(org, repo string) project
}

// project is the configuration for this project.
type project interface {
	getOrg() string
	getRepo() string
	// getPath returns the path to checkout the repository into. It may be
	// different than "github.com/<org>/<repo>".
	getPath() string
	isSuperUser(user string) bool
	getChecks() []check
}

// getID returns the "org/repo" identifier for a project.
func getID(p project) string {
	return p.getOrg() + "/" + p.getRepo()
}

// check is a single command to run.
type check struct {
	Cmd []string // Command to run.
	Env []string // Optional environment variables to use.
}

// Worker configuration via gohci.yml.

// projectDef defines a github repository being tested in the worker gohci.yml
// configuration file.
type projectDef struct {
	Org        string   // Organisation name (e.g. a user)
	Repo       string   // Project name
	AltPath    string   // Alternative package path to use. Defaults to the actual path.
	SuperUsers []string // List of github accounts that can trigger a run. In practice any user with write access is a super user but OAuth2 tokens with limited scopes cannot get this information.
	Checks     []check  // Commands to run to test the repository. They are run one after the other from the repository's root.
}

// getPath implements project.
func (p *projectDef) getPath() string {
	if len(p.AltPath) != 0 {
		return strings.Replace(p.AltPath, "/", string(os.PathSeparator), -1)
	}
	return filepath.Join("github.com", p.Org, p.Repo)
}

// isSuperUser returns true if the user can trigger tasks.
func (p *projectDef) isSuperUser(u string) bool {
	for _, s := range p.SuperUsers {
		if s == u {
			return true
		}
	}
	return false
}

func (p *projectDef) getOrg() string {
	return p.Org
}

func (p *projectDef) getRepo() string {
	return p.Repo
}

func (p *projectDef) getChecks() []check {
	return p.Checks
}

// workerConfig is a worker configuration.
type workerConfig struct {
	Port              int          // TCP port number for HTTP server.
	WebHookSecret     string       // https://developer.github.com/webhooks/
	Oauth2AccessToken string       // https://github.com/settings/tokens, check "repo:status" and "gist"
	Name              string       // Display name to use in the status report on Github.
	Projects          []projectDef // All the projects this workre handles.
}

// loadConfig loads the current config or returns the default one.
//
// It saves a reformatted version on disk if it was not in the canonical format.
func loadConfig(fileName string) (*workerConfig, error) {
	// Create a dummy config file to make it easier to edit.
	c := &workerConfig{
		Port:              8080,
		Oauth2AccessToken: "Get one at https://github.com/settings/tokens",
		Projects: []projectDef{
			{
				Org:     "the user",
				Repo:    "the project",
				AltPath: "",
				// Since github user names cannot have a space in it, we know it
				// doesn't open a security hole.
				SuperUsers: []string{"admin user1", "admin user2"},
				Checks: []check{
					{Cmd: []string{"go", "test", "./..."}},
				},
			},
		},
	}
	b, err := ioutil.ReadFile(fileName)
	if err != nil || c.Name == "" || c.WebHookSecret == "" {
		// Defer these since they require actual work.
		if c.WebHookSecret == "" {
			var b [32]byte
			if _, err := rand.Read(b[:]); err != nil {
				return nil, err
			}
			c.WebHookSecret = base64.RawURLEncoding.EncodeToString(b[:])
		}
		if c.Name == "" {
			if c.Name, _ = os.Hostname(); c.Name == "" {
				c.Name = "gohci"
			}
		}
		b, err = yaml.Marshal(c)
		if err != nil {
			return nil, err
		}
		// Makes it editable in notepad.exe.
{'fraction_non_alphanumeric': 0.1172130156042095, 'fraction_numerical': 0.005806217491230193, 'mean_word_length': 2.9484240687679084, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4835268', 'n_tokens_mistral': 2754, 'n_tokens_neox': 2583, 'n_words': 966}