<commit_before>package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/SudoQ/crisp/item"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

type Service struct {
	URL    string        `json:"url"`
	Port   string        `json:"port"`
	Period time.Duration `json:"-"`
	Limit  uint          `json:"limit"`
	Cache  *item.Item    `json:"-"`
	logger *log.Logger   `json:"-"`
}

func New(targetUrl, port string, limit uint) *Service {
	period, err := LimitToPeriod(limit)
	if err != nil {
		limit = 1
		period, _ = LimitToPeriod(limit)
	}
	srv := &Service{
		URL:    targetUrl,
		Port:   port,
		Period: period,
		Limit:  limit,
		Cache:  nil,
		logger: nil,
	}
	srv.initLogger()
	return srv
}

func (this *Service) initLogger() {
	u, err := url.Parse(this.URL)
	label := u.Host
	if err != nil {
		label = "?"
	}
	this.logger = log.New(os.Stdin, fmt.Sprintf("crisp[%s]: ", label), log.Lshortfile)
}

func LimitToPeriod(limit uint) (time.Duration, error) {
	if limit == 0 {
		return 0, errors.New("Division with zero")
	}

	period := (60.0 / float64(limit)) * 60
	return (time.Duration(period) * time.Second), nil
}

func NewFromJSON(jsonBlob []byte) (*Service, error) {
	var srv Service
	err := json.Unmarshal(jsonBlob, &srv)
	if err != nil {
		return nil, err
	}
	srv.Period, err = LimitToPeriod(srv.Limit)
	if err != nil {
		srv.Limit = 1
		srv.Period, _ = LimitToPeriod(srv.Limit)
	}
	srv.initLogger()
	return &srv, nil
}

func NewFromFile(filename string) (*Service, error) {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return NewFromJSON(jsonBlob)
}

func (this *Service) LoadConfig(filename string) error {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = json.Unmarshal(jsonBlob, this)
	this.initLogger()
	return err
}

func (this *Service) JSON() ([]byte, error) {
	blob, err := json.Marshal(this)
	if err != nil {
		return nil, err
	}
	return blob, nil
}

func (this *Service) SaveConfig(filename string) error {
	jsonBlob, err := this.JSON()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, jsonBlob, 0644)
	return err
}

func (this *Service) Collect() {
	for {
		func() {
			now := time.Now()
			timeDelta := now.Sub(this.Cache.Timestamp)
			if this.Cache.Timestamp.Before(now) && timeDelta > this.Period {
				this.logger.Printf("GET %s\n", this.URL)

				resp, err := http.Get(this.URL)
				if err != nil {
					this.logger.Println(err)
					return
				}

				payload, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					this.logger.Println(err)
					return
				}

				newItem := item.New(now, payload)
				this.Cache = newItem
				cacheFilename := "cache.json"
				err = this.Cache.WriteFile(cacheFilename)
				if err != nil {
					this.logger.Fatal(err)
				}
				this.logger.Printf("Saved cache to %s\n", cacheFilename)

			}
		}()
		select {
		case <-time.After(this.Period):
			continue
		}
	}
}

func (this *Service) LoadCache(filename string) error {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	newItem, err := item.NewFromJSON(content)
	if err != nil {
		return err
	}
	this.Cache = newItem
	return nil
}

func (this *Service) Info() string {
	return fmt.Sprintf("Crisp API caching service v0.1")
}

func (this *Service) Run() {
	err := this.LoadCache("cache.json")
	if err != nil {
		this.logger.Fatal(err)
	}
	go this.Collect()
	r := mux.NewRouter()
	r.HandleFunc("/", this.HomeHandler)
	r.HandleFunc("/info", this.InfoHandler)
	r.HandleFunc("/cache.json", this.CacheHandler)
	port := fmt.Sprintf(":%s", this.Port)
	http.ListenAndServe(port, r)
}

func (this *Service) HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(this.Cache.Payload)
}

func (this *Service) InfoHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)
	w.Write([]byte(this.Info()))
}

func (this *Service) CacheHandler(w http.ResponseWriter, r *http.Request) {
	response, err := this.Cache.JSON()
	if err != nil {
		this.logger.Println(err)
		w.WriteHeader(404)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(response)
}
<commit_msg>Preparing for introducing a subscription functionallity<commit_after>package service

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/SudoQ/crisp/item"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

type Service struct {
	URL    string        `json:"url"`
	Port   string        `json:"port"`
	Period time.Duration `json:"-"`
	Limit  uint          `json:"limit"`
	Cache  *item.Item    `json:"-"`
	logger *log.Logger   `json:"-"`
}

func New(targetUrl, port string, limit uint) *Service {
	period, err := LimitToPeriod(limit)
	if err != nil {
		limit = 1
		period, _ = LimitToPeriod(limit)
	}
	srv := &Service{
		URL:    targetUrl,
		Port:   port,
		Period: period,
		Limit:  limit,
		Cache:  nil,
		logger: nil,
	}
	srv.initLogger()
	return srv
}

func (this *Service) initLogger() {
	u, err := url.Parse(this.URL)
	label := u.Host
	if err != nil {
		label = "?"
	}
	this.logger = log.New(os.Stdin, fmt.Sprintf("crisp[%s]: ", label), log.Lshortfile)
}

func LimitToPeriod(limit uint) (time.Duration, error) {
	if limit == 0 {
		return 0, errors.New("Division with zero")
	}

	period := (60.0 / float64(limit)) * 60
	return (time.Duration(period) * time.Second), nil
}

func NewFromJSON(jsonBlob []byte) (*Service, error) {
	var srv Service
	err := json.Unmarshal(jsonBlob, &srv)
	if err != nil {
		return nil, err
	}
	srv.Period, err = LimitToPeriod(srv.Limit)
	if err != nil {
		srv.Limit = 1
		srv.Period, _ = LimitToPeriod(srv.Limit)
	}
	srv.initLogger()
	return &srv, nil
}

func NewFromFile(filename string) (*Service, error) {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return NewFromJSON(jsonBlob)
}

func (this *Service) LoadConfig(filename string) error {
	jsonBlob, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	err = json.Unmarshal(jsonBlob, this)
	this.initLogger()
	return err
}

func (this *Service) JSON() ([]byte, error) {
	blob, err := json.Marshal(this)
	if err != nil {
		return nil, err
	}
	return blob, nil
}

func (this *Service) SaveConfig(filename string) error {
	jsonBlob, err := this.JSON()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(filename, jsonBlob, 0644)
	return err
}

func (this *Service) Collect() {
	for {
		func() {
			now := time.Now()
			timeDelta := now.Sub(this.Cache.Timestamp)
			if this.Cache.Timestamp.Before(now) && timeDelta > this.Period {
				this.logger.Printf("GET %s\n", this.URL)

				resp, err := http.Get(this.URL)
				if err != nil {
					this.logger.Println(err)
					return
				}

				payload, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					this.logger.Println(err)
					return
				}

				newItem := item.New(now, payload)
				this.Cache = newItem
				cacheFilename := "cache.json"
				err = this.Cache.WriteFile(cacheFilename)
				if err != nil {
					this.logger.Fatal(err)
				}
				this.logger.Printf("Saved cache to %s\n", cacheFilename)

			}
		}()
		select {
		case <-time.After(this.Period):
			continue
		}
	}
}

func (this *Service) LoadCache(filename string) error {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	newItem, err := item.NewFromJSON(content)
	if err != nil {
		return err
	}
	this.Cache = newItem
	return nil
}

func (this *Service) Subscribe() {
	// Subscribers provide thier url where the service should send the response
	// This method adds the subscriber to the subscriber list
}

func (this *Service) Publish() {
	// Listen for new event on a channel from collect
	// Send out to subscribers
}

func (this *Service) Info() string {
	return fmt.Sprintf("Crisp API caching service v0.1")
}

func (this *Service) Run() {
	err := this.LoadCache("cache.json")
	if err != nil {
		this.logger.Fatal(err)
	}
	go this.Collect()
	r := mux.NewRouter()
	r.HandleFunc("/", this.HomeHandler)
	r.HandleFunc("/info", this.InfoHandler)
	r.HandleFunc("/cache.json", this.CacheHandler)
	r.HandleFunc("/subscribe", this.SubscriptionHandler)
	port := fmt.Sprintf(":%s", this.Port)
	http.ListenAndServe(port, r)
}

func (this *Service) HomeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(this.Cache.Payload)
}

func (this *Service) InfoHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)
	w.Write([]byte(this.Info()))
}

func (this *Service) CacheHandler(w http.ResponseWriter, r *http.Request) {
	response, err := this.Cache.JSON()
	if err != nil {
		this.logger.Println(err)
		w.WriteHeader(404)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(response)
}

func (this *Service) SubscriptionHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(404)
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/fcgi"
	"net/url"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
)

var (
	form          url.Values
	header        http.Header
	cookies       []*http.Cookie
	writer        http.ResponseWriter
	request       http.Request
	exit_error    bool
	server        *GochanServer
	referrerRegex *regexp.Regexp
)

type GochanServer struct {
	writer     http.ResponseWriter
	request    http.Request
	namespaces map[string]func(http.ResponseWriter, *http.Request, interface{})
}

func (s GochanServer) AddNamespace(base_path string, namespace_function func(http.ResponseWriter, *http.Request, interface{})) {
	s.namespaces[base_path] = namespace_function
}

func (s GochanServer) getFileData(writer http.ResponseWriter, url string) ([]byte, bool) {
	var file_bytes []byte
	filepath := path.Join(config.DocumentRoot, url)
	results, err := os.Stat(filepath)
	if err != nil {
		// the requested path isn't a file or directory, 404
		return file_bytes, false
	} else {
		//the file exists, or there is a folder here
		if results.IsDir() {
			found_index := false
			newpath := ""

			//check to see if one of the specified index pages exists
			for i := 0; i < len(config.FirstPage); i++ {
				newpath = path.Join(filepath, config.FirstPage[i])
				_, err := os.Stat(newpath)
				if err == nil {
					// serve the index page
					writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
					file_bytes, err = ioutil.ReadFile(newpath)
					return file_bytes, true
					found_index = true
					break
				}
			}

			if !found_index {
				// none of the index pages specified in config.cfg exist
				return file_bytes, false
			}
		} else {
			//the file exists, and is not a folder
			file_bytes, err = ioutil.ReadFile(filepath)
			extension := getFileExtension(url)
			switch {
			case extension == "png":
				writer.Header().Add("Content-Type", "image/png")
				writer.Header().Add("Cache-Control", "max-age=86400")
			case extension == "gif":
				writer.Header().Add("Content-Type", "image/gif")
				writer.Header().Add("Cache-Control", "max-age=86400")
			case extension == "jpg":
				writer.Header().Add("Content-Type", "image/jpeg")
				writer.Header().Add("Cache-Control", "max-age=86400")
			case extension == "css":
				writer.Header().Add("Content-Type", "text/css")
				writer.Header().Add("Cache-Control", "max-age=43200")
			case extension == "js":
				writer.Header().Add("Content-Type", "text/javascript")
				writer.Header().Add("Cache-Control", "max-age=43200")
			case extension == "json":
				writer.Header().Add("Content-Type", "application/json")
				writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
			}
			if extension == "html" || extension == "htm" {
				writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
			}
			access_log.Print("Success: 200 from " + getRealIP(&request) + " @ " + request.RequestURI)
			return file_bytes, true
		}
	}
	return file_bytes, false
}

func (s GochanServer) Redirect(location string) {
	http.Redirect(writer, &request, location, http.StatusFound)
}

func (s GochanServer) serve404(writer http.ResponseWriter, request *http.Request) {
	error_page, err := ioutil.ReadFile(config.DocumentRoot + "/error/404.html")
	if err != nil {
		writer.Write([]byte("Requested page not found, and 404 error page not found"))
	} else {
		writer.Write(error_page)
	}
	error_log.Print("Error: 404 Not Found from " + getRealIP(request) + " @ " + request.RequestURI)
}

func (s GochanServer) ServeErrorPage(writer http.ResponseWriter, err string) {
	error_page_bytes, _ := ioutil.ReadFile("templates/error.html")
	error_page := string(error_page_bytes)
	error_page = strings.Replace(error_page, "{ERRORTEXT}", err, -1)
	writer.Write([]byte(error_page))
	exit_error = true
}

