<commit_before>// Copyright 2018 The ACH Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

// Package ach reads and writes (ACH) Automated Clearing House files. ACH is the
// primary method of electronic money movement through the United States.
//
// https://en.wikipedia.org/wiki/Automated_Clearing_House
package ach

import (
	"fmt"
	"strconv"
)

// First position of all Record Types. These codes are uniquely assigned to
// the first byte of each row in a file.
const (
	fileHeaderPos   = "1"
	batchHeaderPos  = "5"
	entryDetailPos  = "6"
	entryAddendaPos = "7"
	batchControlPos = "8"
	fileControlPos  = "9"

	// RecordLength character count of each line representing a letter in a file
	RecordLength = 94
)

// Errors strings specific to parsing a Batch container
var (
	msgFileCalculatedControlEquality = "calculated %v is out-of-balance with control %v"
	// specific messages
	msgRecordLength      = "must be 94 characters and found %d"
	msgFileBatchOutside  = "outside of current batch"
	msgFileBatchInside   = "inside of current batch"
	msgFileControl       = "none or more than one file control exists"
	msgFileHeader        = "none or more than one file headers exists"
	msgUnknownRecordType = "%s is an unknown record type"
	msgFileNoneSEC       = "%v SEC(standard entry class) is not implemented"
)

// FileError is an error describing issues validating a file
type FileError struct {
	FieldName string
	Value     string
	Msg       string
}

func (e *FileError) Error() string {
	return fmt.Sprintf("%s %s", e.FieldName, e.Msg)
}

// File contains the structures of a parsed ACH File.
type File struct {
	// ID is a client defined string used as a reference to this record.
	ID      string      `json:"id"`
	Header  FileHeader  `json:"fileHeader"`
	Batches []Batcher   `json:"batches"`
	Control FileControl `json:"fileControl"`

	// NotificationOfChange (Notification of change) is a slice of references to BatchCOR in file.Batches
	NotificationOfChange []*BatchCOR
	// ReturnEntries is a slice of references to file.Batches that contain return entries
	ReturnEntries []Batcher

	converters
}

// NewFile constructs a file template.
func NewFile() *File {
	return &File{
		Header:  NewFileHeader(),
		Control: NewFileControl(),
	}
}

// Create creates a valid file and requires that the FileHeader and at least one Batch
func (f *File) Create() error {
	// Requires a valid FileHeader to build FileControl
	if err := f.Header.Validate(); err != nil {
		return err
	}
	// Requires at least one Batch in the new file.
	if len(f.Batches) <= 0 {
		return &FileError{FieldName: "Batches", Value: strconv.Itoa(len(f.Batches)), Msg: "must have []*Batches to be built"}
	}
	// add 2 for FileHeader/control and reset if build was called twice do to error
	totalRecordsInFile := 2
	batchSeq := 1
	fileEntryAddendaCount := 0
	fileEntryHashSum := 0
	totalDebitAmount := 0
	totalCreditAmount := 0
	for i, batch := range f.Batches {
		// create ascending batch numbers
		f.Batches[i].GetHeader().BatchNumber = batchSeq
		f.Batches[i].GetControl().BatchNumber = batchSeq
		batchSeq++
		// sum file entry and addenda records. Assume batch.Create() batch properly calculated control
		fileEntryAddendaCount = fileEntryAddendaCount + batch.GetControl().EntryAddendaCount
		// add 2 for Batch header/control + entry added count
		totalRecordsInFile = totalRecordsInFile + 2 + batch.GetControl().EntryAddendaCount
		// sum hash from batch control. Assume Batch.Build properly calculated field.
		fileEntryHashSum = fileEntryHashSum + batch.GetControl().EntryHash
		totalDebitAmount = totalDebitAmount + batch.GetControl().TotalDebitEntryDollarAmount
		totalCreditAmount = totalCreditAmount + batch.GetControl().TotalCreditEntryDollarAmount

	}
	// create FileControl from calculated values
	fc := NewFileControl()
	fc.BatchCount = batchSeq - 1
	// blocking factor of 10 is static default value in f.Header.blockingFactor.
	if (totalRecordsInFile % 10) != 0 {
		fc.BlockCount = totalRecordsInFile/10 + 1
	} else {
		fc.BlockCount = totalRecordsInFile / 10
	}
	fc.EntryAddendaCount = fileEntryAddendaCount
	fc.EntryHash = fileEntryHashSum
	fc.TotalDebitEntryDollarAmountInFile = totalDebitAmount
	fc.TotalCreditEntryDollarAmountInFile = totalCreditAmount
	f.Control = fc

	return nil
}

// AddBatch appends a Batch to the ach.File
func (f *File) AddBatch(batch Batcher) []Batcher {
	switch batch.(type) {
	case *BatchCOR:
		f.NotificationOfChange = append(f.NotificationOfChange, batch.(*BatchCOR))
	}
	if batch.Category() == CategoryReturn {
		f.ReturnEntries = append(f.ReturnEntries, batch)
	}
	f.Batches = append(f.Batches, batch)
	return f.Batches
}

// SetHeader allows for header to be built.
func (f *File) SetHeader(h FileHeader) *File {
	f.Header = h
	return f
}

// Validate NACHA rules on the entire batch before being added to a File
func (f *File) Validate() error {
	// The value of the Batch Count Field is equal to the number of Company/Batch/Header Records in the file.
	if f.Control.BatchCount != len(f.Batches) {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, len(f.Batches), f.Control.BatchCount)
		return &FileError{FieldName: "BatchCount", Value: strconv.Itoa(len(f.Batches)), Msg: msg}
	}

	if err := f.isEntryAddendaCount(); err != nil {
		return err
	}

	if err := f.isFileAmount(); err != nil {
		return err
	}

	return f.isEntryHash()
}

// isEntryAddenda is prepared by hashing the RDFI’s 8-digit Routing Number in each entry.
//The Entry Hash provides a check against inadvertent alteration of data
func (f *File) isEntryAddendaCount() error {
	count := 0
	// we assume that each batch block has already validated the addenda count is accurate in batch control.
	for _, batch := range f.Batches {
		count += batch.GetControl().EntryAddendaCount
	}
	if f.Control.EntryAddendaCount != count {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, count, f.Control.EntryAddendaCount)
		return &FileError{FieldName: "EntryAddendaCount", Value: f.Control.EntryAddendaCountField(), Msg: msg}
	}
	return nil
}

// isFileAmount tThe Total Debit and Credit Entry Dollar Amounts Fields contain accumulated
// Entry Detail debit and credit totals within the file
func (f *File) isFileAmount() error {
	debit := 0
	credit := 0
	for _, batch := range f.Batches {
		debit += batch.GetControl().TotalDebitEntryDollarAmount
		credit += batch.GetControl().TotalCreditEntryDollarAmount
	}
	if f.Control.TotalDebitEntryDollarAmountInFile != debit {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, debit, f.Control.TotalDebitEntryDollarAmountInFile)
		return &FileError{FieldName: "TotalDebitEntryDollarAmountInFile", Value: f.Control.TotalDebitEntryDollarAmountInFileField(), Msg: msg}
	}
	if f.Control.TotalCreditEntryDollarAmountInFile != credit {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, credit, f.Control.TotalCreditEntryDollarAmountInFile)
		return &FileError{FieldName: "TotalCreditEntryDollarAmountInFile", Value: f.Control.TotalCreditEntryDollarAmountInFileField(), Msg: msg}
	}
	return nil
}

// isEntryHash validates the hash by recalculating the result
func (f *File) isEntryHash() error {
	hashField := f.calculateEntryHash()
	if hashField != f.Control.EntryHashField() {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, hashField, f.Control.EntryHashField())
		return &FileError{FieldName: "EntryHash", Value: f.Control.EntryHashField(), Msg: msg}
	}
	return nil
}

// calculateEntryHash This field is prepared by hashing the 8-digit Routing Number in each batch.
// The Entry Hash provides a check against inadvertent alteration of data
func (f *File) calculateEntryHash() string {
	hash := 0
	for _, batch := range f.Batches {
		hash = hash + batch.GetControl().EntryHash
	}
	return f.numericField(hash, 10)
}
<commit_msg>Remove NOC and Returns if they don't exist<commit_after>// Copyright 2018 The ACH Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

// Package ach reads and writes (ACH) Automated Clearing House files. ACH is the
// primary method of electronic money movement through the United States.
//
// https://en.wikipedia.org/wiki/Automated_Clearing_House
package ach

import (
	"fmt"
	"strconv"
)

// First position of all Record Types. These codes are uniquely assigned to
// the first byte of each row in a file.
const (
	fileHeaderPos   = "1"
	batchHeaderPos  = "5"
	entryDetailPos  = "6"
	entryAddendaPos = "7"
	batchControlPos = "8"
	fileControlPos  = "9"

	// RecordLength character count of each line representing a letter in a file
	RecordLength = 94
)

// Errors strings specific to parsing a Batch container
var (
	msgFileCalculatedControlEquality = "calculated %v is out-of-balance with control %v"
	// specific messages
	msgRecordLength      = "must be 94 characters and found %d"
	msgFileBatchOutside  = "outside of current batch"
	msgFileBatchInside   = "inside of current batch"
	msgFileControl       = "none or more than one file control exists"
	msgFileHeader        = "none or more than one file headers exists"
	msgUnknownRecordType = "%s is an unknown record type"
	msgFileNoneSEC       = "%v SEC(standard entry class) is not implemented"
)

// FileError is an error describing issues validating a file
type FileError struct {
	FieldName string
	Value     string
	Msg       string
}

func (e *FileError) Error() string {
	return fmt.Sprintf("%s %s", e.FieldName, e.Msg)
}

// File contains the structures of a parsed ACH File.
type File struct {
	// ID is a client defined string used as a reference to this record.
	ID      string      `json:"id"`
	Header  FileHeader  `json:"fileHeader"`
	Batches []Batcher   `json:"batches"`
	Control FileControl `json:"fileControl"`

	// NotificationOfChange (Notification of change) is a slice of references to BatchCOR in file.Batches
	NotificationOfChange []*BatchCOR `json:"notificationOfChange,omitempty"`
	// ReturnEntries is a slice of references to file.Batches that contain return entries
	ReturnEntries []Batcher `json:"returnEntries,omitempty"`

	converters
}

// NewFile constructs a file template.
func NewFile() *File {
	return &File{
		Header:  NewFileHeader(),
		Control: NewFileControl(),
	}
}

// Create creates a valid file and requires that the FileHeader and at least one Batch
func (f *File) Create() error {
	// Requires a valid FileHeader to build FileControl
	if err := f.Header.Validate(); err != nil {
		return err
	}
	// Requires at least one Batch in the new file.
	if len(f.Batches) <= 0 {
		return &FileError{FieldName: "Batches", Value: strconv.Itoa(len(f.Batches)), Msg: "must have []*Batches to be built"}
	}
	// add 2 for FileHeader/control and reset if build was called twice do to error
	totalRecordsInFile := 2
	batchSeq := 1
	fileEntryAddendaCount := 0
	fileEntryHashSum := 0
	totalDebitAmount := 0
	totalCreditAmount := 0
	for i, batch := range f.Batches {
		// create ascending batch numbers
		f.Batches[i].GetHeader().BatchNumber = batchSeq
		f.Batches[i].GetControl().BatchNumber = batchSeq
		batchSeq++
		// sum file entry and addenda records. Assume batch.Create() batch properly calculated control
		fileEntryAddendaCount = fileEntryAddendaCount + batch.GetControl().EntryAddendaCount
		// add 2 for Batch header/control + entry added count
		totalRecordsInFile = totalRecordsInFile + 2 + batch.GetControl().EntryAddendaCount
		// sum hash from batch control. Assume Batch.Build properly calculated field.
		fileEntryHashSum = fileEntryHashSum + batch.GetControl().EntryHash
		totalDebitAmount = totalDebitAmount + batch.GetControl().TotalDebitEntryDollarAmount
		totalCreditAmount = totalCreditAmount + batch.GetControl().TotalCreditEntryDollarAmount

	}
	// create FileControl from calculated values
	fc := NewFileControl()
	fc.BatchCount = batchSeq - 1
	// blocking factor of 10 is static default value in f.Header.blockingFactor.
	if (totalRecordsInFile % 10) != 0 {
		fc.BlockCount = totalRecordsInFile/10 + 1
	} else {
		fc.BlockCount = totalRecordsInFile / 10
	}
	fc.EntryAddendaCount = fileEntryAddendaCount
	fc.EntryHash = fileEntryHashSum
	fc.TotalDebitEntryDollarAmountInFile = totalDebitAmount
	fc.TotalCreditEntryDollarAmountInFile = totalCreditAmount
	f.Control = fc

	return nil
}

