<commit_before>package main

import (
	"flag"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
)

// Series represents a book series
type Series struct {
	Name  string  `json:"name,omitempty"`
	ID    string  `json:"id,omitempty"`
	Index float64 `json:"index,omitempty"`
}

// Book represents a book
type Book struct {
	ID          string `json:"id"`
	Title       string `json:"title"`
	Author      string `json:"author,omitempty"`
	AuthorID    string `json:"authorid"`
	Publisher   string `json:"publisher,omitempty"`
	Description string `json:"description,omitempty"`
	Series      Series `json:"series,omitempty"`
	Filepath    string `json:"filepath"`
	HasCover    bool   `json:"hascover"`
}

var bookdir *string
var tempdir *string
var addr *string

func main() {
	wd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Fatal error: %s\n", err)
	}

	bookdir = flag.String("bookdir", wd, "The directory to get books from. This directory must exist.")
	tempdir = flag.String("tempdir", filepath.Join(wd, "_temp"), "The directory to use for storing temporary files such as book cover thumbnails. This directory is create on start and deleted on exit.")
	addr = flag.String("addr", ":8090", "The address to bind to.")
	flag.Parse()

	if _, err := os.Stat(*bookdir); err != nil {
		if os.IsNotExist(err) {
			log.Fatalf("Fatal error: book directory %s does not exist\n", *bookdir)
		}
	}

	*bookdir, err = filepath.Abs(*bookdir)
	if err != nil {
		log.Fatalf("Fatal error: Could not resolve book directory %s: %s\n", *bookdir, err)
	}

	if _, err := os.Stat(*tempdir); os.IsNotExist(err) {
		os.Mkdir(*tempdir, os.ModePerm)
	}

	*tempdir, err = filepath.Abs(*tempdir)
	if err != nil {
		log.Fatalf("Fatal error: Could not resolve temp directory %s: %s\n", *tempdir, err)
	}

	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-sigs
		log.Println("Cleaning up covers")
		os.RemoveAll(*tempdir)
		os.Exit(0)
	}()

	books, err := indexBooks()
	if err != nil {
		log.Fatalf("Error indexing books: %s\n", err)
	}

	runServer(books, *addr)
}
<commit_msg>Exit if no books found (#1)<commit_after>package main

import (
	"flag"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
)

// Series represents a book series
type Series struct {
	Name  string  `json:"name,omitempty"`
	ID    string  `json:"id,omitempty"`
	Index float64 `json:"index,omitempty"`
}

// Book represents a book
type Book struct {
	ID          string `json:"id"`
	Title       string `json:"title"`
	Author      string `json:"author,omitempty"`
	AuthorID    string `json:"authorid"`
	Publisher   string `json:"publisher,omitempty"`
	Description string `json:"description,omitempty"`
	Series      Series `json:"series,omitempty"`
	Filepath    string `json:"filepath"`
	HasCover    bool   `json:"hascover"`
}

var bookdir *string
var tempdir *string
var addr *string

func main() {
	wd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Fatal error: %s\n", err)
	}

	bookdir = flag.String("bookdir", wd, "The directory to get books from. This directory must exist.")
	tempdir = flag.String("tempdir", filepath.Join(wd, "_temp"), "The directory to use for storing temporary files such as book cover thumbnails. This directory is create on start and deleted on exit.")
	addr = flag.String("addr", ":8090", "The address to bind to.")
	flag.Parse()

	if _, err := os.Stat(*bookdir); err != nil {
		if os.IsNotExist(err) {
			log.Fatalf("Fatal error: book directory %s does not exist\n", *bookdir)
		}
	}

	*bookdir, err = filepath.Abs(*bookdir)
	if err != nil {
		log.Fatalf("Fatal error: Could not resolve book directory %s: %s\n", *bookdir, err)
	}

	if _, err := os.Stat(*tempdir); os.IsNotExist(err) {
		os.Mkdir(*tempdir, os.ModePerm)
	}

	*tempdir, err = filepath.Abs(*tempdir)
	if err != nil {
		log.Fatalf("Fatal error: Could not resolve temp directory %s: %s\n", *tempdir, err)
	}

	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-sigs
		log.Println("Cleaning up covers")
		os.RemoveAll(*tempdir)
		os.Exit(0)
	}()

	books, err := indexBooks()
	if err != nil {
		log.Fatalf("Fatal error indexing books: %s\n", err)
	}

	if len(books) == 0 {
		log.Fatalln("Fatal error: no books found")
	}

	runServer(books, *addr)
}
<|endoftext|>


<commit_before>package frame

import (
	"unicode"

	"github.com/nyaosorg/go-readline-ny"
)

type _Coloring struct {
	bits int
	last rune
}

func (s *_Coloring) Init() {
	s.bits = 0
}

func (s *_Coloring) Next(codepoint rune) int {
	const (
		backquotedBit = 1
		percentBit    = 2
		quotedBit     = 4
		optionBit     = 8
	)
	newbits := s.bits
	if codepoint == '`' {
		newbits ^= backquotedBit
	} else if codepoint == '%' {
		newbits ^= percentBit
	} else if codepoint == '"' {
		newbits ^= quotedBit
	} else if s.last == ' ' && (codepoint == '/' || codepoint == '-') {
		newbits ^= optionBit
	} else if (s.bits&optionBit) != 0 && !unicode.IsLetter(codepoint) {
		newbits &^= optionBit
	} else if s.last == '%' && (s.bits&percentBit) != 0 && unicode.IsDigit(codepoint) {
		newbits &^= percentBit
	}
	bits := s.bits | newbits
	color := readline.White
	if unicode.IsControl(codepoint) {
		color = readline.Blue
	} else if codepoint == '\u3000' {
		color = 41
	} else if (bits & percentBit) != 0 {
		color = readline.Cyan
	} else if (bits & backquotedBit) != 0 {
		color = readline.Red
	} else if (bits & quotedBit) != 0 {
		color = readline.Magenta
	} else if (bits & optionBit) != 0 {
		color = readline.Yellow
	} else if codepoint == '&' || codepoint == '|' || codepoint == '<' || codepoint == '>' || (s.last == ' ' && codepoint == ';') {
		color = readline.Green
	} else {
		color = readline.White
	}
	s.bits = newbits
	s.last = codepoint
	return color
}
<commit_msg>Change the color of the option-string dark-yellow<commit_after>package frame

import (
	"unicode"

	"github.com/nyaosorg/go-readline-ny"
)

type _Coloring struct {
	bits int
	last rune
}

func (s *_Coloring) Init() {
	s.bits = 0
}

func (s *_Coloring) Next(codepoint rune) int {
	const (
		backquotedBit = 1
		percentBit    = 2
		quotedBit     = 4
		optionBit     = 8
	)
	newbits := s.bits
	if codepoint == '`' {
		newbits ^= backquotedBit
	} else if codepoint == '%' {
		newbits ^= percentBit
	} else if codepoint == '"' {
		newbits ^= quotedBit
	} else if s.last == ' ' && (codepoint == '/' || codepoint == '-') {
		newbits ^= optionBit
	} else if (s.bits&optionBit) != 0 && !unicode.IsLetter(codepoint) {
		newbits &^= optionBit
	} else if s.last == '%' && (s.bits&percentBit) != 0 && unicode.IsDigit(codepoint) {
		newbits &^= percentBit
	}
	bits := s.bits | newbits
	color := readline.White
	if unicode.IsControl(codepoint) {
		color = readline.Blue
	} else if codepoint == '\u3000' {
		color = 41
	} else if (bits & percentBit) != 0 {
		color = readline.Cyan
	} else if (bits & backquotedBit) != 0 {
		color = readline.Red
	} else if (bits & quotedBit) != 0 {
		color = readline.Magenta
	} else if (bits & optionBit) != 0 {
		color = readline.DarkYellow
	} else if codepoint == '&' || codepoint == '|' || codepoint == '<' || codepoint == '>' || (s.last == ' ' && codepoint == ';') {
		color = readline.Green
	} else {
		color = readline.White
	}
	s.bits = newbits
	s.last = codepoint
	return color
}
<|endoftext|>


<commit_before>// Copyright 2010 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.

// +build linux