func (s GochanServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	for name, namespace_function := range s.namespaces {
		//if len(request.URL)
		if request.URL.Path == "/"+name {
			namespace_function(writer, request, nil)
			return
		}
	}
	fb, found := s.getFileData(writer, request.URL.Path)
	writer.Header().Add("Cache-Control", "max-age=86400")
	if !found {
		s.serve404(writer, request)
		return
	}
	writer.Write(fb)
}

func initServer() {
	listener, err := net.Listen("tcp", config.ListenIP+":"+strconv.Itoa(config.Port))
	if err != nil {
		fmt.Printf("Failed listening on %s:%d, see log for details", config.ListenIP, config.Port)
		error_log.Fatal(err.Error())
	}
	server = new(GochanServer)
	server.namespaces = make(map[string]func(http.ResponseWriter, *http.Request, interface{}))

	// Check if Akismet API key is usable at startup.
	if config.AkismetAPIKey != "" {
		checkAkismetAPIKey()
	}

	// Compile regex for checking referrers.
	referrerRegex = regexp.MustCompile(config.DomainRegex)

	testfunc := func(writer http.ResponseWriter, response *http.Request, data interface{}) {
		if writer != nil {
			writer.Write([]byte("hahahaha"))
		}
	}
	server.AddNamespace("example", testfunc)
	server.AddNamespace("manage", callManageFunction)
	server.AddNamespace("post", makePost)
	server.AddNamespace("util", utilHandler)
	// eventually plugins will be able to register new namespaces. Or they will be restricted to something like /plugin
	if config.UseFastCGI {
		fcgi.Serve(listener, server)
	} else {
		http.Serve(listener, server)
	}
}

func getRealIP(r *http.Request) (ip string) {
	// HTTP_CF_CONNECTING_IP > X-Forwarded-For > RemoteAddr
	if r.Header.Get("HTTP_CF_CONNECTING_IP") != "" {
		ip = r.Header.Get("HTTP_CF_CONNECTING_IP")
	} else {
		if r.Header.Get("X-Forwarded-For") != "" {
			ip = r.Header.Get("X-Forwarded-For")
		} else {
			ip = r.RemoteAddr
		}
	}
	return
}

func validReferrer(request http.Request) (valid bool) {

	referrerCopy := request.Referer()
	valid = referrerRegex.MatchString(referrerCopy)
	// Old Referrer check.
	// valid = !(request.Referer() == "" || len(request.Referer()) < len(config.SiteDomain) || request.Referer()[7:len(config.SiteDomain)+7] != config.SiteDomain)
	return
}

// register /util handler
func utilHandler(writer http.ResponseWriter, request *http.Request, data interface{}) {
	writer.Header().Add("Content-Type", "text/css")
	action := request.FormValue("action")
	board := request.FormValue("board")
	var err error
	if action == "" && request.PostFormValue("delete_btn") != "Delete" && request.PostFormValue("report_btn") != "Report" {
		http.Redirect(writer, request, path.Join(config.SiteWebfolder, "/"), http.StatusFound)
		return
	}
	var posts_arr []string
	for key, _ := range request.PostForm {
		if strings.Index(key, "check") == 0 {
			posts_arr = append(posts_arr, key[5:])
		}
	}
	if request.PostFormValue("delete_btn") == "Delete" {
		// Delete a post or thread
		file_only := request.FormValue("fileonly") == "on"
		password := md5_sum(request.FormValue("password"))
		rank := getStaffRank()

		if request.FormValue("password") == "" && rank == 0 {
			server.ServeErrorPage(writer, "Password required for post deletion")
			return
		}

		for _, post := range posts_arr {
			var parent_id int
			var filename string
			var filetype string
			var password_checksum string
			var board_id int
			//post_int,err := strconv.Atoi(post)

			err = db.QueryRow("SELECT `parentid`,`filename`,`password` FROM `"+config.DBprefix+"posts` WHERE `id` = "+post+" AND `deleted_timestamp` = '"+nil_timestamp+"'").Scan(&parent_id, &filename, &password_checksum)
			if err == sql.ErrNoRows {
				//the post has already been deleted
				writer.Header().Add("refresh", "3;url="+request.Referer())
				fmt.Fprintf(writer, "%s has already been deleted or is a post in a deleted thread.\n
", post)
				continue
			}
			if err != nil {
				server.ServeErrorPage(writer, err.Error())
				return
			}

			err = db.QueryRow("SELECT `id` FROM `" + config.DBprefix + "boards` WHERE `dir` = '" + board + "'").Scan(&board_id)
			if err != nil {
				server.ServeErrorPage(writer, err.Error())
				return
			}

			if password != password_checksum && rank == 0 {
				fmt.Fprintf(writer, "Incorrect password for %s\n", post)
				continue
			}

			if file_only {

				if filename != "" && filename != "deleted" {
					filetype = filename[strings.Index(filename, ".")+1:]
					filename = filename[:strings.Index(filename, ".")]
					err := os.Remove(path.Join(config.DocumentRoot, board, "/src/"+filename+"."+filetype))
					if err != nil {
						server.ServeErrorPage(writer, err.Error())
						return
					}
					err = os.Remove(path.Join(config.DocumentRoot, board, "/thumb/"+filename+"t."+filetype))
					if err != nil {
						server.ServeErrorPage(writer, err.Error())
						return
					}
					_, err = db.Exec("UPDATE `" + config.DBprefix + "posts` SET `filename` = 'deleted' WHERE `id` = " + post)
					if err != nil {
						server.ServeErrorPage(writer, err.Error())
						return
					}
				}
				writer.Header().Add("refresh", "3;url="+request.Referer())
				fmt.Fprintf(writer, "Attached image from %s deleted successfully
\n<meta http-equiv=\"refresh\" content=\"1;url="+config.DocumentRoot+"/"+board+"/\">", post)
			} else {

				// delete the post
				_, err = db.Exec("UPDATE `" + config.DBprefix + "posts` SET `deleted_timestamp` = '" + getSQLDateTime() + "' WHERE `id` = " + post)
				if parent_id == 0 {
					err = os.Remove(path.Join(config.DocumentRoot, board, "/res/"+post+".html"))
				} else {
					_board, _ := getBoardArr("`id` = " + strconv.Itoa(board_id))
					buildBoardPages(&_board[0])
				}

				// if the deleted post is actually a thread, delete its posts
				_, _ = db.Exec("UPDATE `" + config.DBprefix + "posts` SET `deleted_timestamp` = '" + getSQLDateTime() + "' WHERE `parentid` = " + post)
				if err != nil {
					server.ServeErrorPage(writer, err.Error())
					return
				}

				// delete the
				var deleted_filename string
				err = db.QueryRow("SELECT `filename` FROM `" + config.DBprefix + "posts` WHERE `id` = " + post + " AND `filename` != ''").Scan(&deleted_filename)
				if err == nil {
					os.Remove(path.Join(config.DocumentRoot, board, "/src/", deleted_filename))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "t.", -1)))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "c.", -1)))
				}

				err = db.QueryRow("SELECT `filename` FROM `" + config.DBprefix + "posts` WHERE `parentid` = " + post + " AND `filename` != ''").Scan(&deleted_filename)
				if err == nil {
					os.Remove(path.Join(config.DocumentRoot, board, "/src/", deleted_filename))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "t.", -1)))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "c.", -1)))
				}

				buildBoards(false, board_id)

				writer.Header().Add("refresh", "3;url="+request.Referer())
				fmt.Fprintf(writer, "%s deleted successfully\n
", post)
			}
		}
	}
}
<commit_msg>Update server.go<commit_after>package main

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/fcgi"
	"net/url"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
)

var (
	form          url.Values
	header        http.Header
	cookies       []*http.Cookie
	writer        http.ResponseWriter
	request       http.Request
	exit_error    bool
	server        *GochanServer
	referrerRegex *regexp.Regexp
)

type GochanServer struct {
	writer     http.ResponseWriter
	request    http.Request
	namespaces map[string]func(http.ResponseWriter, *http.Request, interface{})
}

func (s GochanServer) AddNamespace(base_path string, namespace_function func(http.ResponseWriter, *http.Request, interface{})) {
	s.namespaces[base_path] = namespace_function
}

func (s GochanServer) getFileData(writer http.ResponseWriter, url string) ([]byte, bool) {
	var file_bytes []byte
	filepath := path.Join(config.DocumentRoot, url)
	results, err := os.Stat(filepath)
	if err != nil {
		// the requested path isn't a file or directory, 404
		return file_bytes, false
	} else {
		//the file exists, or there is a folder here
		if results.IsDir() {
			found_index := false
			newpath := ""

			//check to see if one of the specified index pages exists
			for _, value := range(config.FirstPage) {
				newpath = path.Join(filepath, value)
				_, err := os.Stat(newpath)
				if err == nil {
					// serve the index page
					writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
					file_bytes, err = ioutil.ReadFile(newpath)
					return file_bytes, true
				}
			}

			if !found_index {
				// none of the index pages specified in config.cfg exist
				return file_bytes, false
			}
		} else {
			//the file exists, and is not a folder
			file_bytes, err = ioutil.ReadFile(filepath)
			extension := getFileExtension(url)
			switch {
			case extension == "png":
				writer.Header().Add("Content-Type", "image/png")
				writer.Header().Add("Cache-Control", "max-age=86400")
			case extension == "gif":
				writer.Header().Add("Content-Type", "image/gif")
				writer.Header().Add("Cache-Control", "max-age=86400")
			case extension == "jpg":
				writer.Header().Add("Content-Type", "image/jpeg")
				writer.Header().Add("Cache-Control", "max-age=86400")
			case extension == "css":
				writer.Header().Add("Content-Type", "text/css")
				writer.Header().Add("Cache-Control", "max-age=43200")
			case extension == "js":
				writer.Header().Add("Content-Type", "text/javascript")
				writer.Header().Add("Cache-Control", "max-age=43200")
			case extension == "json":
				writer.Header().Add("Content-Type", "application/json")
				writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
			}
			if extension == "html" || extension == "htm" {
				writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
			}
			access_log.Print("Success: 200 from " + getRealIP(&request) + " @ " + request.RequestURI)
			return file_bytes, true
		}
	}
	return file_bytes, false
}

func (s GochanServer) Redirect(location string) {
	http.Redirect(writer, &request, location, http.StatusFound)
}

func (s GochanServer) serve404(writer http.ResponseWriter, request *http.Request) {
	error_page, err := ioutil.ReadFile(config.DocumentRoot + "/error/404.html")
	if err != nil {
		writer.Write([]byte("Requested page not found, and 404 error page not found"))
	} else {
		writer.Write(error_page)
	}
	error_log.Print("Error: 404 Not Found from " + getRealIP(request) + " @ " + request.RequestURI)
}

func (s GochanServer) ServeErrorPage(writer http.ResponseWriter, err string) {
	error_page_bytes, _ := ioutil.ReadFile("templates/error.html")
	error_page := string(error_page_bytes)
	error_page = strings.Replace(error_page, "{ERRORTEXT}", err, -1)
	writer.Write([]byte(error_page))
	exit_error = true
}

func (s GochanServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	for name, namespace_function := range s.namespaces {
		//if len(request.URL)
		if request.URL.Path == "/"+name {
			namespace_function(writer, request, nil)
			return
		}
	}
	fb, found := s.getFileData(writer, request.URL.Path)
	writer.Header().Add("Cache-Control", "max-age=86400")
	if !found {
		s.serve404(writer, request)
		return
	}
	writer.Write(fb)
}

func initServer() {
	listener, err := net.Listen("tcp", config.ListenIP+":"+strconv.Itoa(config.Port))
	if err != nil {
		fmt.Printf("Failed listening on %s:%d, see log for details", config.ListenIP, config.Port)
		error_log.Fatal(err.Error())
	}
	server = new(GochanServer)
	server.namespaces = make(map[string]func(http.ResponseWriter, *http.Request, interface{}))

	// Check if Akismet API key is usable at startup.
	if config.AkismetAPIKey != "" {
		checkAkismetAPIKey()
	}

	// Compile regex for checking referrers.
	referrerRegex = regexp.MustCompile(config.DomainRegex)

	testfunc := func(writer http.ResponseWriter, response *http.Request, data interface{}) {
		if writer != nil {
			writer.Write([]byte("hahahaha"))
		}
	}
	server.AddNamespace("example", testfunc)
	server.AddNamespace("manage", callManageFunction)
	server.AddNamespace("post", makePost)
	server.AddNamespace("util", utilHandler)
	// eventually plugins will be able to register new namespaces. Or they will be restricted to something like /plugin
	if config.UseFastCGI {
		fcgi.Serve(listener, server)
	} else {
		http.Serve(listener, server)
	}
}