// AddBatch appends a Batch to the ach.File
func (f *File) AddBatch(batch Batcher) []Batcher {
	switch batch.(type) {
	case *BatchCOR:
		f.NotificationOfChange = append(f.NotificationOfChange, batch.(*BatchCOR))
	}
	if batch.Category() == CategoryReturn {
		f.ReturnEntries = append(f.ReturnEntries, batch)
	}
	f.Batches = append(f.Batches, batch)
	return f.Batches
}

// SetHeader allows for header to be built.
func (f *File) SetHeader(h FileHeader) *File {
	f.Header = h
	return f
}

// Validate NACHA rules on the entire batch before being added to a File
func (f *File) Validate() error {
	// The value of the Batch Count Field is equal to the number of Company/Batch/Header Records in the file.
	if f.Control.BatchCount != len(f.Batches) {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, len(f.Batches), f.Control.BatchCount)
		return &FileError{FieldName: "BatchCount", Value: strconv.Itoa(len(f.Batches)), Msg: msg}
	}

	if err := f.isEntryAddendaCount(); err != nil {
		return err
	}

	if err := f.isFileAmount(); err != nil {
		return err
	}

	return f.isEntryHash()
}

// isEntryAddenda is prepared by hashing the RDFI’s 8-digit Routing Number in each entry.
//The Entry Hash provides a check against inadvertent alteration of data
func (f *File) isEntryAddendaCount() error {
	count := 0
	// we assume that each batch block has already validated the addenda count is accurate in batch control.
	for _, batch := range f.Batches {
		count += batch.GetControl().EntryAddendaCount
	}
	if f.Control.EntryAddendaCount != count {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, count, f.Control.EntryAddendaCount)
		return &FileError{FieldName: "EntryAddendaCount", Value: f.Control.EntryAddendaCountField(), Msg: msg}
	}
	return nil
}

// isFileAmount tThe Total Debit and Credit Entry Dollar Amounts Fields contain accumulated
// Entry Detail debit and credit totals within the file
func (f *File) isFileAmount() error {
	debit := 0
	credit := 0
	for _, batch := range f.Batches {
		debit += batch.GetControl().TotalDebitEntryDollarAmount
		credit += batch.GetControl().TotalCreditEntryDollarAmount
	}
	if f.Control.TotalDebitEntryDollarAmountInFile != debit {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, debit, f.Control.TotalDebitEntryDollarAmountInFile)
		return &FileError{FieldName: "TotalDebitEntryDollarAmountInFile", Value: f.Control.TotalDebitEntryDollarAmountInFileField(), Msg: msg}
	}
	if f.Control.TotalCreditEntryDollarAmountInFile != credit {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, credit, f.Control.TotalCreditEntryDollarAmountInFile)
		return &FileError{FieldName: "TotalCreditEntryDollarAmountInFile", Value: f.Control.TotalCreditEntryDollarAmountInFileField(), Msg: msg}
	}
	return nil
}

// isEntryHash validates the hash by recalculating the result
func (f *File) isEntryHash() error {
	hashField := f.calculateEntryHash()
	if hashField != f.Control.EntryHashField() {
		msg := fmt.Sprintf(msgFileCalculatedControlEquality, hashField, f.Control.EntryHashField())
		return &FileError{FieldName: "EntryHash", Value: f.Control.EntryHashField(), Msg: msg}
	}
	return nil
}

// calculateEntryHash This field is prepared by hashing the 8-digit Routing Number in each batch.
// The Entry Hash provides a check against inadvertent alteration of data
func (f *File) calculateEntryHash() string {
	hash := 0
	for _, batch := range f.Batches {
		hash = hash + batch.GetControl().EntryHash
	}
	return f.numericField(hash, 10)
}
<|endoftext|>


<commit_before>package file

import (
	"os"
	"time"
)

// Limit of the waiting time in seconds to wait for files to be released.
var WaitTimeout float64 = 30.0

// Interval to retry file locking.
var RetryInterval time.Duration = 50 * time.Millisecond

// Types of Locks
type LockType int

const (
	// Shared Lock
	SHARED_LOCK LockType = iota
	// Exclusive Lock
	EXCLUSIVE_LOCK
)

// Open the file with locking. OpenToRead is the same as Open(path, os.O_RDONLY, 0400, SHARED_LOCK)
func OpenToRead(path string) (*os.File, error) {
	return Open(path, os.O_RDONLY, 0400, SHARED_LOCK)
}