// Package fsnotify implements a wrapper for the Linux inotify system.
package fsnotify

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type FileEvent struct {
	mask   uint32 // Mask of events
	cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

// IsCreate reports whether the FileEvent was triggerd by a creation
func (e *FileEvent) IsCreate() bool {
	return (e.mask&IN_CREATE) == IN_CREATE || (e.mask&IN_MOVED_TO) == IN_MOVED_TO
}

// IsDelete reports whether the FileEvent was triggerd by a delete
func (e *FileEvent) IsDelete() bool {
	return (e.mask&IN_DELETE_SELF) == IN_DELETE_SELF || (e.mask&IN_DELETE) == IN_DELETE
}

// IsModify reports whether the FileEvent was triggerd by a file modification or attribute change
func (e *FileEvent) IsModify() bool {
	return ((e.mask&IN_MODIFY) == IN_MODIFY || (e.mask&IN_ATTRIB) == IN_ATTRIB)
}

// IsRename reports whether the FileEvent was triggerd by a change name
func (e *FileEvent) IsRename() bool {
	return ((e.mask&IN_MOVE_SELF) == IN_MOVE_SELF || (e.mask&IN_MOVED_FROM) == IN_MOVED_FROM)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu            sync.Mutex        // Map access
	fd            int               // File descriptor (as returned by the inotify_init() syscall)
	watches       map[string]*watch // Map of inotify watches (key: path)
	fsnFlags      map[string]uint32 // Map of watched files to flags used for filter
	fsnmut        sync.Mutex        // Protects access to fsnFlags.
	paths         map[int]string    // Map of watched paths (key: watch descriptor)
	Error         chan error        // Errors are sent on this channel
	internalEvent chan *FileEvent   // Events are queued on this channel
	Event         chan *FileEvent   // Events are returned on this channel
	done          chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed      bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:            fd,
		watches:       make(map[string]*watch),
		fsnFlags:      make(map[string]uint32),
		paths:         make(map[int]string),
		internalEvent: make(chan *FileEvent),
		Event:         make(chan *FileEvent),
		Error:         make(chan error),
		done:          make(chan bool, 1),
	}

	go w.readEvents()
	go w.purgeEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Remove all watches
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	// Send "quit" message to the reader goroutine
	w.done <- true

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) addWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	watchEntry, found := w.watches[path]
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}
	wd, errno := syscall.InotifyAddWatch(w.fd, path, flags)
	if wd == -1 {
		return errno
	}

	w.mu.Lock()
	w.watches[path] = &watch{wd: uint32(wd), flags: flags}
	w.paths[wd] = path
	w.mu.Unlock()

	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) watch(path string) error {
	return w.addWatch(path, OS_AGNOSTIC_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) removeWatch(path string) error {
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var (
		buf   [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
		n     int                                     // Number of bytes read with read()
		errno error                                   // Syscall errno
	)

	for {
		n, errno = syscall.Read(w.fd, buf[0:])
		// See if there is a message on the "done" channel
		var done bool
		select {
		case done = <-w.done:
		default:
		}

		// If EOF or a "done" message is received
		if n == 0 || done {
			syscall.Close(w.fd)
			close(w.internalEvent)
			close(w.Error)
			return
		}

		if n < 0 {
			w.Error <- os.NewSyscallError("read", errno)
			continue
		}
		if n < syscall.SizeofInotifyEvent {
			w.Error <- errors.New("inotify: short read in readEvents()")
			continue
		}

		var offset uint32 = 0
		// We don't know how many events we just read into the buffer
		// While the offset points to at least one whole event...
		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
			// Point "raw" to the event in the buffer
			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
			event := new(FileEvent)
			event.mask = uint32(raw.Mask)
			event.cookie = uint32(raw.Cookie)
			nameLen := uint32(raw.Len)
			// If the event happened to the watched directory or the watched file, the kernel
			// doesn't append the filename to the event, but we would like to always fill the
			// the "Name" field with a valid filename. We retrieve the path of the watch from
			// the "paths" map.
			w.mu.Lock()
			event.Name = w.paths[int(raw.Wd)]
			w.mu.Unlock()
			watchedName := event.Name
			if nameLen > 0 {
				// Point "bytes" at the first byte of the filename
				bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
				// The filename is padded with NUL bytes. TrimRight() gets rid of those.
				event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
			}

			// Setup FSNotify flags (inherit from directory watch)
			fsnFlags := w.fsnFlags[watchedName]
			_, fsnFound := w.fsnFlags[event.Name]
			if !fsnFound {
				w.fsnFlags[event.Name] = fsnFlags
			}

			// Send the events that are not ignored on the events channel
			if (event.mask & IN_IGNORED) == 0 {
				w.internalEvent <- event
			}

			// Move to the next event in the buffer
			offset += syscall.SizeofInotifyEvent + nameLen
		}
	}
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	OS_AGNOSTIC_EVENTS = IN_MOVED_TO | IN_MOVED_FROM | IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE | IN_DELETE_SELF

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)
<commit_msg>Mutex for map access on Linux<commit_after>// Copyright 2010 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.

// +build linux

// Package fsnotify implements a wrapper for the Linux inotify system.
package fsnotify

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type FileEvent struct {
	mask   uint32 // Mask of events
	cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

// IsCreate reports whether the FileEvent was triggerd by a creation
func (e *FileEvent) IsCreate() bool {
	return (e.mask&IN_CREATE) == IN_CREATE || (e.mask&IN_MOVED_TO) == IN_MOVED_TO
}

// IsDelete reports whether the FileEvent was triggerd by a delete
func (e *FileEvent) IsDelete() bool {
	return (e.mask&IN_DELETE_SELF) == IN_DELETE_SELF || (e.mask&IN_DELETE) == IN_DELETE
}

// IsModify reports whether the FileEvent was triggerd by a file modification or attribute change
func (e *FileEvent) IsModify() bool {
	return ((e.mask&IN_MODIFY) == IN_MODIFY || (e.mask&IN_ATTRIB) == IN_ATTRIB)
}

// IsRename reports whether the FileEvent was triggerd by a change name
func (e *FileEvent) IsRename() bool {
	return ((e.mask&IN_MOVE_SELF) == IN_MOVE_SELF || (e.mask&IN_MOVED_FROM) == IN_MOVED_FROM)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu            sync.Mutex        // Map access
	fd            int               // File descriptor (as returned by the inotify_init() syscall)
	watches       map[string]*watch // Map of inotify watches (key: path)
	fsnFlags      map[string]uint32 // Map of watched files to flags used for filter
	fsnmut        sync.Mutex        // Protects access to fsnFlags.
	paths         map[int]string    // Map of watched paths (key: watch descriptor)
	Error         chan error        // Errors are sent on this channel
	internalEvent chan *FileEvent   // Events are queued on this channel
	Event         chan *FileEvent   // Events are returned on this channel
	done          chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed      bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:            fd,
		watches:       make(map[string]*watch),
		fsnFlags:      make(map[string]uint32),
		paths:         make(map[int]string),
		internalEvent: make(chan *FileEvent),
		Event:         make(chan *FileEvent),
		Error:         make(chan error),
		done:          make(chan bool, 1),
	}

	go w.readEvents()
	go w.purgeEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Remove all watches
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	// Send "quit" message to the reader goroutine
	w.done <- true

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) addWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	w.mu.Lock()
	watchEntry, found := w.watches[path]
	w.mu.Unlock()
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}
	wd, errno := syscall.InotifyAddWatch(w.fd, path, flags)
	if wd == -1 {
		return errno
	}

	w.mu.Lock()
	w.watches[path] = &watch{wd: uint32(wd), flags: flags}
	w.paths[wd] = path
	w.mu.Unlock()

	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) watch(path string) error {
	return w.addWatch(path, OS_AGNOSTIC_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) removeWatch(path string) error {
	w.mu.Lock()
	defer w.mu.Unlock()
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var (
		buf   [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
		n     int                                     // Number of bytes read with read()
		errno error                                   // Syscall errno
	)

	for {
		n, errno = syscall.Read(w.fd, buf[0:])
		// See if there is a message on the "done" channel
		var done bool
		select {
		case done = <-w.done:
		default:
		}

		// If EOF or a "done" message is received
		if n == 0 || done {
			syscall.Close(w.fd)
			close(w.internalEvent)
			close(w.Error)
			return
		}

		if n < 0 {
			w.Error <- os.NewSyscallError("read", errno)
			continue
		}
		if n < syscall.SizeofInotifyEvent {
			w.Error <- errors.New("inotify: short read in readEvents()")
			continue
		}

		var offset uint32 = 0
		// We don't know how many events we just read into the buffer
		// While the offset points to at least one whole event...
		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
			// Point "raw" to the event in the buffer
			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
			event := new(FileEvent)
			event.mask = uint32(raw.Mask)
			event.cookie = uint32(raw.Cookie)
			nameLen := uint32(raw.Len)
			// If the event happened to the watched directory or the watched file, the kernel
			// doesn't append the filename to the event, but we would like to always fill the
			// the "Name" field with a valid filename. We retrieve the path of the watch from
			// the "paths" map.
			w.mu.Lock()
			event.Name = w.paths[int(raw.Wd)]
			w.mu.Unlock()
			watchedName := event.Name
			if nameLen > 0 {
				// Point "bytes" at the first byte of the filename
				bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
				// The filename is padded with NUL bytes. TrimRight() gets rid of those.
				event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
			}

			// Setup FSNotify flags (inherit from directory watch)
			w.fsnmut.Lock()
			fsnFlags := w.fsnFlags[watchedName]
			_, fsnFound := w.fsnFlags[event.Name]
			if !fsnFound {
				w.fsnFlags[event.Name] = fsnFlags
			}
			w.fsnmut.Unlock()

			// Send the events that are not ignored on the events channel
			if (event.mask & IN_IGNORED) == 0 {
				w.internalEvent <- event
			}

			// Move to the next event in the buffer
			offset += syscall.SizeofInotifyEvent + nameLen
		}
	}
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	OS_AGNOSTIC_EVENTS = IN_MOVED_TO | IN_MOVED_FROM | IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE | IN_DELETE_SELF

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)
<|endoftext|>


<commit_before>package geos

import (
	"testing"
)

func TestVersion(t *testing.T) {
	expected := "3.3.8-CAPI-1.7.8"
	if actual := Version(); actual != expected {
		t.Errorf("Version(): want %v, got %v", expected, actual)
	}
}
<commit_msg>Match geos version against regex<commit_after>package geos

import (
	"regexp"
	"testing"
)

func TestVersion(t *testing.T) {
	const re = `3\.3\.\d+-CAPI-1\.7\.\d+$`
	version := Version()
	matched, err := regexp.MatchString(re, version)
	if err != nil {
		t.Fatal("Version regex:", err)
	}
	if !matched {
		t.Errorf("Version(): %q didn't match regex \"%s\"", version, re)
	}
}
<|endoftext|>


<commit_before>package b2

import (
	"bytes"
	"io/ioutil"
	"testing"

	"net/http" // remove soon with refactor
)

func Test_listBuckets(t *testing.T) {
	resp := createTestResponse(200, `{"buckets":
[{"bucketId":"id","accountId":"id","bucketName":"name","bucketType":"allPrivate"}]}`)

	b := &B2{}
	buckets, err := b.listBuckets(resp)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}

	if len(buckets) != 1 {
		t.Errorf("Expected one bucket, instead got %d", len(buckets))
	}
	if buckets[0].BucketID != "id" {
		t.Errorf(`Expected "id", instead got %s`, buckets[0].BucketID)
	}
	if buckets[0].BucketName != "name" {
		t.Errorf(`Expected "name", instead got %s`, buckets[0].BucketName)
	}
	if buckets[0].BucketType != AllPrivate {
		t.Errorf("Expected AllPrivate, instead got %+v", buckets[0].BucketType)
	}
	if *buckets[0].B2 != *b {
		t.Errorf("Expected bucket B2 to be *b, instead got %+v", *buckets[0].B2)
	}

	resps := createTestErrorResponses()
	for i, resp := range resps {
		buckets, err := b.listBuckets(resp)
		testErrorResponse(err, 400+i, t)
		if buckets != nil {
			t.Errorf("Expected b to be nil, instead got %+v", b)
		}
	}
}

func Test_B2_createBucket(t *testing.T) {
	resp := createTestResponse(200,
		`{"bucketId":"id","accountId":"id","bucketName":"bucket","bucketType":"allPrivate"}`)

	b := &B2{}
	bucket, err := b.createBucket(resp)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	if bucket.BucketID != "id" {
		t.Errorf(`Expected "id", instead got %s`, bucket.BucketID)
	}
	if bucket.BucketName != "bucket" {
		t.Errorf(`Expected "bucket", instead got %s`, bucket.BucketName)
	}
	if bucket.BucketType != AllPrivate {
		t.Errorf("Expected bucket type to be private, instead got %s", bucket.BucketType)
	}
	if bucket.B2 != b {
		t.Errorf("Expected bucket B2 to be test B2, instead got %+v", bucket.B2)
	}

	resps := createTestErrorResponses()
	for i, resp := range resps {
		buckets, err := b.listBuckets(resp)
		testErrorResponse(err, 400+i, t)
		if buckets != nil {
			t.Errorf("Expected b to be nil, instead got %+v", b)
		}
	}
}

func Test_Bucket_update(t *testing.T) {
	resp := createTestResponse(200,
		`{"bucketId":"id","accountId":"id","bucketName":"bucket","bucketType":"allPublic"}`)

	b := &B2{}
	bucket := makeTestBucket(b)
	err := bucket.update(resp)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}

	// bucket type should change
	if bucket.BucketType != AllPublic {
		t.Errorf("Expected bucket type to be private, instead got %s", bucket.BucketType)
	}

	// nothing else should have changed
	if bucket.BucketID != "id" {
		t.Errorf(`Expected "id", instead got %s`, bucket.BucketID)
	}
	if bucket.BucketName != "bucket" {
		t.Errorf(`Expected "bucket", instead got %s`, bucket.BucketName)
	}
	if bucket.B2 != b {
		t.Errorf("Expected bucket B2 to be test B2, instead got %+v", bucket.B2)
	}

	resps := createTestErrorResponses()
	for i, resp := range resps {
		bucket := makeTestBucket(b)
		err := bucket.update(resp)
		testErrorResponse(err, 400+i, t)
		if bucket.BucketType != AllPrivate {
			t.Errorf("Expected bucket type to be private, instead got %+v", bucket.BucketType)
		}
	}
}

func Test_Bucket_Delete_Success(t *testing.T) {
	s, c := setupRequest(200,
		`{"bucketId":"id","accountId":"id","bucketName":"bucket","bucketType":"allPublic"}`)
	defer s.Close()

	b := makeTestB2(c)
	bucket := makeTestBucket(b)
	err := bucket.Delete()
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
}

func Test_Bucket_Delete_Errors(t *testing.T) {
	codes, bodies := errorResponses()
	for i := range codes {
		s, c := setupRequest(codes[i], bodies[i])

		b := makeTestB2(c)
		bucket := makeTestBucket(b)
		err := bucket.Update(AllPublic)
		testErrorResponse(err, codes[i], t)

		s.Close()
	}
}