func getRealIP(r *http.Request) (ip string) {
	// HTTP_CF_CONNECTING_IP > X-Forwarded-For > RemoteAddr
	if r.Header.Get("HTTP_CF_CONNECTING_IP") != "" {
		ip = r.Header.Get("HTTP_CF_CONNECTING_IP")
	} else {
		if r.Header.Get("X-Forwarded-For") != "" {
			ip = r.Header.Get("X-Forwarded-For")
		} else {
			ip = r.RemoteAddr
		}
	}
	return
}

func validReferrer(request http.Request) (valid bool) {

	referrerCopy := request.Referer()
	valid = referrerRegex.MatchString(referrerCopy)
	// Old Referrer check.
	// valid = !(request.Referer() == "" || len(request.Referer()) < len(config.SiteDomain) || request.Referer()[7:len(config.SiteDomain)+7] != config.SiteDomain)
	return
}

// register /util handler
func utilHandler(writer http.ResponseWriter, request *http.Request, data interface{}) {
	writer.Header().Add("Content-Type", "text/css")
	action := request.FormValue("action")
	board := request.FormValue("board")
	var err error
	if action == "" && request.PostFormValue("delete_btn") != "Delete" && request.PostFormValue("report_btn") != "Report" {
		http.Redirect(writer, request, path.Join(config.SiteWebfolder, "/"), http.StatusFound)
		return
	}
	var posts_arr []string
	for key, _ := range request.PostForm {
		if strings.Index(key, "check") == 0 {
			posts_arr = append(posts_arr, key[5:])
		}
	}
	if request.PostFormValue("delete_btn") == "Delete" {
		// Delete a post or thread
		file_only := request.FormValue("fileonly") == "on"
		password := md5_sum(request.FormValue("password"))
		rank := getStaffRank()

		if request.FormValue("password") == "" && rank == 0 {
			server.ServeErrorPage(writer, "Password required for post deletion")
			return
		}

		for _, post := range posts_arr {
			var parent_id int
			var filename string
			var filetype string
			var password_checksum string
			var board_id int
			//post_int,err := strconv.Atoi(post)

			err = db.QueryRow("SELECT `parentid`,`filename`,`password` FROM `"+config.DBprefix+"posts` WHERE `id` = "+post+" AND `deleted_timestamp` = '"+nil_timestamp+"'").Scan(&parent_id, &filename, &password_checksum)
			if err == sql.ErrNoRows {
				//the post has already been deleted
				writer.Header().Add("refresh", "3;url="+request.Referer())
				fmt.Fprintf(writer, "%s has already been deleted or is a post in a deleted thread.\n
", post)
				continue
			}
			if err != nil {
				server.ServeErrorPage(writer, err.Error())
				return
			}

			err = db.QueryRow("SELECT `id` FROM `" + config.DBprefix + "boards` WHERE `dir` = '" + board + "'").Scan(&board_id)
			if err != nil {
				server.ServeErrorPage(writer, err.Error())
				return
			}

			if password != password_checksum && rank == 0 {
				fmt.Fprintf(writer, "Incorrect password for %s\n", post)
				continue
			}

			if file_only {

				if filename != "" && filename != "deleted" {
					filetype = filename[strings.Index(filename, ".")+1:]
					filename = filename[:strings.Index(filename, ".")]
					err := os.Remove(path.Join(config.DocumentRoot, board, "/src/"+filename+"."+filetype))
					if err != nil {
						server.ServeErrorPage(writer, err.Error())
						return
					}
					err = os.Remove(path.Join(config.DocumentRoot, board, "/thumb/"+filename+"t."+filetype))
					if err != nil {
						server.ServeErrorPage(writer, err.Error())
						return
					}
					_, err = db.Exec("UPDATE `" + config.DBprefix + "posts` SET `filename` = 'deleted' WHERE `id` = " + post)
					if err != nil {
						server.ServeErrorPage(writer, err.Error())
						return
					}
				}
				writer.Header().Add("refresh", "3;url="+request.Referer())
				fmt.Fprintf(writer, "Attached image from %s deleted successfully
\n<meta http-equiv=\"refresh\" content=\"1;url="+config.DocumentRoot+"/"+board+"/\">", post)
			} else {

				// delete the post
				_, err = db.Exec("UPDATE `" + config.DBprefix + "posts` SET `deleted_timestamp` = '" + getSQLDateTime() + "' WHERE `id` = " + post)
				if parent_id == 0 {
					err = os.Remove(path.Join(config.DocumentRoot, board, "/res/"+post+".html"))
				} else {
					_board, _ := getBoardArr("`id` = " + strconv.Itoa(board_id))
					buildBoardPages(&_board[0])
				}

				// if the deleted post is actually a thread, delete its posts
				_, _ = db.Exec("UPDATE `" + config.DBprefix + "posts` SET `deleted_timestamp` = '" + getSQLDateTime() + "' WHERE `parentid` = " + post)
				if err != nil {
					server.ServeErrorPage(writer, err.Error())
					return
				}

				// delete the
				var deleted_filename string
				err = db.QueryRow("SELECT `filename` FROM `" + config.DBprefix + "posts` WHERE `id` = " + post + " AND `filename` != ''").Scan(&deleted_filename)
				if err == nil {
					os.Remove(path.Join(config.DocumentRoot, board, "/src/", deleted_filename))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "t.", -1)))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "c.", -1)))
				}

				err = db.QueryRow("SELECT `filename` FROM `" + config.DBprefix + "posts` WHERE `parentid` = " + post + " AND `filename` != ''").Scan(&deleted_filename)
				if err == nil {
					os.Remove(path.Join(config.DocumentRoot, board, "/src/", deleted_filename))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "t.", -1)))
					os.Remove(path.Join(config.DocumentRoot, board, "/thumb/", strings.Replace(deleted_filename, ".", "c.", -1)))
				}

				buildBoards(false, board_id)

				writer.Header().Add("refresh", "3;url="+request.Referer())
				fmt.Fprintf(writer, "%s deleted successfully\n
", post)
			}
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 The Go 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 main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var cmdClean = &Command{
	UsageLine: "clean [-i] [-r] [-n] [-x] [packages]",
	Short:     "remove object files",
	Long: `
Clean removes object files from package source directories.
The go command builds most objects in a temporary directory,
so go clean is mainly concerned with object files left by other
tools or by manual invocations of go build.

Specifically, clean removes the following files from each of the
source directories corresponding to the import paths:

	_obj/            old object directory, left from Makefiles
	_test/           old test directory, left from Makefiles
	_testmain.go     old gotest file, left from Makefiles
	test.out         old test log, left from Makefiles
	build.out        old test log, left from Makefiles
	*.[568ao]        object files, left from Makefiles

	DIR(.exe)        from go build
	DIR.test(.exe)   from go test -c
	MAINFILE(.exe)   from go build MAINFILE.go
	*.so             from SWIG

In the list, DIR represents the final path element of the
directory, and MAINFILE is the base name of any Go source
file in the directory that is not included when building
the package.

The -i flag causes clean to remove the corresponding installed
archive or binary (what 'go install' would create).

The -n flag causes clean to print the remove commands it would execute,
but not run them.

The -r flag causes clean to be applied recursively to all the
dependencies of the packages named by the import paths.

The -x flag causes clean to print remove commands as it executes them.

For more about specifying packages, see 'go help packages'.
	`,
}

var cleanI bool // clean -i flag
var cleanN bool // clean -n flag
var cleanR bool // clean -r flag
var cleanX bool // clean -x flag

func init() {
	// break init cycle
	cmdClean.Run = runClean

	cmdClean.Flag.BoolVar(&cleanI, "i", false, "")
	cmdClean.Flag.BoolVar(&cleanN, "n", false, "")
	cmdClean.Flag.BoolVar(&cleanR, "r", false, "")
	cmdClean.Flag.BoolVar(&cleanX, "x", false, "")
}

func runClean(cmd *Command, args []string) {
	for _, pkg := range packagesAndErrors(args) {
		clean(pkg)
	}
}

var cleaned = map[*Package]bool{}

// TODO: These are dregs left by Makefile-based builds.
// Eventually, can stop deleting these.
var cleanDir = map[string]bool{
	"_test": true,
	"_obj":  true,
}

var cleanFile = map[string]bool{
	"_testmain.go": true,
	"test.out":     true,
	"build.out":    true,
	"a.out":        true,
}

var cleanExt = map[string]bool{
	".5":  true,
	".6":  true,
	".8":  true,
	".a":  true,
	".o":  true,
	".so": true,
}

func clean(p *Package) {
	if cleaned[p] {
		return
	}
	cleaned[p] = true

	if p.Dir == "" {
		errorf("can't load package: %v", p.Error)
		return
	}
	dirs, err := ioutil.ReadDir(p.Dir)
	if err != nil {
		errorf("go clean %s: %v", p.Dir, err)
		return
	}

	var b builder
	b.print = fmt.Print

	packageFile := map[string]bool{}
	if p.Name != "main" {
		// Record which files are not in package main.
		// The others are.
		keep := func(list []string) {
			for _, f := range list {
				packageFile[f] = true
			}
		}
		keep(p.GoFiles)
		keep(p.CgoFiles)
		keep(p.TestGoFiles)
		keep(p.XTestGoFiles)
	}

	_, elem := filepath.Split(p.Dir)
	allRemove := []string{
		elem,
		elem + ".exe",
		elem + ".test",
		elem + ".test.exe",
	}
	for _, dir := range dirs {
		name := dir.Name()
		if packageFile[name] {
			continue
		}
		if !dir.IsDir() && strings.HasSuffix(name, ".go") {
			base := name[:len(name)-len(".go")]
			allRemove = append(allRemove, base, base+".exe")
		}
	}
	if cleanN || cleanX {
		b.showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
	}

	toRemove := map[string]bool{}
	for _, name := range allRemove {
		toRemove[name] = true
	}
	for _, dir := range dirs {
		name := dir.Name()
		if dir.IsDir() {
			// TODO: Remove once Makefiles are forgotten.
			if cleanDir[name] {
				if cleanN || cleanX {
					b.showcmd(p.Dir, "rm -r %s", name)
					if cleanN {
						continue
					}
				}
				if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
					errorf("go clean: %v", err)
				}
			}
			continue
		}

		if cleanN {
			continue
		}

		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
			removeFile(filepath.Join(p.Dir, name))
		}
	}

	if cleanI && p.target != "" {
		if cleanN || cleanX {
			b.showcmd("", "rm -f %s", p.target)
		}
		if !cleanN {
			removeFile(p.target)
		}
	}

	if cleanI && p.usesSwig() {
		for _, f := range stringList(p.SwigFiles, p.SwigCXXFiles) {
			dir := p.swigDir(&buildContext)
			soname := p.swigSoname(f)
			target := filepath.Join(dir, soname)
			if cleanN || cleanX {
				b.showcmd("", "rm -f %s", target)
			}
			if !cleanN {
				removeFile(target)
			}
		}
	}

	if cleanR {
		for _, p1 := range p.imports {
			clean(p1)
		}
	}
}