// Open the file with locking. OpenToRead is the same as Open(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
func OpenToUpdate(path string) (*os.File, error) {
	return Open(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
}

// Open the file with locking. Create is the same as Open(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600, EXCLUSIVE_LOCK)
func Create(path string) (*os.File, error) {
	return Open(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600, EXCLUSIVE_LOCK)
}

// Open the file with locking. OpenToReadWithTimeout is the same as OpenWithTimeout(path, os.O_RDONLY, 0400, SHARED_LOCK)
func OpenToReadWithTimeout(path string) (*os.File, error) {
	return OpenWithTimeout(path, os.O_RDONLY, 0400, SHARED_LOCK)
}

// Open the file with locking. OpenToUpdateWithTimeout is the same as OpenWithTimeout(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
func OpenToUpdateWithTimeout(path string) (*os.File, error) {
	return OpenWithTimeout(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
}

// Open the file with file locking. If the file is already locked, waits until the file is released.
func Open(path string, flag int, perm os.FileMode, lockType LockType) (*os.File, error) {
	fp, err := os.OpenFile(path, flag, perm)
	if err != nil {
		return nil, NewIOError(err.Error())
	}

	err = Lock(fp, lockType)
	if err != nil {
		fp.Close()
		return nil, err
	}
	return fp, nil
}

// Open the file with file locking. If the file is already locked, waits for up to WaitTimeout seconds.
func OpenWithTimeout(path string, flag int, perm os.FileMode, lockType LockType) (*os.File, error) {
	fp, err := os.OpenFile(path, flag, perm)
	if err != nil {
		return nil, NewIOError(err.Error())
	}

	err = LockWithTimeout(fp, lockType)

	if err != nil {
		fp.Close()
		return nil, err
	}

	return fp, nil
}

// Places a lock on the file. If the file is already locked, waits until the file is released.
func Lock(fp *os.File, lockType LockType) error {
	var err error
	switch lockType {
	case EXCLUSIVE_LOCK:
		err = LockEX(fp)
	default:
		err = LockSH(fp)
	}

	if err != nil {
		return NewLockError(err.Error())
	}
	return nil
}

// Places a lock on the file. If the file is already locked, returns an error immediately.
func TryLock(fp *os.File, lockType LockType) error {
	var err error
	switch lockType {
	case EXCLUSIVE_LOCK:
		err = TryLockEX(fp)
	default:
		err = TryLockSH(fp)
	}

	if err != nil {
		return NewLockError(err.Error())
	}
	return nil
}

// Places a lock on the file. If the file is already locked, waits for up to WaitTimeout seconds.
func LockWithTimeout(fp *os.File, lockType LockType) error {
	start := time.Now()

	err := TryLock(fp, lockType)
	if err == nil {
		return nil
	}

	if 0 < WaitTimeout {
		for {
			if time.Since(start).Seconds() > WaitTimeout {
				err = NewTimeoutError(fp.Name())
				break
			}
			time.Sleep(RetryInterval)

			if err = TryLock(fp, lockType); err == nil {
				break
			}
		}
	}

	return err
}

// Unlocks and closes the file
func Close(fp *os.File) error {
	defer func() {
		fp.Close()
	}()

	if err := Unlock(fp); err != nil {
		return NewLockError(err.Error())
	}
	return nil
}
<commit_msg>Modify file permission in Create function.<commit_after>package file

import (
	"os"
	"time"
)

// Limit of the waiting time in seconds to wait for files to be released.
var WaitTimeout float64 = 30.0

// Interval to retry file locking.
var RetryInterval time.Duration = 50 * time.Millisecond

// Types of Locks
type LockType int

const (
	// Shared Lock
	SHARED_LOCK LockType = iota
	// Exclusive Lock
	EXCLUSIVE_LOCK
)

// Opens the file with locking. OpenToRead is the same as Open(path, os.O_RDONLY, 0400, SHARED_LOCK)
func OpenToRead(path string) (*os.File, error) {
	return Open(path, os.O_RDONLY, 0400, SHARED_LOCK)
}

// Opens the file with locking. OpenToRead is the same as Open(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
func OpenToUpdate(path string) (*os.File, error) {
	return Open(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
}

// Opens the file with locking. Create is the same as Open(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600, EXCLUSIVE_LOCK)
func Create(path string) (*os.File, error) {
	return Open(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600, EXCLUSIVE_LOCK)
}

// Opens the file with locking. OpenToReadWithTimeout is the same as OpenWithTimeout(path, os.O_RDONLY, 0400, SHARED_LOCK)
func OpenToReadWithTimeout(path string) (*os.File, error) {
	return OpenWithTimeout(path, os.O_RDONLY, 0400, SHARED_LOCK)
}

// Opens the file with locking. OpenToUpdateWithTimeout is the same as OpenWithTimeout(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
func OpenToUpdateWithTimeout(path string) (*os.File, error) {
	return OpenWithTimeout(path, os.O_RDWR, 0600, EXCLUSIVE_LOCK)
}

// Opens the file with file locking. If the file is already locked, waits until the file is released.
func Open(path string, flag int, perm os.FileMode, lockType LockType) (*os.File, error) {
	fp, err := os.OpenFile(path, flag, perm)
	if err != nil {
		return nil, NewIOError(err.Error())
	}

	err = Lock(fp, lockType)
	if err != nil {
		fp.Close()
		return nil, err
	}
	return fp, nil
}

// Opens the file with file locking. If the file is already locked, waits for up to WaitTimeout seconds.
func OpenWithTimeout(path string, flag int, perm os.FileMode, lockType LockType) (*os.File, error) {
	fp, err := os.OpenFile(path, flag, perm)
	if err != nil {
		return nil, NewIOError(err.Error())
	}

	err = LockWithTimeout(fp, lockType)

	if err != nil {
		fp.Close()
		return nil, err
	}

	return fp, nil
}

// Places a lock on the file. If the file is already locked, waits until the file is released.
func Lock(fp *os.File, lockType LockType) error {
	var err error
	switch lockType {
	case EXCLUSIVE_LOCK:
		err = LockEX(fp)
	default:
		err = LockSH(fp)
	}

	if err != nil {
		return NewLockError(err.Error())
	}
	return nil
}

// Places a lock on the file. If the file is already locked, returns an error immediately.
func TryLock(fp *os.File, lockType LockType) error {
	var err error
	switch lockType {
	case EXCLUSIVE_LOCK:
		err = TryLockEX(fp)
	default:
		err = TryLockSH(fp)
	}

	if err != nil {
		return NewLockError(err.Error())
	}
	return nil
}

// Places a lock on the file. If the file is already locked, waits for up to WaitTimeout seconds.
func LockWithTimeout(fp *os.File, lockType LockType) error {
	start := time.Now()

	err := TryLock(fp, lockType)
	if err == nil {
		return nil
	}

	if 0 < WaitTimeout {
		for {
			if time.Since(start).Seconds() > WaitTimeout {
				err = NewTimeoutError(fp.Name())
				break
			}
			time.Sleep(RetryInterval)

			if err = TryLock(fp, lockType); err == nil {
				break
			}
		}
	}

	return err
}

// Unlocks and closes the file
func Close(fp *os.File) error {
	defer func() {
		fp.Close()
	}()

	if err := Unlock(fp); err != nil {
		return NewLockError(err.Error())
	}
	return nil
}
<|endoftext|>


<commit_before>// Package ztex manages ZTEX USB FPGA modules.
package ztex

import (
	"time"

	"github.com/google/gousb"
)

var (
	// VendorID is the ZTEX USB vendor ID (VID).
	VendorID = gousb.ID(0x221A)

	// ProductID is the standard ZTEX USB product ID (PID)
	ProductID = gousb.ID(0x0100)

	// ControlTimeout is the timeout for control transfers.
	ControlTimeout = 1000 * time.Millisecond
)

// Device represents a ZTEX USB FPGA module.
type Device struct {
	ProductName  [128]byte
	SerialNumber [64]byte
	FXVersion    byte    // 2 = FX2, 3 = FX3.
	BoardSeries  byte    // 1 = Series 1, 2 = Series 2.  (255 = Unknown)
	BoardNumber  byte    // E.g., 2.14b -> 2.  (255 = Unknown)
	BoardVariant [2]byte // E.g., 2.14b -> [2]byte{'b', 0}.

	// Endpoint for fast FPGA configuration.  (0 = Unsupported)
	FastConfigurationEndpoint byte
	// Interface for fast FPGA configuration.  (0 = Unsupported)
	FastConfigurationInterface byte
	/// Default interface major version number.  (0 = Not Available)
	DefaultInterfaceMajorVersion byte
	/// Default interface minor version number.  (0 = Not Available)
	DefaultInterfaceMinorVersion byte
	/// Output endpoint of default interface.  (255 = Not Available)
	DefaultInterfaceOutputEndpoint byte
	/// Input endpoint of default interface.  (255 = Not Available)
	DefaultInterfaceInputEndpoint byte

	// Device is the USB device handle for the module.
	Device *gousb.Device
}

func OpenDevice(ctx *gousb.Context) (*Device, error) {
	d := &Device{}

	if dev, err := ctx.OpenDeviceWithVIDPID(VendorID, ProductID); err != nil {
		return nil, err
	} else {
		dev.ControlTimeout = ControlTimeout
		d.Device = dev
	}

	buf := make([]byte, 128)

	// VR 0x33: High speed FPGA configuration support: Read Endpoint settings
	if nbt, err := d.Device.Control(0xc0, 0x33, 0, 0, buf); err != nil {
		return nil, err
	} else if nbt == 2 {
		d.FastConfigurationEndpoint = buf[0]
		d.FastConfigurationInterface = buf[1]
	}

	// VR 0x3b: MAC EEPROM support: Read from MAC EEPROM
	if nbt, err := d.Device.Control(0xc0, 0x3b, 0, 0, buf); err != nil {
		return nil, err
	} else if nbt == 128 {
		d.FXVersion = buf[3]
		d.BoardSeries = buf[4]
		d.BoardNumber = buf[5]
		d.BoardVariant[0] = buf[6]
		d.BoardVariant[1] = buf[7]
	} else {
		d.BoardSeries = 255
		d.BoardNumber = 255
	}

	// VR 0x64: Default firmware interface: Return Default Interface information
	if nbt, err := d.Device.Control(0xc0, 0x64, 0, 0, buf); err != nil {
		return nil, err
	} else if nbt == 4 {
		d.DefaultInterfaceMajorVersion = buf[0]
		d.DefaultInterfaceMinorVersion = buf[3]
		d.DefaultInterfaceOutputEndpoint = buf[1] & 127
		d.DefaultInterfaceInputEndpoint = buf[2] | 128
	} else if nbt == 3 {
		d.DefaultInterfaceMajorVersion = buf[0]
		d.DefaultInterfaceOutputEndpoint = buf[1] & 127
		d.DefaultInterfaceInputEndpoint = buf[2] | 128
	} else {
		d.DefaultInterfaceOutputEndpoint = 255
		d.DefaultInterfaceInputEndpoint = 255
	}

	if prd, err := d.Device.Product(); err != nil {
		return nil, err
	} else {
		for i, b := range []byte(prd) {
			if i >= 128 {
				break
			}
			d.ProductName[i] = b
		}
	}

	if srl, prd, err := d.Device.SerialNumber(); err != nil {
		return nil, err
	} else {
		for i, b := range []byte(srl) {
			if i >= 128 {
				break
			}
			d.ProductName[i] = b
		}
	}

	return d, nil
}

// Close releases all external resources associated with the device.
func (d *Device) Close() error { return d.Device.Close() }
<commit_msg>Reorganize device.<commit_after>// Package ztex manages ZTEX USB FPGA modules.
package ztex

import (
	"errors"
	"fmt"
	"time"

	"github.com/google/gousb"
)

const (
	// VendorID is the ZTEX USB vendor ID (VID).
	VendorID = gousb.ID(0x221A)
	// ProductID is the standard ZTEX USB product ID (PID)
	ProductID = gousb.ID(0x0100)

	// ControlTimeout is the default timeout for control transfers.
	ControlTimeout = 1000 * time.Millisecond
)

// FirmwareVersion indicates which version of the ZTEX firmware is
// present.  Currently only FX1 and FX2 are supported.
type FirmwareVersion uint8

// String returns a human-readable description of a firmware version.
func (f FirmwareVersion) String() string {
	switch f {
	case 2:
		return "FX2"
	case 3:
		return "FX3"
	case 255:
		fallthrough
	default:
		return "Unknown"
	}
}

// Number returns the raw representation of a firmware version.
func (f FirmwareVersion) Number() uint8 { return uint8(f) }

// BoardSeries indicates an entire generation of boards.  Currently only
// Series 1 and Series 2 boards are supported.
type BoardSeries uint8

// String returns a human-readable description of a board series.
func (b BoardSeries) String() string {
	switch b {
	case 1:
		return "1"
	case 2:
		return "2"
	default:
		return "Unknown"
	}
}

// Number returns the raw representation of a board series.
func (b BoardSeries) Number() uint8 { return uint8(b) }

// BoardNumber indicates a board in a series.
type BoardNumber uint8

// String returns a human-readable description of a board number.
func (b BoardNumber) String() string {
	if b == 255 {
		return "Unknown"
	}
	return fmt.Sprintf("%d", uint8(b))
}

// Number returns the raw representation of a board number.
func (b BoardNumber) Number() uint8 { return uint8(b) }

// BoardVariant indicates a variation on a board series and number.
type BoardVariant []byte

// String returns a human-readable description of a board variant.
func (b BoardVariant) String() string { return string(b) }

// Number returns the raw representation of a board variant.
func (b BoardVariant) Bytes() []byte { return []byte(b) }

// BoardVersion comprises the series, number, and variant of a ZTEX USB
// FPGA module.  For example, a ZTEX USB FPGA module 2.14b device would be
// represented by
//
//   BoardVersion{
//     Series: BoardSeries(2),
//     Number: BoardNumber(14),
//     Variant: BoardVariant("b"),
//   }
//
// as a BoardVersion structure.
type BoardVersion struct {
	Series  BoardSeries
	Number  BoardNumber
	Variant BoardVariant
}

// String returns a human-readable representation of a board version.
func (b BoardVersion) String() string {
	return b.Series.String() + "." + b.Number.String() + b.Variant.String()
}

// Device represents a ZTEX USB FPGA module.
type Device struct {
	*gousb.Device

	Firmware FirmwareVersion
	Board    BoardVersion

	// Endpoint for fast FPGA configuration.  (0 = Unsupported)
	FastConfigurationEndpoint byte
	// Interface for fast FPGA configuration.  (0 = Unsupported)
	FastConfigurationInterface byte
	// Default interface major version number.  (0 = Not Available)
	DefaultInterfaceMajorVersion byte
	// Default interface minor version number.  (0 = Not Available)
	DefaultInterfaceMinorVersion byte
	// Output endpoint of default interface.  (255 = Not Available)
	DefaultInterfaceOutputEndpoint byte
	// Input endpoint of default interface.  (255 = Not Available)
	DefaultInterfaceInputEndpoint byte
}

// OpenDevice opens the
func OpenDevice(ctx *gousb.Context) (*Device, error) {
	d := &Device{}
	if dev, err := ctx.OpenDeviceWithVIDPID(VendorID, ProductID); err != nil {
		return nil, err
	} else if dev == nil {
		return nil, errors.New("no device")
	} else {
		d.Device = dev
		d.Device.ControlTimeout = ControlTimeout
	}

	buf := make([]byte, 128)

	// VR 0x3b: MAC EEPROM support: Read from MAC EEPROM
	if nbt, err := d.Control(0xc0, 0x3b, 0, 0, buf); err != nil {
		return nil, err
	} else if nbt == 128 {
		d.Firmware = FirmwareVersion(buf[3])
		d.Board = BoardVersion{
			Series:  BoardSeries(buf[4]),
			Number:  BoardNumber(buf[5]),
			Variant: BoardVariant(buf[6:8]),
		}
	} else {
		d.Firmware = FirmwareVersion(255)
		d.Board = BoardVersion{
			Series:  BoardSeries(255),
			Number:  BoardNumber(255),
			Variant: BoardVariant([]byte{255, 255}),
		}
	}

	// VR 0x33: High speed FPGA configuration support: Read Endpoint settings
	if nbt, err := d.Control(0xc0, 0x33, 0, 0, buf); err != nil {
		return nil, err
	} else if nbt == 2 {
		d.FastConfigurationEndpoint = buf[0]
		d.FastConfigurationInterface = buf[1]
	} else {
		return nil, errors.New("internal error")
	}

	// VR 0x64: Default firmware interface: Return Default Interface information
	if nbt, err := d.Control(0xc0, 0x64, 0, 0, buf); err != nil {
		return nil, err
	} else if nbt == 4 {
		d.DefaultInterfaceMajorVersion = buf[0]
		d.DefaultInterfaceMinorVersion = buf[3]
		d.DefaultInterfaceOutputEndpoint = buf[1] & 127
		d.DefaultInterfaceInputEndpoint = buf[2] | 128
	} else if nbt == 3 {
		d.DefaultInterfaceMajorVersion = buf[0]
		d.DefaultInterfaceOutputEndpoint = buf[1] & 127
		d.DefaultInterfaceInputEndpoint = buf[2] | 128
	} else {
		d.DefaultInterfaceOutputEndpoint = 255
		d.DefaultInterfaceInputEndpoint = 255
	}

	return d, nil
}
<|endoftext|>


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

package com

import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strings"
)

// get filepath base name
func Basename(file string) string {
	return path.Base(file)
}

// get filepath dir name
func Dirname(file string) string {
	return path.Dir(file)
}

// get absolute filepath, based on built excutable file
func RealPath(file string) (string, error) {
	if path.IsAbs(file) {
		return file, nil
	}
	wd, err := os.Getwd()
	return path.Join(wd, file), err
}

// get file modified time
func FileMTime(file string) (int64, error) {
	f, e := os.Stat(file)
	if e != nil {
		return 0, e
	}
	return f.ModTime().Unix(), nil
}

// get file size as how many bytes
func FileSize(file string) (int64, error) {
	f, e := os.Stat(file)
	if e != nil {
		return 0, e
	}
	return f.Size(), nil
}

// copy file to new path
func Copy(file string, to string) (bool, error) {
	sf, e := os.Open(file)
	if e != nil {
		return false, e
	}
	defer sf.Close()
	df, e2 := os.Create(to)
	if e2 != nil {
		return false, e2
	}
	defer df.Close()
	// buffer reader, do chunk transfer
	buf := make([]byte, 1024)
	for {
		// read a chunk
		n, err := sf.Read(buf)
		if err != nil && err != io.EOF {
			return false, err
		}
		if n == 0 {
			break
		}
		// write a chunk
		if _, err := df.Write(buf[:n]); err != nil {
			return false, err
		}
	}
	return true, nil
}

// move file to new path
func Move(file string, to string) (bool, error) {
	// copy
	r, e := Copy(file, to)
	if e != nil {
		return r, e
	}
	// then remove
	e = os.Remove(file)
	if e != nil {
		r = false
	}
	return r, e
}

// delete file
func Unlink(file string) error {
	return os.Remove(file)
}

// rename file name
func Rename(file string, to string) error {
	return os.Rename(file, to)
}

// IsFile checks whether the path is a file,
// it returns false when it's a directory or does not exist.
func IsFile(filePath string) bool {
	f, e := os.Stat(filePath)
	if e != nil {
		return false
	}
	return !f.IsDir()
}

// IsExist returns whether a file or directory exists.
func IsExist(path string) bool {
	_, err := os.Stat(path)
	return err == nil || os.IsExist(err)
}

// SaveFile saves content type '[]byte' to file by given path.
// It returns error when fail to finish operation.
func SaveFile(filePath string, b []byte) (int, error) {
	os.MkdirAll(path.Dir(filePath), os.ModePerm)
	fw, err := os.Create(filePath)
	if err != nil {
		return 0, err
	}
	defer fw.Close()
	return fw.Write(b)
}

// SaveFileS saves content type 'string' to file by given path.
// It returns error when fail to finish operation.
func SaveFileS(filePath string, s string) (int, error) {
	return SaveFile(filePath, []byte(s))
}

// ReadFile reads data type '[]byte' from file by given path.
// It returns error when fail to finish operation.
func ReadFile(filePath string) ([]byte, error) {
	b, err := ioutil.ReadFile(filePath)
	if err != nil {
		return []byte(""), err
	}
	return b, nil
}

// ReadFileS reads data type 'string' from file by given path.
// It returns error when fail to finish operation.
func ReadFileS(filePath string) (string, error) {
	b, err := ReadFile(filePath)
	return string(b), err
}

// Unzip unzips .zip file to 'destPath' and returns sub-directories.
// It returns error when fail to finish operation.
func Unzip(srcPath, destPath string) ([]string, error) {
	// Open a zip archive for reading
	r, err := zip.OpenReader(srcPath)
	if err != nil {
		return nil, err
	}
	defer r.Close()

	dirs := make([]string, 0, 5)
	// Iterate through the files in the archive
	for _, f := range r.File {
		// Get files from archive
		rc, err := f.Open()
		if err != nil {
			return nil, err
		}

		dir := path.Dir(strings.TrimSuffix(
			f.FileInfo().Name(), "/"))
		// Create diretory before create file
		os.MkdirAll(destPath+"/"+dir, os.ModePerm)
		dirs = AppendStr(dirs, dir)

		// Write data to file
		fw, _ := os.Create(destPath + "/" + f.FileInfo().Name())
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(fw, rc)
		fw.Close()
		if err != nil {
			return nil, err
		}
	}
	return dirs, nil
}

// UnTarGz ungzips and untars .tar.gz file to 'destPath' and returns sub-directories.
// It returns error when fail to finish operation.
func UnTarGz(srcFilePath string, destDirPath string) ([]string, error) {
	// Create destination directory
	os.Mkdir(destDirPath, os.ModePerm)

	fr, err := os.Open(srcFilePath)
	if err != nil {
		return nil, err
	}
	defer fr.Close()

	// Gzip reader
	gr, err := gzip.NewReader(fr)
	if err != nil {
		return nil, err
	}
	defer gr.Close()

	// Tar reader
	tr := tar.NewReader(gr)

	dirs := make([]string, 0, 5)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// End of tar archive
			break
		}

		// Check if it is diretory or file
		if hdr.Typeflag != tar.TypeDir {
			// Get files from archive
			// Create diretory before create file
			dir := path.Dir(hdr.Name)
			os.MkdirAll(destDirPath+"/"+dir, os.ModePerm)
			dirs = AppendStr(dirs, dir)

			// Write data to file
			fw, _ := os.Create(destDirPath + "/" + hdr.Name)
			if err != nil {
				return nil, err
			}
			_, err = io.Copy(fw, tr)
			if err != nil {
				return nil, err
			}
		}
	}
	return dirs, nil
}
<commit_msg>Updated func: Unzip<commit_after>// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package com

