<commit_before>package main

import (
	"net/http"
	"text/template"
)

type Template struct {
	// add something
}

func base(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//t := template.New("base.html")
		t, r := template.ParseFiles("base.html")
		t.Execute(w, r)
	}
}

func commits(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//commit := pushCommits()
		//t := template.New("_commits.html")
		t, r := template.ParseFiles("_commits.html")
		t.Execute(w, r)
	}
}

func status(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//t := template.New("_status.html")
		t, r := template.ParseFiles("_status.html")
		t.Execute(w, r)
	}
}

func details(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//t := template.New("_details.html")
		t, r := template.ParseFiles("_details.html")
		t.Execute(w, r)
	}
}

func diff(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func pull(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//remote = data['remote']
		//branch = data['branch']
	}
}

func push(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//remote = data['remote']
		//branch = data['branch']
	}
}

func checkout(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func create_branch(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//name = data['name']
		// add something
	}
}

func diff_stat(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func stage(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//path = data['path']
		// add something
	}
}

func unstage(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//path = data['path']
		// add something
	}
}

func get_heads_with_commit(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func get_branches_in_remote(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func merge(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func open(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func run(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func stop(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func stopped(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func exit(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func only_server(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func terminate_gitj_sig() {
	//terminate_gitj()
}

func terminate_gitj(w http.ResponseWriter, r *http.Request) {
	//exit()
}

func main() {

	// Adding function handlers
	// For Testing the Go Build
	http.HandleFunc("/static/templates/base.html", base)
	http.HandleFunc("/static/templates/_commits.html", commits)
	http.HandleFunc("/static/templates/_status.html", status)
	http.HandleFunc("/static/templates/_details.html", details)

	// TODO: Add more...

	// Event Listener
    http.ListenAndServe(":8080", http.FileServer(http.Dir(".")))
}
<commit_msg>add hints<commit_after>package main

import (
	"net/http"
	"text/template"
)

type Template struct {
	//io.Writer
	// add something
}

func base(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//t := template.New("base.html")
		t, r := template.ParseFiles("base.html")
		t.Execute(w, r)
	}
}

func commits(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//t, w := template.ParseFiles("_commits.html")
		t := template.New("_commits.html")
		t, r := template.ParseFiles("_commits.html")
		t.Execute(w, r)
		//io.Writer.Write(w)
	}
}

func status(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//t := template.New("_status.html")
		t, r := template.ParseFiles("_status.html")
		t.Execute(w, r)
	}
}

func details(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//t := template.New("_details.html")
		t, r := template.ParseFiles("_details.html")
		t.Execute(w, r)
	}
}

func diff(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func pull(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//remote := data('remote')
		//branch = data['branch']
	}
}

func push(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//remote = data['remote']
		//branch = data['branch']
	}
}

func checkout(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func create_branch(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//name = data['name']
		// add something
	}
}

func diff_stat(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func stage(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//path = data['path']
		// add something
	}
}

func unstage(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		//path = data['path']
		// add something
	}
}

func get_heads_with_commit(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func get_branches_in_remote(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func merge(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func open(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func run(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func stop(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func stopped(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func exit(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func only_server(w http.ResponseWriter, r *http.Request) {
	if(r.URL.Path[1:] == "") {
		// add something
	}
}

func terminate_gitj_sig() {
	//terminate_gitj()
}

func terminate_gitj(w http.ResponseWriter, r *http.Request) {
	//exit()
}

func main() {

	// Adding function handlers
	// For Testing the Go Build
	http.HandleFunc("/static/templates/base.html", base)
	http.HandleFunc("/static/templates/_commits.html", commits)
	http.HandleFunc("/static/templates/_status.html", status)
	http.HandleFunc("/static/templates/_details.html", details)

	// TODO: Add more...

	// Event Listener
    http.ListenAndServe(":8080", http.FileServer(http.Dir(".")))
}
<|endoftext|>


<commit_before>package modd

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path/filepath"
	"time"

	"github.com/cortesi/modd/conf"
	"github.com/cortesi/modd/notify"
	"github.com/cortesi/modd/shell"
	"github.com/cortesi/modd/watch"
	"github.com/cortesi/termlog"
)

// Version is the modd release version
const Version = "0.3"

const lullTime = time.Millisecond * 100

const shellVarName = "@shell"

// CommonExcludes is a list of commonly excluded files suitable for passing in
// the excludes parameter to Watch - includes repo directories, temporary
// files, and so forth.
var CommonExcludes = []string{
	// VCS
	"**/.git/**",
	"**/.hg/**",
	"**/.svn/**",
	"**/.bzr/**",

	// OSX
	"**/.DS_Store/**",

	// Temporary files
	"**.tmp",
	"**~",
	"**#",
	"**.bak",
	"**.swp",
	"**.___jb_old___",
	"**.___jb_bak___",

	// Python
	"**.py[cod]",

	// Node
	"**/node_modules/**",
}

// ModRunner coordinates running the modd command
type ModRunner struct {
	Log        termlog.TermLog
	Config     *conf.Config
	ConfPath   string
	ConfReload bool
	Notifiers  []notify.Notifier
}

// NewModRunner constructs a new ModRunner
func NewModRunner(confPath string, log termlog.TermLog, notifiers []notify.Notifier, confreload bool) (*ModRunner, error) {
	mr := &ModRunner{
		Log:        log,
		ConfPath:   confPath,
		ConfReload: confreload,
		Notifiers:  notifiers,
	}
	err := mr.ReadConfig()
	if err != nil {
		return nil, err
	}
	return mr, nil
}

// ReadConfig parses the configuration file in ConfPath
func (mr *ModRunner) ReadConfig() error {
	ret, err := ioutil.ReadFile(mr.ConfPath)
	if err != nil {
		return fmt.Errorf("Error reading config file %s: %s", mr.ConfPath, err)
	}
	newcnf, err := conf.Parse(mr.ConfPath, string(ret))
	if err != nil {
		return fmt.Errorf("Error reading config file %s: %s", mr.ConfPath, err)
	}
	newcnf.CommonExcludes(CommonExcludes)
	mr.Config = newcnf
	return nil
}

// PrepOnly runs all prep functions and exits
func (mr *ModRunner) PrepOnly() error {
	for _, b := range mr.Config.Blocks {
		err := RunPreps(b, mr.Config.GetVariables(), nil, mr.Log, mr.Notifiers)
		if err != nil {
			return err
		}
	}
	return nil
}

// Gives control of chan to caller
func (mr *ModRunner) runOnChan(modchan chan *watch.Mod, readyCallback func()) error {
	err := mr.PrepOnly()
	if err != nil {
		return err
	}

	dworld, err := NewDaemonWorld(mr.Config, mr.Log)
	if err != nil {
		return err
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	defer signal.Reset(os.Interrupt, os.Kill)
	defer dworld.Shutdown(os.Kill)
	go func() {
		dworld.Shutdown(<-c)
		os.Exit(0)
	}()

	dworld.Start()
	watchpaths := mr.Config.WatchPatterns()
	if mr.ConfReload {
		watchpaths = append(watchpaths, filepath.Dir(mr.ConfPath))
	}

	// FIXME: This takes a long time. We could start it in parallel with the
	// first process run in a goroutine
	watcher, err := watch.Watch(watchpaths, lullTime, modchan)
	if err != nil {
		return fmt.Errorf("Error watching: %s", err)
	}
	defer watcher.Stop()
	go readyCallback()

	for mod := range modchan {
		if mod == nil {
			break
		}

		if mr.ConfReload && mod.Has(mr.ConfPath) {
			err := mr.ReadConfig()
			if err != nil {
				mr.Log.Warn("Reloading config - error reading %s: %s", mr.ConfPath, err)
				continue
			} else {
				mr.Log.Notice("Reloaded config %s", mr.ConfPath)
				return nil
			}
		}

		mr.Log.SayAs("debug", "Delta: \n%s", mod.String())
		for i, b := range mr.Config.Blocks {
			lmod, err := mod.Filter(b.Include, b.Exclude)
			if err != nil {
				mr.Log.Shout("Error filtering events: %s", err)
				continue
			}
			if lmod.Empty() {
				continue
			}
			err = RunPreps(b, mr.Config.GetVariables(), lmod, mr.Log, mr.Notifiers)
			if err != nil {
				if _, ok := err.(ProcError); ok {
					continue
				} else {
					return err
				}
			}
			dworld.DaemonPens[i].Restart()
		}
	}
	return nil
}

// Run is the top-level runner for modd
func (mr *ModRunner) Run() error {
	for {
		shellMethod := mr.Config.GetVariables()[shellVarName]
		if !shell.Has(shellMethod) {
			return fmt.Errorf("No shell interface %q", shellMethod)
		}
		modchan := make(chan *watch.Mod, 1024)
		err := mr.runOnChan(modchan, func() {})
		if err != nil {
			return err
		}
	}
}
<commit_msg>Nicer errors when reloading config<commit_after>package modd

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path/filepath"
	"time"

	"github.com/cortesi/modd/conf"
	"github.com/cortesi/modd/notify"
	"github.com/cortesi/modd/shell"
	"github.com/cortesi/modd/watch"
	"github.com/cortesi/termlog"
)

// Version is the modd release version
const Version = "0.3"

const lullTime = time.Millisecond * 100

const shellVarName = "@shell"

// CommonExcludes is a list of commonly excluded files suitable for passing in
// the excludes parameter to Watch - includes repo directories, temporary
// files, and so forth.
var CommonExcludes = []string{
	// VCS
	"**/.git/**",
	"**/.hg/**",
	"**/.svn/**",
	"**/.bzr/**",

	// OSX
	"**/.DS_Store/**",

	// Temporary files
	"**.tmp",
	"**~",
	"**#",
	"**.bak",
	"**.swp",
	"**.___jb_old___",
	"**.___jb_bak___",

	// Python
	"**.py[cod]",

	// Node
	"**/node_modules/**",
}

// ModRunner coordinates running the modd command
type ModRunner struct {
	Log        termlog.TermLog
	Config     *conf.Config
	ConfPath   string
	ConfReload bool
	Notifiers  []notify.Notifier
}

// NewModRunner constructs a new ModRunner
func NewModRunner(confPath string, log termlog.TermLog, notifiers []notify.Notifier, confreload bool) (*ModRunner, error) {
	mr := &ModRunner{
		Log:        log,
		ConfPath:   confPath,
		ConfReload: confreload,
		Notifiers:  notifiers,
	}
	err := mr.ReadConfig()
	if err != nil {
		return nil, err
	}
	return mr, nil
}

// ReadConfig parses the configuration file in ConfPath
func (mr *ModRunner) ReadConfig() error {
	ret, err := ioutil.ReadFile(mr.ConfPath)
	if err != nil {
		return fmt.Errorf("Error reading config file %s: %s", mr.ConfPath, err)
	}
	newcnf, err := conf.Parse(mr.ConfPath, string(ret))
	if err != nil {
		return fmt.Errorf("Error reading config file %s: %s", mr.ConfPath, err)
	}

	shellMethod := newcnf.GetVariables()[shellVarName]
	if !shell.Has(shellMethod) {
		return fmt.Errorf("No shell interface %q", shellMethod)
	}

	// FIXME: this needs to be configurable
	newcnf.CommonExcludes(CommonExcludes)
	mr.Config = newcnf
	return nil
}

// PrepOnly runs all prep functions and exits
func (mr *ModRunner) PrepOnly() error {
	for _, b := range mr.Config.Blocks {
		err := RunPreps(b, mr.Config.GetVariables(), nil, mr.Log, mr.Notifiers)
		if err != nil {
			return err
		}
	}
	return nil
}

// Gives control of chan to caller
func (mr *ModRunner) runOnChan(modchan chan *watch.Mod, readyCallback func()) error {
	err := mr.PrepOnly()
	if err != nil {
		return err
	}

	dworld, err := NewDaemonWorld(mr.Config, mr.Log)
	if err != nil {
		return err
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	defer signal.Reset(os.Interrupt, os.Kill)
	defer dworld.Shutdown(os.Kill)
	go func() {
		dworld.Shutdown(<-c)
		os.Exit(0)
	}()

	dworld.Start()
	watchpaths := mr.Config.WatchPatterns()
	if mr.ConfReload {
		watchpaths = append(watchpaths, filepath.Dir(mr.ConfPath))
	}

	// FIXME: This takes a long time. We could start it in parallel with the
	// first process run in a goroutine
	watcher, err := watch.Watch(watchpaths, lullTime, modchan)
	if err != nil {
		return fmt.Errorf("Error watching: %s", err)
	}
	defer watcher.Stop()
	go readyCallback()

	for mod := range modchan {
		if mod == nil {
			break
		}

		if mr.ConfReload && mod.Has(mr.ConfPath) {
			mr.Log.Notice("Reloading config %s", mr.ConfPath)
			err := mr.ReadConfig()
			if err != nil {
				mr.Log.Warn("%s", err)
				continue
			} else {
				return nil
			}
		}

		mr.Log.SayAs("debug", "Delta: \n%s", mod.String())
		for i, b := range mr.Config.Blocks {
			lmod, err := mod.Filter(b.Include, b.Exclude)
			if err != nil {
				mr.Log.Shout("Error filtering events: %s", err)
				continue
			}
			if lmod.Empty() {
				continue
			}
			err = RunPreps(b, mr.Config.GetVariables(), lmod, mr.Log, mr.Notifiers)
			if err != nil {
				if _, ok := err.(ProcError); ok {
					continue
				} else {
					return err
				}
			}
			dworld.DaemonPens[i].Restart()
		}
	}
	return nil
}

// Run is the top-level runner for modd
func (mr *ModRunner) Run() error {
	for {
		modchan := make(chan *watch.Mod, 1024)
		err := mr.runOnChan(modchan, func() {})
		if err != nil {
			return err
		}
	}
}
<|endoftext|>


<commit_before>package nnmc

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/net/html/charset"
)

// NNMc values:
// client http.Client with cookie
type NNMc struct {
	client http.Client
}

// Topic from forum
type Topic struct {
	Href    string
	Name    string
	Year    string
	Quality string
}

// Film all values
// ID            id
// Name          Название
// EngName       Английское название
// Href          Ссылка
// Year          Год
// Genre         Жанр
// Country       Производство
// Director      Режиссер
// Producer      Продюсер
// Actors        Актеры
// Description   Описание
// Age           Возраст
// ReleaseDate   Дата мировой премьеры
// RussianDate   Дата премьеры в России
// Duration      Продолжительность
// Quality       Качество видео
// Translation   Перевод
// SubtitlesType Вид субтитров
// Subtitles     Субтитры
// Video         Видео
// Audio         Аудио
// Kinopoisk     Рейтинг кинопоиска
// Imdb          Рейтинг IMDb
// NNM           Рейтинг nnm-club
// Sound         Звук
// Size          Размер
// DateCreate    Дата создания раздачи
// Torrent       Ссылка на torrent
// Poster        Ссылка на постер
// Hide          Скрывать в общем списке
type Film struct {
	ID            int64   `gorm:"column:id" sql:"AUTO_INCREMENT"`
	Name          string  `gorm:"column:name"`
	EngName       string  `gorm:"column:eng_name"`
	Href          string  `gorm:"column:href"`
	Year          int64   `gorm:"column:year"`
	Genre         string  `gorm:"column:genre"`
	Country       string  `gorm:"column:country"`
	Director      string  `gorm:"column:director"`
	Producer      string  `gorm:"column:producer"`
	Actors        string  `gorm:"column:actors"`
	Description   string  `gorm:"column:description"`
	Age           string  `gorm:"column:age"`
	ReleaseDate   string  `gorm:"column:release_date"`
	RussianDate   string  `gorm:"column:russian_date"`
	Duration      int64   `gorm:"column:duration"`
	Quality       string  `gorm:"column:quality"`
	Translation   string  `gorm:"column:translation"`
	SubtitlesType string  `gorm:"column:subtitles_type"`
	Subtitles     string  `gorm:"column:subtitles"`
	Video         string  `gorm:"column:video"`
	Audio         string  `gorm:"column:audio"`
	Kinopoisk     float64 `gorm:"column:kinopoisk"`
	IMDb          float64 `gorm:"column:imdb"`
	NNM           float64 `gorm:"column:nnm"`
	Sound         string  `gorm:"column:sound"`
	Size          int64   `gorm:"column:size"`
	DateCreate    string  `gorm:"column:date_create"`
	Torrent       string  `gorm:"column:torrent"`
	Poster        string  `gorm:"column:poster"`
	Hide          bool    `gorm:"column:hide" sql:"default:0"`
}

// Init nnmc with login password
func Init(login string, password string) (*NNMc, error) {
	var client http.Client
	cookieJar, _ := cookiejar.New(nil)
	client.Jar = cookieJar
	urlPost := "http://nnm-club.me/forum/login.php"
	form := url.Values{}
	form.Set("username", login)
	form.Add("password", password)
	form.Add("redirect", "")
	form.Add("login", "âõîä")
	req, _ := http.NewRequest("POST", urlPost, bytes.NewBufferString(form.Encode()))
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
	_, err := client.Do(req)
	return &NNMc{client: client}, err
}

// getHTML get body from url
func getHTML(url string, n *NNMc) ([]byte, error) {
	resp, err := n.client.Get(url)
	if err != nil {
		log.Println("client Get error:", err)
		return nil, err
	}
	defer resp.Body.Close()
	utf8body, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
	if err != nil {
		log.Println("Encoding error:", err)
		return nil, err
	}
	doc, err := ioutil.ReadAll(utf8body)
	if err != nil {
		log.Println("ioutil.ReadAll error:", err)
	}
	doc = replaceAll(doc, " ", " ")
	doc = replaceAll(doc, "&", "&")
	return doc, nil
}

// ParseForumTree get topics from forumTree
func (n *NNMc) ParseForumTree(url string) ([]Topic, error) {
	var (
		topics []Topic
		reTree = regexp.MustCompile(`<a href="(viewtopic.php\?t=\d+)"class="topictitle">(.+?)\s\((\d{4})\)\s(.+?)</a>`)
	)
	body, err := getHTML(url, n)
	if err != nil {
		return topics, err
	}
	if reTree.Match(body) == false {
		return topics, fmt.Errorf("No topic in body")
	}
	findResult := reTree.FindAllSubmatch(body, -1)
	for _, v := range findResult {
		var t Topic
		t.Href = "http://nnm-club.me/forum/" + string(v[1])
		t.Name = string(v[2])
		t.Year = string(v[3])
		t.Quality = string(v[4])
		topics = append(topics, t)
	}
	return topics, nil
}

// ParseTopic get film from topic
func (n *NNMc) ParseTopic(topic Topic) (Film, error) {
	var (
		film     Film
		reTopic  = regexp.MustCompile(`(Производство|Жанр|Режиссер|Продюсер|Актеры|Описание фильма|Описание|Возраст|Дата мировой премьеры|Дата премьеры в России|Дата Российской премьеры|Дата российской премьеры|Продолжительность|Качество видео|Качество|Перевод|Вид субтитров|Субтитры|Видео|Аудио):<\/span>(.+?)<`)
		reDate   = regexp.MustCompile(`> (\d{1,2} .{3} \d{4}).{9}<`)
		reSize   = regexp.MustCompile(`Размер блока: \d{1,2} MB"> (\d{1,2},\d{1,2}|\d{3,4}|\d{1,2})\s`)
		reRating = regexp.MustCompile(`>(\d,\d|\d)<\/span>.+?\(Голосов:`)
		reDl     = regexp.MustCompile(`<a href="download\.php\?id=(\d{5,7})" rel="nofollow">Скачать<`)
		reImg    = regexp.MustCompile(`"postImg postImgAligned img-right" title="http:\/\/assets\.nnm-club\.ws\/forum\/image\.php\?link=(.+?jpg)`)
	)
	name := strings.Split(topic.Name, "/")
	switch len(name) {
	case 1:
		film.Name = strings.Trim(name[0], " ")
	case 2:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	case 3:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	}
	film.Href = topic.Href
	if year64, err := strconv.ParseInt(topic.Year, 10, 64); err == nil {
		film.Year = year64
	}
	body, err := getHTML(film.Href, n)
	if err != nil {
		return film, err
	}
	if reTopic.Match(body) == false {
		return film, fmt.Errorf("No topic in body")
	}
	findAttrs := reTopic.FindAllSubmatch(body, -1)
	for _, v := range findAttrs {
		one := strings.Trim(string(v[1]), " ")
		two := strings.Replace(string(v[2]), "
", "", -1)
		two = strings.Trim(two, " ")
		switch one {
		case "Производство":
			film.Country = two
		case "Жанр":
			film.Genre = strings.ToLower(two)
		case "Режиссер":
			film.Director = two
		case "Продюсер":
			film.Producer = two
		case "Актеры":
			film.Actors = two
		case "Описание фильма", "Описание":
			film.Description = two
		case "Возраст":
			film.Age = two
		case "Дата мировой премьеры":
			film.ReleaseDate = two
		case "Дата премьеры в России", "Дата российской премьеры", "Дата Российской премьеры":
			film.RussianDate = two
		case "Продолжительность":
			if i64, err := strconv.ParseInt(two, 10, 64); err == nil {
				film.Duration = i64
			}
		case "Качество видео", "Качество":
			film.Quality = two
		case "Перевод":
			film.Translation = two
		case "Вид субтитров":
			film.SubtitlesType = two
		case "Субтитры":
			film.Subtitles = two
		case "Видео":
			film.Video = two
		case "Аудио":
			film.Audio = two
		}
	}
	if reDl.Match(body) == false {
		return film, fmt.Errorf("No torrent url in body")
	}
	findDl := reDl.FindAllSubmatch(body, -1)
	film.Torrent = "http://nnm-club.me/forum/download.php?id=" + string(findDl[0][1])
	if reDate.Match(body) == true {
		film.DateCreate = replaceDate(string(reDate.FindSubmatch(body)[1]))
	}
	if reSize.Match(body) == true {
		size := string(reSize.FindSubmatch(body)[1])
		size = strings.Replace(size, ",", ".", -1)
		if s64, err := strconv.ParseFloat(size, 64); err == nil {
			if s64 < 100 {
				s64 = s64 * 1000
			}
			film.Size = int64(s64)
		}
	}
	if reRating.Match(body) == true {
		rating := string(reRating.FindSubmatch(body)[1])
		rating = strings.Replace(rating, ",", ".", -1)
		if r64, err := strconv.ParseFloat(rating, 64); err == nil {
			film.NNM = r64
		}
	}
	if reImg.Match(body) == true {
		film.Poster = string(reImg.FindSubmatch(body)[1])
	}
	return film, nil
}

func replaceAll(body []byte, from string, to string) []byte {
	var reStr = regexp.MustCompile(from)
	result := reStr.ReplaceAll(body, []byte(to))
	return result
}

func replaceDate(s string) string {
	s = strings.Replace(s, " Янв ", ".01.", -1)
	s = strings.Replace(s, " Фев ", ".02.", -1)
	s = strings.Replace(s, " Мар ", ".03.", -1)
	s = strings.Replace(s, " Апр ", ".04.", -1)
	s = strings.Replace(s, " Май ", ".05.", -1)
	s = strings.Replace(s, " Июн ", ".06.", -1)
	s = strings.Replace(s, " Июл ", ".07.", -1)
	s = strings.Replace(s, " Авг ", ".08.", -1)
	s = strings.Replace(s, " Сен ", ".09.", -1)
	s = strings.Replace(s, " Окт ", ".10.", -1)
	s = strings.Replace(s, " Ноя ", ".11.", -1)
	s = strings.Replace(s, " Дек ", ".12.", -1)
	return s
}
<commit_msg>fix regexp size<commit_after>package nnmc

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/net/html/charset"
)

// NNMc values:
// client http.Client with cookie
type NNMc struct {
	client http.Client
}

// Topic from forum
type Topic struct {
	Href    string
	Name    string
	Year    string
	Quality string
}

// Film all values
// ID            id
// Name          Название
// EngName       Английское название
// Href          Ссылка
// Year          Год
// Genre         Жанр
// Country       Производство
// Director      Режиссер
// Producer      Продюсер
// Actors        Актеры
// Description   Описание
// Age           Возраст
// ReleaseDate   Дата мировой премьеры
// RussianDate   Дата премьеры в России
// Duration      Продолжительность
// Quality       Качество видео
// Translation   Перевод
// SubtitlesType Вид субтитров
// Subtitles     Субтитры
// Video         Видео
// Audio         Аудио
// Kinopoisk     Рейтинг кинопоиска
// Imdb          Рейтинг IMDb
// NNM           Рейтинг nnm-club
// Sound         Звук
// Size          Размер
// DateCreate    Дата создания раздачи
// Torrent       Ссылка на torrent
// Poster        Ссылка на постер
// Hide          Скрывать в общем списке
type Film struct {
	ID            int64   `gorm:"column:id" sql:"AUTO_INCREMENT"`
	Name          string  `gorm:"column:name"`
	EngName       string  `gorm:"column:eng_name"`
	Href          string  `gorm:"column:href"`
	Year          int64   `gorm:"column:year"`
	Genre         string  `gorm:"column:genre"`
	Country       string  `gorm:"column:country"`
	Director      string  `gorm:"column:director"`
	Producer      string  `gorm:"column:producer"`
	Actors        string  `gorm:"column:actors"`
	Description   string  `gorm:"column:description"`
	Age           string  `gorm:"column:age"`
	ReleaseDate   string  `gorm:"column:release_date"`
	RussianDate   string  `gorm:"column:russian_date"`
	Duration      int64   `gorm:"column:duration"`
	Quality       string  `gorm:"column:quality"`
	Translation   string  `gorm:"column:translation"`
	SubtitlesType string  `gorm:"column:subtitles_type"`
	Subtitles     string  `gorm:"column:subtitles"`
	Video         string  `gorm:"column:video"`
	Audio         string  `gorm:"column:audio"`
	Kinopoisk     float64 `gorm:"column:kinopoisk"`
	IMDb          float64 `gorm:"column:imdb"`
	NNM           float64 `gorm:"column:nnm"`
	Sound         string  `gorm:"column:sound"`
	Size          int64   `gorm:"column:size"`
	DateCreate    string  `gorm:"column:date_create"`
	Torrent       string  `gorm:"column:torrent"`
	Poster        string  `gorm:"column:poster"`
	Hide          bool    `gorm:"column:hide" sql:"default:0"`
}

// Init nnmc with login password
func Init(login string, password string) (*NNMc, error) {
	var client http.Client
	cookieJar, _ := cookiejar.New(nil)
	client.Jar = cookieJar
	urlPost := "http://nnm-club.me/forum/login.php"
	form := url.Values{}
	form.Set("username", login)
	form.Add("password", password)
	form.Add("redirect", "")
	form.Add("login", "âõîä")
	req, _ := http.NewRequest("POST", urlPost, bytes.NewBufferString(form.Encode()))
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
	_, err := client.Do(req)
	return &NNMc{client: client}, err
}

// getHTML get body from url
func getHTML(url string, n *NNMc) ([]byte, error) {
	resp, err := n.client.Get(url)
	if err != nil {
		log.Println("client Get error:", err)
		return nil, err
	}
	defer resp.Body.Close()
	utf8body, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
	if err != nil {
		log.Println("Encoding error:", err)
		return nil, err
	}
	doc, err := ioutil.ReadAll(utf8body)
	if err != nil {
		log.Println("ioutil.ReadAll error:", err)
	}
	doc = replaceAll(doc, " ", " ")
	doc = replaceAll(doc, "&", "&")
	return doc, nil
}

// ParseForumTree get topics from forumTree
func (n *NNMc) ParseForumTree(url string) ([]Topic, error) {
	var (
		topics []Topic
		reTree = regexp.MustCompile(`<a href="(viewtopic.php\?t=\d+)"class="topictitle">(.+?)\s\((\d{4})\)\s(.+?)</a>`)
	)
	body, err := getHTML(url, n)
	if err != nil {
		return topics, err
	}
	if reTree.Match(body) == false {
		return topics, fmt.Errorf("No topic in body")
	}
	findResult := reTree.FindAllSubmatch(body, -1)
	for _, v := range findResult {
		var t Topic
		t.Href = "http://nnm-club.me/forum/" + string(v[1])
		t.Name = string(v[2])
		t.Year = string(v[3])
		t.Quality = string(v[4])
		topics = append(topics, t)
	}
	return topics, nil
}

// ParseTopic get film from topic
func (n *NNMc) ParseTopic(topic Topic) (Film, error) {
	var (
		film     Film
		reTopic  = regexp.MustCompile(`(Производство|Жанр|Режиссер|Продюсер|Актеры|Описание фильма|Описание|Возраст|Дата мировой премьеры|Дата премьеры в России|Дата Российской премьеры|Дата российской премьеры|Продолжительность|Качество видео|Качество|Перевод|Вид субтитров|Субтитры|Видео|Аудио):<\/span>(.+?)<`)
		reDate   = regexp.MustCompile(`> (\d{1,2} .{3} \d{4}).{9}<`)
		reSize   = regexp.MustCompile(`Размер блока: \d.+?B"> (\d{1,2},\d{1,2}|\d{3,4}|\d{1,2})\s`)
		reRating = regexp.MustCompile(`>(\d,\d|\d)<\/span>.+?\(Голосов:`)
		reDl     = regexp.MustCompile(`<a href="download\.php\?id=(\d{5,7})" rel="nofollow">Скачать<`)
		reImg    = regexp.MustCompile(`"postImg postImgAligned img-right" title="http:\/\/assets\.nnm-club\.ws\/forum\/image\.php\?link=(.+?jpg)`)
	)
	name := strings.Split(topic.Name, "/")
	switch len(name) {
	case 1:
		film.Name = strings.Trim(name[0], " ")
	case 2:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	case 3:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	}
	film.Href = topic.Href
	if year64, err := strconv.ParseInt(topic.Year, 10, 64); err == nil {
		film.Year = year64
	}
	body, err := getHTML(film.Href, n)
	if err != nil {
		return film, err
	}
	if reTopic.Match(body) == false {
		return film, fmt.Errorf("No topic in body")
	}
	findAttrs := reTopic.FindAllSubmatch(body, -1)
	for _, v := range findAttrs {
		one := strings.Trim(string(v[1]), " ")
		two := strings.Replace(string(v[2]), "
", "", -1)
		two = strings.Trim(two, " ")
		switch one {
		case "Производство":
			film.Country = two
		case "Жанр":
			film.Genre = strings.ToLower(two)
		case "Режиссер":
			film.Director = two
		case "Продюсер":
			film.Producer = two
		case "Актеры":
			film.Actors = two
		case "Описание фильма", "Описание":
			film.Description = two
		case "Возраст":
			film.Age = two
		case "Дата мировой премьеры":
			film.ReleaseDate = two
		case "Дата премьеры в России", "Дата российской премьеры", "Дата Российской премьеры":
			film.RussianDate = two
		case "Продолжительность":
			if i64, err := strconv.ParseInt(two, 10, 64); err == nil {
				film.Duration = i64
			}
		case "Качество видео", "Качество":
			film.Quality = two
		case "Перевод":
			film.Translation = two
		case "Вид субтитров":
			film.SubtitlesType = two
		case "Субтитры":
			film.Subtitles = two
		case "Видео":
			film.Video = two
		case "Аудио":
			film.Audio = two
		}
	}
	if reDl.Match(body) == false {
		return film, fmt.Errorf("No torrent url in body")
	}
	findDl := reDl.FindAllSubmatch(body, -1)
	film.Torrent = "http://nnm-club.me/forum/download.php?id=" + string(findDl[0][1])
	if reDate.Match(body) == true {
		film.DateCreate = replaceDate(string(reDate.FindSubmatch(body)[1]))
	}
	if reSize.Match(body) == true {
		size := string(reSize.FindSubmatch(body)[1])
		size = strings.Replace(size, ",", ".", -1)
		if s64, err := strconv.ParseFloat(size, 64); err == nil {
			if s64 < 100 {
				s64 = s64 * 1000
			}
			film.Size = int64(s64)
		}
	}
	if reRating.Match(body) == true {
		rating := string(reRating.FindSubmatch(body)[1])
		rating = strings.Replace(rating, ",", ".", -1)
		if r64, err := strconv.ParseFloat(rating, 64); err == nil {
			film.NNM = r64
		}
	}
	if reImg.Match(body) == true {
		film.Poster = string(reImg.FindSubmatch(body)[1])
	}
	return film, nil
}

func replaceAll(body []byte, from string, to string) []byte {
	var reStr = regexp.MustCompile(from)
	result := reStr.ReplaceAll(body, []byte(to))
	return result
}

func replaceDate(s string) string {
	s = strings.Replace(s, " Янв ", ".01.", -1)
	s = strings.Replace(s, " Фев ", ".02.", -1)
	s = strings.Replace(s, " Мар ", ".03.", -1)
	s = strings.Replace(s, " Апр ", ".04.", -1)
	s = strings.Replace(s, " Май ", ".05.", -1)
	s = strings.Replace(s, " Июн ", ".06.", -1)
	s = strings.Replace(s, " Июл ", ".07.", -1)
	s = strings.Replace(s, " Авг ", ".08.", -1)
	s = strings.Replace(s, " Сен ", ".09.", -1)
	s = strings.Replace(s, " Окт ", ".10.", -1)
	s = strings.Replace(s, " Ноя ", ".11.", -1)
	s = strings.Replace(s, " Дек ", ".12.", -1)
	return s
}
<|endoftext|>


<commit_before>package restic

import (
	"encoding/json"
	"fmt"
	"os"
	"os/user"
	"strconv"
	"syscall"
	"time"

	"github.com/juju/arrar"
	"github.com/restic/restic/backend"
	"github.com/restic/restic/debug"
	"github.com/restic/restic/pack"
	"github.com/restic/restic/server"
)

type Node struct {
	Name       string       `json:"name"`
	Type       string       `json:"type"`
	Mode       os.FileMode  `json:"mode,omitempty"`
	ModTime    time.Time    `json:"mtime,omitempty"`
	AccessTime time.Time    `json:"atime,omitempty"`
	ChangeTime time.Time    `json:"ctime,omitempty"`
	UID        uint32       `json:"uid"`
	GID        uint32       `json:"gid"`
	User       string       `json:"user,omitempty"`
	Group      string       `json:"group,omitempty"`
	Inode      uint64       `json:"inode,omitempty"`
	Size       uint64       `json:"size,omitempty"`
	Links      uint64       `json:"links,omitempty"`
	LinkTarget string       `json:"linktarget,omitempty"`
	Device     uint64       `json:"device,omitempty"`
	Content    []backend.ID `json:"content"`
	Subtree    backend.ID   `json:"subtree,omitempty"`

	Error string `json:"error,omitempty"`

	tree *Tree

	path  string
	err   error
	blobs server.Blobs
}

func (node Node) String() string {
	switch node.Type {
	case "file":
		return fmt.Sprintf("%s %5d %5d %6d %s %s",
			node.Mode, node.UID, node.GID, node.Size, node.ModTime, node.Name)
	case "dir":
		return fmt.Sprintf("%s %5d %5d %6d %s %s",
			node.Mode|os.ModeDir, node.UID, node.GID, node.Size, node.ModTime, node.Name)
	}

	return fmt.Sprintf("<Node(%s) %s>", node.Type, node.Name)
}

func (node Node) Tree() *Tree {
	return node.tree
}

func NodeFromFileInfo(path string, fi os.FileInfo) (*Node, error) {
	node := &Node{
		path:    path,
		Name:    fi.Name(),
		Mode:    fi.Mode() & (os.ModePerm | os.ModeType),
		ModTime: fi.ModTime(),
	}

	node.Type = nodeTypeFromFileInfo(fi)
	if node.Type == "file" {
		node.Size = uint64(fi.Size())
	}

	err := node.fillExtra(path, fi)
	return node, err
}

func nodeTypeFromFileInfo(fi os.FileInfo) string {
	switch fi.Mode() & (os.ModeType | os.ModeCharDevice) {
	case 0:
		return "file"
	case os.ModeDir:
		return "dir"
	case os.ModeSymlink:
		return "symlink"
	case os.ModeDevice | os.ModeCharDevice:
		return "chardev"
	case os.ModeDevice:
		return "dev"
	case os.ModeNamedPipe:
		return "fifo"
	case os.ModeSocket:
		return "socket"
	}

	return ""
}

func (node *Node) CreateAt(path string, s *server.Server) error {
	switch node.Type {
	case "dir":
		if err := node.createDirAt(path); err != nil {
			return err
		}
	case "file":
		if err := node.createFileAt(path, s); err != nil {
			return err
		}
	case "symlink":
		if err := node.createSymlinkAt(path); err != nil {
			return err
		}
	case "dev":
		if err := node.createDevAt(path); err != nil {
			return arrar.Annotate(err, "Mknod")
		}
	case "chardev":
		if err := node.createCharDevAt(path); err != nil {
			return arrar.Annotate(err, "Mknod")
		}
	case "fifo":
		if err := node.createFifoAt(path); err != nil {
			return arrar.Annotate(err, "Mkfifo")
		}
	case "socket":
		return nil
	default:
		return fmt.Errorf("filetype %q not implemented!\n", node.Type)
	}

	return node.restoreMetadata(path)
}

func (node Node) restoreMetadata(path string) error {
	var err error

	err = os.Lchown(path, int(node.UID), int(node.GID))
	if err != nil {
		return arrar.Annotate(err, "Lchown")
	}

	if node.Type == "symlink" {
		return nil
	}

	err = os.Chmod(path, node.Mode)
	if err != nil {
		return arrar.Annotate(err, "Chmod")
	}

	var utimes = []syscall.Timespec{
		syscall.NsecToTimespec(node.AccessTime.UnixNano()),
		syscall.NsecToTimespec(node.ModTime.UnixNano()),
	}
	err = syscall.UtimesNano(path, utimes)
	if err != nil {
		return arrar.Annotate(err, "Utimesnano")
	}

	return nil
}

func (node Node) createDirAt(path string) error {
	err := os.Mkdir(path, node.Mode)
	if err != nil {
		return arrar.Annotate(err, "Mkdir")
	}

	return nil
}

func (node Node) createFileAt(path string, s *server.Server) error {
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
	defer f.Close()

	if err != nil {
		return arrar.Annotate(err, "OpenFile")
	}

	for _, id := range node.Content {
		buf, err := s.LoadBlob(pack.Data, id)
		if err != nil {
			return arrar.Annotate(err, "Load")
		}

		_, err = f.Write(buf)
		if err != nil {
			return arrar.Annotate(err, "Write")
		}
	}

	return nil
}

func (node Node) createSymlinkAt(path string) error {
	err := os.Symlink(node.LinkTarget, path)
	if err != nil {
		return arrar.Annotate(err, "Symlink")
	}

	return nil
}

func (node Node) MarshalJSON() ([]byte, error) {
	type nodeJSON Node
	nj := nodeJSON(node)
	name := strconv.Quote(node.Name)
	nj.Name = name[1 : len(name)-1]

	return json.Marshal(nj)
}

func (node *Node) UnmarshalJSON(data []byte) error {
	type nodeJSON Node
	nj := (*nodeJSON)(node)

	err := json.Unmarshal(data, nj)
	if err != nil {
		return err
	}

	nj.Name, err = strconv.Unquote(`"` + nj.Name + `"`)
	return err
}

func (node Node) Equals(other Node) bool {
	if node.Name != other.Name {
		return false
	}
	if node.Type != other.Type {
		return false
	}
	if node.Mode != other.Mode {
		return false
	}
	if node.ModTime != other.ModTime {
		return false
	}
	if node.AccessTime != other.AccessTime {
		return false
	}
	if node.ChangeTime != other.ChangeTime {
		return false
	}
	if node.UID != other.UID {
		return false
	}
	if node.GID != other.GID {
		return false
	}
	if node.User != other.User {
		return false
	}
	if node.Group != other.Group {
		return false
	}
	if node.Inode != other.Inode {
		return false
	}
	if node.Size != other.Size {
		return false
	}
	if node.Links != other.Links {
		return false
	}
	if node.LinkTarget != other.LinkTarget {
		return false
	}
	if node.Device != other.Device {
		return false
	}
	if !node.sameContent(other) {
		return false
	}
	if !node.Subtree.Equal(other.Subtree) {
		return false
	}
	if node.Error != other.Error {
		return false
	}

	return true
}

func (node Node) sameContent(other Node) bool {
	if node.Content == nil {
		return other.Content == nil
	}

	if other.Content == nil {
		return false
	}

	if len(node.Content) != len(other.Content) {
		return false
	}

	for i := 0; i < len(node.Content); i++ {
		if !node.Content[i].Equal(other.Content[i]) {
			return false
		}
	}

	return true
}

func (node *Node) isNewer(path string, fi os.FileInfo) bool {
	if node.Type != "file" {
		debug.Log("node.isNewer", "node %v is newer: not file", path)
		return true
	}

	tpe := nodeTypeFromFileInfo(fi)
	if node.Name != fi.Name() || node.Type != tpe {
		debug.Log("node.isNewer", "node %v is newer: name or type changed", path)
		return true
	}

	extendedStat := fi.Sys().(*syscall.Stat_t)
	inode := extendedStat.Ino
	size := uint64(extendedStat.Size)

	if node.ModTime != fi.ModTime() ||
		node.ChangeTime != changeTime(extendedStat) ||
		node.Inode != inode ||
		node.Size != size {
		debug.Log("node.isNewer", "node %v is newer: timestamp, size or inode changed", path)
		return true
	}

	debug.Log("node.isNewer", "node %v is not newer", path)
	return false
}

func (node *Node) fillUser(stat *syscall.Stat_t) error {
	node.UID = stat.Uid
	node.GID = stat.Gid

	u, err := user.LookupId(strconv.Itoa(int(stat.Uid)))
	if err != nil {
		return err
	}

	node.User = u.Username
	return nil
}

func (node *Node) fillExtra(path string, fi os.FileInfo) error {
	stat, ok := fi.Sys().(*syscall.Stat_t)
	if !ok {
		return nil
	}

	node.Inode = stat.Ino

	node.fillUser(stat)
	node.fillTimes(stat)

	var err error

	switch node.Type {
	case "file":
		node.Size = uint64(stat.Size)
		node.Links = uint64(stat.Nlink)
	case "dir":
	case "symlink":
		node.LinkTarget, err = os.Readlink(path)
	case "dev":
		node.Device = uint64(stat.Rdev)
	case "chardev":
		node.Device = uint64(stat.Rdev)
	case "fifo":
	case "socket":
	default:
		err = fmt.Errorf("invalid node type %q", node.Type)
	}

	return err
}
<commit_msg>uid lookup cache<commit_after>package restic

import (
	"encoding/json"
	"fmt"
	"os"
	"os/user"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/juju/arrar"
	"github.com/restic/restic/backend"
	"github.com/restic/restic/debug"
	"github.com/restic/restic/pack"
	"github.com/restic/restic/server"
)

type Node struct {
	Name       string       `json:"name"`
	Type       string       `json:"type"`
	Mode       os.FileMode  `json:"mode,omitempty"`
	ModTime    time.Time    `json:"mtime,omitempty"`
	AccessTime time.Time    `json:"atime,omitempty"`
	ChangeTime time.Time    `json:"ctime,omitempty"`
	UID        uint32       `json:"uid"`
	GID        uint32       `json:"gid"`
	User       string       `json:"user,omitempty"`
	Group      string       `json:"group,omitempty"`
	Inode      uint64       `json:"inode,omitempty"`
	Size       uint64       `json:"size,omitempty"`
	Links      uint64       `json:"links,omitempty"`
	LinkTarget string       `json:"linktarget,omitempty"`
	Device     uint64       `json:"device,omitempty"`
	Content    []backend.ID `json:"content"`
	Subtree    backend.ID   `json:"subtree,omitempty"`

	Error string `json:"error,omitempty"`

	tree *Tree

	path  string
	err   error
	blobs server.Blobs
}

func (node Node) String() string {
	switch node.Type {
	case "file":
		return fmt.Sprintf("%s %5d %5d %6d %s %s",
			node.Mode, node.UID, node.GID, node.Size, node.ModTime, node.Name)
	case "dir":
		return fmt.Sprintf("%s %5d %5d %6d %s %s",
			node.Mode|os.ModeDir, node.UID, node.GID, node.Size, node.ModTime, node.Name)
	}

	return fmt.Sprintf("<Node(%s) %s>", node.Type, node.Name)
}

func (node Node) Tree() *Tree {
	return node.tree
}

func NodeFromFileInfo(path string, fi os.FileInfo) (*Node, error) {
	node := &Node{
		path:    path,
		Name:    fi.Name(),
		Mode:    fi.Mode() & (os.ModePerm | os.ModeType),
		ModTime: fi.ModTime(),
	}

	node.Type = nodeTypeFromFileInfo(fi)
	if node.Type == "file" {
		node.Size = uint64(fi.Size())
	}

	err := node.fillExtra(path, fi)
	return node, err
}

func nodeTypeFromFileInfo(fi os.FileInfo) string {
	switch fi.Mode() & (os.ModeType | os.ModeCharDevice) {
	case 0:
		return "file"
	case os.ModeDir:
		return "dir"
	case os.ModeSymlink:
		return "symlink"
	case os.ModeDevice | os.ModeCharDevice:
		return "chardev"
	case os.ModeDevice:
		return "dev"
	case os.ModeNamedPipe:
		return "fifo"
	case os.ModeSocket:
		return "socket"
	}

	return ""
}

func (node *Node) CreateAt(path string, s *server.Server) error {
	switch node.Type {
	case "dir":
		if err := node.createDirAt(path); err != nil {
			return err
		}
	case "file":
		if err := node.createFileAt(path, s); err != nil {
			return err
		}
	case "symlink":
		if err := node.createSymlinkAt(path); err != nil {
			return err
		}
	case "dev":
		if err := node.createDevAt(path); err != nil {
			return arrar.Annotate(err, "Mknod")
		}
	case "chardev":
		if err := node.createCharDevAt(path); err != nil {
			return arrar.Annotate(err, "Mknod")
		}
	case "fifo":
		if err := node.createFifoAt(path); err != nil {
			return arrar.Annotate(err, "Mkfifo")
		}
	case "socket":
		return nil
	default:
		return fmt.Errorf("filetype %q not implemented!\n", node.Type)
	}

	return node.restoreMetadata(path)
}

func (node Node) restoreMetadata(path string) error {
	var err error

	err = os.Lchown(path, int(node.UID), int(node.GID))
	if err != nil {
		return arrar.Annotate(err, "Lchown")
	}

	if node.Type == "symlink" {
		return nil
	}

	err = os.Chmod(path, node.Mode)
	if err != nil {
		return arrar.Annotate(err, "Chmod")
	}

	var utimes = []syscall.Timespec{
		syscall.NsecToTimespec(node.AccessTime.UnixNano()),
		syscall.NsecToTimespec(node.ModTime.UnixNano()),
	}
	err = syscall.UtimesNano(path, utimes)
	if err != nil {
		return arrar.Annotate(err, "Utimesnano")
	}

	return nil
}

func (node Node) createDirAt(path string) error {
	err := os.Mkdir(path, node.Mode)
	if err != nil {
		return arrar.Annotate(err, "Mkdir")
	}

	return nil
}

func (node Node) createFileAt(path string, s *server.Server) error {
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600)
	defer f.Close()

	if err != nil {
		return arrar.Annotate(err, "OpenFile")
	}

	for _, id := range node.Content {
		buf, err := s.LoadBlob(pack.Data, id)
		if err != nil {
			return arrar.Annotate(err, "Load")
		}

		_, err = f.Write(buf)
		if err != nil {
			return arrar.Annotate(err, "Write")
		}
	}

	return nil
}

func (node Node) createSymlinkAt(path string) error {
	err := os.Symlink(node.LinkTarget, path)
	if err != nil {
		return arrar.Annotate(err, "Symlink")
	}

	return nil
}

func (node Node) MarshalJSON() ([]byte, error) {
	type nodeJSON Node
	nj := nodeJSON(node)
	name := strconv.Quote(node.Name)
	nj.Name = name[1 : len(name)-1]

	return json.Marshal(nj)
}

func (node *Node) UnmarshalJSON(data []byte) error {
	type nodeJSON Node
	nj := (*nodeJSON)(node)

	err := json.Unmarshal(data, nj)
	if err != nil {
		return err
	}

	nj.Name, err = strconv.Unquote(`"` + nj.Name + `"`)
	return err
}

func (node Node) Equals(other Node) bool {
	if node.Name != other.Name {
		return false
	}
	if node.Type != other.Type {
		return false
	}
	if node.Mode != other.Mode {
		return false
	}
	if node.ModTime != other.ModTime {
		return false
	}
	if node.AccessTime != other.AccessTime {
		return false
	}
	if node.ChangeTime != other.ChangeTime {
		return false
	}
	if node.UID != other.UID {
		return false
	}
	if node.GID != other.GID {
		return false
	}
	if node.User != other.User {
		return false
	}
	if node.Group != other.Group {
		return false
	}
	if node.Inode != other.Inode {
		return false
	}
	if node.Size != other.Size {
		return false
	}
	if node.Links != other.Links {
		return false
	}
	if node.LinkTarget != other.LinkTarget {
		return false
	}
	if node.Device != other.Device {
		return false
	}
	if !node.sameContent(other) {
		return false
	}
	if !node.Subtree.Equal(other.Subtree) {
		return false
	}
	if node.Error != other.Error {
		return false
	}

	return true
}

func (node Node) sameContent(other Node) bool {
	if node.Content == nil {
		return other.Content == nil
	}

	if other.Content == nil {
		return false
	}

	if len(node.Content) != len(other.Content) {
		return false
	}

	for i := 0; i < len(node.Content); i++ {
		if !node.Content[i].Equal(other.Content[i]) {
			return false
		}
	}

	return true
}

func (node *Node) isNewer(path string, fi os.FileInfo) bool {
	if node.Type != "file" {
		debug.Log("node.isNewer", "node %v is newer: not file", path)
		return true
	}

	tpe := nodeTypeFromFileInfo(fi)
	if node.Name != fi.Name() || node.Type != tpe {
		debug.Log("node.isNewer", "node %v is newer: name or type changed", path)
		return true
	}

	extendedStat := fi.Sys().(*syscall.Stat_t)
	inode := extendedStat.Ino
	size := uint64(extendedStat.Size)

	if node.ModTime != fi.ModTime() ||
		node.ChangeTime != changeTime(extendedStat) ||
		node.Inode != inode ||
		node.Size != size {
		debug.Log("node.isNewer", "node %v is newer: timestamp, size or inode changed", path)
		return true
	}

	debug.Log("node.isNewer", "node %v is not newer", path)
	return false
}

func (node *Node) fillUser(stat *syscall.Stat_t) error {
	node.UID = stat.Uid
	node.GID = stat.Gid

	username, err := lookupUsername(strconv.Itoa(int(stat.Uid)))
	if err != nil {
		return err
	}

	node.User = username
	return nil
}

var (
	uidLookupCache      = make(map[string]string)
	uidLookupCacheMutex = sync.RWMutex{}
)

func lookupUsername(uid string) (string, error) {
	uidLookupCacheMutex.RLock()
	value, ok := uidLookupCache[uid]
	uidLookupCacheMutex.RUnlock()

	if ok {
		return value, nil
	}

	u, err := user.LookupId(uid)
	if err != nil {
		return "", err
	}

	uidLookupCacheMutex.Lock()
	uidLookupCache[uid] = u.Username
	uidLookupCacheMutex.Unlock()

	return u.Username, nil
}

func (node *Node) fillExtra(path string, fi os.FileInfo) error {
	stat, ok := fi.Sys().(*syscall.Stat_t)
	if !ok {
		return nil
	}

	node.Inode = stat.Ino

	node.fillUser(stat)
	node.fillTimes(stat)

	var err error

	switch node.Type {
	case "file":
		node.Size = uint64(stat.Size)
		node.Links = uint64(stat.Nlink)
	case "dir":
	case "symlink":
		node.LinkTarget, err = os.Readlink(path)
	case "dev":
		node.Device = uint64(stat.Rdev)
	case "chardev":
		node.Device = uint64(stat.Rdev)
	case "fifo":
	case "socket":
	default:
		err = fmt.Errorf("invalid node type %q", node.Type)
	}

	return err
}
<|endoftext|>


<commit_before>// Copyright 2014 Gordon Klaus. 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 (
	"code.google.com/p/gordon-go/go/types"
	. "code.google.com/p/gordon-go/gui"
	"go/token"
	"math"
	"strconv"
	"strings"
)

type node interface {
	View
	Mouser
	block() *block
	setBlock(b *block)
	inputs() []*port
	outputs() []*port
	inConns() []*connection
	outConns() []*connection
}

type nodeBase struct {
	*ViewBase
	self node
	AggregateMouser

	blk  *block
	text *TextBase
	ins  []*port
	outs []*port

	godefer     string
	godeferText Text

	focused bool
	gap     float64
}

func newNodeBase(self node) *nodeBase {
	return newGoDeferNodeBase(self, "")
}

func newGoDeferNodeBase(self node, godefer string) *nodeBase {
	n := &nodeBase{self: self, godefer: godefer}
	n.ViewBase = NewView(n)
	n.AggregateMouser = AggregateMouser{NewClickFocuser(self), NewMover(self)}
	n.text = NewText("")
	n.text.SetBackgroundColor(Color{0, 0, 0, 0})
	n.text.TextChanged = func(string) {
		if n.text.GetText() == "" {
			n.godeferText.SetText(godefer[:len(godefer)-1])
		}
		width := Width(n.godeferText) + Width(n.text)
		height := Height(n.godeferText)
		n.godeferText.Move(Pt(-width/2, -height/2))
		n.text.Move(Pt(-width/2+Width(n.godeferText), -height/2))
		n.gap = height / 2
		n.reform()
	}
	n.Add(n.text)
	n.godeferText = NewText(godefer)
	n.godeferText.SetBackgroundColor(Color{0, 0, 0, 0})
	n.godeferText.SetTextColor(color(special{}, true, false))
	n.Add(n.godeferText)
	n.ViewBase.Self = self
	return n
}

func (n *nodeBase) newInput(v *types.Var) *port {
	p := newInput(n.self, v)
	n.Add(p)
	n.ins = append(n.ins, p)
	n.reform()
	return p
}

func (n *nodeBase) newOutput(v *types.Var) *port {
	p := newOutput(n.self, v)
	if n.godefer == "" {
		n.Add(p)
		n.outs = append(n.outs, p)
		n.reform()
	}
	return p
}

func (n *nodeBase) addSeqPorts() {
	n.newInput(newVar("seq", seqType))
	n.newOutput(newVar("seq", seqType))
	n.reform()
}

func (n *nodeBase) removePortBase(p *port) { // intentionally named to not implement interface{removePort(*port)}
	for _, c := range p.conns {
		c.blk.removeConn(c)
	}
	ports := &n.ins
	if p.out {
		ports = &n.outs
	}
	for i, p2 := range *ports {
		if p2 == p {
			*ports = append((*ports)[:i], (*ports)[i+1:]...)
			n.Remove(p)
			n.reform()

			if i > 0 && (*ports)[i-1].obj.Type != seqType { // assumes sequencing port, if present, is at index 0
				i--
			}
			if i < len(*ports) {
				SetKeyFocus((*ports)[i])
			} else {
				SetKeyFocus(n.self)
			}
			break
		}
	}
}

func (n *nodeBase) reform() {
	ins, outs := ins(n), outs(n)

	numIn := float64(len(ins))
	numOut := float64(len(outs))
	rx, ry := (math.Max(numIn, numOut)+1)*portSize/2, 1.0*portSize

	rect := ZR
	for i, p := range ins {
		x := portSize * (float64(i) - (numIn-1)/2)
		y := n.gap + ry*math.Sqrt(rx*rx-x*x)/rx
		if numIn > 1 {
			y += 8
		}
		MoveCenter(p, Pt(x, y))
		rect = rect.Union(RectInParent(p))
	}
	for i, p := range outs {
		x := portSize * (float64(i) - (numOut-1)/2)
		y := -n.gap - ry*math.Sqrt(rx*rx-x*x)/rx
		if numOut > 1 {
			y -= 8
		}
		MoveCenter(p, Pt(x, y))
		rect = rect.Union(RectInParent(p))
	}
	if p := seqIn(n); p != nil {
		MoveCenter(p, Pt(0, n.gap))
	}
	if p := seqOut(n); p != nil {
		MoveCenter(p, Pt(0, -n.gap))
	}
	if _, ok := n.self.(*portsNode); ok {
		// portsNode rect must have an edge at y==0 or arrangement will diverge
		n.SetRect(rect)
	} else {
		ResizeToFit(n, 0)
	}
	rearrange(n.blk)
}

func (n nodeBase) block() *block      { return n.blk }
func (n *nodeBase) setBlock(b *block) { n.blk = b }
func (n nodeBase) inputs() []*port    { return n.ins }
func (n nodeBase) outputs() []*port   { return n.outs }

func (n nodeBase) inConns() (conns []*connection) {
	for _, p := range n.inputs() {
		for _, c := range p.conns {
			conns = append(conns, c)
		}
	}
	return
}

func (n nodeBase) outConns() (conns []*connection) {
	for _, p := range n.outputs() {
		for _, c := range p.conns {
			conns = append(conns, c)
		}
	}
	return
}

func (n *nodeBase) Move(p Point) {
	n.ViewBase.Move(p)
	nodeMoved(n.self)
}

func nodeMoved(n node) {
	for _, c := range append(n.inConns(), n.outConns()...) {
		c.reform()
	}
}

func (n *nodeBase) TookKeyFocus() {
	n.focused = true
	c := Color{.25, .25, .25, 1}
	n.text.SetBackgroundColor(c)
	n.godeferText.SetBackgroundColor(c)
}

func (n *nodeBase) LostKeyFocus() {
	n.focused = false
	c := Color{0, 0, 0, 0}
	n.text.SetBackgroundColor(c)
	n.godeferText.SetBackgroundColor(c)
}

func (n *nodeBase) Paint() {
	SetColor(Color{.5, .5, .5, 1})
	for _, p := range append(ins(n), outs(n)...) {
		pt := CenterInParent(p)
		dy := n.gap
		if p.out {
			dy = -dy
		}
		y := (pt.Y-dy)/2 + dy
		DrawCubic([4]Point{Pt(0, dy), Pt(0, y), Pt(pt.X, y), pt}, int(pt.Len()/2))
	}
}

var seqType = struct{ types.Type }{}

func seqIn(n node) *port {
	for _, in := range n.inputs() {
		if in.obj.Type == seqType {
			return in
		}
	}
	return nil
}

func seqOut(n node) *port {
	for _, out := range n.outputs() {
		if out.obj.Type == seqType {
			return out
		}
	}
	return nil
}

func ins(n node) (p []*port) {
	for _, in := range n.inputs() {
		if in.obj.Type != seqType {
			p = append(p, in)
		}
	}
	return
}

func outs(n node) (p []*port) {
	for _, out := range n.outputs() {
		if out.obj.Type != seqType {
			p = append(p, out)
		}
	}
	return
}

type basicLiteralNode struct {
	*nodeBase
	kind token.Token
}

func newBasicLiteralNode(kind token.Token) *basicLiteralNode {
	n := &basicLiteralNode{kind: kind}
	n.nodeBase = newNodeBase(n)
	out := n.newOutput(nil)
	switch kind {
	case token.INT, token.FLOAT:
		if kind == token.INT {
			out.setType(types.Typ[types.UntypedInt])
		} else {
			out.setType(types.Typ[types.UntypedFloat])
		}
		n.text.SetValidator(func(s *string) bool {
			*s = strings.TrimLeft(*s, "0")
			if *s == "" || *s == "-" {
				*s = "0"
			}
			if (*s)[0] == '.' {
				*s = "0" + *s
			}
			if l := len(*s); (*s)[l-1] == '-' {
				if (*s)[0] == '-' {
					*s = (*s)[1 : l-1]
				} else {
					*s = "-" + (*s)[:l-1]
				}
			}
			if _, err := strconv.ParseInt(*s, 10, 64); err == nil {
				n.kind = token.INT
				out.setType(types.Typ[types.UntypedInt])
			} else {
				if _, err := strconv.ParseFloat(*s, 4096); err == nil {
					n.kind = token.FLOAT
					out.setType(types.Typ[types.UntypedFloat])
				} else {
					return false
				}
			}
			return true
		})
	case token.IMAG:
		// TODO
	case token.STRING:
		out.setType(types.Typ[types.UntypedString])
	case token.CHAR:
		out.setType(types.Typ[types.UntypedRune])
		n.text.SetValidator(func(s *string) bool {
			if *s == "" {
				return false
			}
			*s = (*s)[len(*s)-1:]
			return true
		})
	}
	n.text.Accept = func(string) { SetKeyFocus(n) }
	return n
}

func (n *basicLiteralNode) KeyPress(k KeyEvent) {
	switch k.Key {
	case KeyEnter:
		s := n.text.GetText()
		t := n.outs[0].obj.Type
		n.text.Reject = func() {
			n.text.SetText(s)
			n.outs[0].setType(t)
			SetKeyFocus(n)
		}
		SetKeyFocus(n.text)
	default:
		n.nodeBase.KeyPress(k)
	}
}

type compositeLiteralNode struct {
	*nodeBase
	typ *typeView
}

func newCompositeLiteralNode() *compositeLiteralNode {
	n := &compositeLiteralNode{}
	n.nodeBase = newNodeBase(n)
	out := n.newOutput(nil)
	n.typ = newTypeView(&out.obj.Type)
	n.typ.mode = compositeOrPtrType
	n.Add(n.typ)
	return n
}
func (n *compositeLiteralNode) editType() {
	n.typ.editType(func() {
		if t := *n.typ.typ; t != nil {
			n.setType(t)
		} else {
			n.blk.removeNode(n)
			SetKeyFocus(n.blk)
		}
	})
}
func (n *compositeLiteralNode) setType(t types.Type) {
	n.typ.setType(t)
	n.outs[0].setType(t)
	n.blk.func_().addPkgRef(t)
	t, _ = indirect(t)
	local := true
	if nt, ok := t.(*types.Named); ok {
		t = nt.UnderlyingT
		local = nt.Obj.Pkg == n.blk.func_().pkg()
	}
	switch t := t.(type) {
	case *types.Struct:
		for _, f := range t.Fields {
			if local || f.IsExported() {
				n.newInput(f)
			}
		}
	case *types.Slice:
		// TODO: variable number of inputs? (same can be achieved using append.)  variable number of index/value input pairs?
	case *types.Map:
		// TODO: variable number of key/value input pairs?
	}
	MoveCenter(n.typ, ZP)
	n.gap = Height(n.typ) / 2
	n.reform()
	SetKeyFocus(n)
}
<commit_msg>Flux:  Bug fix.  Don't slice empty godefer string.<commit_after>// Copyright 2014 Gordon Klaus. 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 (
	"code.google.com/p/gordon-go/go/types"
	. "code.google.com/p/gordon-go/gui"
	"go/token"
	"math"
	"strconv"
	"strings"
)

type node interface {
	View
	Mouser
	block() *block
	setBlock(b *block)
	inputs() []*port
	outputs() []*port
	inConns() []*connection
	outConns() []*connection
}

type nodeBase struct {
	*ViewBase
	self node
	AggregateMouser

	blk  *block
	text *TextBase
	ins  []*port
	outs []*port

	godefer     string
	godeferText Text

	focused bool
	gap     float64
}

func newNodeBase(self node) *nodeBase {
	return newGoDeferNodeBase(self, "")
}

func newGoDeferNodeBase(self node, godefer string) *nodeBase {
	n := &nodeBase{self: self, godefer: godefer}
	n.ViewBase = NewView(n)
	n.AggregateMouser = AggregateMouser{NewClickFocuser(self), NewMover(self)}
	n.text = NewText("")
	n.text.SetBackgroundColor(Color{0, 0, 0, 0})
	n.text.TextChanged = func(string) {
		if n.text.GetText() == "" && godefer != "" {
			n.godeferText.SetText(godefer[:len(godefer)-1])
		}
		width := Width(n.godeferText) + Width(n.text)
		height := Height(n.godeferText)
		n.godeferText.Move(Pt(-width/2, -height/2))
		n.text.Move(Pt(-width/2+Width(n.godeferText), -height/2))
		n.gap = height / 2
		n.reform()
	}
	n.Add(n.text)
	n.godeferText = NewText(godefer)
	n.godeferText.SetBackgroundColor(Color{0, 0, 0, 0})
	n.godeferText.SetTextColor(color(special{}, true, false))
	n.Add(n.godeferText)
	n.ViewBase.Self = self
	return n
}

func (n *nodeBase) newInput(v *types.Var) *port {
	p := newInput(n.self, v)
	n.Add(p)
	n.ins = append(n.ins, p)
	n.reform()
	return p
}

func (n *nodeBase) newOutput(v *types.Var) *port {
	p := newOutput(n.self, v)
	if n.godefer == "" {
		n.Add(p)
		n.outs = append(n.outs, p)
		n.reform()
	}
	return p
}

func (n *nodeBase) addSeqPorts() {
	n.newInput(newVar("seq", seqType))
	n.newOutput(newVar("seq", seqType))
	n.reform()
}

func (n *nodeBase) removePortBase(p *port) { // intentionally named to not implement interface{removePort(*port)}
	for _, c := range p.conns {
		c.blk.removeConn(c)
	}
	ports := &n.ins
	if p.out {
		ports = &n.outs
	}
	for i, p2 := range *ports {
		if p2 == p {
			*ports = append((*ports)[:i], (*ports)[i+1:]...)
			n.Remove(p)
			n.reform()

			if i > 0 && (*ports)[i-1].obj.Type != seqType { // assumes sequencing port, if present, is at index 0
				i--
			}
			if i < len(*ports) {
				SetKeyFocus((*ports)[i])
			} else {
				SetKeyFocus(n.self)
			}
			break
		}
	}
}

func (n *nodeBase) reform() {
	ins, outs := ins(n), outs(n)

	numIn := float64(len(ins))
	numOut := float64(len(outs))
	rx, ry := (math.Max(numIn, numOut)+1)*portSize/2, 1.0*portSize

	rect := ZR
	for i, p := range ins {
		x := portSize * (float64(i) - (numIn-1)/2)
		y := n.gap + ry*math.Sqrt(rx*rx-x*x)/rx
		if numIn > 1 {
			y += 8
		}
		MoveCenter(p, Pt(x, y))
		rect = rect.Union(RectInParent(p))
	}
	for i, p := range outs {
		x := portSize * (float64(i) - (numOut-1)/2)
		y := -n.gap - ry*math.Sqrt(rx*rx-x*x)/rx
		if numOut > 1 {
			y -= 8
		}
		MoveCenter(p, Pt(x, y))
		rect = rect.Union(RectInParent(p))
	}
	if p := seqIn(n); p != nil {
		MoveCenter(p, Pt(0, n.gap))
	}
	if p := seqOut(n); p != nil {
		MoveCenter(p, Pt(0, -n.gap))
	}
	if _, ok := n.self.(*portsNode); ok {
		// portsNode rect must have an edge at y==0 or arrangement will diverge
		n.SetRect(rect)
	} else {
		ResizeToFit(n, 0)
	}
	rearrange(n.blk)
}

func (n nodeBase) block() *block      { return n.blk }
func (n *nodeBase) setBlock(b *block) { n.blk = b }
func (n nodeBase) inputs() []*port    { return n.ins }
func (n nodeBase) outputs() []*port   { return n.outs }

func (n nodeBase) inConns() (conns []*connection) {
	for _, p := range n.inputs() {
		for _, c := range p.conns {
			conns = append(conns, c)
		}
	}
	return
}

func (n nodeBase) outConns() (conns []*connection) {
	for _, p := range n.outputs() {
		for _, c := range p.conns {
			conns = append(conns, c)
		}
	}
	return
}

func (n *nodeBase) Move(p Point) {
	n.ViewBase.Move(p)
	nodeMoved(n.self)
}

func nodeMoved(n node) {
	for _, c := range append(n.inConns(), n.outConns()...) {
		c.reform()
	}
}

func (n *nodeBase) TookKeyFocus() {
	n.focused = true
	c := Color{.25, .25, .25, 1}
	n.text.SetBackgroundColor(c)
	n.godeferText.SetBackgroundColor(c)
}

func (n *nodeBase) LostKeyFocus() {
	n.focused = false
	c := Color{0, 0, 0, 0}
	n.text.SetBackgroundColor(c)
	n.godeferText.SetBackgroundColor(c)
}

func (n *nodeBase) Paint() {
	SetColor(Color{.5, .5, .5, 1})
	for _, p := range append(ins(n), outs(n)...) {
		pt := CenterInParent(p)
		dy := n.gap
		if p.out {
			dy = -dy
		}
		y := (pt.Y-dy)/2 + dy
		DrawCubic([4]Point{Pt(0, dy), Pt(0, y), Pt(pt.X, y), pt}, int(pt.Len()/2))
	}
}

var seqType = struct{ types.Type }{}

func seqIn(n node) *port {
	for _, in := range n.inputs() {
		if in.obj.Type == seqType {
			return in
		}
	}
	return nil
}

func seqOut(n node) *port {
	for _, out := range n.outputs() {
		if out.obj.Type == seqType {
			return out
		}
	}
	return nil
}

func ins(n node) (p []*port) {
	for _, in := range n.inputs() {
		if in.obj.Type != seqType {
			p = append(p, in)
		}
	}
	return
}

func outs(n node) (p []*port) {
	for _, out := range n.outputs() {
		if out.obj.Type != seqType {
			p = append(p, out)
		}
	}
	return
}

type basicLiteralNode struct {
	*nodeBase
	kind token.Token
}

func newBasicLiteralNode(kind token.Token) *basicLiteralNode {
	n := &basicLiteralNode{kind: kind}
	n.nodeBase = newNodeBase(n)
	out := n.newOutput(nil)
	switch kind {
	case token.INT, token.FLOAT:
		if kind == token.INT {
			out.setType(types.Typ[types.UntypedInt])
		} else {
			out.setType(types.Typ[types.UntypedFloat])
		}
		n.text.SetValidator(func(s *string) bool {
			*s = strings.TrimLeft(*s, "0")
			if *s == "" || *s == "-" {
				*s = "0"
			}
			if (*s)[0] == '.' {
				*s = "0" + *s
			}
			if l := len(*s); (*s)[l-1] == '-' {
				if (*s)[0] == '-' {
					*s = (*s)[1 : l-1]
				} else {
					*s = "-" + (*s)[:l-1]
				}
			}
			if _, err := strconv.ParseInt(*s, 10, 64); err == nil {
				n.kind = token.INT
				out.setType(types.Typ[types.UntypedInt])
			} else {
				if _, err := strconv.ParseFloat(*s, 4096); err == nil {
					n.kind = token.FLOAT
					out.setType(types.Typ[types.UntypedFloat])
				} else {
					return false
				}
			}
			return true
		})
	case token.IMAG:
		// TODO
	case token.STRING:
		out.setType(types.Typ[types.UntypedString])
	case token.CHAR:
		out.setType(types.Typ[types.UntypedRune])
		n.text.SetValidator(func(s *string) bool {
			if *s == "" {
				return false
			}
			*s = (*s)[len(*s)-1:]
			return true
		})
	}
	n.text.Accept = func(string) { SetKeyFocus(n) }
	return n
}

func (n *basicLiteralNode) KeyPress(k KeyEvent) {
	switch k.Key {
	case KeyEnter:
		s := n.text.GetText()
		t := n.outs[0].obj.Type
		n.text.Reject = func() {
			n.text.SetText(s)
			n.outs[0].setType(t)
			SetKeyFocus(n)
		}
		SetKeyFocus(n.text)
	default:
		n.nodeBase.KeyPress(k)
	}
}

type compositeLiteralNode struct {
	*nodeBase
	typ *typeView
}

func newCompositeLiteralNode() *compositeLiteralNode {
	n := &compositeLiteralNode{}
	n.nodeBase = newNodeBase(n)
	out := n.newOutput(nil)
	n.typ = newTypeView(&out.obj.Type)
	n.typ.mode = compositeOrPtrType
	n.Add(n.typ)
	return n
}
func (n *compositeLiteralNode) editType() {
	n.typ.editType(func() {
		if t := *n.typ.typ; t != nil {
			n.setType(t)
		} else {
			n.blk.removeNode(n)
			SetKeyFocus(n.blk)
		}
	})
}
func (n *compositeLiteralNode) setType(t types.Type) {
	n.typ.setType(t)
	n.outs[0].setType(t)
	n.blk.func_().addPkgRef(t)
	t, _ = indirect(t)
	local := true
	if nt, ok := t.(*types.Named); ok {
		t = nt.UnderlyingT
		local = nt.Obj.Pkg == n.blk.func_().pkg()
	}
	switch t := t.(type) {
	case *types.Struct:
		for _, f := range t.Fields {
			if local || f.IsExported() {
				n.newInput(f)
			}
		}
	case *types.Slice:
		// TODO: variable number of inputs? (same can be achieved using append.)  variable number of index/value input pairs?
	case *types.Map:
		// TODO: variable number of key/value input pairs?
	}
	MoveCenter(n.typ, ZP)
	n.gap = Height(n.typ) / 2
	n.reform()
	SetKeyFocus(n)
}
<|endoftext|>


<commit_before>package main

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

// this will store notes in ~/notes if the NOGODIR environment variable isn't
// set
var NotesSubDir = "notes"

// this will be used as the default editor if the EDITOR environment variable
// isn't set
var DefaultEditor = "vim"

// the help text that gets displayed when something goes wrong or when you run
// help
var helpText = `
nogo - the notes helper

actions:

  nogo help

  nogo new
  nogo new [topic]
  nogo new [topic] [event]

  nogo ls
  nogo ls [topic]

  nogo edit
  nogo edit [topic]
  nogo edit [topic] [note name substring]
`

// directory holds the string that represents the notes that your notes are
// stored in
var directory string

// editor holds the string that represents the editor that you use to edit your
// notes. it defaults to "vim" but it is overriden by the $EDITOR environment
// variable
var editor string

// init gathers the environment variables on the system and interpolates
// relevant strings
func init() {
	directory = os.Getenv("NOGODIR")
	user, _ := user.Current()
	if directory == "" {
		directory = filepath.Join(user.HomeDir, NotesSubDir)
	}

	editor = os.Getenv("EDITOR")
	if editor == "" {
		editor = DefaultEditor
	}
}

// help prints the help text to stdout and then exits with a given exit code
func help(exit int) {
	fmt.Println(helpText)
	os.Exit(exit)
}

// acceptInput asks the command-line user a given question and returns the text
// that they submitted
func acceptInput(question string) string {
	reader := bufio.NewReader(os.Stdin)

	fmt.Print(question)
	response, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println("Oops, what was that?")
		os.Exit(1)
	}

	return strings.TrimSpace(response)
}

// normalizeString accepts a pointer to a string and modifies it to replace all
// spaces with dashes
func normalizeString(s *string) {
	*s = strings.Replace(*s, " ", "-", -1)
}

// openFile accepts a file name as a parameter and opens it with the editor
// that is stored in the "editor" variable
func openFile(fileName string) {
	cmd := exec.Command(editor, fileName)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		fmt.Println("Couldn't open the file:", err)
		os.Exit(1)
	}
}

// createFile creates a given filename if it doesn't exist
func createFile(fileName string) {
	_, err := os.Create(fileName)
	if err != nil {
		fmt.Println("Oops, I couldn't create the file:", fileName)
	}
}

// createDir creates a directory if the directory path doesn't already exist
func createDir(dirName string) {
	err := os.MkdirAll(dirName, 0755)
	if err != nil {
		fmt.Println("Oops, I couldn't make the directory:", dirName)
		os.Exit(1)
	}
}

// handleNew is the function that gets executed when you type "nogo new"
func handleNew() {
	// Ask for the topic of notes
	topic := acceptInput("Enter the notes topic: ")
	normalizeString(&topic)

	handleNewTopic(topic)
}

// handleNewTopic is the function that gets executed when you type
// "nogo new [topic]"
func handleNewTopic(topic string) {
	// Ask for the event name of the notes
	event := acceptInput("Enter the event name: ")
	normalizeString(&event)

	handleNewEvent(topic, event)
}

// handleNewEvent is the function that gets executed when you type
// "nogo new [topic] [event]"
func handleNewEvent(topic, event string) {
	// Create the topic folder if it doesn't exist
	notesDirectory := filepath.Join(directory, topic)
	createDir(notesDirectory)

	// Make the filename and filepath for the new notes file
	fileName := fmt.Sprintf("%s.md", event)
	fullPath := filepath.Join(notesDirectory, fileName)

	// Create the new notes file
	createFile(fullPath)

	// Open the new file in your editor of choice
	openFile(fullPath)
}

// parseFilename accepts a string as input and returns a new string, in which,
// all dashes are replaced with spaces and the file extension is stripped
func parseFilename(filename string) string {
	return strings.Replace(strings.TrimSuffix(filename, ".md"), "-", " ", -1)
}

// isDir returns true if the given path is a directory
func isDir(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()
	fi, err := f.Stat()
	if err != nil {
		return false
	}
	return fi.IsDir()
}

// listTopics lists the existing topics, while avoiding .git artifacts
func listTopics() {
	files, err := ioutil.ReadDir(directory)
	if err != nil {
		fmt.Println("Oops, couldn't read that directory:", directory)
		os.Exit(1)
	}

	fmt.Println()

	toPrint := []string{}
	for _, file := range files {
		path := filepath.Join(directory, file.Name())
		if isDir(path) && !strings.HasPrefix(file.Name(), ".git") {
			toPrint = append(toPrint, file.Name())
		}
	}

	if len(toPrint) == 0 {
		fmt.Println("looks like there aren't any topics to list!")
	} else {
		fmt.Println("all topics:")
	}

	for _, file := range toPrint {
		fmt.Println("  ", file)
	}
	fmt.Println()
}

// findTopicBySubstring accepts a substring of an existing topic and returns
// the complete topic as well as an error indicating that either something went
// wrong or the topic substring wasn't found
func findTopicBySubstring(substring string) (string, error) {
	topics, err := ioutil.ReadDir(directory)
	if err != nil {
		return "", err
	}
	for _, topic := range topics {
		if strings.Contains(topic.Name(), substring) {
			return topic.Name(), nil
		}
	}

	return "", fmt.Errorf("couldn't find '%s'", substring)
}

// listNotes accepts a topic substring and lists all of the notes in that topic
func listNotes(topic string) {
	completeTopic, err := findTopicBySubstring(topic)
	if err != nil {
		fmt.Println("Oops, couldn't find that topic:", topic)
		os.Exit(1)
	}
	topicDir := filepath.Join(directory, completeTopic)
	files, err := ioutil.ReadDir(topicDir)
	if err != nil {
		fmt.Println("Oops, couldn't read that directory:", topicDir)
		os.Exit(1)
	}

	fmt.Println()
	if len(files) == 0 {
		fmt.Println("looks like there aren't any notes to list!")
	} else {
		fmt.Printf("notes in %s:\n", completeTopic)
	}

	for _, file := range files {
		fmt.Println("  ", parseFilename(file.Name()))
	}

	fmt.Println()
}

// findFileInList takes a list of files, a target filename substring and the
// relevant topic. it then iterates through the files, identifies the proper
// file and opens it in your editor of choice
func findFileInList(files []os.FileInfo, target, completeTopic string) {
	target = strings.Replace(target, " ", "-", -1)
	var found bool

	for _, file := range files {
		if strings.Contains(file.Name(), target) {
			found = true
			fullPath := fmt.Sprintf("%s/%s/%s", directory, completeTopic, file.Name())
			openFile(fullPath)
			os.Exit(0)
		}
	}

	if !found {
		fmt.Println("Oops, couldn't find that file!")
		os.Exit(1)
	}
}

// editFileWithoutTopic is the function that gets called when you type
// "nogo edit"
func editFileWithoutTopic() {
	topic := acceptInput("\nWhat topic would you like to edit? ")
	editFileInTopic(topic)
}

func findFilesInTopic(topic string) ([]os.FileInfo, string) {
	completeTopic, err := findTopicBySubstring(topic)
	if err != nil {
		fmt.Println("Oops, couldn't find that topic:", topic)
		os.Exit(1)
	}
	topicDir := filepath.Join(directory, completeTopic)
	files, err := ioutil.ReadDir(topicDir)
	if err != nil {
		fmt.Println("Oops, couldn't read that directory:", topicDir)
		os.Exit(1)
	}
	return files, completeTopic
}

// editFileInTopic is the function that gets called when you type
// "nogo edit [topic]". it also gets called as the second part of
// "nogo edit" once the topic is given by the user.
func editFileInTopic(topic string) {
	files, completeTopic := findFilesInTopic(topic)
	if len(files) > 0 {
		fmt.Printf("\nFiles in %s:\n", completeTopic)
	}
	for _, file := range files {
		fmt.Printf("   %s\n", parseFilename(file.Name()))
	}
	fmt.Println()
	file := acceptInput(fmt.Sprintf("What file would you like to edit in %s? ", completeTopic))
	findFileInList(files, file, completeTopic)
}

// editFile accepts a topic substring and a target file name substring and
// opens it in your editor of choice
func editFile(topic, target string) {
	files, completeTopic := findFilesInTopic(topic)
	findFileInList(files, target, completeTopic)
}

// main is what gets executed when you run nogo from the command line
func main() {
	if len(os.Args) == 1 {
		help(0)
	}
	action := os.Args[1]
	commandArgs := len(os.Args) - 2

	switch action {
	case "help":
		help(0)
	case "new":
		switch commandArgs {
		case 0:
			handleNew()
		case 1:
			handleNewTopic(os.Args[2])
		case 2:
			handleNewEvent(os.Args[2], os.Args[3])
		default:
			help(1)
		}
	case "ls":
		switch commandArgs {
		case 0:
			listTopics()
		case 1:
			listNotes(os.Args[2])
		default:
			help(1)
		}
	case "edit":
		switch commandArgs {
		case 0:
			editFileWithoutTopic()
		case 1:
			editFileInTopic(os.Args[2])
		default:
			editFile(os.Args[2], strings.Join(os.Args[3:], "-"))
		}
	default:
		help(1)
	}
}
<commit_msg>Repeat prompt in acceptInput until value is provided<commit_after>package main

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

// this will store notes in ~/notes if the NOGODIR environment variable isn't
// set
var NotesSubDir = "notes"

// this will be used as the default editor if the EDITOR environment variable
// isn't set
var DefaultEditor = "vim"

// the help text that gets displayed when something goes wrong or when you run
// help
var helpText = `
nogo - the notes helper

actions:

  nogo help

  nogo new
  nogo new [topic]
  nogo new [topic] [event]

  nogo ls
  nogo ls [topic]

  nogo edit
  nogo edit [topic]
  nogo edit [topic] [note name substring]
`

// directory holds the string that represents the notes that your notes are
// stored in
var directory string

// editor holds the string that represents the editor that you use to edit your
// notes. it defaults to "vim" but it is overriden by the $EDITOR environment
// variable
var editor string

// init gathers the environment variables on the system and interpolates
// relevant strings
func init() {
	directory = os.Getenv("NOGODIR")
	user, _ := user.Current()
	if directory == "" {
		directory = filepath.Join(user.HomeDir, NotesSubDir)
	}

	editor = os.Getenv("EDITOR")
	if editor == "" {
		editor = DefaultEditor
	}
}

// help prints the help text to stdout and then exits with a given exit code
func help(exit int) {
	fmt.Println(helpText)
	os.Exit(exit)
}

// acceptInput asks the command-line user a given question and returns the text
// that they submitted
func acceptInput(question string) string {
	reader := bufio.NewReader(os.Stdin)
	var (
		response string
		err      error
	)
	for response == "" {

		fmt.Print(question)
		response, err = reader.ReadString('\n')
		if err != nil {
			fmt.Println("Oops, what was that?")
			os.Exit(1)
		}
		response = strings.TrimSpace(response)
	}
	return response
}

// normalizeString accepts a pointer to a string and modifies it to replace all
// spaces with dashes
func normalizeString(s *string) {
	*s = strings.Replace(*s, " ", "-", -1)
}

// openFile accepts a file name as a parameter and opens it with the editor
// that is stored in the "editor" variable
func openFile(fileName string) {
	cmd := exec.Command(editor, fileName)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		fmt.Println("Couldn't open the file:", err)
		os.Exit(1)
	}
}

// createFile creates a given filename if it doesn't exist
func createFile(fileName string) {
	_, err := os.Create(fileName)
	if err != nil {
		fmt.Println("Oops, I couldn't create the file:", fileName)
	}
}

// createDir creates a directory if the directory path doesn't already exist
func createDir(dirName string) {
	err := os.MkdirAll(dirName, 0755)
	if err != nil {
		fmt.Println("Oops, I couldn't make the directory:", dirName)
		os.Exit(1)
	}
}

// handleNew is the function that gets executed when you type "nogo new"
func handleNew() {
	// Ask for the topic of notes
	topic := acceptInput("Enter the notes topic: ")
	normalizeString(&topic)

	handleNewTopic(topic)
}

// handleNewTopic is the function that gets executed when you type
// "nogo new [topic]"
func handleNewTopic(topic string) {
	// Ask for the event name of the notes
	event := acceptInput("Enter the event name: ")
	normalizeString(&event)

	handleNewEvent(topic, event)
}

// handleNewEvent is the function that gets executed when you type
// "nogo new [topic] [event]"
func handleNewEvent(topic, event string) {
	// Create the topic folder if it doesn't exist
	notesDirectory := filepath.Join(directory, topic)
	createDir(notesDirectory)

	// Make the filename and filepath for the new notes file
	fileName := fmt.Sprintf("%s.md", event)
	fullPath := filepath.Join(notesDirectory, fileName)

	// Create the new notes file
	createFile(fullPath)

	// Open the new file in your editor of choice
	openFile(fullPath)
}

// parseFilename accepts a string as input and returns a new string, in which,
// all dashes are replaced with spaces and the file extension is stripped
func parseFilename(filename string) string {
	return strings.Replace(strings.TrimSuffix(filename, ".md"), "-", " ", -1)
}

// isDir returns true if the given path is a directory
func isDir(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()
	fi, err := f.Stat()
	if err != nil {
		return false
	}
	return fi.IsDir()
}

// listTopics lists the existing topics, while avoiding .git artifacts
func listTopics() {
	files, err := ioutil.ReadDir(directory)
	if err != nil {
		fmt.Println("Oops, couldn't read that directory:", directory)
		os.Exit(1)
	}

	fmt.Println()

	toPrint := []string{}
	for _, file := range files {
		path := filepath.Join(directory, file.Name())
		if isDir(path) && !strings.HasPrefix(file.Name(), ".git") {
			toPrint = append(toPrint, file.Name())
		}
	}

	if len(toPrint) == 0 {
		fmt.Println("looks like there aren't any topics to list!")
	} else {
		fmt.Println("all topics:")
	}

	for _, file := range toPrint {
		fmt.Println("  ", file)
	}
	fmt.Println()
}

// findTopicBySubstring accepts a substring of an existing topic and returns
// the complete topic as well as an error indicating that either something went
// wrong or the topic substring wasn't found
func findTopicBySubstring(substring string) (string, error) {
	topics, err := ioutil.ReadDir(directory)
	if err != nil {
		return "", err
	}
	for _, topic := range topics {
		if strings.Contains(topic.Name(), substring) {
			return topic.Name(), nil
		}
	}

	return "", fmt.Errorf("couldn't find '%s'", substring)
}

// listNotes accepts a topic substring and lists all of the notes in that topic
func listNotes(topic string) {
	completeTopic, err := findTopicBySubstring(topic)
	if err != nil {
		fmt.Println("Oops, couldn't find that topic:", topic)
		os.Exit(1)
	}
	topicDir := filepath.Join(directory, completeTopic)
	files, err := ioutil.ReadDir(topicDir)
	if err != nil {
		fmt.Println("Oops, couldn't read that directory:", topicDir)
		os.Exit(1)
	}

	fmt.Println()
	if len(files) == 0 {
		fmt.Println("looks like there aren't any notes to list!")
	} else {
		fmt.Printf("notes in %s:\n", completeTopic)
	}

	for _, file := range files {
		fmt.Println("  ", parseFilename(file.Name()))
	}

	fmt.Println()
}

// findFileInList takes a list of files, a target filename substring and the
// relevant topic. it then iterates through the files, identifies the proper
// file and opens it in your editor of choice
func findFileInList(files []os.FileInfo, target, completeTopic string) {
	target = strings.Replace(target, " ", "-", -1)
	var found bool

	for _, file := range files {
		if strings.Contains(file.Name(), target) {
			found = true
			fullPath := fmt.Sprintf("%s/%s/%s", directory, completeTopic, file.Name())
			openFile(fullPath)
			os.Exit(0)
		}
	}

	if !found {
		fmt.Println("Oops, couldn't find that file!")
		os.Exit(1)
	}
}

// editFileWithoutTopic is the function that gets called when you type
// "nogo edit"
func editFileWithoutTopic() {
	topic := acceptInput("\nWhat topic would you like to edit? ")
	editFileInTopic(topic)
}

func findFilesInTopic(topic string) ([]os.FileInfo, string) {
	completeTopic, err := findTopicBySubstring(topic)
	if err != nil {
		fmt.Println("Oops, couldn't find that topic:", topic)
		os.Exit(1)
	}
	topicDir := filepath.Join(directory, completeTopic)
	files, err := ioutil.ReadDir(topicDir)
	if err != nil {
		fmt.Println("Oops, couldn't read that directory:", topicDir)
		os.Exit(1)
	}
	return files, completeTopic
}

// editFileInTopic is the function that gets called when you type
// "nogo edit [topic]". it also gets called as the second part of
// "nogo edit" once the topic is given by the user.
func editFileInTopic(topic string) {
	files, completeTopic := findFilesInTopic(topic)
	if len(files) > 0 {
		fmt.Printf("\nFiles in %s:\n", completeTopic)
	}
	for _, file := range files {
		fmt.Printf("   %s\n", parseFilename(file.Name()))
	}
	fmt.Println()
	file := acceptInput(fmt.Sprintf("What file would you like to edit in %s? ", completeTopic))
	findFileInList(files, file, completeTopic)
}

// editFile accepts a topic substring and a target file name substring and
// opens it in your editor of choice
func editFile(topic, target string) {
	files, completeTopic := findFilesInTopic(topic)
	findFileInList(files, target, completeTopic)
}

// main is what gets executed when you run nogo from the command line
func main() {
	if len(os.Args) == 1 {
		help(0)
	}
	action := os.Args[1]
	commandArgs := len(os.Args) - 2

	switch action {
	case "help":
		help(0)
	case "new":
		switch commandArgs {
		case 0:
			handleNew()
		case 1:
			handleNewTopic(os.Args[2])
		case 2:
			handleNewEvent(os.Args[2], os.Args[3])
		default:
			help(1)
		}
	case "ls":
		switch commandArgs {
		case 0:
			listTopics()
		case 1:
			listNotes(os.Args[2])
		default:
			help(1)
		}
	case "edit":
		switch commandArgs {
		case 0:
			editFileWithoutTopic()
		case 1:
			editFileInTopic(os.Args[2])
		default:
			editFile(os.Args[2], strings.Join(os.Args[3:], "-"))
		}
	default:
		help(1)
	}
}
<|endoftext|>