// removeFile tries to remove file f, if error other than file doesn't exist
// occurs, it will report the error.
func removeFile(f string) {
	if err := os.Remove(f); err != nil && !os.IsNotExist(err) {
		errorf("go clean: %v", err)
	}
}
<commit_msg>cmd/go: only try to clean executables for package main<commit_after>// Copyright 2012 The Go 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 main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var cmdClean = &Command{
	UsageLine: "clean [-i] [-r] [-n] [-x] [packages]",
	Short:     "remove object files",
	Long: `
Clean removes object files from package source directories.
The go command builds most objects in a temporary directory,
so go clean is mainly concerned with object files left by other
tools or by manual invocations of go build.

Specifically, clean removes the following files from each of the
source directories corresponding to the import paths:

	_obj/            old object directory, left from Makefiles
	_test/           old test directory, left from Makefiles
	_testmain.go     old gotest file, left from Makefiles
	test.out         old test log, left from Makefiles
	build.out        old test log, left from Makefiles
	*.[568ao]        object files, left from Makefiles

	DIR(.exe)        from go build
	DIR.test(.exe)   from go test -c
	MAINFILE(.exe)   from go build MAINFILE.go
	*.so             from SWIG

In the list, DIR represents the final path element of the
directory, and MAINFILE is the base name of any Go source
file in the directory that is not included when building
the package.

The -i flag causes clean to remove the corresponding installed
archive or binary (what 'go install' would create).

The -n flag causes clean to print the remove commands it would execute,
but not run them.

The -r flag causes clean to be applied recursively to all the
dependencies of the packages named by the import paths.

The -x flag causes clean to print remove commands as it executes them.

For more about specifying packages, see 'go help packages'.
	`,
}

var cleanI bool // clean -i flag
var cleanN bool // clean -n flag
var cleanR bool // clean -r flag
var cleanX bool // clean -x flag

func init() {
	// break init cycle
	cmdClean.Run = runClean

	cmdClean.Flag.BoolVar(&cleanI, "i", false, "")
	cmdClean.Flag.BoolVar(&cleanN, "n", false, "")
	cmdClean.Flag.BoolVar(&cleanR, "r", false, "")
	cmdClean.Flag.BoolVar(&cleanX, "x", false, "")
}

func runClean(cmd *Command, args []string) {
	for _, pkg := range packagesAndErrors(args) {
		clean(pkg)
	}
}

var cleaned = map[*Package]bool{}

// TODO: These are dregs left by Makefile-based builds.
// Eventually, can stop deleting these.
var cleanDir = map[string]bool{
	"_test": true,
	"_obj":  true,
}

var cleanFile = map[string]bool{
	"_testmain.go": true,
	"test.out":     true,
	"build.out":    true,
	"a.out":        true,
}

var cleanExt = map[string]bool{
	".5":  true,
	".6":  true,
	".8":  true,
	".a":  true,
	".o":  true,
	".so": true,
}

func clean(p *Package) {
	if cleaned[p] {
		return
	}
	cleaned[p] = true

	if p.Dir == "" {
		errorf("can't load package: %v", p.Error)
		return
	}
	dirs, err := ioutil.ReadDir(p.Dir)
	if err != nil {
		errorf("go clean %s: %v", p.Dir, err)
		return
	}

	var b builder
	b.print = fmt.Print

	packageFile := map[string]bool{}
	if p.Name != "main" {
		// Record which files are not in package main.
		// The others are.
		keep := func(list []string) {
			for _, f := range list {
				packageFile[f] = true
			}
		}
		keep(p.GoFiles)
		keep(p.CgoFiles)
		keep(p.TestGoFiles)
		keep(p.XTestGoFiles)
	}

	_, elem := filepath.Split(p.Dir)
	var allRemove []string

	// Remove dir-named executable only if this is package main.
	if p.Name == "main" {
		allRemove = append(allRemove,
			elem,
			elem+".exe",
		)
	}

	// Remove package test executables.
	allRemove = append(allRemove,
		elem+".test",
		elem+".test.exe",
	)

	// Remove a potental executable for each .go file in the directory that
	// is not part of the directory's package.
	for _, dir := range dirs {
		name := dir.Name()
		if packageFile[name] {
			continue
		}
		if !dir.IsDir() && strings.HasSuffix(name, ".go") {
			// TODO(adg,rsc): check that this .go file is actually
			// in "package main", and therefore capable of building
			// to an executable file.
			base := name[:len(name)-len(".go")]
			allRemove = append(allRemove, base, base+".exe")
		}
	}

	if cleanN || cleanX {
		b.showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
	}

	toRemove := map[string]bool{}
	for _, name := range allRemove {
		toRemove[name] = true
	}
	for _, dir := range dirs {
		name := dir.Name()
		if dir.IsDir() {
			// TODO: Remove once Makefiles are forgotten.
			if cleanDir[name] {
				if cleanN || cleanX {
					b.showcmd(p.Dir, "rm -r %s", name)
					if cleanN {
						continue
					}
				}
				if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
					errorf("go clean: %v", err)
				}
			}
			continue
		}

		if cleanN {
			continue
		}

		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
			removeFile(filepath.Join(p.Dir, name))
		}
	}

	if cleanI && p.target != "" {
		if cleanN || cleanX {
			b.showcmd("", "rm -f %s", p.target)
		}
		if !cleanN {
			removeFile(p.target)
		}
	}

	if cleanI && p.usesSwig() {
		for _, f := range stringList(p.SwigFiles, p.SwigCXXFiles) {
			dir := p.swigDir(&buildContext)
			soname := p.swigSoname(f)
			target := filepath.Join(dir, soname)
			if cleanN || cleanX {
				b.showcmd("", "rm -f %s", target)
			}
			if !cleanN {
				removeFile(target)
			}
		}
	}

	if cleanR {
		for _, p1 := range p.imports {
			clean(p1)
		}
	}
}

// removeFile tries to remove file f, if error other than file doesn't exist
// occurs, it will report the error.
func removeFile(f string) {
	if err := os.Remove(f); err != nil && !os.IsNotExist(err) {
		errorf("go clean: %v", err)
	}
}
<|endoftext|>


<commit_before>package addrs

import (
	"fmt"

	"github.com/hashicorp/terraform/tfdiags"

	"github.com/hashicorp/hcl2/hcl"
)

// ProviderConfig is the address of a provider configuration.
type ProviderConfig struct {
	Type string

	// If not empty, Alias identifies which non-default (aliased) provider
	// configuration this address refers to.
	Alias string
}

// NewDefaultProviderConfig returns the address of the default (un-aliased)
// configuration for the provider with the given type name.
func NewDefaultProviderConfig(typeName string) ProviderConfig {
	return ProviderConfig{
		Type: typeName,
	}
}

// ParseProviderConfigCompact parses the given absolute traversal as a relative
// provider address in compact form. The following are examples of traversals
// that can be successfully parsed as compact relative provider configuration
// addresses:
//
//     aws
//     aws.foo
//
// This function will panic if given a relative traversal.
//
// If the returned diagnostics contains errors then the result value is invalid
// and must not be used.
func ParseProviderConfigCompact(traversal hcl.Traversal) (ProviderConfig, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	ret := ProviderConfig{
		Type: traversal.RootName(),
	}

	if len(traversal) < 2 {
		// Just a type name, then.
		return ret, diags
	}

	aliasStep := traversal[1]
	switch ts := aliasStep.(type) {
	case hcl.TraverseAttr:
		ret.Alias = ts.Name
		return ret, diags
	default:
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "The provider type name must either stand alone or be followed by an alias name separated with a dot.",
			Subject:  aliasStep.SourceRange().Ptr(),
		})
	}

	if len(traversal) > 2 {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "Extraneous extra operators after provider configuration address.",
			Subject:  traversal[2:].SourceRange().Ptr(),
		})
	}

	return ret, diags
}

// Absolute returns an AbsProviderConfig from the receiver and the given module
// instance address.
func (pc ProviderConfig) Absolute(module ModuleInstance) AbsProviderConfig {
	return AbsProviderConfig{
		Module:         module,
		ProviderConfig: pc,
	}
}

func (pc ProviderConfig) String() string {
	if pc.Type == "" {
		// Should never happen; always indicates a bug
		return "provider.<invalid>"
	}

	if pc.Alias != "" {
		return fmt.Sprintf("provider.%s.%s", pc.Type, pc.Alias)
	}

	return "provider." + pc.Type
}

// StringCompact is an alternative to String that returns the form that can
// be parsed by ParseProviderConfigCompact, without the "provider." prefix.
func (pc ProviderConfig) StringCompact() string {
	if pc.Alias != "" {
		return fmt.Sprintf("%s.%s", pc.Type, pc.Alias)
	}
	return pc.Type
}

// AbsProviderConfig is the absolute address of a provider configuration
// within a particular module instance.
type AbsProviderConfig struct {
	Module         ModuleInstance
	ProviderConfig ProviderConfig
}

// ParseAbsProviderConfig parses the given traversal as an absolute provider
// address. The following are examples of traversals that can be successfully
// parsed as absolute provider configuration addresses:
//
//     provider.aws
//     provider.aws.foo
//     module.bar.provider.aws
//     module.bar.module.baz.provider.aws.foo
//     module.foo[1].provider.aws.foo
//
// This type of address is used, for example, to record the relationships
// between resources and provider configurations in the state structure.
// This type of address is not generally used in the UI, except in error
// messages that refer to provider configurations.
func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) {
	modInst, remain, diags := parseModuleInstancePrefix(traversal)
	ret := AbsProviderConfig{
		Module: modInst,
	}
	if len(remain) < 2 || remain.RootName() != "provider" {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "Provider address must begin with \"provider.\", followed by a provider type name.",
			Subject:  remain.SourceRange().Ptr(),
		})
		return ret, diags
	}
	if len(remain) > 3 {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "Extraneous operators after provider configuration alias.",
			Subject:  hcl.Traversal(remain[3:]).SourceRange().Ptr(),
		})
		return ret, diags
	}

	if tt, ok := remain[1].(hcl.TraverseAttr); ok {
		ret.ProviderConfig.Type = tt.Name
	} else {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "The prefix \"provider.\" must be followed by a provider type name.",
			Subject:  remain[1].SourceRange().Ptr(),
		})
		return ret, diags
	}

	if len(remain) == 3 {
		if tt, ok := remain[2].(hcl.TraverseAttr); ok {
			ret.ProviderConfig.Alias = tt.Name
		} else {
			diags = diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid provider configuration address",
				Detail:   "Provider type name must be followed by a configuration alias name.",
				Subject:  remain[2].SourceRange().Ptr(),
			})
			return ret, diags
		}
	}

	return ret, diags
}

// ProviderConfigDefault returns the address of the default provider config
// of the given type inside the recieving module instance.
func (m ModuleInstance) ProviderConfigDefault(name string) AbsProviderConfig {
	return AbsProviderConfig{
		Module: m,
		ProviderConfig: ProviderConfig{
			Type: name,
		},
	}
}

// ProviderConfigAliased returns the address of an aliased provider config
// of with given type and alias inside the recieving module instance.
func (m ModuleInstance) ProviderConfigAliased(name, alias string) AbsProviderConfig {
	return AbsProviderConfig{
		Module: m,
		ProviderConfig: ProviderConfig{
			Type:  name,
			Alias: alias,
		},
	}
}

// Inherited returns an address that the receiving configuration address might
// inherit from in a parent module. The second bool return value indicates if
// such inheritance is possible, and thus whether the returned address is valid.
//
// Inheritance is possible only for default (un-aliased) providers in modules
// other than the root module. Even if a valid address is returned, inheritence
// may not be performed for other reasons, such as if the calling module
// provided explicit provider configurations within the call for this module.
// The ProviderTransformer graph transform in the main terraform module has
// the authoritative logic for provider inheritance, and this method is here
// mainly just for its benefit.
func (pc AbsProviderConfig) Inherited() (AbsProviderConfig, bool) {
	// Can't inherit if we're already in the root.
	if len(pc.Module) == 0 {
		return AbsProviderConfig{}, false
	}

	// Can't inherit if we have an alias.
	if pc.ProviderConfig.Alias != "" {
		return AbsProviderConfig{}, false
	}

	// Otherwise, we might inherit from a configuration with the same
	// provider name in the parent module instance.
	parentMod := pc.Module.Parent()
	return pc.ProviderConfig.Absolute(parentMod), true
}

func (pc AbsProviderConfig) String() string {
	if len(pc.Module) == 0 {
		return pc.ProviderConfig.String()
	}
	return fmt.Sprintf("%s.%s", pc.Module.String(), pc.ProviderConfig.String())
}
<commit_msg>addrs: Helper function for parsing absolute provider config strings<commit_after>package addrs

import (
	"fmt"

	"github.com/hashicorp/terraform/tfdiags"

	"github.com/hashicorp/hcl2/hcl"
	"github.com/hashicorp/hcl2/hcl/hclsyntax"
)