import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strings"
)

// get filepath base name
func Basename(file string) string {
	return path.Base(file)
}

// get filepath dir name
func Dirname(file string) string {
	return path.Dir(file)
}

// get absolute filepath, based on built excutable file
func RealPath(file string) (string, error) {
	if path.IsAbs(file) {
		return file, nil
	}
	wd, err := os.Getwd()
	return path.Join(wd, file), err
}

// get file modified time
func FileMTime(file string) (int64, error) {
	f, e := os.Stat(file)
	if e != nil {
		return 0, e
	}
	return f.ModTime().Unix(), nil
}

// get file size as how many bytes
func FileSize(file string) (int64, error) {
	f, e := os.Stat(file)
	if e != nil {
		return 0, e
	}
	return f.Size(), nil
}

// copy file to new path
func Copy(file string, to string) (bool, error) {
	sf, e := os.Open(file)
	if e != nil {
		return false, e
	}
	defer sf.Close()
	df, e2 := os.Create(to)
	if e2 != nil {
		return false, e2
	}
	defer df.Close()
	// buffer reader, do chunk transfer
	buf := make([]byte, 1024)
	for {
		// read a chunk
		n, err := sf.Read(buf)
		if err != nil && err != io.EOF {
			return false, err
		}
		if n == 0 {
			break
		}
		// write a chunk
		if _, err := df.Write(buf[:n]); err != nil {
			return false, err
		}
	}
	return true, nil
}

// move file to new path
func Move(file string, to string) (bool, error) {
	// copy
	r, e := Copy(file, to)
	if e != nil {
		return r, e
	}
	// then remove
	e = os.Remove(file)
	if e != nil {
		r = false
	}
	return r, e
}

// delete file
func Unlink(file string) error {
	return os.Remove(file)
}

// rename file name
func Rename(file string, to string) error {
	return os.Rename(file, to)
}

// IsFile checks whether the path is a file,
// it returns false when it's a directory or does not exist.
func IsFile(filePath string) bool {
	f, e := os.Stat(filePath)
	if e != nil {
		return false
	}
	return !f.IsDir()
}

// IsExist returns whether a file or directory exists.
func IsExist(path string) bool {
	_, err := os.Stat(path)
	return err == nil || os.IsExist(err)
}

// SaveFile saves content type '[]byte' to file by given path.
// It returns error when fail to finish operation.
func SaveFile(filePath string, b []byte) (int, error) {
	os.MkdirAll(path.Dir(filePath), os.ModePerm)
	fw, err := os.Create(filePath)
	if err != nil {
		return 0, err
	}
	defer fw.Close()
	return fw.Write(b)
}

// SaveFileS saves content type 'string' to file by given path.
// It returns error when fail to finish operation.
func SaveFileS(filePath string, s string) (int, error) {
	return SaveFile(filePath, []byte(s))
}

// ReadFile reads data type '[]byte' from file by given path.
// It returns error when fail to finish operation.
func ReadFile(filePath string) ([]byte, error) {
	b, err := ioutil.ReadFile(filePath)
	if err != nil {
		return []byte(""), err
	}
	return b, nil
}

// ReadFileS reads data type 'string' from file by given path.
// It returns error when fail to finish operation.
func ReadFileS(filePath string) (string, error) {
	b, err := ReadFile(filePath)
	return string(b), err
}

// Unzip unzips .zip file to 'destPath' and returns sub-directories.
// It returns error when fail to finish operation.
func Unzip(srcPath, destPath string) ([]string, error) {
	// Open a zip archive for reading
	r, err := zip.OpenReader(srcPath)
	if err != nil {
		return nil, err
	}
	defer r.Close()

	dirs := make([]string, 0, 5)
	// Iterate through the files in the archive
	for _, f := range r.File {
		// Get files from archive
		rc, err := f.Open()
		if err != nil {
			return nil, err
		}

		fn := strings.TrimSuffix(f.FileInfo().Name(), "/")
		dir := path.Dir(fn)
		// Create diretory before create file
		os.MkdirAll(destPath+"/"+dir, os.ModePerm)
		dirs = AppendStr(dirs, dir)

		// Write data to file
		fw, _ := os.Create(destPath + "/" + fn)
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(fw, rc)
		fw.Close()
		if err != nil {
			return nil, err
		}
	}
	return dirs, nil
}

// UnTarGz ungzips and untars .tar.gz file to 'destPath' and returns sub-directories.
// It returns error when fail to finish operation.
func UnTarGz(srcFilePath string, destDirPath string) ([]string, error) {
	// Create destination directory
	os.Mkdir(destDirPath, os.ModePerm)

	fr, err := os.Open(srcFilePath)
	if err != nil {
		return nil, err
	}
	defer fr.Close()

	// Gzip reader
	gr, err := gzip.NewReader(fr)
	if err != nil {
		return nil, err
	}
	defer gr.Close()

	// Tar reader
	tr := tar.NewReader(gr)

	dirs := make([]string, 0, 5)
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// End of tar archive
			break
		}

		// Check if it is diretory or file
		if hdr.Typeflag != tar.TypeDir {
			// Get files from archive
			// Create diretory before create file
			dir := path.Dir(hdr.Name)
			os.MkdirAll(destDirPath+"/"+dir, os.ModePerm)
			dirs = AppendStr(dirs, dir)

			// Write data to file
			fw, _ := os.Create(destDirPath + "/" + hdr.Name)
			if err != nil {
				return nil, err
			}
			_, err = io.Copy(fw, tr)
			if err != nil {
				return nil, err
			}
		}
	}
	return dirs, nil
}
<|endoftext|>


<commit_before>package torrent

import (
	"strings"

	"github.com/anacrolix/missinggo/bitmap"

	"github.com/anacrolix/torrent/metainfo"
	pp "github.com/anacrolix/torrent/peer_protocol"
)

// Provides access to regions of torrent data that correspond to its files.
type File struct {
	t      *Torrent
	path   string
	offset int64
	length int64
	fi     metainfo.FileInfo
	prio   piecePriority
}

func (f *File) Torrent() *Torrent {
	return f.t
}

// Data for this file begins this many bytes into the Torrent.
func (f *File) Offset() int64 {
	return f.offset
}

// The FileInfo from the metainfo.Info to which this file corresponds.
func (f File) FileInfo() metainfo.FileInfo {
	return f.fi
}

// The file's path components joined by '/'.
func (f File) Path() string {
	return f.path
}

// The file's length in bytes.
func (f *File) Length() int64 {
	return f.length
}

// Number of bytes of the entire file we have completed. This is the sum of
// completed pieces, and dirtied chunks of incomplete pieces.
func (f *File) BytesCompleted() int64 {
	f.t.cl.rLock()
	defer f.t.cl.rUnlock()
	return f.bytesCompleted()
}

func (f *File) bytesCompleted() int64 {
	return f.length - f.bytesLeft()
}

func (f *File) bytesLeft() (left int64) {
	firstPieceIndex := f.firstPieceIndex()
	endPieceIndex := f.endPieceIndex()
	bitmap.Flip(f.t.completedPieces, firstPieceIndex, endPieceIndex+1).IterTyped(func(piece int) bool {
		p := &f.t.pieces[piece]
		left += int64(p.length() - p.numDirtyBytes())
		return true
	})
	startPiece := f.t.piece(firstPieceIndex)
	endChunk := int(f.offset%f.t.info.PieceLength) * int(startPiece.numChunks()) / int(startPiece.length())
	bitmap.Flip(startPiece.dirtyChunks, 0, endChunk).IterTyped(func(chunk int) bool {
		left -= int64(startPiece.chunkSize())
		return true
	})
	endPiece := f.t.piece(endPieceIndex)
	startChunk := int((f.offset+f.length)%f.t.info.PieceLength) * int(endPiece.numChunks()) / int(endPiece.length())
	lastChunkIndex := int(endPiece.lastChunkIndex())
	bitmap.Flip(endPiece.dirtyChunks, startChunk, int(endPiece.numChunks())).IterTyped(func(chunk int) bool {
		if chunk == lastChunkIndex {
			left -= int64(endPiece.chunkIndexSpec(pp.Integer(chunk)).Length)
		} else {
			left -= int64(endPiece.chunkSize())
		}
		return true
	})
	return
}

// The relative file path for a multi-file torrent, and the torrent name for a
// single-file torrent.
func (f *File) DisplayPath() string {
	fip := f.FileInfo().Path
	if len(fip) == 0 {
		return f.t.info.Name
	}
	return strings.Join(fip, "/")

}

// The download status of a piece that comprises part of a File.
type FilePieceState struct {
	Bytes int64 // Bytes within the piece that are part of this File.
	PieceState
}

// Returns the state of pieces in this file.
func (f *File) State() (ret []FilePieceState) {
	f.t.cl.rLock()
	defer f.t.cl.rUnlock()
	pieceSize := int64(f.t.usualPieceSize())
	off := f.offset % pieceSize
	remaining := f.length
	for i := pieceIndex(f.offset / pieceSize); ; i++ {
		if remaining == 0 {
			break
		}
		len1 := pieceSize - off
		if len1 > remaining {
			len1 = remaining
		}
		ps := f.t.pieceState(i)
		ret = append(ret, FilePieceState{len1, ps})
		off = 0
		remaining -= len1
	}
	return
}

// Requests that all pieces containing data in the file be downloaded.
func (f *File) Download() {
	f.SetPriority(PiecePriorityNormal)
}

func byteRegionExclusivePieces(off, size, pieceSize int64) (begin, end int) {
	begin = int((off + pieceSize - 1) / pieceSize)
	end = int((off + size) / pieceSize)
	return
}

// Deprecated: Use File.SetPriority.
func (f *File) Cancel() {
	f.SetPriority(PiecePriorityNone)
}

func (f *File) NewReader() Reader {
	tr := reader{
		mu:        f.t.cl.locker(),
		t:         f.t,
		readahead: 5 * 1024 * 1024,
		offset:    f.Offset(),
		length:    f.Length(),
	}
	f.t.addReader(&tr)
	return &tr
}

// Sets the minimum priority for pieces in the File.
func (f *File) SetPriority(prio piecePriority) {
	f.t.cl.lock()
	defer f.t.cl.unlock()
	if prio == f.prio {
		return
	}
	f.prio = prio
	f.t.updatePiecePriorities(f.firstPieceIndex(), f.endPieceIndex())
}

// Returns the priority per File.SetPriority.
func (f *File) Priority() piecePriority {
	f.t.cl.lock()
	defer f.t.cl.unlock()
	return f.prio
}

func (f *File) firstPieceIndex() pieceIndex {
	if f.t.usualPieceSize() == 0 {
		return 0
	}
	return pieceIndex(f.offset / int64(f.t.usualPieceSize()))
}