func Test_B2_createBucketRequest(t *testing.T) {
	b := makeTestB2(http.Client{})

	reqs := [][]byte{}
	fields := [][]byte{
		[]byte("accountId"), []byte("bucketId"), []byte("bucketName"), []byte("bucketType")}
	brs := []bucketRequest{
		{}, // List Buckets
		{BucketName: "bucket", BucketType: AllPublic}, // Create Bucket
		{BucketID: "id", BucketType: AllPrivate},      // Bucket Update
		{BucketID: "id"},                              // Bucket Delete
	}
	finds := [][][]byte{
		{fields[0]},                       // List Buckets
		{fields[0], fields[2], fields[3]}, // Create Bucket
		{fields[0], fields[1], fields[3]}, // Bucket Update
		{fields[0], fields[1]},            // Bucket Delete
	}

	// Setup all request bodies
	req1, err := b.createBucketRequest("/", brs[0])
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	body1, err := ioutil.ReadAll(req1.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body1)

	req2, err := b.createBucketRequest("/", brs[1])
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	body2, err := ioutil.ReadAll(req2.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body2)

	req3, err := b.createBucketRequest("/", brs[2])
	if err != nil {
		t.Errorf("Expected no error, instead got %s", err)
	}
	body3, err := ioutil.ReadAll(req3.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body3)

	req4, err := b.createBucketRequest("/", brs[3])
	if err != nil {
		t.Errorf("Expected no error, instead got %s", err)
	}
	body4, err := ioutil.ReadAll(req4.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body4)

	for i, reqBody := range reqs {
		for _, elem := range fields {
			// should have but doesn't
			if sliceContains(finds[i], elem) && !bytes.Contains(reqBody, elem) {
				t.Errorf("reqBody should have %q, but only had %q", elem, reqBody)
			}
			// shouldn't have but does
			if !sliceContains(finds[i], elem) && bytes.Contains(reqBody, elem) {
				t.Errorf("reqBody should not have %q, but does, and has %q", elem, reqBody)
			}
		}
	}
}

func makeTestBucket(b *B2) *Bucket {
	return &Bucket{
		BucketID:   "id",
		BucketName: "bucket",
		BucketType: AllPrivate,
		B2:         b,
	}
}

func sliceContains(haystack [][]byte, needle []byte) bool {
	for _, straw := range haystack {
		if bytes.Equal(straw, needle) {
			return true
		}
	}
	return false
}
<commit_msg>Refactor bucketDelete test<commit_after>package b2

import (
	"bytes"
	"io/ioutil"
	"testing"

	"net/http" // remove soon with refactor
)

func Test_listBuckets(t *testing.T) {
	resp := createTestResponse(200, `{"buckets":
[{"bucketId":"id","accountId":"id","bucketName":"name","bucketType":"allPrivate"}]}`)

	b := &B2{}
	buckets, err := b.listBuckets(resp)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}

	if len(buckets) != 1 {
		t.Errorf("Expected one bucket, instead got %d", len(buckets))
	}
	if buckets[0].BucketID != "id" {
		t.Errorf(`Expected "id", instead got %s`, buckets[0].BucketID)
	}
	if buckets[0].BucketName != "name" {
		t.Errorf(`Expected "name", instead got %s`, buckets[0].BucketName)
	}
	if buckets[0].BucketType != AllPrivate {
		t.Errorf("Expected AllPrivate, instead got %+v", buckets[0].BucketType)
	}
	if *buckets[0].B2 != *b {
		t.Errorf("Expected bucket B2 to be *b, instead got %+v", *buckets[0].B2)
	}

	resps := createTestErrorResponses()
	for i, resp := range resps {
		buckets, err := b.listBuckets(resp)
		testErrorResponse(err, 400+i, t)
		if buckets != nil {
			t.Errorf("Expected b to be nil, instead got %+v", b)
		}
	}
}

func Test_B2_createBucket(t *testing.T) {
	resp := createTestResponse(200,
		`{"bucketId":"id","accountId":"id","bucketName":"bucket","bucketType":"allPrivate"}`)

	b := &B2{}
	bucket, err := b.createBucket(resp)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	if bucket.BucketID != "id" {
		t.Errorf(`Expected "id", instead got %s`, bucket.BucketID)
	}
	if bucket.BucketName != "bucket" {
		t.Errorf(`Expected "bucket", instead got %s`, bucket.BucketName)
	}
	if bucket.BucketType != AllPrivate {
		t.Errorf("Expected bucket type to be private, instead got %s", bucket.BucketType)
	}
	if bucket.B2 != b {
		t.Errorf("Expected bucket B2 to be test B2, instead got %+v", bucket.B2)
	}

	resps := createTestErrorResponses()
	for i, resp := range resps {
		buckets, err := b.listBuckets(resp)
		testErrorResponse(err, 400+i, t)
		if buckets != nil {
			t.Errorf("Expected b to be nil, instead got %+v", b)
		}
	}
}

func Test_Bucket_update(t *testing.T) {
	resp := createTestResponse(200,
		`{"bucketId":"id","accountId":"id","bucketName":"bucket","bucketType":"allPublic"}`)

	b := &B2{}
	bucket := makeTestBucket(b)
	err := bucket.update(resp)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}

	// bucket type should change
	if bucket.BucketType != AllPublic {
		t.Errorf("Expected bucket type to be private, instead got %s", bucket.BucketType)
	}

	// nothing else should have changed
	if bucket.BucketID != "id" {
		t.Errorf(`Expected "id", instead got %s`, bucket.BucketID)
	}
	if bucket.BucketName != "bucket" {
		t.Errorf(`Expected "bucket", instead got %s`, bucket.BucketName)
	}
	if bucket.B2 != b {
		t.Errorf("Expected bucket B2 to be test B2, instead got %+v", bucket.B2)
	}

	resps := createTestErrorResponses()
	for i, resp := range resps {
		bucket := makeTestBucket(b)
		err := bucket.update(resp)
		testErrorResponse(err, 400+i, t)
		if bucket.BucketType != AllPrivate {
			t.Errorf("Expected bucket type to be private, instead got %+v", bucket.BucketType)
		}
	}
}

func Test_Bucket_bucketDelete(t *testing.T) {
	resp := createTestResponse(200,
		`{"bucketId":"id","accountId":"id","bucketName":"bucket","bucketType":"allPublic"}`)

	bucket := makeTestBucket(&B2{})
	err := bucket.bucketDelete(resp)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}

	resps := createTestErrorResponses()
	for i, resp := range resps {
		bucket := makeTestBucket(&B2{})
		err := bucket.bucketDelete(resp)
		testErrorResponse(err, 400+i, t)
	}
}

func Test_B2_createBucketRequest(t *testing.T) {
	b := makeTestB2(http.Client{})

	reqs := [][]byte{}
	fields := [][]byte{
		[]byte("accountId"), []byte("bucketId"), []byte("bucketName"), []byte("bucketType")}
	brs := []bucketRequest{
		{}, // List Buckets
		{BucketName: "bucket", BucketType: AllPublic}, // Create Bucket
		{BucketID: "id", BucketType: AllPrivate},      // Bucket Update
		{BucketID: "id"},                              // Bucket Delete
	}
	finds := [][][]byte{
		{fields[0]},                       // List Buckets
		{fields[0], fields[2], fields[3]}, // Create Bucket
		{fields[0], fields[1], fields[3]}, // Bucket Update
		{fields[0], fields[1]},            // Bucket Delete
	}

	// Setup all request bodies
	req1, err := b.createBucketRequest("/", brs[0])
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	body1, err := ioutil.ReadAll(req1.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body1)

	req2, err := b.createBucketRequest("/", brs[1])
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	body2, err := ioutil.ReadAll(req2.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body2)

	req3, err := b.createBucketRequest("/", brs[2])
	if err != nil {
		t.Errorf("Expected no error, instead got %s", err)
	}
	body3, err := ioutil.ReadAll(req3.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body3)

	req4, err := b.createBucketRequest("/", brs[3])
	if err != nil {
		t.Errorf("Expected no error, instead got %s", err)
	}
	body4, err := ioutil.ReadAll(req4.Body)
	if err != nil {
		t.Fatalf("Expected no error, instead got %s", err)
	}
	reqs = append(reqs, body4)

	for i, reqBody := range reqs {
		for _, elem := range fields {
			// should have but doesn't
			if sliceContains(finds[i], elem) && !bytes.Contains(reqBody, elem) {
				t.Errorf("reqBody should have %q, but only had %q", elem, reqBody)
			}
			// shouldn't have but does
			if !sliceContains(finds[i], elem) && bytes.Contains(reqBody, elem) {
				t.Errorf("reqBody should not have %q, but does, and has %q", elem, reqBody)
			}
		}
	}
}

func makeTestBucket(b *B2) *Bucket {
	return &Bucket{
		BucketID:   "id",
		BucketName: "bucket",
		BucketType: AllPrivate,
		B2:         b,
	}
}

func sliceContains(haystack [][]byte, needle []byte) bool {
	for _, straw := range haystack {
		if bytes.Equal(straw, needle) {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"time"

	"github.com/ofesseler/gluster_exporter/structs"
	"github.com/prometheus/common/log"
)

func execGlusterCommand(arg ...string) (*bytes.Buffer, error) {
	stdoutBuffer := &bytes.Buffer{}
	argXML := append(arg, "--xml")
	glusterExec := exec.Command(GlusterCmd, argXML...)
	glusterExec.Stdout = stdoutBuffer
	err := glusterExec.Run()

	if err != nil {
		log.Errorf("tried to execute %v and got error: %v", arg, err)
		return stdoutBuffer, err
	}
	return stdoutBuffer, nil
}

func execMountCheck() (*bytes.Buffer, error) {
	stdoutBuffer := &bytes.Buffer{}
	mountCmd := exec.Command("mount", "-t", "fuse.glusterfs")

	mountCmd.Stdout = stdoutBuffer

	return stdoutBuffer, mountCmd.Run()
}

func execTouchOnVolumes(mountpoint string) (bool, error) {
	testFileName := fmt.Sprintf("%v/%v_%v", mountpoint, "gluster_mount.test", time.Now())
	_, createErr := os.Create(testFileName)
	if createErr != nil {
		return false, createErr
	}
	removeErr := os.Remove(testFileName)
	if removeErr != nil {
		return false, removeErr
	}
	return true, nil
}

// ExecVolumeInfo executes "gluster volume info" at the local machine and
// returns VolumeInfoXML struct and error
func ExecVolumeInfo() (structs.VolumeInfoXML, error) {
	args := []string{"volume", "info"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolumeInfoXML{}, cmdErr
	}
	volumeInfo, err := structs.VolumeInfoXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeInfo, err
	}

	return volumeInfo, nil
}

// ExecVolumeList executes "gluster volume info" at the local machine and
// returns VolumeList struct and error
func ExecVolumeList() (structs.VolList, error) {
	args := []string{"volume", "list"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolList{}, cmdErr
	}
	volumeList, err := structs.VolumeListXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeList.VolList, err
	}

	return volumeList.VolList, nil
}

// ExecPeerStatus executes "gluster peer status" at the local machine and
// returns PeerStatus struct and error
func ExecPeerStatus() (structs.PeerStatus, error) {
	args := []string{"peer", "status"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.PeerStatus{}, cmdErr
	}
	peerStatus, err := structs.PeerStatusXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return peerStatus.PeerStatus, err
	}

	return peerStatus.PeerStatus, nil
}

// ExecVolumeProfileGvInfoCumulative executes "gluster volume {volume] profile info cumulative" at the local machine and
// returns VolumeInfoXML struct and error
func ExecVolumeProfileGvInfoCumulative(volumeName string) (structs.VolProfile, error) {
	args := []string{"volume", "profile", volumeName, "info", "cumulative"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolProfile{}, cmdErr
	}
	volumeProfile, err := structs.VolumeProfileGvInfoCumulativeXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeProfile.VolProfile, err
	}
	return volumeProfile.VolProfile, nil
}

// ExecVolumeStatusAllDetail executes "gluster volume status all detail" at the local machine
// returns VolumeStatusXML struct and error
func ExecVolumeStatusAllDetail() (structs.VolumeStatusXML, error) {
	args := []string{"volume", "status", "all", "detail"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolumeStatusXML{}, cmdErr
	}
	volumeStatus, err := structs.VolumeStatusAllDetailXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeStatus, err
	}
	return volumeStatus, nil
}