// ProviderConfig is the address of a provider configuration.
type ProviderConfig struct {
	Type string

	// If not empty, Alias identifies which non-default (aliased) provider
	// configuration this address refers to.
	Alias string
}

// NewDefaultProviderConfig returns the address of the default (un-aliased)
// configuration for the provider with the given type name.
func NewDefaultProviderConfig(typeName string) ProviderConfig {
	return ProviderConfig{
		Type: typeName,
	}
}

// ParseProviderConfigCompact parses the given absolute traversal as a relative
// provider address in compact form. The following are examples of traversals
// that can be successfully parsed as compact relative provider configuration
// addresses:
//
//     aws
//     aws.foo
//
// This function will panic if given a relative traversal.
//
// If the returned diagnostics contains errors then the result value is invalid
// and must not be used.
func ParseProviderConfigCompact(traversal hcl.Traversal) (ProviderConfig, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics
	ret := ProviderConfig{
		Type: traversal.RootName(),
	}

	if len(traversal) < 2 {
		// Just a type name, then.
		return ret, diags
	}

	aliasStep := traversal[1]
	switch ts := aliasStep.(type) {
	case hcl.TraverseAttr:
		ret.Alias = ts.Name
		return ret, diags
	default:
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "The provider type name must either stand alone or be followed by an alias name separated with a dot.",
			Subject:  aliasStep.SourceRange().Ptr(),
		})
	}

	if len(traversal) > 2 {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "Extraneous extra operators after provider configuration address.",
			Subject:  traversal[2:].SourceRange().Ptr(),
		})
	}

	return ret, diags
}

// Absolute returns an AbsProviderConfig from the receiver and the given module
// instance address.
func (pc ProviderConfig) Absolute(module ModuleInstance) AbsProviderConfig {
	return AbsProviderConfig{
		Module:         module,
		ProviderConfig: pc,
	}
}

func (pc ProviderConfig) String() string {
	if pc.Type == "" {
		// Should never happen; always indicates a bug
		return "provider.<invalid>"
	}

	if pc.Alias != "" {
		return fmt.Sprintf("provider.%s.%s", pc.Type, pc.Alias)
	}

	return "provider." + pc.Type
}

// StringCompact is an alternative to String that returns the form that can
// be parsed by ParseProviderConfigCompact, without the "provider." prefix.
func (pc ProviderConfig) StringCompact() string {
	if pc.Alias != "" {
		return fmt.Sprintf("%s.%s", pc.Type, pc.Alias)
	}
	return pc.Type
}

// AbsProviderConfig is the absolute address of a provider configuration
// within a particular module instance.
type AbsProviderConfig struct {
	Module         ModuleInstance
	ProviderConfig ProviderConfig
}

// ParseAbsProviderConfig parses the given traversal as an absolute provider
// address. The following are examples of traversals that can be successfully
// parsed as absolute provider configuration addresses:
//
//     provider.aws
//     provider.aws.foo
//     module.bar.provider.aws
//     module.bar.module.baz.provider.aws.foo
//     module.foo[1].provider.aws.foo
//
// This type of address is used, for example, to record the relationships
// between resources and provider configurations in the state structure.
// This type of address is not generally used in the UI, except in error
// messages that refer to provider configurations.
func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) {
	modInst, remain, diags := parseModuleInstancePrefix(traversal)
	ret := AbsProviderConfig{
		Module: modInst,
	}
	if len(remain) < 2 || remain.RootName() != "provider" {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "Provider address must begin with \"provider.\", followed by a provider type name.",
			Subject:  remain.SourceRange().Ptr(),
		})
		return ret, diags
	}
	if len(remain) > 3 {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "Extraneous operators after provider configuration alias.",
			Subject:  hcl.Traversal(remain[3:]).SourceRange().Ptr(),
		})
		return ret, diags
	}

	if tt, ok := remain[1].(hcl.TraverseAttr); ok {
		ret.ProviderConfig.Type = tt.Name
	} else {
		diags = diags.Append(&hcl.Diagnostic{
			Severity: hcl.DiagError,
			Summary:  "Invalid provider configuration address",
			Detail:   "The prefix \"provider.\" must be followed by a provider type name.",
			Subject:  remain[1].SourceRange().Ptr(),
		})
		return ret, diags
	}

	if len(remain) == 3 {
		if tt, ok := remain[2].(hcl.TraverseAttr); ok {
			ret.ProviderConfig.Alias = tt.Name
		} else {
			diags = diags.Append(&hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid provider configuration address",
				Detail:   "Provider type name must be followed by a configuration alias name.",
				Subject:  remain[2].SourceRange().Ptr(),
			})
			return ret, diags
		}
	}

	return ret, diags
}

// ParseAbsProviderConfigStr is a helper wrapper around ParseAbsProviderConfig
// that takes a string and parses it with the HCL native syntax traversal parser
// before interpreting it.
//
// This should be used only in specialized situations since it will cause the
// created references to not have any meaningful source location information.
// If a reference string is coming from a source that should be identified in
// error messages then the caller should instead parse it directly using a
// suitable function from the HCL API and pass the traversal itself to
// ParseAbsProviderConfig.
//
// Error diagnostics are returned if either the parsing fails or the analysis
// of the traversal fails. There is no way for the caller to distinguish the
// two kinds of diagnostics programmatically. If error diagnostics are returned
// the returned address is invalid.
func ParseAbsProviderConfigStr(str string) (AbsProviderConfig, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics

	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
	diags = diags.Append(parseDiags)
	if parseDiags.HasErrors() {
		return AbsProviderConfig{}, diags
	}

	addr, addrDiags := ParseAbsProviderConfig(traversal)
	diags = diags.Append(addrDiags)
	return addr, diags
}

// ProviderConfigDefault returns the address of the default provider config
// of the given type inside the recieving module instance.
func (m ModuleInstance) ProviderConfigDefault(name string) AbsProviderConfig {
	return AbsProviderConfig{
		Module: m,
		ProviderConfig: ProviderConfig{
			Type: name,
		},
	}
}

// ProviderConfigAliased returns the address of an aliased provider config
// of with given type and alias inside the recieving module instance.
func (m ModuleInstance) ProviderConfigAliased(name, alias string) AbsProviderConfig {
	return AbsProviderConfig{
		Module: m,
		ProviderConfig: ProviderConfig{
			Type:  name,
			Alias: alias,
		},
	}
}

// Inherited returns an address that the receiving configuration address might
// inherit from in a parent module. The second bool return value indicates if
// such inheritance is possible, and thus whether the returned address is valid.
//
// Inheritance is possible only for default (un-aliased) providers in modules
// other than the root module. Even if a valid address is returned, inheritence
// may not be performed for other reasons, such as if the calling module
// provided explicit provider configurations within the call for this module.
// The ProviderTransformer graph transform in the main terraform module has
// the authoritative logic for provider inheritance, and this method is here
// mainly just for its benefit.
func (pc AbsProviderConfig) Inherited() (AbsProviderConfig, bool) {
	// Can't inherit if we're already in the root.
	if len(pc.Module) == 0 {
		return AbsProviderConfig{}, false
	}

	// Can't inherit if we have an alias.
	if pc.ProviderConfig.Alias != "" {
		return AbsProviderConfig{}, false
	}

	// Otherwise, we might inherit from a configuration with the same
	// provider name in the parent module instance.
	parentMod := pc.Module.Parent()
	return pc.ProviderConfig.Absolute(parentMod), true
}

func (pc AbsProviderConfig) String() string {
	if len(pc.Module) == 0 {
		return pc.ProviderConfig.String()
	}
	return fmt.Sprintf("%s.%s", pc.Module.String(), pc.ProviderConfig.String())
}
<|endoftext|>


<commit_before>package setting

import (
	"fmt"
	"os"
	"reflect"
	"strings"

	"github.com/go-errors/errors"
	"github.com/go-ini/ini"
	"github.com/praqma/git-phlow/executor"
	"github.com/praqma/git-phlow/githandler"
	"github.com/praqma/git-phlow/plugins"
)

//Load internals
const (
	phlow_file_name     = ".phlow"
	git_config_fileName = ".gitconfig"
	default_block       = "default"
)

//Default configuration
const (
	internal_default_service                = "github"
	internal_default_integration_branch     = "master"
	internal_default_remote                 = "origin"
	internal_default_issue_url              = "https://api.github.com"
	internal_default_delivery_branch_prefix = "ready"
	internal_default_scope                  = "internal"
	internal_default_file                   = "none"
	internal_pipeline_url                   = "none"
)

//Uses git config commandline interface
//ToolsSetting ...
type ToolsSetting struct {
	User    string
	Token   string
	AutoAdd bool
}

//ProjectSetting ...
type ProjectSetting struct {
	Service              string `ini:"service"`
	IntegrationBranch    string `ini:"integration_branch"`
	Remote               string `ini:"remote"`
	IssueURL             string `ini:"issue_url"`
	PipelineUrl          string `ini:"pipeline"`
	DeliveryBranchPrefix string `ini:"delivery_branch_prefix"`
	Scope                string
	File                 string
	INIBlock             string
}

//NewProjectStg ...
//initializes project settings from .phlow INI files
func NewProjectStg(INIBlock string) *ProjectSetting {
	r := GetLocal()
	g := GetGlobal()
	return LoadProjectSettings(r, g, INIBlock)
}

//NewToolStg ...
//Initializes Tool settings from .gitconfig
func NewToolStg() *ToolsSetting {
	return LoadToolSettings(executor.RunCommand)
}

//LoadProjectSettings ...
func LoadProjectSettings(local, global string, INIBlock string) *ProjectSetting {

	supportedScopes := []string{local, global}
	supportedConfigFiles := []string{phlow_file_name, git_config_fileName}

	//If params are not set, we use the default config
	if INIBlock == "" {
		INIBlock = default_block
	}

	var configScope, configFile string

	var loadSetting = func() *ini.Section {
		for _, scope := range supportedScopes {
			for _, file := range supportedConfigFiles {

				config, err := ini.LooseLoad(scope + "/" + file)
				if err != nil {
					panic(err)
				}

				if sec, _ := config.GetSection(INIBlock); sec != nil {
					configScope = scope
					configFile = file
					return config.Section(INIBlock)
				}
			}
		}
		return nil
	}

	loadedSetting := loadSetting()
	if loadedSetting == nil {
		if INIBlock != default_block {
			fmt.Printf("Error: '%s' configuration does not exist in you configuration files. Following paths were searched: \n", INIBlock)
			for _, scope := range supportedScopes {
				for _, file := range supportedConfigFiles {
					fmt.Println("paths: " + scope + "/" + file)
				}
			}
			os.Exit(1)
		}

		//Try to get default branch otherwise just create the default
		defaultBranch, err := GetDefaultBranchFromInternalDefault()
		if err != nil || strings.TrimSpace(defaultBranch) == "" {
			defaultBranch = internal_default_integration_branch
		}

		err = BootstrapPhlowConfig(local, defaultBranch)
		if err != nil {
			fmt.Println("Could not create a local .phlow config file")
			os.Exit(1)
		}

		//return internal default because no other configuration exist and no other is specified by params
		return &ProjectSetting{
			Service:              internal_default_service,
			IntegrationBranch:    defaultBranch,
			Remote:               internal_default_remote,
			IssueURL:             internal_default_issue_url,
			DeliveryBranchPrefix: internal_default_delivery_branch_prefix,
			PipelineUrl:          internal_pipeline_url,
			Scope:                internal_default_scope,
			File:                 internal_default_file,
		}
	}

	//Map section into object
	conf := new(ProjectSetting)
	err := loadSetting().MapTo(conf)
	if err != nil {
		panic(err)
	}

	//Add configuration origin
	conf.File = configFile
	conf.Scope = configScope
	conf.INIBlock = INIBlock

	if err := ValidateLoadedSetting(conf); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	return conf
}

//BootstrapPhlowConfig ...
//Creates a new .phlow ini file on given location
func BootstrapPhlowConfig(local, integrationBranch string) error {
	fmt.Println("No .phlow config found")
	cfg := ini.Empty()
	sec, _ := cfg.NewSection("default")
	sec.Key("remote").SetValue(internal_default_remote)
	sec.Key("service").SetValue(internal_default_service)
	sec.Key("integration_branch").SetValue(integrationBranch)
	sec.Key("issue_url").SetValue(internal_default_issue_url)
	sec.Key("delivery_branch_prefix").SetValue(internal_default_delivery_branch_prefix)

	err := cfg.SaveTo(local + "/" + phlow_file_name)
	if err != nil {
		return err
	}
	fmt.Println("Bootstrapping new .phlow file")
	return nil
}