func (f *File) endPieceIndex() pieceIndex {
	if f.t.usualPieceSize() == 0 {
		return 0
	}
	return pieceIndex((f.offset+f.length-1)/int64(f.t.usualPieceSize())) + 1
}
<commit_msg>Change File.endPieceIndex calculation<commit_after>package torrent

import (
	"strings"

	"github.com/anacrolix/missinggo/bitmap"

	"github.com/anacrolix/torrent/metainfo"
	pp "github.com/anacrolix/torrent/peer_protocol"
)

// Provides access to regions of torrent data that correspond to its files.
type File struct {
	t      *Torrent
	path   string
	offset int64
	length int64
	fi     metainfo.FileInfo
	prio   piecePriority
}

func (f *File) Torrent() *Torrent {
	return f.t
}

// Data for this file begins this many bytes into the Torrent.
func (f *File) Offset() int64 {
	return f.offset
}

// The FileInfo from the metainfo.Info to which this file corresponds.
func (f File) FileInfo() metainfo.FileInfo {
	return f.fi
}

// The file's path components joined by '/'.
func (f File) Path() string {
	return f.path
}

// The file's length in bytes.
func (f *File) Length() int64 {
	return f.length
}

// Number of bytes of the entire file we have completed. This is the sum of
// completed pieces, and dirtied chunks of incomplete pieces.
func (f *File) BytesCompleted() int64 {
	f.t.cl.rLock()
	defer f.t.cl.rUnlock()
	return f.bytesCompleted()
}

func (f *File) bytesCompleted() int64 {
	return f.length - f.bytesLeft()
}

func (f *File) bytesLeft() (left int64) {
	firstPieceIndex := f.firstPieceIndex()
	endPieceIndex := f.endPieceIndex()
	bitmap.Flip(f.t.completedPieces, firstPieceIndex, endPieceIndex+1).IterTyped(func(piece int) bool {
		p := &f.t.pieces[piece]
		left += int64(p.length() - p.numDirtyBytes())
		return true
	})
	startPiece := f.t.piece(firstPieceIndex)
	endChunk := int(f.offset%f.t.info.PieceLength) * int(startPiece.numChunks()) / int(startPiece.length())
	bitmap.Flip(startPiece.dirtyChunks, 0, endChunk).IterTyped(func(chunk int) bool {
		left -= int64(startPiece.chunkSize())
		return true
	})
	endPiece := f.t.piece(endPieceIndex)
	startChunk := int((f.offset+f.length)%f.t.info.PieceLength) * int(endPiece.numChunks()) / int(endPiece.length())
	lastChunkIndex := int(endPiece.lastChunkIndex())
	bitmap.Flip(endPiece.dirtyChunks, startChunk, int(endPiece.numChunks())).IterTyped(func(chunk int) bool {
		if chunk == lastChunkIndex {
			left -= int64(endPiece.chunkIndexSpec(pp.Integer(chunk)).Length)
		} else {
			left -= int64(endPiece.chunkSize())
		}
		return true
	})
	return
}

// The relative file path for a multi-file torrent, and the torrent name for a
// single-file torrent.
func (f *File) DisplayPath() string {
	fip := f.FileInfo().Path
	if len(fip) == 0 {
		return f.t.info.Name
	}
	return strings.Join(fip, "/")

}

// The download status of a piece that comprises part of a File.
type FilePieceState struct {
	Bytes int64 // Bytes within the piece that are part of this File.
	PieceState
}

// Returns the state of pieces in this file.
func (f *File) State() (ret []FilePieceState) {
	f.t.cl.rLock()
	defer f.t.cl.rUnlock()
	pieceSize := int64(f.t.usualPieceSize())
	off := f.offset % pieceSize
	remaining := f.length
	for i := pieceIndex(f.offset / pieceSize); ; i++ {
		if remaining == 0 {
			break
		}
		len1 := pieceSize - off
		if len1 > remaining {
			len1 = remaining
		}
		ps := f.t.pieceState(i)
		ret = append(ret, FilePieceState{len1, ps})
		off = 0
		remaining -= len1
	}
	return
}

// Requests that all pieces containing data in the file be downloaded.
func (f *File) Download() {
	f.SetPriority(PiecePriorityNormal)
}

func byteRegionExclusivePieces(off, size, pieceSize int64) (begin, end int) {
	begin = int((off + pieceSize - 1) / pieceSize)
	end = int((off + size) / pieceSize)
	return
}

// Deprecated: Use File.SetPriority.
func (f *File) Cancel() {
	f.SetPriority(PiecePriorityNone)
}

func (f *File) NewReader() Reader {
	tr := reader{
		mu:        f.t.cl.locker(),
		t:         f.t,
		readahead: 5 * 1024 * 1024,
		offset:    f.Offset(),
		length:    f.Length(),
	}
	f.t.addReader(&tr)
	return &tr
}

// Sets the minimum priority for pieces in the File.
func (f *File) SetPriority(prio piecePriority) {
	f.t.cl.lock()
	defer f.t.cl.unlock()
	if prio == f.prio {
		return
	}
	f.prio = prio
	f.t.updatePiecePriorities(f.firstPieceIndex(), f.endPieceIndex())
}

// Returns the priority per File.SetPriority.
func (f *File) Priority() piecePriority {
	f.t.cl.lock()
	defer f.t.cl.unlock()
	return f.prio
}

func (f *File) firstPieceIndex() pieceIndex {
	if f.t.usualPieceSize() == 0 {
		return 0
	}
	return pieceIndex(f.offset / int64(f.t.usualPieceSize()))
}

func (f *File) endPieceIndex() pieceIndex {
	if f.t.usualPieceSize() == 0 {
		return 0
	}
	return pieceIndex((f.offset + f.length + int64(f.t.usualPieceSize()) - 1) / int64(f.t.usualPieceSize()))
}
<|endoftext|>


<commit_before>package fins

import (
	"fmt"
	"os"
	"sync"

	"github.com/jarosser06/fins/pkg/version"
	"github.com/jarosser06/fins/supermarket"
	chef "github.com/marpaia/chef-golang"
	"github.com/op/go-logging"
)

var (
	logFormat = logging.MustStringFormatter("%{color:yellow} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}")
	log       = logging.MustGetLogger("fins")
)

// Main data structure for fins, keeps the config, client(s)
type Fins struct {
	cache             RemoteCache
	config            Config
	chefClient        chef.Chef
	supermarketClient supermarket.Client
}

// Returns a Fins struct
func Init(config string, logLevel string) Fins {
	c, err := LoadConfig(config)
	if err != nil {
		fmt.Println("Error: ", err)
	}
	f := Fins{config: c}

	// Setup logging
	l, err := logging.LogLevel(logLevel)
	if err != nil {
		fmt.Println("Error: Bad log level ", logLevel)
	}
	b := logging.NewLogBackend(os.Stdout, "", 0)
	bFormatted := logging.NewBackendFormatter(b, logFormat)

	bLeveled := logging.AddModuleLevel(bFormatted)
	bLeveled.SetLevel(l, "")
	logging.SetBackend(bLeveled)

	// Get the chef client
	f.chefClient, err = c.ChefClient()
	if err != nil {
		log.Fatal("error creating ChefClient %v", err)
	}

	// Get the supermarket client
	f.supermarketClient = supermarket.NewClient()

	return f
}

// Displays Outdated Versions of Cookbooks in a Chef server
func (f *Fins) Outdated() int {
	var wg sync.WaitGroup
	var supCookbook supermarket.Cookbook

	for name, ver := range f.ChefServerCookbooks() {

		wg.Add(1)

		go func() {
			supCookbook = f.LatestSupermarketCookbook(name)
			wg.Done()
		}()
		wg.Wait()

		var supVer string
		if len(supCookbook.Version) == 0 {
			log.Debug("%s not found in supermarket", name)
			supVer = "not found"
			// Skip version compare
			goto L1
		} else {
			supVer = supCookbook.Version
		}

		if version.Compare(ver, supVer) != version.LessThan {
			continue
		}
	L1:
		fmt.Printf(
			"%s:\n  Chef Server: %s\n  Supermarket: %s\n",
			name,
			ver,
			supVer,
		)
	}

	return 0
}

// Displays a diff between the chef server and an environment
func (f *Fins) DiffServer(envName string) int {
	env, eOk := f.Environment(envName)
	if !eOk {
		fmt.Printf("Error: environment %s not found\n", envName)
		return 1
	}

	c := f.ChefServerCookbooks()
	if len(env.CookbookVersions) > 0 {
		for name, v := range env.CookbookVersions {

			// Handle when a cookbook isn't found
			var cVer string
			if ver, ok := c[name]; ok {
				cVer = ver
			} else {
				cVer = "no cookbook exists"
			}

			if !version.MatchConstraint(cVer, v) {
				fmt.Printf(
					"%s:\n  Environment Constraint: %s\n  Latest Cookbook Version: %s\n",
					name,
					v,
					cVer,
				)
			}
		}
	}

	return 0
}

// Displays a diff between two chef environments
func (f *Fins) DiffEnvironments(env1 string, env2 string) int {
	e1, eOk := f.Environment(env1)
	if !eOk {
		fmt.Printf("Error: environment %s not found\n", env1)
		return 1
	}

	e2, eOk := f.Environment(env2)
	if !eOk {
		fmt.Printf("Error: environment %s not found\n", env2)
		return 1
	}

	c := f.ChefServerCookbooks()
	for _, cName := range mergeCookbookLists(e1.CookbookVersions, e2.CookbookVersions) {
		//for cName, e1Ver := range e1.CookbookVersions {
		var e1Ver, e2Ver string
		if ver, ok := e1.CookbookVersions[cName]; ok {
			log.Debug("found cookbook constraint %s for %s", ver, cName)
			e1Ver = ver
		} else {
			log.Debug("%s has no constraint for %s", env2, cName)
			e1Ver = "no version constraint"
		}

		if ver, ok := e2.CookbookVersions[cName]; ok {
			log.Debug("found cookbook constraint %s for %s", ver, cName)
			e2Ver = ver
		} else {
			log.Debug("%s has no constraint for %s", env2, cName)
			e2Ver = "no version constraint"
		}

		if e1Ver == e2Ver {
			log.Debug("env \"%s\"@%s and env \"%s\"@%s constrains match", e1Ver, env1, e2Ver, env2)
			continue
		}

		var cVer string
		if ver, ok := c[cName]; ok {
			cVer = ver
		} else {
			cVer = "no cookbook exists"
		}

		fmt.Printf(
			"%s:\n  %s Constraint: %s\n  %s Constraint: %s\n  Latest Cookbook Version: %s\n",
			cName,
			env1,
			e1Ver,
			env2,
			e2Ver,
			cVer,
		)
	}

	return 0
}

// Take the Cookbook Version map and produce a slice of cookbook names
// to be iterated over
func mergeCookbookLists(list1 map[string]string, list2 map[string]string) []string {
	var result []string
	var wg sync.WaitGroup
	in := make(chan string)
	out := make(chan string)

	// Send cookbooks from list1 onto the channel
	wg.Add(2)
	go func() {
		for name, _ := range list1 {
			in <- name
		}
		wg.Done()
	}()

	go func() {
		for name, _ := range list2 {
			in <- name
		}
		wg.Done()
	}()

	go func() {
		wg.Wait()
		close(in)
	}()

	// Check for duplicates and discard
	go func() {
		listMap := make(map[string]bool)
		for {
			if name, ok := <-in; ok {
				if _, k := listMap[name]; !k {
					listMap[name] = true
					out <- name
				}
			} else {
				break
			}
		}
		close(out)
	}()

	for {
		if name, ok := <-out; ok {
			result = append(result, name)
		} else {
			break
		}
	}

	return result
}
<commit_msg>removed unused go routine<commit_after>package fins

import (
	"fmt"
	"os"
	"sync"

	"github.com/jarosser06/fins/pkg/version"
	"github.com/jarosser06/fins/supermarket"
	chef "github.com/marpaia/chef-golang"
	"github.com/op/go-logging"
)

var (
	logFormat = logging.MustStringFormatter("%{color:yellow} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}")
	log       = logging.MustGetLogger("fins")
)

// Main data structure for fins, keeps the config, client(s)
type Fins struct {
	cache             RemoteCache
	config            Config
	chefClient        chef.Chef
	supermarketClient supermarket.Client
}