// ExecVolumeHealInfo executes volume heal info on host system and processes input
// returns (int) number of unsynced files
func ExecVolumeHealInfo(volumeName string) (int, error) {
	args := []string{"volume", "heal", volumeName, "info"}
	entriesOutOfSync := 0
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return -1, cmdErr
	}
	healInfo, err := structs.VolumeHealInfoXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Error(err)
		return -1, err
	}

	for _, brick := range healInfo.HealInfo.Bricks.Brick {
		var count int
		count, _ = strconv.Atoi(brick.NumberOfEntries)
		entriesOutOfSync += count
	}
	return entriesOutOfSync, nil
}

// ExecVolumeQuotaList executes volume quota list on host system and processes input
// returns QuotaList structs and errors
func ExecVolumeQuotaList(volumeName string) (structs.VolumeQuotaXML, error) {
	args := []string{"volume", "quota", volumeName, "list"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolumeQuotaXML{}, cmdErr
	}
	volumeQuota, err := structs.VolumeQuotaListXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeQuota, err
	}
	return volumeQuota, nil
}
<commit_msg>Add Atoi error handling to fix gosec warning.<commit_after>package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"time"

	"github.com/ofesseler/gluster_exporter/structs"
	"github.com/prometheus/common/log"
)

func execGlusterCommand(arg ...string) (*bytes.Buffer, error) {
	stdoutBuffer := &bytes.Buffer{}
	argXML := append(arg, "--xml")
	glusterExec := exec.Command(GlusterCmd, argXML...)
	glusterExec.Stdout = stdoutBuffer
	err := glusterExec.Run()

	if err != nil {
		log.Errorf("tried to execute %v and got error: %v", arg, err)
		return stdoutBuffer, err
	}
	return stdoutBuffer, nil
}

func execMountCheck() (*bytes.Buffer, error) {
	stdoutBuffer := &bytes.Buffer{}
	mountCmd := exec.Command("mount", "-t", "fuse.glusterfs")

	mountCmd.Stdout = stdoutBuffer

	return stdoutBuffer, mountCmd.Run()
}

func execTouchOnVolumes(mountpoint string) (bool, error) {
	testFileName := fmt.Sprintf("%v/%v_%v", mountpoint, "gluster_mount.test", time.Now())
	_, createErr := os.Create(testFileName)
	if createErr != nil {
		return false, createErr
	}
	removeErr := os.Remove(testFileName)
	if removeErr != nil {
		return false, removeErr
	}
	return true, nil
}

// ExecVolumeInfo executes "gluster volume info" at the local machine and
// returns VolumeInfoXML struct and error
func ExecVolumeInfo() (structs.VolumeInfoXML, error) {
	args := []string{"volume", "info"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolumeInfoXML{}, cmdErr
	}
	volumeInfo, err := structs.VolumeInfoXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeInfo, err
	}

	return volumeInfo, nil
}

// ExecVolumeList executes "gluster volume info" at the local machine and
// returns VolumeList struct and error
func ExecVolumeList() (structs.VolList, error) {
	args := []string{"volume", "list"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolList{}, cmdErr
	}
	volumeList, err := structs.VolumeListXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeList.VolList, err
	}

	return volumeList.VolList, nil
}

// ExecPeerStatus executes "gluster peer status" at the local machine and
// returns PeerStatus struct and error
func ExecPeerStatus() (structs.PeerStatus, error) {
	args := []string{"peer", "status"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.PeerStatus{}, cmdErr
	}
	peerStatus, err := structs.PeerStatusXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return peerStatus.PeerStatus, err
	}

	return peerStatus.PeerStatus, nil
}

// ExecVolumeProfileGvInfoCumulative executes "gluster volume {volume] profile info cumulative" at the local machine and
// returns VolumeInfoXML struct and error
func ExecVolumeProfileGvInfoCumulative(volumeName string) (structs.VolProfile, error) {
	args := []string{"volume", "profile", volumeName, "info", "cumulative"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolProfile{}, cmdErr
	}
	volumeProfile, err := structs.VolumeProfileGvInfoCumulativeXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeProfile.VolProfile, err
	}
	return volumeProfile.VolProfile, nil
}

// ExecVolumeStatusAllDetail executes "gluster volume status all detail" at the local machine
// returns VolumeStatusXML struct and error
func ExecVolumeStatusAllDetail() (structs.VolumeStatusXML, error) {
	args := []string{"volume", "status", "all", "detail"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolumeStatusXML{}, cmdErr
	}
	volumeStatus, err := structs.VolumeStatusAllDetailXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeStatus, err
	}
	return volumeStatus, nil
}

// ExecVolumeHealInfo executes volume heal info on host system and processes input
// returns (int) number of unsynced files
func ExecVolumeHealInfo(volumeName string) (int, error) {
	args := []string{"volume", "heal", volumeName, "info"}
	entriesOutOfSync := 0
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return -1, cmdErr
	}
	healInfo, err := structs.VolumeHealInfoXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Error(err)
		return -1, err
	}

	for _, brick := range healInfo.HealInfo.Bricks.Brick {
		var count int
		var err error
		count, err = strconv.Atoi(brick.NumberOfEntries)
		if err != nil {
			log.Error(err)
			return -1, err
		}
		entriesOutOfSync += count
	}
	return entriesOutOfSync, nil
}