//ValidateLoadedSetting ...
func ValidateLoadedSetting(setting *ProjectSetting) error {
	r := reflect.ValueOf(setting).Elem()
	t := r.Type()

	//Non Optional Field checks..
	for i := 0; i < t.NumField(); i++ {
		if t.Field(i).Name == "Service" && (r.Field(i).String() == "") {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "service", setting.INIBlock))
		}

		if t.Field(i).Name == "IssueURL" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "issue_url", setting.INIBlock))
		}

		if t.Field(i).Name == "IntegrationBranch" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "integration_branch", setting.INIBlock))
		}

		if t.Field(i).Name == "Remote" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "remote", setting.INIBlock))
		}

		if t.Field(i).Name == "DeliveryBranchPrefix" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "delivery_branch_prefix", setting.INIBlock))
		}
	}
	return nil
}

//LoadToolSettings ...
func LoadToolSettings(run executor.Runner) *ToolsSetting {
	var set = ToolsSetting{}

	user, err := run("git", "config", "--get", "phlow.user")
	if err != nil {
		panic(err)
	}
	set.User = strings.Replace(user, "\n", "", -1)

	token, err := run("git", "config", "--global", "phlow.token")
	if err != nil {
		panic(err)
	}
	set.Token = strings.Replace(token, "\n", "", -1)

	return &set
}

//GetGlobal ...
func GetGlobal() string {
	return os.Getenv("HOME")
}

//GetLocal ...
func GetLocal() string {
	absoluteRepoPath, err := executor.RunCommand("git", "rev-parse", "--show-toplevel")
	if err != nil {
		panic(err)
	}
	return strings.TrimSpace(absoluteRepoPath)
}

//GetDefaultBranchFromInternalDefault ...
//Trying to retrieve the default branch from github
func GetDefaultBranchFromInternalDefault() (string, error) {
	git := githandler.Git{Run: executor.RunGit}

	remote, err := git.LSRemote("--get-url", internal_default_remote)
	if err != nil {
		return "", err
	}
	orgAndRepo := githandler.OrgAndRepo(remote)
	token, err := git.Config("--get", "phlow.token")

	branch, err := plugins.DefaultBranchGitHub(internal_default_issue_url, orgAndRepo.Organisation, orgAndRepo.Repository, token)
	if err != nil {
		return "", err
	}
	return branch, nil
}
<commit_msg>close #223 fix windows pathseparator in setting<commit_after>package setting

import (
	"fmt"
	"os"
	"reflect"
	"strings"

	"github.com/go-errors/errors"
	"github.com/go-ini/ini"
	"github.com/praqma/git-phlow/executor"
	"github.com/praqma/git-phlow/githandler"
	"github.com/praqma/git-phlow/plugins"
)

//Load internals
const (
	phlow_file_name     = ".phlow"
	git_config_fileName = ".gitconfig"
	default_block       = "default"
)

//Default configuration
const (
	internal_default_service                = "github"
	internal_default_integration_branch     = "master"
	internal_default_remote                 = "origin"
	internal_default_issue_url              = "https://api.github.com"
	internal_default_delivery_branch_prefix = "ready"
	internal_default_scope                  = "internal"
	internal_default_file                   = "none"
	internal_pipeline_url                   = "none"
)

//Uses git config commandline interface
//ToolsSetting ...
type ToolsSetting struct {
	User    string
	Token   string
	AutoAdd bool
}

//ProjectSetting ...
type ProjectSetting struct {
	Service              string `ini:"service"`
	IntegrationBranch    string `ini:"integration_branch"`
	Remote               string `ini:"remote"`
	IssueURL             string `ini:"issue_url"`
	PipelineUrl          string `ini:"pipeline"`
	DeliveryBranchPrefix string `ini:"delivery_branch_prefix"`
	Scope                string
	File                 string
	INIBlock             string
}

//NewProjectStg ...
//initializes project settings from .phlow INI files
func NewProjectStg(INIBlock string) *ProjectSetting {
	r := GetLocal()
	g := GetGlobal()
	return LoadProjectSettings(r, g, INIBlock)
}

//NewToolStg ...
//Initializes Tool settings from .gitconfig
func NewToolStg() *ToolsSetting {
	return LoadToolSettings(executor.RunCommand)
}

//LoadProjectSettings ...
func LoadProjectSettings(local, global string, INIBlock string) *ProjectSetting {

	supportedScopes := []string{local, global}
	supportedConfigFiles := []string{phlow_file_name, git_config_fileName}
	pathSeparator := os.PathSeparator

	//If params are not set, we use the default config
	if INIBlock == "" {
		INIBlock = default_block
	}

	var configScope, configFile string

	var loadSetting = func() *ini.Section {
		for _, scope := range supportedScopes {
			for _, file := range supportedConfigFiles {

				config, err := ini.LooseLoad(scope + string(pathSeparator) + file)
				if err != nil {
					panic(err)
				}

				if sec, _ := config.GetSection(INIBlock); sec != nil {
					configScope = scope
					configFile = file
					return config.Section(INIBlock)
				}
			}
		}
		return nil
	}

	loadedSetting := loadSetting()
	if loadedSetting == nil {
		if INIBlock != default_block {
			fmt.Printf("Error: '%s' configuration does not exist in you configuration files. Following paths were searched: \n", INIBlock)
			for _, scope := range supportedScopes {
				for _, file := range supportedConfigFiles {
					fmt.Println("paths: " + scope + string(pathSeparator) + file)
				}
			}
			os.Exit(1)
		}

		//Try to get default branch otherwise just create the default
		defaultBranch, err := GetDefaultBranchFromInternalDefault()
		if err != nil || strings.TrimSpace(defaultBranch) == "" {
			defaultBranch = internal_default_integration_branch
		}

		err = BootstrapPhlowConfig(local, defaultBranch)
		if err != nil {
			fmt.Println("Could not create a local .phlow config file")
			os.Exit(1)
		}

		//return internal default because no other configuration exist and no other is specified by params
		return &ProjectSetting{
			Service:              internal_default_service,
			IntegrationBranch:    defaultBranch,
			Remote:               internal_default_remote,
			IssueURL:             internal_default_issue_url,
			DeliveryBranchPrefix: internal_default_delivery_branch_prefix,
			PipelineUrl:          internal_pipeline_url,
			Scope:                internal_default_scope,
			File:                 internal_default_file,
		}
	}

	//Map section into object
	conf := new(ProjectSetting)
	err := loadSetting().MapTo(conf)
	if err != nil {
		panic(err)
	}

	//Add configuration origin
	conf.File = configFile
	conf.Scope = configScope
	conf.INIBlock = INIBlock

	if err := ValidateLoadedSetting(conf); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	return conf
}

//BootstrapPhlowConfig ...
//Creates a new .phlow ini file on given location
func BootstrapPhlowConfig(local, integrationBranch string) error {
	fmt.Println("No .phlow config found")
	cfg := ini.Empty()
	sec, _ := cfg.NewSection("default")
	sec.Key("remote").SetValue(internal_default_remote)
	sec.Key("service").SetValue(internal_default_service)
	sec.Key("integration_branch").SetValue(integrationBranch)
	sec.Key("issue_url").SetValue(internal_default_issue_url)
	sec.Key("delivery_branch_prefix").SetValue(internal_default_delivery_branch_prefix)

	err := cfg.SaveTo(local + "/" + phlow_file_name)
	if err != nil {
		return err
	}
	fmt.Println("Bootstrapping new .phlow file")
	return nil
}

//ValidateLoadedSetting ...
func ValidateLoadedSetting(setting *ProjectSetting) error {
	r := reflect.ValueOf(setting).Elem()
	t := r.Type()

	//Non Optional Field checks..
	for i := 0; i < t.NumField(); i++ {
		if t.Field(i).Name == "Service" && (r.Field(i).String() == "") {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "service", setting.INIBlock))
		}

		if t.Field(i).Name == "IssueURL" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "issue_url", setting.INIBlock))
		}

		if t.Field(i).Name == "IntegrationBranch" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "integration_branch", setting.INIBlock))
		}

		if t.Field(i).Name == "Remote" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "remote", setting.INIBlock))
		}

		if t.Field(i).Name == "DeliveryBranchPrefix" && r.Field(i).String() == "" {
			return errors.New(fmt.Sprintf("Error in configuration file: %s\n"+
				"Non-optional field missing: %s \nIn configuration block: %s \n ", setting.Scope+"/"+setting.File, "delivery_branch_prefix", setting.INIBlock))
		}
	}
	return nil
}

//LoadToolSettings ...
func LoadToolSettings(run executor.Runner) *ToolsSetting {
	var set = ToolsSetting{}

	user, err := run("git", "config", "--get", "phlow.user")
	if err != nil {
		panic(err)
	}
	set.User = strings.Replace(user, "\n", "", -1)

	token, err := run("git", "config", "--global", "phlow.token")
	if err != nil {
		panic(err)
	}
	set.Token = strings.Replace(token, "\n", "", -1)

	return &set
}

//GetGlobal ...
func GetGlobal() string {
	return os.Getenv("HOME")
}

//GetLocal ...
func GetLocal() string {
	absoluteRepoPath, err := executor.RunCommand("git", "rev-parse", "--show-toplevel")
	if err != nil {
		panic(err)
	}
	return strings.TrimSpace(absoluteRepoPath)
}

//GetDefaultBranchFromInternalDefault ...
//Trying to retrieve the default branch from github
func GetDefaultBranchFromInternalDefault() (string, error) {
	git := githandler.Git{Run: executor.RunGit}

	remote, err := git.LSRemote("--get-url", internal_default_remote)
	if err != nil {
		return "", err
	}
	orgAndRepo := githandler.OrgAndRepo(remote)
	token, err := git.Config("--get", "phlow.token")

	branch, err := plugins.DefaultBranchGitHub(internal_default_issue_url, orgAndRepo.Organisation, orgAndRepo.Repository, token)
	if err != nil {
		return "", err
	}
	return branch, nil
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"plugin"
	"rais/src/iiif"

	"github.com/uoregon-libraries/gopkg/logger"
)

type plugIDToPath func(iiif.ID) (string, error)

var idToPathPlugins []plugIDToPath

// LoadPlugins searches for any plugins in the current working directory /plugins
func LoadPlugins(l *logger.Logger) {
	var dir = filepath.Dir(os.Args[0])
	var plugdir = filepath.Join(dir, "plugins")
	l.Debugf("Looking for plugins in %q", plugdir)

	var _, err = os.Stat(plugdir)
	if os.IsNotExist(err) {
		// If there's no plugin dir, we just keep chugging along normally
		l.Debugf("Plugin directory not found; skipping plugin loading")
		return
	}
	if err != nil {
		l.Fatalf("Unable to stat %q: %s", plugdir, err)
	}

	var infos []os.FileInfo
	infos, err = ioutil.ReadDir(plugdir)
	if err != nil {
		l.Fatalf("Unable to read plugin directory %q: %s", plugdir, err)
	}

	for _, info := range infos {
		var fullpath = filepath.Join(plugdir, info.Name())
		if info.IsDir() {
			l.Warnf("Skipping unknown subdirectory %q (plugin subdirectories are not supported)", fullpath)
		}

		if filepath.Ext(fullpath) != ".so" {
			l.Warnf("Skipping unknown file %q (plugins must be compiled .so files)", fullpath)
		}

		l.Infof("Loading plugin %q", fullpath)
		loadPlugin(fullpath, l)
	}
}

func loadPlugin(fullpath string, l *logger.Logger) {
	var p, err = plugin.Open(fullpath)
	if err != nil {
		l.Errorf("Error loading plugin %q: %s", fullpath, err)
		return
	}

	var sym plugin.Symbol

	sym, err = p.Lookup("SetLogger")
	if err == nil {
		var setLogger, ok = sym.(func(*logger.Logger))
		if ok {
			l.Debugf("Attaching central logger to plugin")
			setLogger(l)
		}
	}

	sym, err = p.Lookup("IDToPath")
	if err == nil {
		var f, ok = sym.(func(iiif.ID) (string, error))
		if !ok {
			l.Errorf("Plugin %q exposes an invalid IDToPath function", fullpath)
			return
		}

		l.Debugf("Adding IDToPath from %q", fullpath)
		idToPathPlugins = append(idToPathPlugins, plugIDToPath(f))
	}
}
<commit_msg>rais-server: load plugins in a defined order<commit_after>package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"plugin"
	"rais/src/iiif"
	"sort"

	"github.com/uoregon-libraries/gopkg/logger"
)