// Returns a Fins struct
func Init(config string, logLevel string) Fins {
	c, err := LoadConfig(config)
	if err != nil {
		fmt.Println("Error: ", err)
	}
	f := Fins{config: c}

	// Setup logging
	l, err := logging.LogLevel(logLevel)
	if err != nil {
		fmt.Println("Error: Bad log level ", logLevel)
	}
	b := logging.NewLogBackend(os.Stdout, "", 0)
	bFormatted := logging.NewBackendFormatter(b, logFormat)

	bLeveled := logging.AddModuleLevel(bFormatted)
	bLeveled.SetLevel(l, "")
	logging.SetBackend(bLeveled)

	// Get the chef client
	f.chefClient, err = c.ChefClient()
	if err != nil {
		log.Fatal("error creating ChefClient %v", err)
	}

	// Get the supermarket client
	f.supermarketClient = supermarket.NewClient()

	return f
}

// Displays Outdated Versions of Cookbooks in a Chef server
func (f *Fins) Outdated() int {
	var supCookbook supermarket.Cookbook

	for name, ver := range f.ChefServerCookbooks() {

		supCookbook = f.LatestSupermarketCookbook(name)

		var supVer string
		if len(supCookbook.Version) == 0 {
			log.Debug("%s not found in supermarket", name)
			supVer = "not found"
			// Skip version compare
			goto L1
		} else {
			supVer = supCookbook.Version
		}

		if version.Compare(ver, supVer) != version.LessThan {
			continue
		}
	L1:
		fmt.Printf(
			"%s:\n  Chef Server: %s\n  Supermarket: %s\n",
			name,
			ver,
			supVer,
		)
	}

	return 0
}

// Displays a diff between the chef server and an environment
func (f *Fins) DiffServer(envName string) int {
	env, eOk := f.Environment(envName)
	if !eOk {
		fmt.Printf("Error: environment %s not found\n", envName)
		return 1
	}

	c := f.ChefServerCookbooks()
	if len(env.CookbookVersions) > 0 {
		for name, v := range env.CookbookVersions {

			// Handle when a cookbook isn't found
			var cVer string
			if ver, ok := c[name]; ok {
				cVer = ver
			} else {
				cVer = "no cookbook exists"
			}

			if !version.MatchConstraint(cVer, v) {
				fmt.Printf(
					"%s:\n  Environment Constraint: %s\n  Latest Cookbook Version: %s\n",
					name,
					v,
					cVer,
				)
			}
		}
	}

	return 0
}

// Displays a diff between two chef environments
func (f *Fins) DiffEnvironments(env1 string, env2 string) int {
	e1, eOk := f.Environment(env1)
	if !eOk {
		fmt.Printf("Error: environment %s not found\n", env1)
		return 1
	}

	e2, eOk := f.Environment(env2)
	if !eOk {
		fmt.Printf("Error: environment %s not found\n", env2)
		return 1
	}

	c := f.ChefServerCookbooks()
	for _, cName := range mergeCookbookLists(e1.CookbookVersions, e2.CookbookVersions) {
		//for cName, e1Ver := range e1.CookbookVersions {
		var e1Ver, e2Ver string
		if ver, ok := e1.CookbookVersions[cName]; ok {
			log.Debug("found cookbook constraint %s for %s", ver, cName)
			e1Ver = ver
		} else {
			log.Debug("%s has no constraint for %s", env2, cName)
			e1Ver = "no version constraint"
		}

		if ver, ok := e2.CookbookVersions[cName]; ok {
			log.Debug("found cookbook constraint %s for %s", ver, cName)
			e2Ver = ver
		} else {
			log.Debug("%s has no constraint for %s", env2, cName)
			e2Ver = "no version constraint"
		}

		if e1Ver == e2Ver {
			log.Debug("env \"%s\"@%s and env \"%s\"@%s constrains match", e1Ver, env1, e2Ver, env2)
			continue
		}

		var cVer string
		if ver, ok := c[cName]; ok {
			cVer = ver
		} else {
			cVer = "no cookbook exists"
		}

		fmt.Printf(
			"%s:\n  %s Constraint: %s\n  %s Constraint: %s\n  Latest Cookbook Version: %s\n",
			cName,
			env1,
			e1Ver,
			env2,
			e2Ver,
			cVer,
		)
	}

	return 0
}

// Take the Cookbook Version map and produce a slice of cookbook names
// to be iterated over
func mergeCookbookLists(list1 map[string]string, list2 map[string]string) []string {
	var result []string
	var wg sync.WaitGroup
	in := make(chan string)
	out := make(chan string)

	// Send cookbooks from list1 onto the channel
	wg.Add(2)
	go func() {
		for name, _ := range list1 {
			in <- name
		}
		wg.Done()
	}()

	go func() {
		for name, _ := range list2 {
			in <- name
		}
		wg.Done()
	}()

	go func() {
		wg.Wait()
		close(in)
	}()

	// Check for duplicates and discard
	go func() {
		listMap := make(map[string]bool)
		for {
			if name, ok := <-in; ok {
				if _, k := listMap[name]; !k {
					listMap[name] = true
					out <- name
				}
			} else {
				break
			}
		}
		close(out)
	}()

	for {
		if name, ok := <-out; ok {
			result = append(result, name)
		} else {
			break
		}
	}

	return result
}
<|endoftext|>


<commit_before>/*
 Copyright (c) 2014 Soichiro Kashima
*/
package fint

import (
	"bufio"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
)

const bufSize = 4096

type Opt struct {
	SrcRoot    string
	ConfigPath string
	Locale     string
	Id         string
}

type Rule struct {
	Pattern string
	Args    []interface{}
	Message map[string]string
}

type Module struct {
	Id    string
	Rules []Rule
}

type RuleSet struct {
	Id          string
	Description string
	Pattern     string
	Modules     []Module
}

type Config struct {
	RuleSets []RuleSet
}

type Violation struct {
	Filename string
	Line     int
	Message  string
}

var (
	opt        *Opt
	config     *Config
	violations []Violation
	term       string
)

func getOpts() (err error) {
	srcRoot := flag.String("s", "", "Source directory")
	configPath := flag.String("c", "conf/config.json", "Config file path")
	locale := flag.String("l", "default", "Message locale")
	id := flag.String("i", "", "ID of the rule set")
	flag.Parse()
	if *srcRoot == "" {
		err = errors.New("fint: source directory is required.")
		return
	}
	if *id == "" {
		err = errors.New("fint: ID of the rule set is required.")
		return
	}
	opt = &Opt{SrcRoot: *srcRoot, ConfigPath: *configPath, Locale: *locale, Id: *id}
	return
}

func printViolation(v Violation) {
	var format string
	if term == "dumb" {
		format = "%s:%d:1: warning: %s\n"
	} else {
		format = "%s:%d:1: warning: %s\n"
	}
	fmt.Printf(format, v.Filename, v.Line, v.Message)
}

func LoadConfig(file []byte) *Config {
	var c Config
	json.Unmarshal(file, &c)
	return &c
}

func checkSourceFile(filename string, rs RuleSet) (vs []Violation, err error) {
	var f *os.File
	f, err = os.Open(filename)
	if err != nil {
		err = errors.New("fint: cannot open " + filename)
		return
	}
	defer f.Close()
	r := bufio.NewReaderSize(f, bufSize)
	for n := 1; true; n++ {
		var (
			lineBytes []byte
			isPrefix  bool
		)
		lineBytes, isPrefix, err = r.ReadLine()
		if isPrefix {
			err = errors.New(fmt.Sprintf("fint: too long line: %s", filename))
			return
		}
		line := string(lineBytes)
		if err != io.EOF && err != nil {
			return
		}
		for i := range rs.Modules {
			switch rs.Modules[i].Id {
			case "pattern_match":
				for j := range rs.Modules[i].Rules {
					if matched, _ := regexp.MatchString(rs.Modules[i].Rules[j].Pattern, line); matched {
						vs = append(vs, Violation{Filename: filename, Line: n, Message: rs.Modules[i].Rules[j].Message[opt.Locale]})
					}
				}
			case "max_length":
				for j := range rs.Modules[i].Rules {
					if matched, _ := regexp.MatchString(rs.Modules[i].Rules[j].Pattern, line); matched {
						max_len := int(rs.Modules[i].Rules[j].Args[0].(float64))
						if too_long := max_len < len(line); too_long {
							vs = append(vs, Violation{Filename: filename, Line: n, Message: fmt.Sprintf(rs.Modules[i].Rules[j].Message[opt.Locale], max_len)})
						}
					}
				}
			}
		}
		if err == io.EOF {
			err = nil
			break
		}
	}
	return
}

func findRuleSet() (rs RuleSet, err error) {
	for i := range config.RuleSets {
		r := config.RuleSets[i]
		if r.Id == opt.Id {
			rs = r
		}
	}
	if rs.Id == "" {
		err = errors.New("fint: no matching ruleset to [" + opt.Id + "]")
	}
	return
}

func checkFile(path string, f os.FileInfo, err error) error {
	if err != nil {
		return err
	}

	rs, errRs := findRuleSet()
	if errRs != nil {
		return errRs
	}

	if matched, _ := regexp.MatchString(rs.Pattern, path); matched {
		v, e := checkSourceFile(path, rs)
		if e != nil {
			return e
		}
		violations = append(violations, v...)
	}
	return nil
}

func pluralize(value int, singular, plural string) string {
	if value < 2 {
		return singular
	}
	return plural
}

func Execute() (err error) {
	conf, err := ioutil.ReadFile(opt.ConfigPath)
	if err != nil {
		return
	}
	config = LoadConfig(conf)

	err = filepath.Walk(opt.SrcRoot, checkFile)
	return
}

func ExecuteAsCommand() {
	err := getOpts()
	if err != nil {
		fmt.Printf("fint: %v\n", err)
		fmt.Println("fint: error while executing lint")
		os.Exit(1)
	}

	term = os.Getenv("TERM")

	err = Execute()
	if err != nil {
		fmt.Printf("fint: %v\n", err)
		fmt.Println("fint: error while executing lint")
		os.Exit(1)
	}
	for i := range violations {
		printViolation(violations[i])
	}

	if 0 < len(violations) {
		fmt.Printf("\n%d %s generated.\n",
			len(violations), pluralize(len(violations), "warning", "warnings"))
		os.Exit(1)
	}
}
<commit_msg>Changed error message prefix string to a constant and wrapped to a function.<commit_after>/*
 Copyright (c) 2014 Soichiro Kashima
*/
package fint

import (
	"bufio"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
)

const (
	errPrefix = "fint: "
	bufSize   = 4096
)

type Opt struct {
	SrcRoot    string
	ConfigPath string
	Locale     string
	Id         string
}

type Rule struct {
	Pattern string
	Args    []interface{}
	Message map[string]string
}

type Module struct {
	Id    string
	Rules []Rule
}

type RuleSet struct {
	Id          string
	Description string
	Pattern     string
	Modules     []Module
}

type Config struct {
	RuleSets []RuleSet
}

type Violation struct {
	Filename string
	Line     int
	Message  string
}

var (
	opt        *Opt
	config     *Config
	violations []Violation
	term       string
)

func newError(message string) error {
	return errors.New(errPrefix + message)
}

func getOpts() (err error) {
	srcRoot := flag.String("s", "", "Source directory")
	configPath := flag.String("c", "conf/config.json", "Config file path")
	locale := flag.String("l", "default", "Message locale")
	id := flag.String("i", "", "ID of the rule set")
	flag.Parse()
	if *srcRoot == "" {
		err = newError("source directory is required.")
		return
	}
	if *id == "" {
		err = newError("ID of the rule set is required.")
		return
	}
	opt = &Opt{SrcRoot: *srcRoot, ConfigPath: *configPath, Locale: *locale, Id: *id}
	return
}

func printViolation(v Violation) {
	var format string
	if term == "dumb" {
		format = "%s:%d:1: warning: %s\n"
	} else {
		format = "%s:%d:1: warning: %s\n"
	}
	fmt.Printf(format, v.Filename, v.Line, v.Message)
}

func LoadConfig(file []byte) *Config {
	var c Config
	json.Unmarshal(file, &c)
	return &c
}

func checkSourceFile(filename string, rs RuleSet) (vs []Violation, err error) {
	var f *os.File
	f, err = os.Open(filename)
	if err != nil {
		err = newError("fint: cannot open " + filename)
		return
	}
	defer f.Close()
	r := bufio.NewReaderSize(f, bufSize)
	for n := 1; true; n++ {
		var (
			lineBytes []byte
			isPrefix  bool
		)
		lineBytes, isPrefix, err = r.ReadLine()
		if isPrefix {
			err = newError(fmt.Sprintf("too long line: %s", filename))
			return
		}
		line := string(lineBytes)
		if err != io.EOF && err != nil {
			return
		}
		for i := range rs.Modules {
			switch rs.Modules[i].Id {
			case "pattern_match":
				for j := range rs.Modules[i].Rules {
					if matched, _ := regexp.MatchString(rs.Modules[i].Rules[j].Pattern, line); matched {
						vs = append(vs, Violation{Filename: filename, Line: n, Message: rs.Modules[i].Rules[j].Message[opt.Locale]})
					}
				}
			case "max_length":
				for j := range rs.Modules[i].Rules {
					if matched, _ := regexp.MatchString(rs.Modules[i].Rules[j].Pattern, line); matched {
						max_len := int(rs.Modules[i].Rules[j].Args[0].(float64))
						if too_long := max_len < len(line); too_long {
							vs = append(vs, Violation{Filename: filename, Line: n, Message: fmt.Sprintf(rs.Modules[i].Rules[j].Message[opt.Locale], max_len)})
						}
					}
				}
			}
		}
		if err == io.EOF {
			err = nil
			break
		}
	}
	return
}