// ExecVolumeQuotaList executes volume quota list on host system and processes input
// returns QuotaList structs and errors
func ExecVolumeQuotaList(volumeName string) (structs.VolumeQuotaXML, error) {
	args := []string{"volume", "quota", volumeName, "list"}
	bytesBuffer, cmdErr := execGlusterCommand(args...)
	if cmdErr != nil {
		return structs.VolumeQuotaXML{}, cmdErr
	}
	volumeQuota, err := structs.VolumeQuotaListXMLUnmarshall(bytesBuffer)
	if err != nil {
		log.Errorf("Something went wrong while unmarshalling xml: %v", err)
		return volumeQuota, err
	}
	return volumeQuota, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

type opts struct {
	Flakes      int
	Fails       int
	Prefix      string
	S3Bucket    string
	DirBasename string
	BuildID     string
	Preserve    bool
	BuildURL    string
	NoCompile   bool
	TestBinary  string
}

func logError(f string, args ...interface{}) {
	s := fmt.Sprintf(f, args...)
	if s[len(s)-1] != '\n' {
		s += "\n"
	}
	fmt.Fprintf(os.Stderr, "%s", s)
}

func reportTestOutcome(outcome string, test string, where string) {
	fmt.Printf("%s: %s", outcome, test)
	if where != "" {
		fmt.Printf(" %s", where)
	}
	fmt.Printf("\n")
}

type runner struct {
	opts   opts
	flakes int
	fails  int
	tests  []string
}

func convertPrefix(p string) string {
	s := fmt.Sprintf("%c", os.PathSeparator)
	return strings.ReplaceAll(p, s, "_")
}

func (r *runner) parseArgs() (err error) {
	flag.IntVar(&r.opts.Flakes, "flakes", 3, "number of allowed flakes")
	flag.IntVar(&r.opts.Fails, "fails", -1, "number of fails allowed before quitting")
	var prfx string
	flag.StringVar(&prfx, "prefix", "", "test set prefix")
	flag.StringVar(&r.opts.S3Bucket, "s3bucket", "", "AWS S3 bucket to write failures to")
	flag.StringVar(&r.opts.BuildID, "build", "", "build ID of the current build")
	flag.BoolVar(&r.opts.Preserve, "preserve", false, "preserve test binary after done")
	flag.StringVar(&r.opts.BuildURL, "build-url", "", "URL for this build (in CI mainly)")
	flag.BoolVar(&r.opts.NoCompile, "no-compile", false, "specify flag if you've pre-compiled the test")
	flag.StringVar(&r.opts.TestBinary, "test-binary", "", "specify the test binary to run")
	flag.Parse()
	r.opts.Prefix = convertPrefix(prfx)
	var d string
	d, err = os.Getwd()
	if err != nil {
		return err
	}
	r.opts.DirBasename = filepath.Base(d)
	return nil
}

func (r *runner) compile() error {
	if r.opts.NoCompile {
		return nil
	}
	fmt.Printf("CMPL: %s\n", r.testerName())
	cmd := exec.Command("go", "test", "-c")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func filter(v []string) []string {
	var ret []string
	for _, s := range v {
		if s != "" {
			ret = append(ret, s)
		}
	}
	return ret
}

func (r *runner) testerName() string {
	if r.opts.TestBinary != "" {
		return r.opts.TestBinary
	}
	return fmt.Sprintf(".%c%s.test", os.PathSeparator, r.opts.DirBasename)
}

func (r *runner) listTests() error {
	cmd := exec.Command(r.testerName(), "-test.list", ".")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return err
	}
	r.tests = filter(strings.Split(out.String(), "\n"))
	return nil
}

func (r *runner) flushTestLogs(test string, log bytes.Buffer) (string, error) {
	buildID := strings.ReplaceAll(r.opts.BuildID, "-", "_")
	logName := fmt.Sprintf("citogo-%s-%s-%s", buildID, r.opts.Prefix, test)
	if r.opts.S3Bucket != "" {
		return r.flushLogsToS3(logName, log)
	}
	return r.flushTestLogsToTemp(logName, log)
}

func (r *runner) flushLogsToS3(logName string, log bytes.Buffer) (string, error) {
	return s3put(&log, r.opts.S3Bucket, logName)
}

func (r *runner) flushTestLogsToTemp(logName string, log bytes.Buffer) (string, error) {
	tmpfile, err := ioutil.TempFile("", fmt.Sprintf("%s-", logName))
	if err != nil {
		return "", err
	}
	_, err = tmpfile.Write(log.Bytes())
	if err != nil {
		return "", err
	}
	err = tmpfile.Close()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("see log: %s", tmpfile.Name()), nil
}

func (r *runner) reportFlake(test string, logs string) {
	hook := os.Getenv("CITOGO_FLAKE_WEBHOOK")
	if hook == "" {
		return
	}
	hook += url.QueryEscape(fmt.Sprintf("❄ _client_ %s %s *%s* %s [%s]", r.opts.BuildID, r.opts.Prefix, test, logs, r.opts.BuildURL))
	_, err := http.Get(hook)
	if err != nil {
		logError("error reporting flake: %s", err.Error())
	}
}

func (r *runner) runTest(test string) error {
	canRerun := r.flakes < r.opts.Flakes
	logs, err := r.runTestOnce(test, canRerun, false)
	if err == errTestFailed && canRerun {
		_, err = r.runTestOnce(test, false, true)
		if err == nil {
			r.reportFlake(test, logs)
			r.flakes++
		}
	}
	return err
}

var errTestFailed = errors.New("test failed")

func (r *runner) runTestOnce(test string, canRerun bool, isRerun bool) (string, error) {
	cmd := exec.Command(r.testerName(), "-test.run", "^"+test+"$")
	var combined bytes.Buffer
	if isRerun {
		cmd.Env = append(os.Environ(), "CITOGO_FLAKE_RERUN=1")
	}
	cmd.Stdout = &combined
	cmd.Stderr = &combined
	err := cmd.Run()
	if err != nil {
		err = errTestFailed
	}
	var where string
	var status string
	if err != nil {
		var flushErr error
		where, flushErr = r.flushTestLogs(test, combined)
		if flushErr != nil {
			return "", flushErr
		}
		if canRerun {
			status = "FLK?"
		} else {
			status = "FAIL"
		}
	} else {
		status = "PASS"
	}
	reportTestOutcome(status, test, where)
	return where, err
}

func (r *runner) runTestFixError(t string) error {
	err := r.runTest(t)
	if err == nil {
		return nil
	}
	if err != errTestFailed {
		return err
	}
	r.fails++
	if r.opts.Fails < 0 {
		// We have an infinite fail budget, so keep plowing through
		// failed tests. This test run is still going to fail.
		return nil
	}
	if r.opts.Fails >= r.fails {
		// We've failed less than our budget, so we can still keep going.
		// This test run is still going to fail.
		return nil
	}
	// We ate up our fail budget.
	return err
}

func (r *runner) runTests() error {
	for _, f := range r.tests {
		err := r.runTestFixError(f)
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *runner) cleanup() {
	if r.opts.Preserve || r.opts.NoCompile {
		return
	}
	n := r.testerName()
	err := os.Remove(n)
	if err != nil {
		logError("could not remove %s: %s", n, err.Error())
	}
}

func (r *runner) debugStartup() {
	dir, _ := os.Getwd()
	fmt.Printf("WDIR: %s\n", dir)
}

func (r *runner) run() error {
	start := time.Now()
	err := r.parseArgs()
	if err != nil {
		return err
	}
	r.debugStartup()
	err = r.compile()
	if err != nil {
		return err
	}
	err = r.listTests()
	if err != nil {
		return err
	}
	err = r.runTests()
	r.cleanup()
	end := time.Now()
	diff := end.Sub(start)
	fmt.Printf("DONE: in %s\n", diff)
	if err != nil {
		return err
	}
	if r.fails > 0 {
		return fmt.Errorf("RED!: %d total tests failed", r.fails)
	}
	return nil
}

func main2() error {
	runner := runner{}
	return runner.run()
}

func main() {
	err := main2()
	if err != nil {
		logError(err.Error())
		fmt.Printf("EXIT: 2\n")
		os.Exit(2)
	}
	fmt.Printf("EXIT: 0\n")
	os.Exit(0)
}
<commit_msg>citogo: timeout after 60s on each individual test (#22386)<commit_after>package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

type opts struct {
	Flakes      int
	Fails       int
	Prefix      string
	S3Bucket    string
	DirBasename string
	BuildID     string
	Preserve    bool
	BuildURL    string
	NoCompile   bool
	TestBinary  string
	Timeout     string
}

func logError(f string, args ...interface{}) {
	s := fmt.Sprintf(f, args...)
	if s[len(s)-1] != '\n' {
		s += "\n"
	}
	fmt.Fprintf(os.Stderr, "%s", s)
}

func reportTestOutcome(outcome string, test string, where string) {
	fmt.Printf("%s: %s", outcome, test)
	if where != "" {
		fmt.Printf(" %s", where)
	}
	fmt.Printf("\n")
}

type runner struct {
	opts   opts
	flakes int
	fails  int
	tests  []string
}

func convertPrefix(p string) string {
	s := fmt.Sprintf("%c", os.PathSeparator)
	return strings.ReplaceAll(p, s, "_")
}

func (r *runner) parseArgs() (err error) {
	flag.IntVar(&r.opts.Flakes, "flakes", 3, "number of allowed flakes")
	flag.IntVar(&r.opts.Fails, "fails", -1, "number of fails allowed before quitting")
	var prfx string
	flag.StringVar(&prfx, "prefix", "", "test set prefix")
	flag.StringVar(&r.opts.S3Bucket, "s3bucket", "", "AWS S3 bucket to write failures to")
	flag.StringVar(&r.opts.BuildID, "build", "", "build ID of the current build")
	flag.BoolVar(&r.opts.Preserve, "preserve", false, "preserve test binary after done")
	flag.StringVar(&r.opts.BuildURL, "build-url", "", "URL for this build (in CI mainly)")
	flag.BoolVar(&r.opts.NoCompile, "no-compile", false, "specify flag if you've pre-compiled the test")
	flag.StringVar(&r.opts.TestBinary, "test-binary", "", "specify the test binary to run")
	flag.StringVar(&r.opts.Timeout, "timeout", "60s", "timeout (in seconds) for any one individual test")
	flag.Parse()
	r.opts.Prefix = convertPrefix(prfx)
	var d string
	d, err = os.Getwd()
	if err != nil {
		return err
	}
	r.opts.DirBasename = filepath.Base(d)
	return nil
}

func (r *runner) compile() error {
	if r.opts.NoCompile {
		return nil
	}
	fmt.Printf("CMPL: %s\n", r.testerName())
	cmd := exec.Command("go", "test", "-c")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func filter(v []string) []string {
	var ret []string
	for _, s := range v {
		if s != "" {
			ret = append(ret, s)
		}
	}
	return ret
}

func (r *runner) testerName() string {
	if r.opts.TestBinary != "" {
		return r.opts.TestBinary
	}
	return fmt.Sprintf(".%c%s.test", os.PathSeparator, r.opts.DirBasename)
}

func (r *runner) listTests() error {
	cmd := exec.Command(r.testerName(), "-test.list", ".")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return err
	}
	r.tests = filter(strings.Split(out.String(), "\n"))
	return nil
}

func (r *runner) flushTestLogs(test string, log bytes.Buffer) (string, error) {
	buildID := strings.ReplaceAll(r.opts.BuildID, "-", "_")
	logName := fmt.Sprintf("citogo-%s-%s-%s", buildID, r.opts.Prefix, test)
	if r.opts.S3Bucket != "" {
		return r.flushLogsToS3(logName, log)
	}
	return r.flushTestLogsToTemp(logName, log)
}

func (r *runner) flushLogsToS3(logName string, log bytes.Buffer) (string, error) {
	return s3put(&log, r.opts.S3Bucket, logName)
}

func (r *runner) flushTestLogsToTemp(logName string, log bytes.Buffer) (string, error) {
	tmpfile, err := ioutil.TempFile("", fmt.Sprintf("%s-", logName))
	if err != nil {
		return "", err
	}
	_, err = tmpfile.Write(log.Bytes())
	if err != nil {
		return "", err
	}
	err = tmpfile.Close()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("see log: %s", tmpfile.Name()), nil
}

func (r *runner) reportFlake(test string, logs string) {
	hook := os.Getenv("CITOGO_FLAKE_WEBHOOK")
	if hook == "" {
		return
	}
	hook += url.QueryEscape(fmt.Sprintf("❄ _client_ %s %s *%s* %s [%s]", r.opts.BuildID, r.opts.Prefix, test, logs, r.opts.BuildURL))
	_, err := http.Get(hook)
	if err != nil {
		logError("error reporting flake: %s", err.Error())
	}
}

func (r *runner) runTest(test string) error {
	canRerun := r.flakes < r.opts.Flakes
	logs, err := r.runTestOnce(test, canRerun, false)
	if err == errTestFailed && canRerun {
		_, err = r.runTestOnce(test, false, true)
		if err == nil {
			r.reportFlake(test, logs)
			r.flakes++
		}
	}
	return err
}

var errTestFailed = errors.New("test failed")

func (r *runner) runTestOnce(test string, canRerun bool, isRerun bool) (string, error) {
	cmd := exec.Command(r.testerName(), "-test.run", "^"+test+"$", "-test.timeout", r.opts.Timeout)
	var combined bytes.Buffer
	if isRerun {
		cmd.Env = append(os.Environ(), "CITOGO_FLAKE_RERUN=1")
	}
	cmd.Stdout = &combined
	cmd.Stderr = &combined
	err := cmd.Run()
	if err != nil {
		err = errTestFailed
	}
	var where string
	var status string
	if err != nil {
		var flushErr error
		where, flushErr = r.flushTestLogs(test, combined)
		if flushErr != nil {
			return "", flushErr
		}
		if canRerun {
			status = "FLK?"
		} else {
			status = "FAIL"
		}
	} else {
		status = "PASS"
	}
	reportTestOutcome(status, test, where)
	return where, err
}

func (r *runner) runTestFixError(t string) error {
	err := r.runTest(t)
	if err == nil {
		return nil
	}
	if err != errTestFailed {
		return err
	}
	r.fails++
	if r.opts.Fails < 0 {
		// We have an infinite fail budget, so keep plowing through
		// failed tests. This test run is still going to fail.
		return nil
	}
	if r.opts.Fails >= r.fails {
		// We've failed less than our budget, so we can still keep going.
		// This test run is still going to fail.
		return nil
	}
	// We ate up our fail budget.
	return err
}

func (r *runner) runTests() error {
	for _, f := range r.tests {
		err := r.runTestFixError(f)
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *runner) cleanup() {
	if r.opts.Preserve || r.opts.NoCompile {
		return
	}
	n := r.testerName()
	err := os.Remove(n)
	if err != nil {
		logError("could not remove %s: %s", n, err.Error())
	}
}

func (r *runner) debugStartup() {
	dir, _ := os.Getwd()
	fmt.Printf("WDIR: %s\n", dir)
}

func (r *runner) run() error {
	start := time.Now()
	err := r.parseArgs()
	if err != nil {
		return err
	}
	r.debugStartup()
	err = r.compile()
	if err != nil {
		return err
	}
	err = r.listTests()
	if err != nil {
		return err
	}
	err = r.runTests()
	r.cleanup()
	end := time.Now()
	diff := end.Sub(start)
	fmt.Printf("DONE: in %s\n", diff)
	if err != nil {
		return err
	}
	if r.fails > 0 {
		return fmt.Errorf("RED!: %d total tests failed", r.fails)
	}
	return nil
}

func main2() error {
	runner := runner{}
	return runner.run()
}

func main() {
	err := main2()
	if err != nil {
		logError(err.Error())
		fmt.Printf("EXIT: 2\n")
		os.Exit(2)
	}
	fmt.Printf("EXIT: 0\n")
	os.Exit(0)
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package build

import (
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/issue9/sliceutil"
	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/ianaindex"

	"github.com/caixw/apidoc/v7/core"
	"github.com/caixw/apidoc/v7/internal/lang"
	"github.com/caixw/apidoc/v7/internal/locale"
)

// Input 指定输入内容的相关信息。
type Input struct {
	Lang      string   `yaml:"lang"`                // 输入的目标语言，值为 internal/lang 中的 Language.ID
	Dir       core.URI `yaml:"dir"`                 // 源代码目录
	Exts      []string `yaml:"exts,omitempty"`      // 需要扫描的文件扩展名，为空则表示采用默认规则。
	Recursive bool     `yaml:"recursive,omitempty"` // 是否查找 Dir 的子目录
	Encoding  string   `yaml:"encoding,omitempty"`  // 源文件的编码，默认为 UTF-8
	Ignores   []string `yaml:"ignores,omitempty"`   // 忽略的文件或目录，比如 node_modules 等可在此指定

	paths     []core.URI        // 根据 Dir、Exts、Ignores 和 Recursive 生成
	encoding  encoding.Encoding // 根据 Encoding 生成
	sanitized bool
}

func (o *Input) sanitize() error {
	if o.sanitized {
		return nil
	}

	if len(o.Dir) == 0 {
		return core.NewError(locale.ErrIsEmpty, "dir").WithField("dir")
	}

	exists, err := o.Dir.Exists()
	if err != nil {
		return core.WithError(err).WithField("dir")
	}
	if !exists {
		return core.NewError(locale.ErrDirNotExists).WithField("dir")
	}

	if scheme, _ := o.Dir.Parse(); scheme != "" && scheme != core.SchemeFile {
		return core.NewError(locale.ErrInvalidURIScheme, scheme).WithField("dir")
	}

	if len(o.Lang) == 0 {
		return core.NewError(locale.ErrIsEmpty, "lang").WithField("lang")
	}

	language := lang.Get(o.Lang)
	if language == nil {
		return core.NewError(locale.ErrInvalidValue).WithField("lang")
	}

	if len(o.Exts) > 0 {
		exts := make([]string, 0, len(o.Exts))
		for _, ext := range o.Exts {
			if len(ext) == 0 {
				continue
			}

			if ext[0] != '.' {
				ext = "." + ext
			}
			exts = append(exts, ext)
		}
		o.Exts = exts
	} else {
		o.Exts = language.Exts
	}

	if err = o.recursivePath(); err != nil {
		return err
	}

	if o.Encoding != "" {
		o.encoding, err = ianaindex.IANA.Encoding(o.Encoding)
		if err != nil {
			return core.WithError(err).WithField("encoding")
		}
	}

	o.sanitized = true
	return nil
}

// 按 Input 中的规则查找所有符合条件的文件列表并保存至 Input.paths
func (o *Input) recursivePath() error {
	local, err := o.Dir.File()
	if err != nil {
		return core.WithError(err).WithField("dir")
	}
	local = filepath.Clean(local)

	walk := func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() && !o.Recursive && path != local {
			return filepath.SkipDir
		}

		match, err := o.isIgnore(local, path)
		if err != nil {
			return err
		}
		if !match {
			o.paths = append(o.paths, core.FileURI(path))
		}
		return nil
	}

	if err := filepath.Walk(local, walk); err != nil {
		return core.WithError(err).WithField("dir")
	}

	if len(o.paths) == 0 {
		return core.NewError(locale.ErrNoFiles).WithField("dir")
	}
	return nil
}

func (o *Input) isIgnore(root, path string) (bool, error) {
	ext := filepath.Ext(path)
	if sliceutil.Count(o.Exts, func(i int) bool { return o.Exts[i] == ext }) == 0 {
		return true, nil
	}

	path = strings.TrimPrefix(path, root)
	if path == "" {
		return false, nil
	}

	if path[0] == '/' || path[0] == os.PathSeparator {
		path = path[1:]
	}

	for _, ignore := range o.Ignores {
		match, err := filepath.Match(ignore, path)
		if err != nil {
			return false, err
		}
		if match {
			return true, nil
		}
	}

	return false, nil
}

// ParseInputs 分析 opt 中所指定的内容并输出到 blocks
//
// 分析后的内容推送至 blocks 中。
func ParseInputs(blocks chan core.Block, h *core.MessageHandler, opt ...*Input) {
	wg := &sync.WaitGroup{}
	for _, i := range opt {
		for _, path := range i.paths {
			wg.Add(1)
			go func(path core.URI, i *Input) {
				i.ParseFile(blocks, h, path)
				wg.Done()
			}(path, i)
		}
	}
	wg.Wait()
}

// ParseFile 分析 uri 指向的文件并输出到 blocks
func (o *Input) ParseFile(blocks chan core.Block, h *core.MessageHandler, uri core.URI) {
	data, err := uri.ReadAll(o.encoding)
	if err != nil {
		h.Error((core.Location{URI: uri}).WithError(err))
		return
	}

	lang.Parse(h, o.Lang, core.Block{
		Data:     data,
		Location: core.Location{URI: uri},
	}, blocks)
}
<commit_msg>fix(build): 修正 windows 下对 Input.Ignores 的处理错误<commit_after>// SPDX-License-Identifier: MIT

package build

import (
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/issue9/sliceutil"
	"golang.org/x/text/encoding"
	"golang.org/x/text/encoding/ianaindex"

	"github.com/caixw/apidoc/v7/core"
	"github.com/caixw/apidoc/v7/internal/lang"
	"github.com/caixw/apidoc/v7/internal/locale"
)

// Input 指定输入内容的相关信息。
type Input struct {
	Lang      string   `yaml:"lang"`                // 输入的目标语言，值为 internal/lang 中的 Language.ID
	Dir       core.URI `yaml:"dir"`                 // 源代码目录
	Exts      []string `yaml:"exts,omitempty"`      // 需要扫描的文件扩展名，为空则表示采用默认规则。
	Recursive bool     `yaml:"recursive,omitempty"` // 是否查找 Dir 的子目录
	Encoding  string   `yaml:"encoding,omitempty"`  // 源文件的编码，默认为 UTF-8
	Ignores   []string `yaml:"ignores,omitempty"`   // 忽略的文件或目录，比如 node_modules 等可在此指定

	paths     []core.URI        // 根据 Dir、Exts、Ignores 和 Recursive 生成
	encoding  encoding.Encoding // 根据 Encoding 生成
	sanitized bool
}

func (o *Input) sanitize() error {
	if o.sanitized {
		return nil
	}

	if len(o.Dir) == 0 {
		return core.NewError(locale.ErrIsEmpty, "dir").WithField("dir")
	}

	exists, err := o.Dir.Exists()
	if err != nil {
		return core.WithError(err).WithField("dir")
	}
	if !exists {
		return core.NewError(locale.ErrDirNotExists).WithField("dir")
	}

	if scheme, _ := o.Dir.Parse(); scheme != "" && scheme != core.SchemeFile {
		return core.NewError(locale.ErrInvalidURIScheme, scheme).WithField("dir")
	}

	if len(o.Lang) == 0 {
		return core.NewError(locale.ErrIsEmpty, "lang").WithField("lang")
	}

	language := lang.Get(o.Lang)
	if language == nil {
		return core.NewError(locale.ErrInvalidValue).WithField("lang")
	}

	if len(o.Exts) > 0 {
		exts := make([]string, 0, len(o.Exts))
		for _, ext := range o.Exts {
			if len(ext) == 0 {
				continue
			}

			if ext[0] != '.' {
				ext = "." + ext
			}
			exts = append(exts, ext)
		}
		o.Exts = exts
	} else {
		o.Exts = language.Exts
	}

	if err = o.recursivePath(); err != nil {
		return err
	}

	if o.Encoding != "" {
		o.encoding, err = ianaindex.IANA.Encoding(o.Encoding)
		if err != nil {
			return core.WithError(err).WithField("encoding")
		}
	}

	o.sanitized = true
	return nil
}

// 按 Input 中的规则查找所有符合条件的文件列表并保存至 Input.paths
func (o *Input) recursivePath() error {
	local, err := o.Dir.File()
	if err != nil {
		return core.WithError(err).WithField("dir")
	}
	local = filepath.Clean(local)

	for i, pattern := range o.Ignores {
		o.Ignores[i] = filepath.FromSlash(pattern)
	}

	walk := func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() && !o.Recursive && path != local {
			return filepath.SkipDir
		}

		ignore, err := o.isIgnore(local, path)
		if err != nil {
			return err
		}
		if !ignore {
			o.paths = append(o.paths, core.FileURI(path))
		}
		return nil
	}

	if err := filepath.Walk(local, walk); err != nil {
		return core.WithError(err).WithField("dir")
	}

	if len(o.paths) == 0 {
		return core.NewError(locale.ErrNoFiles).WithField("dir")
	}
	return nil
}

func (o *Input) isIgnore(root, path string) (bool, error) {
	ext := filepath.Ext(path)
	if sliceutil.Count(o.Exts, func(i int) bool { return o.Exts[i] == ext }) == 0 {
		return true, nil
	}

	path = strings.TrimPrefix(filepath.FromSlash(path), filepath.FromSlash(root))
	if path == "" {
		return false, nil
	}

	if path[0] == '/' || path[0] == os.PathSeparator {
		path = path[1:]
	}

	for _, ignore := range o.Ignores {
		match, err := filepath.Match(ignore, path)
		if err != nil {
			return false, err
		}
		if match {
			return true, nil
		}
	}

	return false, nil
}

// ParseInputs 分析 opt 中所指定的内容并输出到 blocks
//
// 分析后的内容推送至 blocks 中。
func ParseInputs(blocks chan core.Block, h *core.MessageHandler, opt ...*Input) {
	wg := &sync.WaitGroup{}
	for _, i := range opt {
		for _, path := range i.paths {
			wg.Add(1)
			go func(path core.URI, i *Input) {
				i.ParseFile(blocks, h, path)
				wg.Done()
			}(path, i)
		}
	}
	wg.Wait()
}

// ParseFile 分析 uri 指向的文件并输出到 blocks
func (o *Input) ParseFile(blocks chan core.Block, h *core.MessageHandler, uri core.URI) {
	data, err := uri.ReadAll(o.encoding)
	if err != nil {
		h.Error((core.Location{URI: uri}).WithError(err))
		return
	}

	lang.Parse(h, o.Lang, core.Block{
		Data:     data,
		Location: core.Location{URI: uri},
	}, blocks)
}
<|endoftext|>


<commit_before>package builder

import (
	log "github.com/Sirupsen/logrus"
	"github.com/appc/spec/schema/types"
	"github.com/blablacar/cnt/spec"
	"github.com/blablacar/cnt/utils"
	"github.com/ghodss/yaml"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

const EXEC_FILES = `
execute_files() {
  fdir=$1
  [ -d "$fdir" ] || return 0

  for file in $fdir/*; do
    [ -e "$file" ] && {
     	[ -x "$file" ] || /cnt/bin/busybox chmod +x "$file"
     	echo -e "\e[1m\e[32mRunning script -> $file\e[0m"
     	$file
    }
  done
}`

const BUILD_SCRIPT = `#!/cnt/bin/busybox sh
set -x
set -e
export TARGET=$( dirname $0 )
export ROOTFS=%%ROOTFS%%
export TERM=xterm

` + EXEC_FILES + `

execute_files "$ROOTFS/cnt/runlevels/inherit-build-early"
execute_files "$TARGET/runlevels/build"
`

const BUILD_SCRIPT_LATE = `#!/cnt/bin/busybox sh
set -x
set -e
export TARGET=$( dirname $0 )
export ROOTFS=%%ROOTFS%%
export TERM=xterm

` + EXEC_FILES + `

execute_files "$TARGET/runlevels/build-late"
execute_files "$ROOTFS/cnt/runlevels/inherit-build-late"
`

const PRESTART = `#!/cnt/bin/busybox sh
set -x
set -e

BASEDIR=${0%/*}
CNT_PATH=/cnt

` + EXEC_FILES + `

execute_files ${CNT_PATH}/runlevels/prestart-early

if [ -d ${CNT_PATH}/attributes ]; then
	echo "$CONFD_OVERRIDE"
    ${BASEDIR}/attributes-merger -i ${CNT_PATH}/attributes -e CONFD_OVERRIDE
    export CONFD_DATA=$(cat attributes.json)
fi
${BASEDIR}/confd -onetime -config-file=${CNT_PATH}/prestart/confd.toml

execute_files ${CNT_PATH}/runlevels/prestart-late
`
const PATH_BIN = "/bin"
const PATH_TESTS = "/tests"
const PATH_INSTALLED = "/installed"
const PATH_MANIFEST = "/manifest"
const PATH_IMAGE_ACI = "/image.aci"
const PATH_IMAGE_ACI_ZIP = "/image-zip.aci"
const PATH_ROOTFS = "/rootfs"
const PATH_TARGET = "/target"
const PATH_CNT = "/cnt"
const PATH_CNT_MANIFEST = "/cnt-manifest.yml"
const PATH_RUNLEVELS = "/runlevels"
const PATH_PRESTART_EARLY = "/prestart-early"
const PATH_PRESTART_LATE = "/prestart-late"
const PATH_INHERIT_BUILD_LATE = "/inherit-build-late"
const PATH_INHERIT_BUILD_EARLY = "/inherit-build-early"
const PATH_ATTRIBUTES = "/attributes"
const PATH_FILES = "/files"
const PATH_CONFD = "/confd"
const PATH_BUILD_LATE = "/build-late"
const PATH_BUILD_SETUP = "/build-setup"
const PATH_BUILD = "/build"
const PATH_CONFDOTD = "/conf.d"
const PATH_TEMPLATES = "/templates"

type Img struct {
	path     string
	target   string
	rootfs   string
	PodName  *spec.ACFullname
	manifest spec.AciManifest
	args     BuildArgs
}

func Version(nameAndVersion string) string {
	split := strings.Split(nameAndVersion, ":")
	if len(split) == 1 {
		return ""
	}
	return split[1]
}

func ShortNameId(name types.ACIdentifier) string {
	return strings.Split(string(name), "/")[1]
}

func ShortName(nameAndVersion string) string {
	return strings.Split(Name(nameAndVersion), "/")[1]
}

func Name(nameAndVersion string) string {
	return strings.Split(nameAndVersion, ":")[0]
}

////////////////////////////////////////////

func NewAciWithManifest(path string, args BuildArgs, manifest spec.AciManifest, checked *chan bool) (*Img, error) {
	log.Debug("New aci", path, args, manifest)
	cnt, err := PrepAci(path, args)
	if err != nil {
		return nil, err
	}
	cnt.manifest = manifest

	go cnt.checkLatestVersions(checked)

	return cnt, nil
}

func NewAci(path string, args BuildArgs) (*Img, error) {
	manifest, err := readManifest(path + PATH_CNT_MANIFEST)
	if err != nil {
		log.Debug(path, PATH_CNT_MANIFEST+" does not exists")
		return nil, err
	}
	return NewAciWithManifest(path, args, *manifest, nil)
}

func PrepAci(aciPath string, args BuildArgs) (*Img, error) {
	cnt := new(Img)
	cnt.args = args

	if fullPath, err := filepath.Abs(aciPath); err != nil {
		panic("Cannot get fullpath of project" + err.Error())
	} else {
		cnt.path = fullPath
		cnt.target = cnt.path + PATH_TARGET
		if args.TargetPath != "" {
			currentAbsDir, err := filepath.Abs(args.TargetPath + "/" + cnt.manifest.NameAndVersion.ShortName())
			if err != nil {
				panic("invalid target path")
			}
			cnt.target = currentAbsDir
		}
		cnt.rootfs = cnt.target + PATH_ROOTFS
	}
	return cnt, nil
}

//////////////////////////////////////////////////////////////////

func readManifest(manifestPath string) (*spec.AciManifest, error) {
	manifest := spec.AciManifest{Aci: spec.AciDefinition{}}

	source, err := ioutil.ReadFile(manifestPath)
	if err != nil {
		return nil, err
	}
	err = yaml.Unmarshal([]byte(source), &manifest)
	if err != nil {
		panic(err)
	}

	return &manifest, nil
}

func (cnt *Img) tarAci(zip bool) {
	target := PATH_IMAGE_ACI[1:]
	if zip {
		target = PATH_IMAGE_ACI_ZIP[1:]
	}
	dir, _ := os.Getwd()
	log.Debug("chdir to", cnt.target)
	os.Chdir(cnt.target)
	utils.Tar(zip, target, PATH_MANIFEST[1:], PATH_ROOTFS[1:])
	log.Debug("chdir to", dir)
	os.Chdir(dir)
}

func (cnt *Img) checkLatestVersions(checked *chan bool) {
	if cnt.manifest.From != "" {
		version, _ := cnt.manifest.From.LatestVersion()
		log.Debug("latest version of from : " + cnt.manifest.NameAndVersion.Name() + ":" + version)
		if version != "" && utils.Version(cnt.manifest.From.Version()).LessThan(utils.Version(version)) {
			log.Warn("---------------------------------")
			log.Warn("From has newer version : " + cnt.manifest.From.Name() + ":" + version)
			log.Warn("---------------------------------")
		}
	}
	for _, dep := range cnt.manifest.Aci.Dependencies {
		version, _ := dep.LatestVersion()
		if version != "" && utils.Version(dep.Version()).LessThan(utils.Version(version)) {
			log.Warn("---------------------------------")
			log.Warn("Newer dependency version : " + dep.Name() + ":" + version)
			log.Warn("---------------------------------")
		}
	}
	if checked != nil {
		*checked <- true
	}
}
<commit_msg>do not check latest version if no version set<commit_after>package builder

import (
	log "github.com/Sirupsen/logrus"
	"github.com/appc/spec/schema/types"
	"github.com/blablacar/cnt/spec"
	"github.com/blablacar/cnt/utils"
	"github.com/ghodss/yaml"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

const EXEC_FILES = `
execute_files() {
  fdir=$1
  [ -d "$fdir" ] || return 0

  for file in $fdir/*; do
    [ -e "$file" ] && {
     	[ -x "$file" ] || /cnt/bin/busybox chmod +x "$file"
     	echo -e "\e[1m\e[32mRunning script -> $file\e[0m"
     	$file
    }
  done
}`

const BUILD_SCRIPT = `#!/cnt/bin/busybox sh
set -x
set -e
export TARGET=$( dirname $0 )
export ROOTFS=%%ROOTFS%%
export TERM=xterm

` + EXEC_FILES + `

execute_files "$ROOTFS/cnt/runlevels/inherit-build-early"
execute_files "$TARGET/runlevels/build"
`

const BUILD_SCRIPT_LATE = `#!/cnt/bin/busybox sh
set -x
set -e
export TARGET=$( dirname $0 )
export ROOTFS=%%ROOTFS%%
export TERM=xterm

` + EXEC_FILES + `

execute_files "$TARGET/runlevels/build-late"
execute_files "$ROOTFS/cnt/runlevels/inherit-build-late"
`

const PRESTART = `#!/cnt/bin/busybox sh
set -x
set -e

BASEDIR=${0%/*}
CNT_PATH=/cnt

` + EXEC_FILES + `

execute_files ${CNT_PATH}/runlevels/prestart-early

if [ -d ${CNT_PATH}/attributes ]; then
	echo "$CONFD_OVERRIDE"
    ${BASEDIR}/attributes-merger -i ${CNT_PATH}/attributes -e CONFD_OVERRIDE
    export CONFD_DATA=$(cat attributes.json)
fi
${BASEDIR}/confd -onetime -config-file=${CNT_PATH}/prestart/confd.toml

execute_files ${CNT_PATH}/runlevels/prestart-late
`
const PATH_BIN = "/bin"
const PATH_TESTS = "/tests"
const PATH_INSTALLED = "/installed"
const PATH_MANIFEST = "/manifest"
const PATH_IMAGE_ACI = "/image.aci"
const PATH_IMAGE_ACI_ZIP = "/image-zip.aci"
const PATH_ROOTFS = "/rootfs"
const PATH_TARGET = "/target"
const PATH_CNT = "/cnt"
const PATH_CNT_MANIFEST = "/cnt-manifest.yml"
const PATH_RUNLEVELS = "/runlevels"
const PATH_PRESTART_EARLY = "/prestart-early"
const PATH_PRESTART_LATE = "/prestart-late"
const PATH_INHERIT_BUILD_LATE = "/inherit-build-late"
const PATH_INHERIT_BUILD_EARLY = "/inherit-build-early"
const PATH_ATTRIBUTES = "/attributes"
const PATH_FILES = "/files"
const PATH_CONFD = "/confd"
const PATH_BUILD_LATE = "/build-late"
const PATH_BUILD_SETUP = "/build-setup"
const PATH_BUILD = "/build"
const PATH_CONFDOTD = "/conf.d"
const PATH_TEMPLATES = "/templates"

type Img struct {
	path     string
	target   string
	rootfs   string
	PodName  *spec.ACFullname
	manifest spec.AciManifest
	args     BuildArgs
}

func Version(nameAndVersion string) string {
	split := strings.Split(nameAndVersion, ":")
	if len(split) == 1 {
		return ""
	}
	return split[1]
}

func ShortNameId(name types.ACIdentifier) string {
	return strings.Split(string(name), "/")[1]
}

func ShortName(nameAndVersion string) string {
	return strings.Split(Name(nameAndVersion), "/")[1]
}

func Name(nameAndVersion string) string {
	return strings.Split(nameAndVersion, ":")[0]
}

////////////////////////////////////////////

func NewAciWithManifest(path string, args BuildArgs, manifest spec.AciManifest, checked *chan bool) (*Img, error) {
	log.Debug("New aci", path, args, manifest)
	cnt, err := PrepAci(path, args)
	if err != nil {
		return nil, err
	}
	cnt.manifest = manifest

	go cnt.checkLatestVersions(checked)

	return cnt, nil
}

func NewAci(path string, args BuildArgs) (*Img, error) {
	manifest, err := readManifest(path + PATH_CNT_MANIFEST)
	if err != nil {
		log.Debug(path, PATH_CNT_MANIFEST+" does not exists")
		return nil, err
	}
	return NewAciWithManifest(path, args, *manifest, nil)
}

func PrepAci(aciPath string, args BuildArgs) (*Img, error) {
	cnt := new(Img)
	cnt.args = args

	if fullPath, err := filepath.Abs(aciPath); err != nil {
		panic("Cannot get fullpath of project" + err.Error())
	} else {
		cnt.path = fullPath
		cnt.target = cnt.path + PATH_TARGET
		if args.TargetPath != "" {
			currentAbsDir, err := filepath.Abs(args.TargetPath + "/" + cnt.manifest.NameAndVersion.ShortName())
			if err != nil {
				panic("invalid target path")
			}
			cnt.target = currentAbsDir
		}
		cnt.rootfs = cnt.target + PATH_ROOTFS
	}
	return cnt, nil
}

//////////////////////////////////////////////////////////////////

func readManifest(manifestPath string) (*spec.AciManifest, error) {
	manifest := spec.AciManifest{Aci: spec.AciDefinition{}}

	source, err := ioutil.ReadFile(manifestPath)
	if err != nil {
		return nil, err
	}
	err = yaml.Unmarshal([]byte(source), &manifest)
	if err != nil {
		panic(err)
	}

	return &manifest, nil
}

func (cnt *Img) tarAci(zip bool) {
	target := PATH_IMAGE_ACI[1:]
	if zip {
		target = PATH_IMAGE_ACI_ZIP[1:]
	}
	dir, _ := os.Getwd()
	log.Debug("chdir to", cnt.target)
	os.Chdir(cnt.target)
	utils.Tar(zip, target, PATH_MANIFEST[1:], PATH_ROOTFS[1:])
	log.Debug("chdir to", dir)
	os.Chdir(dir)
}

func (cnt *Img) checkLatestVersions(checked *chan bool) {
	if cnt.manifest.From != "" && cnt.manifest.From.Version() != "" {
		version, _ := cnt.manifest.From.LatestVersion()
		log.Debug("latest version of from : " + cnt.manifest.NameAndVersion.Name() + ":" + version)
		if version != "" && utils.Version(cnt.manifest.From.Version()).LessThan(utils.Version(version)) {
			log.Warn("---------------------------------")
			log.Warn("From has newer version : " + cnt.manifest.From.Name() + ":" + version)
			log.Warn("---------------------------------")
		}
	}
	for _, dep := range cnt.manifest.Aci.Dependencies {
		if dep.Version() == "" {
			continue
		}
		version, _ := dep.LatestVersion()
		if version != "" && utils.Version(dep.Version()).LessThan(utils.Version(version)) {
			log.Warn("---------------------------------")
			log.Warn("Newer dependency version : " + dep.Name() + ":" + version)
			log.Warn("---------------------------------")
		}
	}
	if checked != nil {
		*checked <- true
	}
}
<|endoftext|>


<commit_before>// Copyright 2011 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.

// This file implements the Check function, which drives type-checking.

package types

import (
	"go/ast"
	"go/token"
	"path"

	"code.google.com/p/go.tools/go/exact"
)

// debugging/development support
const (
	debug = true  // leave on during development
	trace = false // turn on for detailed type resolution traces
)

// exprInfo stores type and constant value for an untyped expression.
type exprInfo struct {
	isLhs bool // expression is lhs operand of a shift with delayed type-check
	typ   *Basic
	val   exact.Value // constant value; or nil (if not a constant)
}

// A checker is an instance of the type-checker.
type checker struct {
	conf *Config
	fset *token.FileSet
	pkg  *Package

	methods     map[string][]*Func     // maps type names to associated methods
	conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
	untyped     map[ast.Expr]exprInfo  // map of expressions without final type
	lhsVarsList [][]*Var               // type switch lhs variable sets, for 'declared but not used' errors
	delayed     []func()               // delayed checks that require fully setup types

	firstErr error // first error encountered
	Info           // collected type info

	objMap   map[Object]*declInfo // if set we are in the package-level declaration phase (otherwise all objects seen must be declared)
	initMap  map[Object]*declInfo // map of variables/functions with init expressions/bodies
	topScope *Scope               // current topScope for lookups
	iota     exact.Value          // current value of iota in a constant declaration; nil otherwise
	decl     *declInfo            // current package-level declaration whose init expression/body is type-checked

	// functions
	funcList []funcInfo // list of functions/methods with correct signatures and non-empty bodies
	funcSig  *Signature // signature of currently type-checked function
	hasLabel bool       // set if a function makes use of labels (only ~1% of functions)

	// debugging
	indent int // indentation for tracing
}

func newChecker(conf *Config, fset *token.FileSet, pkg *Package) *checker {
	return &checker{
		conf:        conf,
		fset:        fset,
		pkg:         pkg,
		methods:     make(map[string][]*Func),
		conversions: make(map[*ast.CallExpr]bool),
		untyped:     make(map[ast.Expr]exprInfo),
	}
}

// addDeclDep adds the dependency edge (check.decl -> to)
// if check.decl exists and to has an init expression.
func (check *checker) addDeclDep(to Object) {
	from := check.decl
	if from == nil {
		return // not in a package-level init expression
	}
	init := check.initMap[to]
	if init == nil {
		return // to does not have a package-level init expression
	}
	m := from.deps
	if m == nil {
		m = make(map[Object]*declInfo)
		from.deps = m
	}
	m[to] = init
}

func (check *checker) delay(f func()) {
	check.delayed = append(check.delayed, f)
}

func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) {
	assert(x != nil && typ != nil)
	if m := check.Types; m != nil {
		m[x] = typ
	}
	if val != nil {
		if m := check.Values; m != nil {
			m[x] = val
		}
	}
}

func (check *checker) recordBuiltinType(f ast.Expr, sig *Signature) {
	// f must be a (possibly parenthesized) identifier denoting a built-in
	// (built-ins in package unsafe always produce a constant result and
	// we don't record their signatures, so we don't see qualified idents
	// here): record the signature for f and possible children.
	for {
		check.recordTypeAndValue(f, sig, nil)
		switch p := f.(type) {
		case *ast.Ident:
			return // we're done
		case *ast.ParenExpr:
			f = p.X
		default:
			unreachable()
		}
	}
}

func (check *checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
	assert(x != nil)
	if a[0] == nil || a[1] == nil {
		return
	}
	assert(isTyped(a[0]) && isTyped(a[1]) && isBoolean(a[1]))
	if m := check.Types; m != nil {
		for {
			assert(m[x] != nil) // should have been recorded already
			pos := x.Pos()
			m[x] = NewTuple(
				NewVar(pos, check.pkg, "", a[0]),
				NewVar(pos, check.pkg, "", a[1]),
			)
			// if x is a parenthesized expression (p.X), update p.X
			p, _ := x.(*ast.ParenExpr)
			if p == nil {
				break
			}
			x = p.X
		}
	}
}

func (check *checker) recordObject(id *ast.Ident, obj Object) {
	assert(id != nil)
	if m := check.Objects; m != nil {
		m[id] = obj
	}
}

func (check *checker) recordImplicit(node ast.Node, obj Object) {
	assert(node != nil && obj != nil)
	if m := check.Implicits; m != nil {
		m[node] = obj
	}
}

func (check *checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) {
	assert(obj != nil && (recv == nil || len(index) > 0))
	check.recordObject(x.Sel, obj)
	// TODO(gri) Should we also call recordTypeAndValue?
	if m := check.Selections; m != nil {
		m[x] = &Selection{kind, recv, obj, index, indirect}
	}
}

func (check *checker) recordScope(node ast.Node, scope *Scope) {
	assert(node != nil && scope != nil)
	if m := check.Scopes; m != nil {
		m[node] = scope
	}
}

// A bailout panic is raised to indicate early termination.
type bailout struct{}

func (check *checker) handleBailout(err *error) {
	switch p := recover().(type) {
	case nil, bailout:
		// normal return or early exit
		*err = check.firstErr
	default:
		// re-panic
		panic(p)
	}
}

func (conf *Config) check(pkgPath string, fset *token.FileSet, files []*ast.File, info *Info) (pkg *Package, err error) {
	// make sure we have a package canonicalization map
	if conf.Packages == nil {
		conf.Packages = make(map[string]*Package)
	}

	pkg = NewPackage(pkgPath, "", NewScope(Universe)) // package name is set below
	check := newChecker(conf, fset, pkg)
	defer check.handleBailout(&err)

	// we need a reasonable package path to continue
	if path.Clean(pkgPath) == "." {
		check.errorf(token.NoPos, "invalid package path provided: %q", pkgPath)
		return
	}

	// determine package name and files
	i := 0
	for _, file := range files {
		switch name := file.Name.Name; pkg.name {
		case "":
			pkg.name = name
			fallthrough
		case name:
			files[i] = file
			i++
		default:
			check.errorf(file.Package, "package %s; expected %s", name, pkg.name)
			// ignore this file
		}
	}

	// install optional info
	if info != nil {
		check.Info = *info
	}

	check.resolveFiles(files[:i])

	// perform delayed checks
	for _, f := range check.delayed {
		f()
	}
	check.delayed = nil // not needed anymore

	// remaining untyped expressions must indeed be untyped
	if debug {
		for x, info := range check.untyped {
			if isTyped(info.typ) {
				check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ)
				panic(0)
			}
		}
	}

	// notify client of any untyped types left
	// TODO(gri) Consider doing this before and
	// after function body checking for smaller
	// map size and more immediate feedback.
	if check.Types != nil || check.Values != nil {
		for x, info := range check.untyped {
			check.recordTypeAndValue(x, info.typ, info.val)
		}
	}

	// copy check.InitOrder back to incoming *info if necessary
	// (In case of early (error) bailout, this is not done, but we don't care in that case.)
	if info != nil {
		info.InitOrder = check.InitOrder
	}

	return
}
<commit_msg>go.tools/go/types: turn off internal debug mode<commit_after>// Copyright 2011 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.

// This file implements the Check function, which drives type-checking.

package types

import (
	"go/ast"
	"go/token"
	"path"

	"code.google.com/p/go.tools/go/exact"
)

// debugging/development support
const (
	debug = false // leave on during development
	trace = false // turn on for detailed type resolution traces
)

// exprInfo stores type and constant value for an untyped expression.
type exprInfo struct {
	isLhs bool // expression is lhs operand of a shift with delayed type-check
	typ   *Basic
	val   exact.Value // constant value; or nil (if not a constant)
}

// A checker is an instance of the type-checker.
type checker struct {
	conf *Config
	fset *token.FileSet
	pkg  *Package

	methods     map[string][]*Func     // maps type names to associated methods
	conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
	untyped     map[ast.Expr]exprInfo  // map of expressions without final type
	lhsVarsList [][]*Var               // type switch lhs variable sets, for 'declared but not used' errors
	delayed     []func()               // delayed checks that require fully setup types

	firstErr error // first error encountered
	Info           // collected type info

	objMap   map[Object]*declInfo // if set we are in the package-level declaration phase (otherwise all objects seen must be declared)
	initMap  map[Object]*declInfo // map of variables/functions with init expressions/bodies
	topScope *Scope               // current topScope for lookups
	iota     exact.Value          // current value of iota in a constant declaration; nil otherwise
	decl     *declInfo            // current package-level declaration whose init expression/body is type-checked

	// functions
	funcList []funcInfo // list of functions/methods with correct signatures and non-empty bodies
	funcSig  *Signature // signature of currently type-checked function
	hasLabel bool       // set if a function makes use of labels (only ~1% of functions)

	// debugging
	indent int // indentation for tracing
}

func newChecker(conf *Config, fset *token.FileSet, pkg *Package) *checker {
	return &checker{
		conf:        conf,
		fset:        fset,
		pkg:         pkg,
		methods:     make(map[string][]*Func),
		conversions: make(map[*ast.CallExpr]bool),
		untyped:     make(map[ast.Expr]exprInfo),
	}
}

// addDeclDep adds the dependency edge (check.decl -> to)
// if check.decl exists and to has an init expression.
func (check *checker) addDeclDep(to Object) {
	from := check.decl
	if from == nil {
		return // not in a package-level init expression
	}
	init := check.initMap[to]
	if init == nil {
		return // to does not have a package-level init expression
	}
	m := from.deps
	if m == nil {
		m = make(map[Object]*declInfo)
		from.deps = m
	}
	m[to] = init
}

func (check *checker) delay(f func()) {
	check.delayed = append(check.delayed, f)
}

func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) {
	assert(x != nil && typ != nil)
	if m := check.Types; m != nil {
		m[x] = typ
	}
	if val != nil {
		if m := check.Values; m != nil {
			m[x] = val
		}
	}
}

func (check *checker) recordBuiltinType(f ast.Expr, sig *Signature) {
	// f must be a (possibly parenthesized) identifier denoting a built-in
	// (built-ins in package unsafe always produce a constant result and
	// we don't record their signatures, so we don't see qualified idents
	// here): record the signature for f and possible children.
	for {
		check.recordTypeAndValue(f, sig, nil)
		switch p := f.(type) {
		case *ast.Ident:
			return // we're done
		case *ast.ParenExpr:
			f = p.X
		default:
			unreachable()
		}
	}
}

func (check *checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
	assert(x != nil)
	if a[0] == nil || a[1] == nil {
		return
	}
	assert(isTyped(a[0]) && isTyped(a[1]) && isBoolean(a[1]))
	if m := check.Types; m != nil {
		for {
			assert(m[x] != nil) // should have been recorded already
			pos := x.Pos()
			m[x] = NewTuple(
				NewVar(pos, check.pkg, "", a[0]),
				NewVar(pos, check.pkg, "", a[1]),
			)
			// if x is a parenthesized expression (p.X), update p.X
			p, _ := x.(*ast.ParenExpr)
			if p == nil {
				break
			}
			x = p.X
		}
	}
}

func (check *checker) recordObject(id *ast.Ident, obj Object) {
	assert(id != nil)
	if m := check.Objects; m != nil {
		m[id] = obj
	}
}

func (check *checker) recordImplicit(node ast.Node, obj Object) {
	assert(node != nil && obj != nil)
	if m := check.Implicits; m != nil {
		m[node] = obj
	}
}

func (check *checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) {
	assert(obj != nil && (recv == nil || len(index) > 0))
	check.recordObject(x.Sel, obj)
	// TODO(gri) Should we also call recordTypeAndValue?
	if m := check.Selections; m != nil {
		m[x] = &Selection{kind, recv, obj, index, indirect}
	}
}

func (check *checker) recordScope(node ast.Node, scope *Scope) {
	assert(node != nil && scope != nil)
	if m := check.Scopes; m != nil {
		m[node] = scope
	}
}

// A bailout panic is raised to indicate early termination.
type bailout struct{}

func (check *checker) handleBailout(err *error) {
	switch p := recover().(type) {
	case nil, bailout:
		// normal return or early exit
		*err = check.firstErr
	default:
		// re-panic
		panic(p)
	}
}

func (conf *Config) check(pkgPath string, fset *token.FileSet, files []*ast.File, info *Info) (pkg *Package, err error) {
	// make sure we have a package canonicalization map
	if conf.Packages == nil {
		conf.Packages = make(map[string]*Package)
	}

	pkg = NewPackage(pkgPath, "", NewScope(Universe)) // package name is set below
	check := newChecker(conf, fset, pkg)
	defer check.handleBailout(&err)

	// we need a reasonable package path to continue
	if path.Clean(pkgPath) == "." {
		check.errorf(token.NoPos, "invalid package path provided: %q", pkgPath)
		return
	}

	// determine package name and files
	i := 0
	for _, file := range files {
		switch name := file.Name.Name; pkg.name {
		case "":
			pkg.name = name
			fallthrough
		case name:
			files[i] = file
			i++
		default:
			check.errorf(file.Package, "package %s; expected %s", name, pkg.name)
			// ignore this file
		}
	}

	// install optional info
	if info != nil {
		check.Info = *info
	}

	check.resolveFiles(files[:i])

	// perform delayed checks
	for _, f := range check.delayed {
		f()
	}
	check.delayed = nil // not needed anymore

	// remaining untyped expressions must indeed be untyped
	if debug {
		for x, info := range check.untyped {
			if isTyped(info.typ) {
				check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ)
				panic(0)
			}
		}
	}

	// notify client of any untyped types left
	// TODO(gri) Consider doing this before and
	// after function body checking for smaller
	// map size and more immediate feedback.
	if check.Types != nil || check.Values != nil {
		for x, info := range check.untyped {
			check.recordTypeAndValue(x, info.typ, info.val)
		}
	}

	// copy check.InitOrder back to incoming *info if necessary
	// (In case of early (error) bailout, this is not done, but we don't care in that case.)
	if info != nil {
		info.InitOrder = check.InitOrder
	}

	return
}
<|endoftext|>