type plugIDToPath func(iiif.ID) (string, error)

var idToPathPlugins []plugIDToPath

// LoadPlugins searches for any plugins in the current working directory /plugins
func LoadPlugins(l *logger.Logger) {
	var dir = filepath.Dir(os.Args[0])
	var plugdir = filepath.Join(dir, "plugins")
	l.Debugf("Looking for plugins in %q", plugdir)

	var _, err = os.Stat(plugdir)
	if os.IsNotExist(err) {
		// If there's no plugin dir, we just keep chugging along normally
		l.Debugf("Plugin directory not found; skipping plugin loading")
		return
	}
	if err != nil {
		l.Fatalf("Unable to stat %q: %s", plugdir, err)
	}

	var infos []os.FileInfo
	infos, err = ioutil.ReadDir(plugdir)
	if err != nil {
		l.Fatalf("Unable to read plugin directory %q: %s", plugdir, err)
	}

	sort.Slice(infos, func(i, j int) bool { return infos[i].Name() < infos[j].Name() })
	for _, info := range infos {
		var fullpath = filepath.Join(plugdir, info.Name())
		if info.IsDir() {
			l.Warnf("Skipping unknown subdirectory %q (plugin subdirectories are not supported)", fullpath)
		}

		if filepath.Ext(fullpath) != ".so" {
			l.Warnf("Skipping unknown file %q (plugins must be compiled .so files)", fullpath)
		}

		l.Infof("Loading plugin %q", fullpath)
		loadPlugin(fullpath, l)
	}
}

func loadPlugin(fullpath string, l *logger.Logger) {
	var p, err = plugin.Open(fullpath)
	if err != nil {
		l.Errorf("Error loading plugin %q: %s", fullpath, err)
		return
	}

	var sym plugin.Symbol

	sym, err = p.Lookup("SetLogger")
	if err == nil {
		var setLogger, ok = sym.(func(*logger.Logger))
		if ok {
			l.Debugf("Attaching central logger to plugin")
			setLogger(l)
		}
	}

	sym, err = p.Lookup("IDToPath")
	if err == nil {
		var f, ok = sym.(func(iiif.ID) (string, error))
		if !ok {
			l.Errorf("Plugin %q exposes an invalid IDToPath function", fullpath)
			return
		}

		l.Debugf("Adding IDToPath from %q", fullpath)
		idToPathPlugins = append(idToPathPlugins, plugIDToPath(f))
	}
}
<|endoftext|>


<commit_before>package api

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/google/jsonapi"
	"github.com/julienschmidt/httprouter"
	"gitlab.com/faststack/machinestack/model"
)

func TestMachineList(t *testing.T) {
	machine := model.Machine{
		Name:   "TestMachineList",
		Image:  "ubuntu/trusty",
		Driver: "lxd",
		UserID: testToken.Claims.(jwt.MapClaims)["id"].(int),
	}
	if err := testDB.Insert(&machine); err != nil {
		t.Fatal(err)
	}

	r, err := http.NewRequest("GET", "/machines", nil)
	if err != nil {
		t.Fatal(err)
	}
	*r = *r.WithContext(context.WithValue(r.Context(), UserContextKey, testToken))

	rr := httptest.NewRecorder()
	router := httprouter.New()
	router.Handle("GET", "/machines", testHandler.MachineList)
	router.ServeHTTP(rr, r)

	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
	}

	var machines []model.Machine
	if err := jsonapi.UnmarshalPayload(rr.Body, machines); err != nil {
		t.Fatal(err)
	}

	found := false
	for _, m := range machines {
		if m.Name == machine.Name {
			found = true
			break
		}
	}

	if !found {
		t.Errorf("machine was not included in list")
	}
}
<commit_msg>fix unmarshal<commit_after>package api

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/google/jsonapi"
	"github.com/julienschmidt/httprouter"
	"gitlab.com/faststack/machinestack/model"
)

func TestMachineList(t *testing.T) {
	machine := model.Machine{
		Name:   "TestMachineList",
		Image:  "ubuntu/trusty",
		Driver: "lxd",
		UserID: testToken.Claims.(jwt.MapClaims)["id"].(int),
	}
	if err := testDB.Insert(&machine); err != nil {
		t.Fatal(err)
	}

	r, err := http.NewRequest("GET", "/machines", nil)
	if err != nil {
		t.Fatal(err)
	}
	*r = *r.WithContext(context.WithValue(r.Context(), UserContextKey, testToken))

	rr := httptest.NewRecorder()
	router := httprouter.New()
	router.Handle("GET", "/machines", testHandler.MachineList)
	router.ServeHTTP(rr, r)

	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
	}

	var machines []*model.Machine
	if err := jsonapi.UnmarshalPayload(rr.Body, machines); err != nil {
		t.Fatal(err)
	}

	found := false
	for _, m := range machines {
		if m.Name == machine.Name {
			found = true
			break
		}
	}

	if !found {
		t.Errorf("machine was not included in list")
	}
}
<|endoftext|>


<commit_before>package via

import (
	"compress/gzip"
	"errors"
	"fmt"
	"github.com/str1ngs/gurl"
	"github.com/str1ngs/util/console"
	"github.com/str1ngs/util/file"
	"github.com/str1ngs/util/json"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"sort"
)

var (
	client  = new(http.Client)
	verbose = false
	elog    = log.New(os.Stderr, "", log.Lshortfile)
	lfmt    = "%-20.20s %v\n"
	debug   = false
	expand  = os.ExpandEnv
)

func Root(s string) {
	config.Root = s
}

func Verbose(b bool) {
	verbose = b
}
func Debug(b bool) {
	debug = b
}
func DownloadSrc(plan *Plan) (err error) {
	if file.Exists(plan.SourcePath()) {
		return nil
	}
	return gurl.Download(cache.Sources(), plan.Url)
}

// Stages the downloaded source via's cache directory
// the stage only happens once unless BuilInStage is used
func Stage(plan *Plan) (err error) {
	if plan.Url == "" || file.Exists(plan.GetStageDir()) {
		// nothing to stage
		return nil
	}
	err = GNUUntar(cache.Stages(), plan.SourceFile())
	if err != nil {
		elog.Println(err)
		return err
	}
	if err := doCommands(join(cache.Stages(), plan.stageDir()), plan.Patch); err != nil {
		return err
	}
	return
}

// Calls each shell command in the plans Build field.
func Build(plan *Plan) (err error) {
	var (
		build = plan.Build
	)
	if file.Exists(plan.PackagePath()) {
		fmt.Printf("FIXME: (short flags)  package %s exists building anyways.\n", plan.PackagePath())
	}
	flags := config.Flags
	if plan.Flags != nil {
		flags = append(flags, plan.Flags...)
	}
	os.Setenv("SRCDIR", plan.GetStageDir())
	os.Setenv("Flags", expand(flags.String()))
	if !file.Exists(plan.BuildDir()) {
		os.MkdirAll(plan.BuildDir(), 0755)
	}
	// Parent plan's Build is run first this plans is added at the end.
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		build = append(parent.Build, plan.Build...)
	}
	return doCommands(plan.BuildDir(), build)
}

func doCommands(dir string, cmds []string) (err error) {
	for i, j := range cmds {
		if debug {
			elog.Println(i, j)
		}
		j := expand(j)
		cmd := exec.Command("sh", "-c", j)
		cmd.Dir = dir
		cmd.Stdin = os.Stdin
		if verbose {
			cmd.Stdout = os.Stdout
		}
		cmd.Stderr = os.Stderr
		err = cmd.Run()
		if err != nil {
			elog.Printf("%s: %s\n", j, err)
			return err
		}
	}
	return nil
}

func Package(bdir string, plan *Plan) (err error) {
	var (
		pack = plan.Package
	)
	pdir := join(cache.Packages(), plan.NameVersion())
	if bdir == "" {
		bdir = join(cache.Builds(), plan.NameVersion())
	}
	if plan.BuildInStage {
		bdir = join(cache.Stages(), plan.stageDir())
	}
	if file.Exists(pdir) {
		err := os.RemoveAll(pdir)
		if err != nil {
			return err
		}
	}
	err = os.Mkdir(pdir, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	os.Setenv("PKGDIR", pdir)
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		pack = parent.Package
		pack = append(pack, plan.Package...)
	}
	err = doCommands(bdir, pack)
	if err != nil {
		return err
	}
	for _, j := range plan.SubPackages {
		sub, err := NewPlan(j)
		if err != nil {
			return err
		}
		if err = Package(bdir, sub); err != nil {
			return err
		}
	}
	return CreatePackage(plan)
	/*
		err = CreatePackage(plan)
		if err != nil {
			return err
		}
		return Sign(plan)
	*/
}

func CreatePackage(plan *Plan) (err error) {
	pfile := plan.PackagePath()
	os.MkdirAll(path.Dir(pfile), 0755)
	fd, err := os.Create(pfile)
	if err != nil {
		elog.Println(err)
		return err
	}
	defer fd.Close()
	gz := gzip.NewWriter(fd)
	defer gz.Close()
	return Tarball(gz, plan)
}

func Install(name string) (err error) {
	plan, err := NewPlan(name)
	if err != nil {
		elog.Println(name, err)
		return
	}
	fmt.Printf(lfmt, "installing", plan.Name)
	if IsInstalled(name) {
		fmt.Printf("FIXME: (short flags) package %s installed upgrading anyways.\n", plan.NameVersion())
		err := Remove(name)
		if err != nil {
			return err
		}
	}
	for _, d := range plan.Depends {
		if IsInstalled(d) {
			continue
		}
		err := Install(d)
		if err != nil {
			return err
		}
	}
	db := path.Join(config.DB.Installed(), plan.Name)
	if file.Exists(db) {
		return fmt.Errorf("%s is already installed", name)
	}
	pfile := plan.PackagePath()
	if !file.Exists(pfile) {
		return errors.New(fmt.Sprintf("%s does not exist", pfile))
		fatal(gurl.Download(config.Repo, config.Binary+"/"+pfile))
		//fatal(gurl.Download(config.Repo, config.Binary+"/"+plan.PackageFile()+".sig"))
	}
	/*
		err = CheckSig(pfile)
		if err != nil {
			return
		}
	*/
	man, err := ReadPackManifest(pfile)
	if err != nil {
		return err
	}
	errs := conflicts(man)
	if len(errs) > 0 {
		for _, e := range errs {
			elog.Println(e)
		}
		//return errs[0]
	}
	fd, err := os.Open(pfile)
	if err != nil {
		return
	}
	defer fd.Close()
	gz, err := gzip.NewReader(fd)
	if err != nil {
		return
	}
	defer gz.Close()
	err = Untar(config.Root, gz)
	if err != nil {
		return err
	}
	err = os.MkdirAll(db, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	err = json.Write(man, join(db, "manifest.json"))
	if err != nil {
		return err
	}
	return PostInstall(plan)
}

func PostInstall(plan *Plan) (err error) {
	return doCommands("/", append(plan.PostInstall, config.PostInstall...))
}

func Remove(name string) (err error) {
	if !IsInstalled(name) {
		err = fmt.Errorf("%s is not installed.", name)
		elog.Println(err)
		return err
	}

	man, err := ReadManifest(name)
	if err != nil {
		return err
	}
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		err = os.Remove(fpath)
		if err != nil {
			elog.Println(f, err)
		}
	}

	return os.RemoveAll(join(config.DB.Installed(), name))
}

// Run all of the functions required to build a package
func BuildSteps(plan *Plan) (err error) {
	if file.Exists(plan.PackageFile()) {
		return fmt.Errorf("package %s exists", plan.PackageFile())
	}
	fmt.Printf(lfmt, "download", plan.NameVersion())
	if err := DownloadSrc(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "stage", plan.NameVersion())
	if err := Stage(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "build", plan.NameVersion())
	if err := Build(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "package", plan.NameVersion())
	if err := Package("", plan); err != nil {
		elog.Println(err)
		return err
	}
	return nil
}