func findRuleSet() (rs RuleSet, err error) {
	for i := range config.RuleSets {
		r := config.RuleSets[i]
		if r.Id == opt.Id {
			rs = r
		}
	}
	if rs.Id == "" {
		err = newError("no matching ruleset to [" + opt.Id + "]")
	}
	return
}

func checkFile(path string, f os.FileInfo, err error) error {
	if err != nil {
		return err
	}

	rs, errRs := findRuleSet()
	if errRs != nil {
		return errRs
	}

	if matched, _ := regexp.MatchString(rs.Pattern, path); matched {
		v, e := checkSourceFile(path, rs)
		if e != nil {
			return e
		}
		violations = append(violations, v...)
	}
	return nil
}

func pluralize(value int, singular, plural string) string {
	if value < 2 {
		return singular
	}
	return plural
}

func Execute() (err error) {
	conf, err := ioutil.ReadFile(opt.ConfigPath)
	if err != nil {
		return
	}
	config = LoadConfig(conf)

	err = filepath.Walk(opt.SrcRoot, checkFile)
	return
}

func ExecuteAsCommand() {
	err := getOpts()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	term = os.Getenv("TERM")

	err = Execute()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	for i := range violations {
		printViolation(violations[i])
	}

	if 0 < len(violations) {
		fmt.Printf("\n%d %s generated.\n",
			len(violations), pluralize(len(violations), "warning", "warnings"))
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package compile

import (
	"path/filepath"
	"strings"
	"unicode"
)

// capitalize changes the first letter of a string to upper case.
func capitalize(s string) string {
	if len(s) == 0 {
		return s
	}
	x := []rune(s)
	return string(unicode.ToUpper(x[0])) + string(x[1:])
	// TODO(abg): surely there has to be a better way
}

// fileBaseName returns the base name of the given file without the extension.
func fileBaseName(p string) string {
	p = filepath.Base(p)
	if i := strings.LastIndexByte(p, '.'); i > 0 {
		p = p[:i]
	}
	return p
}

// splitInclude splits the given string at the first ".".
//
// If the given string doesn't have a '.', the second returned string contains
// the entirety of it.
func splitInclude(s string) (string, string) {
	if i := strings.IndexRune(s, '.'); i > 0 {
		return s[:i], s[i+1:]
	}
	return "", s
}
<commit_msg>Simpler fileBaseName implementation<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package compile

import (
	"path/filepath"
	"strings"
	"unicode"
)

// capitalize changes the first letter of a string to upper case.
func capitalize(s string) string {
	if len(s) == 0 {
		return s
	}
	x := []rune(s)
	return string(unicode.ToUpper(x[0])) + string(x[1:])
	// TODO(abg): surely there has to be a better way
}

// fileBaseName returns the base name of the given file without the extension.
func fileBaseName(p string) string {
	return strings.TrimSuffix(filepath.Base(p), filepath.Ext(p))
}

// splitInclude splits the given string at the first ".".
//
// If the given string doesn't have a '.', the second returned string contains
// the entirety of it.
func splitInclude(s string) (string, string) {
	if i := strings.IndexRune(s, '.'); i > 0 {
		return s[:i], s[i+1:]
	}
	return "", s
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"strings"
)

// Flag defines a flag for a command.
// These will be parsed in Go and passed to the Run method in the Context struct.
type Flag struct {
	Name        string `json:"name"`
	Char        string `json:"char"`
	Description string `json:"description"`
	HasValue    bool   `json:"hasValue"`
	Hidden      bool   `json:"hidden"`
	Required    bool   `json:"required"`
}

var appFlag = &Flag{
	Name:        "app",
	Char:        "a",
	HasValue:    true,
	Description: "app to run command against",
}

var remoteFlag = &Flag{
	Name:        "remote",
	Char:        "r",
	HasValue:    true,
	Description: "git remote of app to run command against",
}

var debuggerFlag = &Flag{
	Name: "debugger",
}

func (f *Flag) String() string {
	s := " "
	switch {
	case f.Char != "" && f.Name != "":
		s = s + "-" + f.Char + ", --" + f.Name
	case f.Char != "":
		s = s + "-" + f.Char
	case f.Name != "":
		s = s + "--" + f.Name
	}
	if f.HasValue {
		s = s + " " + strings.ToUpper(f.Name)
	}
	return s
}

func parseFlag(input string, flags []*Flag) (*Flag, string, error) {
	keyvalue := strings.SplitN(input, "=", 2)
	key := keyvalue[0]
	value := ""
	if len(keyvalue) == 2 {
		value = keyvalue[1]
	}
	if len(key) > 2 && key[1] != '-' {
		return parseFlag(key[:2]+"="+key[2:], flags)
	}
	for _, flag := range flags {
		if key == "-"+flag.Char || key == "--"+flag.Name {
			if flag.HasValue {
				if value == "" {
					return nil, "", errors.New(flag.String() + " needs a value")
				}
				return flag, value, nil
			}
			if value != "" {
				return nil, "", errors.New(flag.String() + " does not take a value")
			}
			return flag, "", nil
		}
	}
	return nil, "", nil
}
<commit_msg>fix bug with "-" flag<commit_after>package main

import (
	"errors"
	"strings"
)

// Flag defines a flag for a command.
// These will be parsed in Go and passed to the Run method in the Context struct.
type Flag struct {
	Name        string `json:"name"`
	Char        string `json:"char"`
	Description string `json:"description"`
	HasValue    bool   `json:"hasValue"`
	Hidden      bool   `json:"hidden"`
	Required    bool   `json:"required"`
}

var appFlag = &Flag{
	Name:        "app",
	Char:        "a",
	HasValue:    true,
	Description: "app to run command against",
}

var remoteFlag = &Flag{
	Name:        "remote",
	Char:        "r",
	HasValue:    true,
	Description: "git remote of app to run command against",
}

var debuggerFlag = &Flag{
	Name: "debugger",
}

func (f *Flag) String() string {
	s := " "
	switch {
	case f.Char != "" && f.Name != "":
		s = s + "-" + f.Char + ", --" + f.Name
	case f.Char != "":
		s = s + "-" + f.Char
	case f.Name != "":
		s = s + "--" + f.Name
	}
	if f.HasValue {
		s = s + " " + strings.ToUpper(f.Name)
	}
	return s
}

func parseFlag(input string, flags []*Flag) (*Flag, string, error) {
	keyvalue := strings.SplitN(input, "=", 2)
	key := keyvalue[0]
	value := ""
	if len(keyvalue) == 2 {
		value = keyvalue[1]
	}
	if len(key) > 2 && key[1] != '-' {
		return parseFlag(key[:2]+"="+key[2:], flags)
	}
	for _, flag := range flags {
		if (flag.Char != "" && key == "-"+flag.Char) || key == "--"+flag.Name {
			if flag.HasValue {
				if value == "" {
					return nil, "", errors.New(flag.String() + " needs a value")
				}
				return flag, value, nil
			}
			if value != "" {
				return nil, "", errors.New(flag.String() + " does not take a value")
			}
			return flag, "", nil
		}
	}
	return nil, "", nil
}
<|endoftext|>


<commit_before>package tview

import (
	"github.com/gdamore/tcell"
)

// Configuration values.
const (
	FlexRow = iota
	FlexColumn
)

// flexItem holds layout options for one item.
type flexItem struct {
	Item       Primitive // The item to be positioned. May be nil for an empty item.
	FixedSize  int       // The item's fixed size which may not be changed, 0 if it has no fixed size.
	Proportion int       // The item's proportion.
	Focus      bool      // Whether or not this item attracts the layout's focus.
}

// Flex is a basic implementation of the Flexbox layout. The contained
// primitives are arranged horizontally or vertically. The way they are
// distributed along that dimension depends on their layout settings, which is
// either a fixed length or a proportional length. See AddItem() for details.
//
// See https://github.com/rivo/tview/wiki/Flex for an example.
type Flex struct {
	*Box

	// The items to be positioned.
	items []flexItem

	// FlexRow or FlexColumn.
	direction int

	// If set to true, will use the entire screen as its available space instead
	// its box dimensions.
	fullScreen bool
}

// NewFlex returns a new flexbox layout container with no primitives and its
// direction set to FlexColumn. To add primitives to this layout, see AddItem().
// To change the direction, see SetDirection().
func NewFlex() *Flex {
	f := &Flex{
		Box:       NewBox(),
		direction: FlexColumn,
	}
	f.focus = f
	return f
}

// SetDirection sets the direction in which the contained primitives are
// distributed. This can be either FlexColumn (default) or FlexRow.
func (f *Flex) SetDirection(direction int) *Flex {
	f.direction = direction
	return f
}

// SetFullScreen sets the flag which, when true, causes the flex layout to use
// the entire screen space instead of whatever size it is currently assigned to.
func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
	f.fullScreen = fullScreen
	return f
}

// AddItem adds a new item to the container. The "fixedSize" argument is a width
// or height that may not be changed by the layout algorithm. A value of 0 means
// that its size is flexible and may be changed. The "proportion" argument
// defines the relative size of the item compared to other flexible-size items.
// For example, items with a proportion of 2 will be twice as large as items
// with a proportion of 1. Must be at least 1 if fixedSize > 0 (ignored
// otherwise)
//
// If "focus" is set to true, the item will receive focus when the Flex
// primitive receives focus. If multiple items have the "focus" flag set to
// true, the first one will receive focus.
//
// You can provide a nil value for the primitive. This will still consume screen
// space but nothing will be drawn.
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
	f.items = append(f.items, flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
	return f
}

// RemoveItem removes all items for the given primitive from the container,
// keeping the order of the remaining items intact.
func (f *Flex) RemoveItem(p Primitive) *Flex {
	for index := len(f.items) - 1; index >= 0; index-- {
		if f.items[index].Item == p {
			f.items = append(f.items[:index], f.items[index+1:]...)
		}
	}
	return f
}

// Draw draws this primitive onto the screen.
func (f *Flex) Draw(screen tcell.Screen) {
	f.Box.Draw(screen)

	// Calculate size and position of the items.

	// Do we use the entire screen?
	if f.fullScreen {
		width, height := screen.Size()
		f.SetRect(0, 0, width, height)
	}

	// How much space can we distribute?
	x, y, width, height := f.GetInnerRect()
	var proportionSum int
	distSize := width
	if f.direction == FlexRow {
		distSize = height
	}
	for _, item := range f.items {
		if item.FixedSize > 0 {
			distSize -= item.FixedSize
		} else {
			proportionSum += item.Proportion
		}
	}

	// Calculate positions and draw items.
	pos := x
	if f.direction == FlexRow {
		pos = y
	}
	for _, item := range f.items {
		size := item.FixedSize
		if size <= 0 {
			size = distSize * item.Proportion / proportionSum
			distSize -= size
			proportionSum -= item.Proportion
		}
		if item.Item != nil {
			if f.direction == FlexColumn {
				item.Item.SetRect(pos, y, size, height)
			} else {
				item.Item.SetRect(x, pos, width, size)
			}
		}
		pos += size

		if item.Item != nil {
			if item.Item.GetFocusable().HasFocus() {
				defer item.Item.Draw(screen)
			} else {
				item.Item.Draw(screen)
			}
		}
	}
}

// Focus is called when this primitive receives focus.
func (f *Flex) Focus(delegate func(p Primitive)) {
	for _, item := range f.items {
		if item.Item != nil && item.Focus {
			delegate(item.Item)
			return
		}
	}
}

// HasFocus returns whether or not this primitive has focus.
func (f *Flex) HasFocus() bool {
	for _, item := range f.items {
		if item.Item != nil && item.Item.GetFocusable().HasFocus() {
			return true
		}
	}
	return false
}
<commit_msg>Documentation fix for flex.AddItem()<commit_after>package tview

import (
	"github.com/gdamore/tcell"
)

// Configuration values.
const (
	FlexRow = iota
	FlexColumn
)

// flexItem holds layout options for one item.
type flexItem struct {
	Item       Primitive // The item to be positioned. May be nil for an empty item.
	FixedSize  int       // The item's fixed size which may not be changed, 0 if it has no fixed size.
	Proportion int       // The item's proportion.
	Focus      bool      // Whether or not this item attracts the layout's focus.
}

// Flex is a basic implementation of the Flexbox layout. The contained
// primitives are arranged horizontally or vertically. The way they are
// distributed along that dimension depends on their layout settings, which is
// either a fixed length or a proportional length. See AddItem() for details.
//
// See https://github.com/rivo/tview/wiki/Flex for an example.
type Flex struct {
	*Box

	// The items to be positioned.
	items []flexItem

	// FlexRow or FlexColumn.
	direction int

	// If set to true, will use the entire screen as its available space instead
	// its box dimensions.
	fullScreen bool
}

// NewFlex returns a new flexbox layout container with no primitives and its
// direction set to FlexColumn. To add primitives to this layout, see AddItem().
// To change the direction, see SetDirection().
func NewFlex() *Flex {
	f := &Flex{
		Box:       NewBox(),
		direction: FlexColumn,
	}
	f.focus = f
	return f
}

// SetDirection sets the direction in which the contained primitives are
// distributed. This can be either FlexColumn (default) or FlexRow.
func (f *Flex) SetDirection(direction int) *Flex {
	f.direction = direction
	return f
}

// SetFullScreen sets the flag which, when true, causes the flex layout to use
// the entire screen space instead of whatever size it is currently assigned to.
func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
	f.fullScreen = fullScreen
	return f
}