var (
	rexName   = regexp.MustCompile("[a-z]+")
	rexTruple = regexp.MustCompile("[0-9]+.[0-9]+.[0-9]+")
	rexDouble = regexp.MustCompile("[0-9]+.[0-9]+")
)

// Creates a new plan from a given Url
func Create(url string) (err error) {
	var (
		xfile   = path.Base(url)
		name    = rexName.FindString(xfile)
		truple  = rexTruple.FindString(xfile)
		double  = rexDouble.FindString(xfile)
		version string
	)
	switch {
	case truple != "":
		version = truple
	case double != "":
		version = double
	}
	plan := &Plan{Name: name, Version: version, Url: url, Group: "core"}
	if file.Exists(plan.Path()) {
		return errors.New(fmt.Sprintf("%s already exists", plan.Path()))
	}
	return plan.Save()
}

func IsInstalled(name string) bool {
	return file.Exists(join(config.DB.Installed(), name))
}

func Lint() (err error) {
	e, err := PlanFiles()
	if err != nil {
		return err
	}
	for _, j := range e {
		plan, err := ReadPath(j)
		if err != nil {
			err = fmt.Errorf("%s %s", j, err)
			elog.Println(err)
			return err
		}
		// If Group is empty, we can set it
		if plan.Group == "" {
			plan.Group = baseDir(j)
		}
		if verbose {
			console.Println("lint", plan.NameVersion(), plan.Package)
		}
		sort.Strings(plan.SubPackages)
		sort.Strings(plan.Flags)
		sort.Strings(plan.Remove)
		sort.Strings(plan.Depends)
		err = plan.Save()
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	console.Flush()
	return nil
}

func fatal(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
func Clean(name string) error {
	plan, err := NewPlan(name)
	if err != nil {
		return err
	}
	dir := join(cache.Builds(), plan.NameVersion())
	if err = os.RemoveAll(dir); err != nil {
		return err
	}

	dir = join(cache.Stages(), plan.stageDir())
	return os.RemoveAll(dir)
}

func PlanFiles() ([]string, error) {
	return filepath.Glob(join(config.Plans, "*", "*.json"))
}

func conflicts(man *Plan) (errs []error) {
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		if file.Exists(fpath) {
			errs = append(errs, fmt.Errorf("%s already exists.", f))
		}
	}
	return errs
}

func GetConfig() *Config {
	return config
}
<commit_msg>debug expanded command<commit_after>package via

import (
	"compress/gzip"
	"errors"
	"fmt"
	"github.com/str1ngs/gurl"
	"github.com/str1ngs/util/console"
	"github.com/str1ngs/util/file"
	"github.com/str1ngs/util/json"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"sort"
)

var (
	client  = new(http.Client)
	verbose = false
	elog    = log.New(os.Stderr, "", log.Lshortfile)
	lfmt    = "%-20.20s %v\n"
	debug   = false
	expand  = os.ExpandEnv
)

func Root(s string) {
	config.Root = s
}

func Verbose(b bool) {
	verbose = b
}
func Debug(b bool) {
	debug = b
}
func DownloadSrc(plan *Plan) (err error) {
	if file.Exists(plan.SourcePath()) {
		return nil
	}
	return gurl.Download(cache.Sources(), plan.Url)
}

// Stages the downloaded source via's cache directory
// the stage only happens once unless BuilInStage is used
func Stage(plan *Plan) (err error) {
	if plan.Url == "" || file.Exists(plan.GetStageDir()) {
		// nothing to stage
		return nil
	}
	err = GNUUntar(cache.Stages(), plan.SourceFile())
	if err != nil {
		elog.Println(err)
		return err
	}
	if err := doCommands(join(cache.Stages(), plan.stageDir()), plan.Patch); err != nil {
		return err
	}
	return
}

// Calls each shell command in the plans Build field.
func Build(plan *Plan) (err error) {
	var (
		build = plan.Build
	)
	if file.Exists(plan.PackagePath()) {
		fmt.Printf("FIXME: (short flags)  package %s exists building anyways.\n", plan.PackagePath())
	}
	flags := config.Flags
	if plan.Flags != nil {
		flags = append(flags, plan.Flags...)
	}
	os.Setenv("SRCDIR", plan.GetStageDir())
	os.Setenv("Flags", expand(flags.String()))
	if !file.Exists(plan.BuildDir()) {
		os.MkdirAll(plan.BuildDir(), 0755)
	}
	// Parent plan's Build is run first this plans is added at the end.
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		build = append(parent.Build, plan.Build...)
	}
	return doCommands(plan.BuildDir(), build)
}

func doCommands(dir string, cmds []string) (err error) {
	for i, j := range cmds {
		j := expand(j)
		if debug {
			elog.Println(i, j)
		}
		cmd := exec.Command("sh", "-c", j)
		cmd.Dir = dir
		cmd.Stdin = os.Stdin
		if verbose {
			cmd.Stdout = os.Stdout
		}
		cmd.Stderr = os.Stderr
		err = cmd.Run()
		if err != nil {
			elog.Printf("%s: %s\n", j, err)
			return err
		}
	}
	return nil
}

func Package(bdir string, plan *Plan) (err error) {
	var (
		pack = plan.Package
	)
	pdir := join(cache.Packages(), plan.NameVersion())
	if bdir == "" {
		bdir = join(cache.Builds(), plan.NameVersion())
	}
	if plan.BuildInStage {
		bdir = join(cache.Stages(), plan.stageDir())
	}
	if file.Exists(pdir) {
		err := os.RemoveAll(pdir)
		if err != nil {
			return err
		}
	}
	err = os.Mkdir(pdir, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	os.Setenv("PKGDIR", pdir)
	if plan.Inherit != "" {
		parent, _ := NewPlan(plan.Inherit)
		pack = parent.Package
		pack = append(pack, plan.Package...)
	}
	err = doCommands(bdir, pack)
	if err != nil {
		return err
	}
	for _, j := range plan.SubPackages {
		sub, err := NewPlan(j)
		if err != nil {
			return err
		}
		if err = Package(bdir, sub); err != nil {
			return err
		}
	}
	return CreatePackage(plan)
	/*
		err = CreatePackage(plan)
		if err != nil {
			return err
		}
		return Sign(plan)
	*/
}

func CreatePackage(plan *Plan) (err error) {
	pfile := plan.PackagePath()
	os.MkdirAll(path.Dir(pfile), 0755)
	fd, err := os.Create(pfile)
	if err != nil {
		elog.Println(err)
		return err
	}
	defer fd.Close()
	gz := gzip.NewWriter(fd)
	defer gz.Close()
	return Tarball(gz, plan)
}

func Install(name string) (err error) {
	plan, err := NewPlan(name)
	if err != nil {
		elog.Println(name, err)
		return
	}
	fmt.Printf(lfmt, "installing", plan.Name)
	if IsInstalled(name) {
		fmt.Printf("FIXME: (short flags) package %s installed upgrading anyways.\n", plan.NameVersion())
		err := Remove(name)
		if err != nil {
			return err
		}
	}
	for _, d := range plan.Depends {
		if IsInstalled(d) {
			continue
		}
		err := Install(d)
		if err != nil {
			return err
		}
	}
	db := path.Join(config.DB.Installed(), plan.Name)
	if file.Exists(db) {
		return fmt.Errorf("%s is already installed", name)
	}
	pfile := plan.PackagePath()
	if !file.Exists(pfile) {
		return errors.New(fmt.Sprintf("%s does not exist", pfile))
		fatal(gurl.Download(config.Repo, config.Binary+"/"+pfile))
		//fatal(gurl.Download(config.Repo, config.Binary+"/"+plan.PackageFile()+".sig"))
	}
	/*
		err = CheckSig(pfile)
		if err != nil {
			return
		}
	*/
	man, err := ReadPackManifest(pfile)
	if err != nil {
		return err
	}
	errs := conflicts(man)
	if len(errs) > 0 {
		for _, e := range errs {
			elog.Println(e)
		}
		//return errs[0]
	}
	fd, err := os.Open(pfile)
	if err != nil {
		return
	}
	defer fd.Close()
	gz, err := gzip.NewReader(fd)
	if err != nil {
		return
	}
	defer gz.Close()
	err = Untar(config.Root, gz)
	if err != nil {
		return err
	}
	err = os.MkdirAll(db, 0755)
	if err != nil {
		elog.Println(err)
		return err
	}
	err = json.Write(man, join(db, "manifest.json"))
	if err != nil {
		return err
	}
	return PostInstall(plan)
}

func PostInstall(plan *Plan) (err error) {
	return doCommands("/", append(plan.PostInstall, config.PostInstall...))
}

func Remove(name string) (err error) {
	if !IsInstalled(name) {
		err = fmt.Errorf("%s is not installed.", name)
		elog.Println(err)
		return err
	}

	man, err := ReadManifest(name)
	if err != nil {
		return err
	}
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		err = os.Remove(fpath)
		if err != nil {
			elog.Println(f, err)
		}
	}

	return os.RemoveAll(join(config.DB.Installed(), name))
}

// Run all of the functions required to build a package
func BuildSteps(plan *Plan) (err error) {
	if file.Exists(plan.PackageFile()) {
		return fmt.Errorf("package %s exists", plan.PackageFile())
	}
	fmt.Printf(lfmt, "download", plan.NameVersion())
	if err := DownloadSrc(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "stage", plan.NameVersion())
	if err := Stage(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "build", plan.NameVersion())
	if err := Build(plan); err != nil {
		elog.Println(err)
		return err
	}
	fmt.Printf(lfmt, "package", plan.NameVersion())
	if err := Package("", plan); err != nil {
		elog.Println(err)
		return err
	}
	return nil
}

var (
	rexName   = regexp.MustCompile("[a-z]+")
	rexTruple = regexp.MustCompile("[0-9]+.[0-9]+.[0-9]+")
	rexDouble = regexp.MustCompile("[0-9]+.[0-9]+")
)

// Creates a new plan from a given Url
func Create(url string) (err error) {
	var (
		xfile   = path.Base(url)
		name    = rexName.FindString(xfile)
		truple  = rexTruple.FindString(xfile)
		double  = rexDouble.FindString(xfile)
		version string
	)
	switch {
	case truple != "":
		version = truple
	case double != "":
		version = double
	}
	plan := &Plan{Name: name, Version: version, Url: url, Group: "core"}
	if file.Exists(plan.Path()) {
		return errors.New(fmt.Sprintf("%s already exists", plan.Path()))
	}
	return plan.Save()
}

func IsInstalled(name string) bool {
	return file.Exists(join(config.DB.Installed(), name))
}

func Lint() (err error) {
	e, err := PlanFiles()
	if err != nil {
		return err
	}
	for _, j := range e {
		plan, err := ReadPath(j)
		if err != nil {
			err = fmt.Errorf("%s %s", j, err)
			elog.Println(err)
			return err
		}
		// If Group is empty, we can set it
		if plan.Group == "" {
			plan.Group = baseDir(j)
		}
		if verbose {
			console.Println("lint", plan.NameVersion(), plan.Package)
		}
		sort.Strings(plan.SubPackages)
		sort.Strings(plan.Flags)
		sort.Strings(plan.Remove)
		sort.Strings(plan.Depends)
		err = plan.Save()
		if err != nil {
			elog.Println(err)
			return err
		}
	}
	console.Flush()
	return nil
}

func fatal(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
func Clean(name string) error {
	plan, err := NewPlan(name)
	if err != nil {
		return err
	}
	dir := join(cache.Builds(), plan.NameVersion())
	if err = os.RemoveAll(dir); err != nil {
		return err
	}

	dir = join(cache.Stages(), plan.stageDir())
	return os.RemoveAll(dir)
}

func PlanFiles() ([]string, error) {
	return filepath.Glob(join(config.Plans, "*", "*.json"))
}

func conflicts(man *Plan) (errs []error) {
	for _, f := range man.Files {
		fpath := join(config.Root, f)
		if file.Exists(fpath) {
			errs = append(errs, fmt.Errorf("%s already exists.", f))
		}
	}
	return errs
}

func GetConfig() *Config {
	return config
}
<|endoftext|>