// AddItem adds a new item to the container. The "fixedSize" argument is a width
// or height that may not be changed by the layout algorithm. A value of 0 means
// that its size is flexible and may be changed. The "proportion" argument
// defines the relative size of the item compared to other flexible-size items.
// For example, items with a proportion of 2 will be twice as large as items
// with a proportion of 1. The proportion must be at least 1 if fixedSize == 0
// (ignored otherwise).
//
// If "focus" is set to true, the item will receive focus when the Flex
// primitive receives focus. If multiple items have the "focus" flag set to
// true, the first one will receive focus.
//
// You can provide a nil value for the primitive. This will still consume screen
// space but nothing will be drawn.
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
	f.items = append(f.items, flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
	return f
}

// RemoveItem removes all items for the given primitive from the container,
// keeping the order of the remaining items intact.
func (f *Flex) RemoveItem(p Primitive) *Flex {
	for index := len(f.items) - 1; index >= 0; index-- {
		if f.items[index].Item == p {
			f.items = append(f.items[:index], f.items[index+1:]...)
		}
	}
	return f
}

// Draw draws this primitive onto the screen.
func (f *Flex) Draw(screen tcell.Screen) {
	f.Box.Draw(screen)

	// Calculate size and position of the items.

	// Do we use the entire screen?
	if f.fullScreen {
		width, height := screen.Size()
		f.SetRect(0, 0, width, height)
	}

	// How much space can we distribute?
	x, y, width, height := f.GetInnerRect()
	var proportionSum int
	distSize := width
	if f.direction == FlexRow {
		distSize = height
	}
	for _, item := range f.items {
		if item.FixedSize > 0 {
			distSize -= item.FixedSize
		} else {
			proportionSum += item.Proportion
		}
	}

	// Calculate positions and draw items.
	pos := x
	if f.direction == FlexRow {
		pos = y
	}
	for _, item := range f.items {
		size := item.FixedSize
		if size <= 0 {
			size = distSize * item.Proportion / proportionSum
			distSize -= size
			proportionSum -= item.Proportion
		}
		if item.Item != nil {
			if f.direction == FlexColumn {
				item.Item.SetRect(pos, y, size, height)
			} else {
				item.Item.SetRect(x, pos, width, size)
			}
		}
		pos += size

		if item.Item != nil {
			if item.Item.GetFocusable().HasFocus() {
				defer item.Item.Draw(screen)
			} else {
				item.Item.Draw(screen)
			}
		}
	}
}

// Focus is called when this primitive receives focus.
func (f *Flex) Focus(delegate func(p Primitive)) {
	for _, item := range f.items {
		if item.Item != nil && item.Focus {
			delegate(item.Item)
			return
		}
	}
}

// HasFocus returns whether or not this primitive has focus.
func (f *Flex) HasFocus() bool {
	for _, item := range f.items {
		if item.Item != nil && item.Item.GetFocusable().HasFocus() {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package tview

import (
	"github.com/gdamore/tcell"
)

// Configuration values.
const (
	FlexRow = iota
	FlexColumn
)

// flexItem holds layout options for one item.
type flexItem struct {
	Item       Primitive // The item to be positioned.
	FixedSize  int       // The item's fixed size which may not be changed, 0 if it has no fixed size.
	Proportion int       // The item's proportion.
	Focus      bool      // Whether or not this item attracts the layout's focus.
}

// Flex is a basic implementation of the Flexbox layout.
//
// See https://github.com/rivo/tview/wiki/Flex for an example.
type Flex struct {
	*Box

	// The items to be positioned.
	items []flexItem

	// FlexRow or FlexColumn.
	direction int

	// If set to true, will use the entire screen as its available space instead
	// its box dimensions.
	fullScreen bool
}

// NewFlex returns a new flexbox layout container with the given primitives.
// The items all have no fixed size. If more control is needed, call AddItem().
// The direction argument must be FlexRow or FlexColumn.
func NewFlex() *Flex {
	f := &Flex{
		Box:       NewBox(),
		direction: FlexColumn,
	}
	f.focus = f
	return f
}

// SetDirection sets the direction in which the contained primitives are
// distributed. This can be either FlexColumn (default) or FlexRow.
func (f *Flex) SetDirection(direction int) *Flex {
	f.direction = direction
	return f
}

// SetFullScreen sets the flag which, when true, causes the flex layout to use
// the entire screen space instead of whatever size it is currently assigned to.
func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
	f.fullScreen = fullScreen
	return f
}

// AddItem adds a new item to the container. The "fixedSize" argument is a width
// or height that may not be changed by the layout algorithm. A value of 0 means
// that its size is flexible and may be changed. The "proportion" argument
// defines the relative size of the item compared to other flexible-size items.
// For example, items with a proportion of 2 will be twice as large as items
// with a proportion of 1. Must be at least 1 if fixedSize > 0 (ignored
// otherwise)
//
// If "focus" is set to true, the item will receive focus when the Flex
// primitive receives focus. If multiple items have the "focus" flag set to
// true, the first one will receive focus.
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
	f.items = append(f.items, flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
	return f
}

// Draw draws this primitive onto the screen.
func (f *Flex) Draw(screen tcell.Screen) {
	f.Box.Draw(screen)

	// Calculate size and position of the items.

	// Do we use the entire screen?
	if f.fullScreen {
		width, height := screen.Size()
		f.SetRect(0, 0, width, height)
	}

	// How much space can we distribute?
	x, y, width, height := f.GetInnerRect()
	var proportionSum int
	distSize := width
	if f.direction == FlexRow {
		distSize = height
	}
	for _, item := range f.items {
		if item.FixedSize > 0 {
			distSize -= item.FixedSize
		} else {
			proportionSum += item.Proportion
		}
	}

	// Calculate positions and draw items.
	pos := x
	if f.direction == FlexRow {
		pos = y
	}
	for _, item := range f.items {
		size := item.FixedSize
		if size <= 0 {
			size = distSize * item.Proportion / proportionSum
			distSize -= size
			proportionSum -= item.Proportion
		}
		if f.direction == FlexColumn {
			item.Item.SetRect(pos, y, size, height)
		} else {
			item.Item.SetRect(x, pos, width, size)
		}
		pos += size

		if item.Item.GetFocusable().HasFocus() {
			defer item.Item.Draw(screen)
		} else {
			item.Item.Draw(screen)
		}
	}
}

// Focus is called when this primitive receives focus.
func (f *Flex) Focus(delegate func(p Primitive)) {
	for _, item := range f.items {
		if item.Focus {
			delegate(item.Item)
			return
		}
	}
}

// HasFocus returns whether or not this primitive has focus.
func (f *Flex) HasFocus() bool {
	for _, item := range f.items {
		if item.Item.GetFocusable().HasFocus() {
			return true
		}
	}
	return false
}
<commit_msg>Flex layout can have nil elements.<commit_after>package tview

import (
	"github.com/gdamore/tcell"
)

// Configuration values.
const (
	FlexRow = iota
	FlexColumn
)

// flexItem holds layout options for one item.
type flexItem struct {
	Item       Primitive // The item to be positioned. May be nil for an empty item.
	FixedSize  int       // The item's fixed size which may not be changed, 0 if it has no fixed size.
	Proportion int       // The item's proportion.
	Focus      bool      // Whether or not this item attracts the layout's focus.
}

// Flex is a basic implementation of the Flexbox layout.
//
// See https://github.com/rivo/tview/wiki/Flex for an example.
type Flex struct {
	*Box

	// The items to be positioned.
	items []flexItem

	// FlexRow or FlexColumn.
	direction int

	// If set to true, will use the entire screen as its available space instead
	// its box dimensions.
	fullScreen bool
}

// NewFlex returns a new flexbox layout container with the given primitives.
// The items all have no fixed size. If more control is needed, call AddItem().
// The direction argument must be FlexRow or FlexColumn.
func NewFlex() *Flex {
	f := &Flex{
		Box:       NewBox(),
		direction: FlexColumn,
	}
	f.focus = f
	return f
}

// SetDirection sets the direction in which the contained primitives are
// distributed. This can be either FlexColumn (default) or FlexRow.
func (f *Flex) SetDirection(direction int) *Flex {
	f.direction = direction
	return f
}

// SetFullScreen sets the flag which, when true, causes the flex layout to use
// the entire screen space instead of whatever size it is currently assigned to.
func (f *Flex) SetFullScreen(fullScreen bool) *Flex {
	f.fullScreen = fullScreen
	return f
}

// AddItem adds a new item to the container. The "fixedSize" argument is a width
// or height that may not be changed by the layout algorithm. A value of 0 means
// that its size is flexible and may be changed. The "proportion" argument
// defines the relative size of the item compared to other flexible-size items.
// For example, items with a proportion of 2 will be twice as large as items
// with a proportion of 1. Must be at least 1 if fixedSize > 0 (ignored
// otherwise)
//
// If "focus" is set to true, the item will receive focus when the Flex
// primitive receives focus. If multiple items have the "focus" flag set to
// true, the first one will receive focus.
//
// You can provide a nil value for the primitive. This will still consume screen
// space but nothing will be drawn.
func (f *Flex) AddItem(item Primitive, fixedSize, proportion int, focus bool) *Flex {
	f.items = append(f.items, flexItem{Item: item, FixedSize: fixedSize, Proportion: proportion, Focus: focus})
	return f
}

// Draw draws this primitive onto the screen.
func (f *Flex) Draw(screen tcell.Screen) {
	f.Box.Draw(screen)

	// Calculate size and position of the items.

	// Do we use the entire screen?
	if f.fullScreen {
		width, height := screen.Size()
		f.SetRect(0, 0, width, height)
	}

	// How much space can we distribute?
	x, y, width, height := f.GetInnerRect()
	var proportionSum int
	distSize := width
	if f.direction == FlexRow {
		distSize = height
	}
	for _, item := range f.items {
		if item.FixedSize > 0 {
			distSize -= item.FixedSize
		} else {
			proportionSum += item.Proportion
		}
	}

	// Calculate positions and draw items.
	pos := x
	if f.direction == FlexRow {
		pos = y
	}
	for _, item := range f.items {
		size := item.FixedSize
		if size <= 0 {
			size = distSize * item.Proportion / proportionSum
			distSize -= size
			proportionSum -= item.Proportion
		}
		if item.Item != nil {
			if f.direction == FlexColumn {
				item.Item.SetRect(pos, y, size, height)
			} else {
				item.Item.SetRect(x, pos, width, size)
			}
		}
		pos += size

		if item.Item != nil {
			if item.Item.GetFocusable().HasFocus() {
				defer item.Item.Draw(screen)
			} else {
				item.Item.Draw(screen)
			}
		}
	}
}

// Focus is called when this primitive receives focus.
func (f *Flex) Focus(delegate func(p Primitive)) {
	for _, item := range f.items {
		if item.Item != nil && item.Focus {
			delegate(item.Item)
			return
		}
	}
}

// HasFocus returns whether or not this primitive has focus.
func (f *Flex) HasFocus() bool {
	for _, item := range f.items {
		if item.Item != nil && item.Item.GetFocusable().HasFocus() {
			return true
		}
	}
	return false
}
<|endoftext|>