<commit_before>package modules

// TODO: Split HostSettings into HostInternalSettings and HostExternalSettings.

// TODO: Reconsider the method by which bandwidth limits, storage limits, and
// prices are set. Want to enable flexibility for the client - probably belongs
// with internal settings.

import (
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/types"
)

const (
	// Some RPCs allow for acceptance or rejection. AcceptResponse is the
	// response given to indicate acceptance. (Any other string indicates
	// rejection, and describes the reason for rejection.)
	AcceptResponse = "accept"

	// HostDir names the directory that contains the host persistence.
	HostDir = "host"

	// MaxFileContractSetLen determines the maximum allowed size of a
	// transaction set that can be sent when trying to negotiate a file
	// contract. The transaction set will contain all of the unconfirmed
	// dependencies of the file contract, meaning that it can be quite large.
	// The transaction pool's size limit for transaction sets has been chosen
	// as a reasonable guideline for determining what is too large.
	MaxFileContractSetLen = TransactionSetSizeLimit - 1e3
)

var (

	// RPCSettings is the specifier for requesting settings from the host.
	RPCSettings = types.Specifier{'S', 'e', 't', 't', 'i', 'n', 'g', 's', 2}

	// RPCNegotiate is the specifier for negotiating a contract with a host.
	RPCNegotiate = types.Specifier{'N', 'e', 'g', 'o', 't', 'i', 'a', 't', 'e', 2}

	// RPCRenew is the specifier to renewing an existing contract.
	RPCRenew = types.Specifier{'R', 'e', 'n', 'e', 'w', 2}

	// RPCRevise is the specifier for revising an existing file contract.
	RPCRevise = types.Specifier{'R', 'e', 'v', 'i', 's', 'e', 2}

	// RPCDownload is the specifier for downloading a file from a host.
	RPCDownload = types.Specifier{'D', 'o', 'w', 'n', 'l', 'o', 'a', 'd', 2}

	// PrefixHostAnnouncement is used to indicate that a transaction's
	// Arbitrary Data field contains a host announcement. The encoded
	// announcement will follow this prefix.
	PrefixHostAnnouncement = types.Specifier{'H', 'o', 's', 't', 'A', 'n', 'n', 'o', 'u', 'n', 'c', 'e', 'm', 'e', 'n', '2'}
)

type (
	// HostBandwidthLimits set limits on the volume and speed of the uploading
	// and downloading of the host. The limits have no bearings on the other
	// modules. The data limits are in bytes per month, and the speed limits
	// are in bytes per second.
	HostBandwidthLimits struct {
		DownloadDataLimit  uint64
		DownloadSpeedLimit uint64
		UploadDataLimit    uint64
		UploadSpeedLimit   uint64
	}

	// HostAnnouncement declares a nodes intent to be a host, providing a net
	// address that can be used to contact the host.
	HostAnnouncement struct {
		IPAddress NetAddress
		PublicKey crypto.PublicKey
	}

	// HostFinancialMetrics provides statistics on the spendings and earnings
	// of the host.
	HostFinancialMetrics struct {
		DownloadBandwidthRevenue           types.Currency
		LockedCollateral                   types.Currency
		LostCollateral                     types.Currency
		LostRevenue                        types.Currency
		PotentialStorageRevenue            types.Currency
		StorageRevenue                     types.Currency
		TransactionFeeExpenses             types.Currency // Amount spent on transaction fees total
		UnsubsidizedTransactionFeeExpenses types.Currency // Amount spent on transaction fees that the renter did help pay for.
		UploadBandwidthRevenue             types.Currency
	}

	// HostSettings are the parameters advertised by the host. These are the
	// values that the renter will request from the host in order to build its
	// database.
	HostSettings struct {
		AcceptingContracts bool              `json:"acceptingcontracts"`
		MaxDuration        types.BlockHeight `json:"maxduration"`
		NetAddress         NetAddress        `json:"netaddress"`
		RemainingStorage   uint64            `json:"remainingstorage"` // Cannot be directly changed.
		SectorSize         uint64            `json:"sectorsize"`       // Currently cannot be changed (future support planned).
		TotalStorage       uint64            `json:"totalstorage"`     // Cannot be directly changed.
		UnlockHash         types.UnlockHash  `json:"unlockhash"`       // Cannot be directly changed.
		WindowSize         types.BlockHeight `json:"windowsize"`

		Collateral             types.Currency `json:"collateral"`
		ContractPrice          types.Currency `json:"contractprice"`
		DownloadBandwidthPrice types.Currency `json:"downloadbandwidthprice"` // The cost for a renter to download something (meaning the host is uploading).
		StoragePrice           types.Currency `json:"storageprice"`
		UploadBandwidthPrice   types.Currency `json:"uploadbandwidthprice"` // The cost for a renter to upload something (meaning the host is downloading).
	}

	// HostRPCMetrics reports the quantity of each type of RPC call that has
	// been made to the host.
	HostRPCMetrics struct {
		DownloadBandwidthConsumed uint64 `json:"downloadbandwidthconsumed"`
		UploadBandwidthConsumed   uint64 `json:"uploadbandwidthconsumed"`

		ErrorCalls        uint64 `json:"errorcalls"` // Calls that resulted in an error.
		UnrecognizedCalls uint64 `json:"unrecognizedcalls"`
		DownloadCalls     uint64 `json:"downloadcalls"`
		RenewCalls        uint64 `json:"renewcalls"`
		ReviseCalls       uint64 `json:"revisecalls"`
		SettingsCalls     uint64 `json:"settingscalls"`
		UploadCalls       uint64 `json:"uploadcalls"`
	}

	// StorageFolderMetadata contians metadata about a storage folder that is
	// tracked by the storage folder manager.
	StorageFolderMetadata struct {
		CapacityRemaining uint64
		Path              string
		TotalCapacity     uint64

		// Successful and unsuccessful operations report the number of
		// successful and unsuccessful disk operations that the storage manager
		// has completed on the storage folder. A large number of unsuccessful
		// operations can indicate that the space allocated for the storage
		// folder is larger than the amount of actual free space on the disk.
		// Things like filesystem overhead can reduce the amount of actual
		// storage available on disk, but should ultimately be less than 1% of
		// the total advertised capacity of a disk. A large number of
		// unsuccessful operations can also indicate that the disk is failing
		// and that it needs to be replaced.
		SuccessfulOperations   uint64
		UnsuccessfulOperations uint64
	}

	// StorageFolderManager tracks and manipulates storage folders. Storage
	// folders are used by the host to store the data that they contractually
	// agree to manage.
	StorageFolderManager interface {
		// AddStorageFolder adds a storage folder to the host. The host may not
		// check that there is enough space available on-disk to support as
		// much storage as requested, though the host should gracefully handle
		// running out of storage unexpectedly.
		AddStorageFolder(path string, size uint64) error

		// ForceRemoveStorageFolder removes a storage folder. The host will try
		// to save the data on the storage folder by moving it to another
		// storage folder, but if there are errors (such as not enough space)
		// the host will forcibly remove the storage folder, discarding the
		// data. This means that the host will be unable to provide storage
		// proofs on the data, and is going to incur penalties.
		ForceRemoveStorageFolder(index int) error

		// RemoveStorageFolder will remove a storage folder from the host. All
		// storage on the folder will be moved to other storage folders,
		// meaning that no data will be lost. If the host is unable to save
		// data, an error will be returned and the operation will be stopped.
		RemoveStorageFolder(index int) error

		// ResetStorageFolderHealth will reset the health statistics on a
		// storage folder.
		ResetStorageFolderHealth(index int) error

		// ResizeStorageFolder will grow or shrink a storage folder in the
		// host. The host may not check that there is enough space on-disk to
		// support growing the storage folder, but should gracefully handle
		// running out of space unexpectedly. When shrinking a storage folder,
		// any data in the folder that needs to be moved will be placed into
		// other storage folders, meaning that no data will be lost. If the
		// host is unable to migrate the data, an error will be returned and
		// the operation will be stopped.
		ResizeStorageFolder(index int, newSize uint64) error

		// StorageFolders will return a list of storage folders tracked by the
		// host.
		StorageFolders() []StorageFolderMetadata
	}

	// Host can take storage from disk and offer it to the network, managing things
	// such as announcements, settings, and implementing all of the RPCs of the
	// host protocol.
	Host interface {
		// Announce submits a host announcement to the blockchain.  After
		// announcing, the host will begin accepting contracts.
		Announce(NetAddress) error

		// ConsistencyCheckAndRepair runs a consistency check on the host,
		// looking for places where some combination of disk errors, usage
		// errors, and development errors have led to inconsistencies in the
		// host. In cases where these inconsistencies can be repaired, the
		// repairs are made.
		// TODO: ConsistencyCheckAndRepair() error

		// DeleteSector deletes a sector, meaning that the host will be unable
		// to upload that sector and be unable to provide a storage proof if
		// that sector is chosen by the blockchain.
		DeleteSector(sectorRoot crypto.Hash) error

		// FileContracts returns a list of file contracts that the host
		// currently has open, along with the volume of data tracked by each
		// file contract.
		FileContracts() ([]types.FileContractID, []uint64)

		// FinancialMetrics returns the financial statistics of the host.
		FinancialMetrics() HostFinancialMetrics

		// NetAddress returns the host's network address
		NetAddress() NetAddress

		// RPCMetrics returns information on the types of RPC calls that have
		// been made to the host.
		RPCMetrics() HostRPCMetrics

		// SetBandwidthLimits puts a limit on how much data transfer the host
		// will tolerate. Altruistic limits indicate how much data the host is
		// willing to transfer for free, and priced limits indicate how much
		// data the host is willing to transfer when the host is getting paid.
		// TODO: SetBandwidthLimits(altruisticLimits, pricedLimits HostBandwidthLimits)

		// SetConfig sets the hosting parameters of the host.
		SetSettings(HostSettings) error

		// Settings returns the host's settings.
		Settings() HostSettings

		// Close saves the state of the host and stops its listener process.
		Close() error

		StorageFolderManager
	}
)

// BandwidthPriceToConsensus converts a human bandwidth price, having the unit
// 'Siacoins per Terabyte', to a consensus storage price, having the unit
// 'Hastings per Byte'.
func BandwidthPriceToConsensus(siacoinsTB uint64) (hastingsByte types.Currency) {
	hastingsTB := types.NewCurrency64(siacoinsTB).Mul(types.SiacoinPrecision)
	return hastingsTB.Div(types.NewCurrency64(1e12))
}

// BandwidthPriceToHuman converts a consensus bandwidth price, having the unit
// 'Hastings per Byte' to a human bandwidth price, having the unit 'Siacoins
// per Terabyte'.
func BandwidthPriceToHuman(hastingsByte types.Currency) (siacoinsTB uint64, err error) {
	hastingsTB := hastingsByte.Mul(types.NewCurrency64(1e12))
	if hastingsTB.Cmp(types.SiacoinPrecision.Div(types.NewCurrency64(2))) < 0 {
		// The result of the final division is going to be less than 0.5,
		// therefore 0 should be returned.
		return 0, nil
	}
	if hastingsTB.Cmp(types.SiacoinPrecision) < 0 {
		// The result of the final division is going to be greater than or
		// equal to 0.5, but less than 1, therefore 1 should be returned.
		return 1, nil
	}
	return hastingsTB.Div(types.SiacoinPrecision).Uint64()
}

// StoragePriceToConsensus converts a human storage price, having the unit
// 'Siacoins per Month per Terabyte', to a consensus storage price, having the
// unit 'Hastings per Block per Byte'.
func StoragePriceToConsensus(siacoinsMonthTB uint64) (hastingsBlockByte types.Currency) {
	// Perform multiplication first to preserve precision.
	hastingsMonthTB := types.NewCurrency64(siacoinsMonthTB).Mul(types.SiacoinPrecision)
	hastingsBlockTB := hastingsMonthTB.Div(types.NewCurrency64(4320))
	return hastingsBlockTB.Div(types.NewCurrency64(1e12))
}

// StoragePriceToHuman converts a consensus storage price, having the unit
// 'Hastings per Block per Byte', to a human storage price, having the unit
// 'Siacoins per Month per Terabyte'. An error is returned if the result would
// overflow a uint64. If the result is between 0 and 1, the value is rounded to
// the nearest value.
func StoragePriceToHuman(hastingsBlockByte types.Currency) (siacoinsMonthTB uint64, err error) {
	// Perform multiplication first to preserve precision.
	hastingsMonthByte := hastingsBlockByte.Mul(types.NewCurrency64(4320))
	hastingsMonthTB := hastingsMonthByte.Mul(types.NewCurrency64(1e12))
	if hastingsMonthTB.Cmp(types.SiacoinPrecision.Div(types.NewCurrency64(2))) < 0 {
		// The result of the final division is going to be less than 0.5,
		// therefore 0 should be returned.
		return 0, nil
	}
	if hastingsMonthTB.Cmp(types.SiacoinPrecision) < 0 {
		// The result of the final division is going to be greater than or
		// equal to 0.5, but less than 1, therefore 1 should be returned.
		return 1, nil
	}
	return hastingsMonthTB.Div(types.SiacoinPrecision).Uint64()
}
<commit_msg>add RevisionAction type<commit_after>package modules

// TODO: Split HostSettings into HostInternalSettings and HostExternalSettings.

// TODO: Reconsider the method by which bandwidth limits, storage limits, and
// prices are set. Want to enable flexibility for the client - probably belongs
// with internal settings.

import (
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/types"
)

const (
	// Some RPCs allow for acceptance or rejection. AcceptResponse is the
	// response given to indicate acceptance. (Any other string indicates
	// rejection, and describes the reason for rejection.)
	AcceptResponse = "accept"

	// HostDir names the directory that contains the host persistence.
	HostDir = "host"

	// MaxFileContractSetLen determines the maximum allowed size of a
	// transaction set that can be sent when trying to negotiate a file
	// contract. The transaction set will contain all of the unconfirmed
	// dependencies of the file contract, meaning that it can be quite large.
	// The transaction pool's size limit for transaction sets has been chosen
	// as a reasonable guideline for determining what is too large.
	MaxFileContractSetLen = TransactionSetSizeLimit - 1e3
)

var (
	// ActionInsert is the specifier for a RevisionAction that modifies sector
	// data.
	ActionInsert = types.Specifier{'I', 'n', 's', 'e', 'r', 't'}

	// ActionDelete is the specifier for a RevisionAction that deletes a
	// sector.
	ActionDelete = types.Specifier{'D', 'e', 'l', 'e', 't', 'e'}

	// RPCSettings is the specifier for requesting settings from the host.
	RPCSettings = types.Specifier{'S', 'e', 't', 't', 'i', 'n', 'g', 's', 2}

	// RPCNegotiate is the specifier for negotiating a contract with a host.
	RPCNegotiate = types.Specifier{'N', 'e', 'g', 'o', 't', 'i', 'a', 't', 'e', 2}

	// RPCRenew is the specifier to renewing an existing contract.
	RPCRenew = types.Specifier{'R', 'e', 'n', 'e', 'w', 2}

	// RPCRevise is the specifier for revising an existing file contract.
	RPCRevise = types.Specifier{'R', 'e', 'v', 'i', 's', 'e', 2}

	// RPCDownload is the specifier for downloading a file from a host.
	RPCDownload = types.Specifier{'D', 'o', 'w', 'n', 'l', 'o', 'a', 'd', 2}

	// PrefixHostAnnouncement is used to indicate that a transaction's
	// Arbitrary Data field contains a host announcement. The encoded
	// announcement will follow this prefix.
	PrefixHostAnnouncement = types.Specifier{'H', 'o', 's', 't', 'A', 'n', 'n', 'o', 'u', 'n', 'c', 'e', 'm', 'e', 'n', '2'}
)

type (
	// A RevisionAction is a description of an edit to be performed on a
	// contract.
	RevisionAction struct {
		Type        types.Specifier
		SectorIndex uint64
	}

	// HostBandwidthLimits set limits on the volume and speed of the uploading
	// and downloading of the host. The limits have no bearings on the other
	// modules. The data limits are in bytes per month, and the speed limits
	// are in bytes per second.
	HostBandwidthLimits struct {
		DownloadDataLimit  uint64
		DownloadSpeedLimit uint64
		UploadDataLimit    uint64
		UploadSpeedLimit   uint64
	}

	// HostAnnouncement declares a nodes intent to be a host, providing a net
	// address that can be used to contact the host.
	HostAnnouncement struct {
		IPAddress NetAddress
		PublicKey crypto.PublicKey
	}

	// HostFinancialMetrics provides statistics on the spendings and earnings
	// of the host.
	HostFinancialMetrics struct {
		DownloadBandwidthRevenue           types.Currency
		LockedCollateral                   types.Currency
		LostCollateral                     types.Currency
		LostRevenue                        types.Currency
		PotentialStorageRevenue            types.Currency
		StorageRevenue                     types.Currency
		TransactionFeeExpenses             types.Currency // Amount spent on transaction fees total
		UnsubsidizedTransactionFeeExpenses types.Currency // Amount spent on transaction fees that the renter did help pay for.
		UploadBandwidthRevenue             types.Currency
	}

	// HostSettings are the parameters advertised by the host. These are the
	// values that the renter will request from the host in order to build its
	// database.
	HostSettings struct {
		AcceptingContracts bool              `json:"acceptingcontracts"`
		MaxDuration        types.BlockHeight `json:"maxduration"`
		NetAddress         NetAddress        `json:"netaddress"`
		RemainingStorage   uint64            `json:"remainingstorage"` // Cannot be directly changed.
		SectorSize         uint64            `json:"sectorsize"`       // Currently cannot be changed (future support planned).
		TotalStorage       uint64            `json:"totalstorage"`     // Cannot be directly changed.
		UnlockHash         types.UnlockHash  `json:"unlockhash"`       // Cannot be directly changed.
		WindowSize         types.BlockHeight `json:"windowsize"`

		Collateral             types.Currency `json:"collateral"`
		ContractPrice          types.Currency `json:"contractprice"`
		DownloadBandwidthPrice types.Currency `json:"downloadbandwidthprice"` // The cost for a renter to download something (meaning the host is uploading).
		StoragePrice           types.Currency `json:"storageprice"`
		UploadBandwidthPrice   types.Currency `json:"uploadbandwidthprice"` // The cost for a renter to upload something (meaning the host is downloading).
	}

	// HostRPCMetrics reports the quantity of each type of RPC call that has
	// been made to the host.
	HostRPCMetrics struct {
		DownloadBandwidthConsumed uint64 `json:"downloadbandwidthconsumed"`
		UploadBandwidthConsumed   uint64 `json:"uploadbandwidthconsumed"`

		ErrorCalls        uint64 `json:"errorcalls"` // Calls that resulted in an error.
		UnrecognizedCalls uint64 `json:"unrecognizedcalls"`
		DownloadCalls     uint64 `json:"downloadcalls"`
		RenewCalls        uint64 `json:"renewcalls"`
		ReviseCalls       uint64 `json:"revisecalls"`
		SettingsCalls     uint64 `json:"settingscalls"`
		UploadCalls       uint64 `json:"uploadcalls"`
	}

	// StorageFolderMetadata contians metadata about a storage folder that is
	// tracked by the storage folder manager.
	StorageFolderMetadata struct {
		CapacityRemaining uint64
		Path              string
		TotalCapacity     uint64

		// Successful and unsuccessful operations report the number of
		// successful and unsuccessful disk operations that the storage manager
		// has completed on the storage folder. A large number of unsuccessful
		// operations can indicate that the space allocated for the storage
		// folder is larger than the amount of actual free space on the disk.
		// Things like filesystem overhead can reduce the amount of actual
		// storage available on disk, but should ultimately be less than 1% of
		// the total advertised capacity of a disk. A large number of
		// unsuccessful operations can also indicate that the disk is failing
		// and that it needs to be replaced.
		SuccessfulOperations   uint64
		UnsuccessfulOperations uint64
	}

	// StorageFolderManager tracks and manipulates storage folders. Storage
	// folders are used by the host to store the data that they contractually
	// agree to manage.
	StorageFolderManager interface {
		// AddStorageFolder adds a storage folder to the host. The host may not
		// check that there is enough space available on-disk to support as
		// much storage as requested, though the host should gracefully handle
		// running out of storage unexpectedly.
		AddStorageFolder(path string, size uint64) error

		// ForceRemoveStorageFolder removes a storage folder. The host will try
		// to save the data on the storage folder by moving it to another
		// storage folder, but if there are errors (such as not enough space)
		// the host will forcibly remove the storage folder, discarding the
		// data. This means that the host will be unable to provide storage
		// proofs on the data, and is going to incur penalties.
		ForceRemoveStorageFolder(index int) error

		// RemoveStorageFolder will remove a storage folder from the host. All
		// storage on the folder will be moved to other storage folders,
		// meaning that no data will be lost. If the host is unable to save
		// data, an error will be returned and the operation will be stopped.
		RemoveStorageFolder(index int) error

		// ResetStorageFolderHealth will reset the health statistics on a
		// storage folder.
		ResetStorageFolderHealth(index int) error

		// ResizeStorageFolder will grow or shrink a storage folder in the
		// host. The host may not check that there is enough space on-disk to
		// support growing the storage folder, but should gracefully handle
		// running out of space unexpectedly. When shrinking a storage folder,
		// any data in the folder that needs to be moved will be placed into
		// other storage folders, meaning that no data will be lost. If the
		// host is unable to migrate the data, an error will be returned and
		// the operation will be stopped.
		ResizeStorageFolder(index int, newSize uint64) error

		// StorageFolders will return a list of storage folders tracked by the
		// host.
		StorageFolders() []StorageFolderMetadata
	}

	// Host can take storage from disk and offer it to the network, managing things
	// such as announcements, settings, and implementing all of the RPCs of the
	// host protocol.
	Host interface {
		// Announce submits a host announcement to the blockchain.  After
		// announcing, the host will begin accepting contracts.
		Announce(NetAddress) error

		// ConsistencyCheckAndRepair runs a consistency check on the host,
		// looking for places where some combination of disk errors, usage
		// errors, and development errors have led to inconsistencies in the
		// host. In cases where these inconsistencies can be repaired, the
		// repairs are made.
		// TODO: ConsistencyCheckAndRepair() error

		// DeleteSector deletes a sector, meaning that the host will be unable
		// to upload that sector and be unable to provide a storage proof if
		// that sector is chosen by the blockchain.
		DeleteSector(sectorRoot crypto.Hash) error

		// FileContracts returns a list of file contracts that the host
		// currently has open, along with the volume of data tracked by each
		// file contract.
		FileContracts() ([]types.FileContractID, []uint64)

		// FinancialMetrics returns the financial statistics of the host.
		FinancialMetrics() HostFinancialMetrics

		// NetAddress returns the host's network address
		NetAddress() NetAddress

		// RPCMetrics returns information on the types of RPC calls that have
		// been made to the host.
		RPCMetrics() HostRPCMetrics

		// SetBandwidthLimits puts a limit on how much data transfer the host
		// will tolerate. Altruistic limits indicate how much data the host is
		// willing to transfer for free, and priced limits indicate how much
		// data the host is willing to transfer when the host is getting paid.
		// TODO: SetBandwidthLimits(altruisticLimits, pricedLimits HostBandwidthLimits)

		// SetConfig sets the hosting parameters of the host.
		SetSettings(HostSettings) error

		// Settings returns the host's settings.
		Settings() HostSettings

		// Close saves the state of the host and stops its listener process.
		Close() error

		StorageFolderManager
	}
)

// BandwidthPriceToConsensus converts a human bandwidth price, having the unit
// 'Siacoins per Terabyte', to a consensus storage price, having the unit
// 'Hastings per Byte'.
func BandwidthPriceToConsensus(siacoinsTB uint64) (hastingsByte types.Currency) {
	hastingsTB := types.NewCurrency64(siacoinsTB).Mul(types.SiacoinPrecision)
	return hastingsTB.Div(types.NewCurrency64(1e12))
}

// BandwidthPriceToHuman converts a consensus bandwidth price, having the unit
// 'Hastings per Byte' to a human bandwidth price, having the unit 'Siacoins
// per Terabyte'.
func BandwidthPriceToHuman(hastingsByte types.Currency) (siacoinsTB uint64, err error) {
	hastingsTB := hastingsByte.Mul(types.NewCurrency64(1e12))
	if hastingsTB.Cmp(types.SiacoinPrecision.Div(types.NewCurrency64(2))) < 0 {
		// The result of the final division is going to be less than 0.5,
		// therefore 0 should be returned.
		return 0, nil
	}
	if hastingsTB.Cmp(types.SiacoinPrecision) < 0 {
		// The result of the final division is going to be greater than or
		// equal to 0.5, but less than 1, therefore 1 should be returned.
		return 1, nil
	}
	return hastingsTB.Div(types.SiacoinPrecision).Uint64()
}

// StoragePriceToConsensus converts a human storage price, having the unit
// 'Siacoins per Month per Terabyte', to a consensus storage price, having the
// unit 'Hastings per Block per Byte'.
func StoragePriceToConsensus(siacoinsMonthTB uint64) (hastingsBlockByte types.Currency) {
	// Perform multiplication first to preserve precision.
	hastingsMonthTB := types.NewCurrency64(siacoinsMonthTB).Mul(types.SiacoinPrecision)
	hastingsBlockTB := hastingsMonthTB.Div(types.NewCurrency64(4320))
	return hastingsBlockTB.Div(types.NewCurrency64(1e12))
}

// StoragePriceToHuman converts a consensus storage price, having the unit
// 'Hastings per Block per Byte', to a human storage price, having the unit
// 'Siacoins per Month per Terabyte'. An error is returned if the result would
// overflow a uint64. If the result is between 0 and 1, the value is rounded to
// the nearest value.
func StoragePriceToHuman(hastingsBlockByte types.Currency) (siacoinsMonthTB uint64, err error) {
	// Perform multiplication first to preserve precision.
	hastingsMonthByte := hastingsBlockByte.Mul(types.NewCurrency64(4320))
	hastingsMonthTB := hastingsMonthByte.Mul(types.NewCurrency64(1e12))
	if hastingsMonthTB.Cmp(types.SiacoinPrecision.Div(types.NewCurrency64(2))) < 0 {
		// The result of the final division is going to be less than 0.5,
		// therefore 0 should be returned.
		return 0, nil
	}
	if hastingsMonthTB.Cmp(types.SiacoinPrecision) < 0 {
		// The result of the final division is going to be greater than or
		// equal to 0.5, but less than 1, therefore 1 should be returned.
		return 1, nil
	}
	return hastingsMonthTB.Div(types.SiacoinPrecision).Uint64()
}
<|endoftext|>


<commit_before>// Provides crypto hash Writer/Reader support for large files or streaming.
//
// Inspired by Java's DigestInputStream/DigestOutputStream https://docs.oracle.com/javase/7/docs/api/java/security/DigestInputStream.html
//
// Example: reader
//
//	package main
//
//	import (
//		"crypto/md5"
//		"encoding/hex"
//		"fmt"
//		"github.com/gnewton/hashstream"
//		"io"
//		"log"
//		"strings"
//	)
//
//	func main() {
//		reader := strings.NewReader("hello")
//		hr, err := hashstream.NewHashReader(reader, md5.New())
//		for {
//			buf := make([]byte, 4)
//			_, err = hr.Read(buf)
//			if err != nil {
//				if err == io.EOF {
//					break
//				}
//				log.Fatal(err)
//			}
//		}
//		fmt.Println(hex.EncodeToString(hr.Sum()))
//	}
//
//      Output: 5d41402abc4b2a76b9719d911017c592
//
// Example: writer
//
package hashstream

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"errors"
	"hash"
	"io"
)

func NewHashReader(r io.Reader, h hash.Hash) (*HashReader, error) {
	if r == nil || h == nil {
		return nil, errors.New("Reader or hash are nil: no allowed")
	}
	return &HashReader{
		reader: r,
		hash:   h,
	}, nil
}

func NewMD5Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, md5.New())
}

func NewSHA1Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha1.New())

}

func NewSHA224Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha256.New224())
}

func NewSHA256Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha256.New())
}

func NewSHA384Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha512.New384())
}

func NewSHA512Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha512.New())
}

type HashReader struct {
	reader io.Reader
	hash   hash.Hash
}

func (l *HashReader) Sum() []byte {
	return sum(l.hash)
}

func sum(h hash.Hash) []byte {
	return h.Sum(nil)
}

func (l *HashReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		n = -1
		err = errors.New("Buffer cannot be length=zero")
		return
	}

	n, err = l.reader.Read(p)

	if err != nil {
		return -1, err
	}
	if n > 0 {
		var buf []byte
		if n == len(p) {
			buf = p
		} else {
			buf = p[0:n]
		}
		n, err = l.hash.Write(buf)
		if err != nil {
			return -1, nil
		}
	}
	return
}
<commit_msg>Added example for writer<commit_after>// Provides crypto hash Writer/Reader support for large files or streaming.
//
// Inspired by Java's DigestInputStream/DigestOutputStream https://docs.oracle.com/javase/7/docs/api/java/security/DigestInputStream.html
//
// Example: reader
//
//	package main
//
//	import (
//		"crypto/md5"
//		"encoding/hex"
//		"fmt"
//		"github.com/gnewton/hashstream"
//		"io"
//		"log"
//		"strings"
//	)
//
//	func main() {
//		reader := strings.NewReader("hello")
//		hr, err := hashstream.NewHashReader(reader, md5.New())
//		for {
//			buf := make([]byte, 4)
//			_, err = hr.Read(buf)
//			if err != nil {
//				if err == io.EOF {
//					break
//				}
//				log.Fatal(err)
//			}
//		}
//		fmt.Println(hex.EncodeToString(hr.Sum()))
//	}
//
//      Output: 5d41402abc4b2a76b9719d911017c592
//
// Example: writer
//
// 	package main
//
// 	import (
// 		"crypto/md5"
// 		"encoding/hex"
// 		"fmt"
// 		"github.com/gnewton/hashstream"
// 		"io/ioutil"
// 		"log"
// 	)
//
// 	func main() {
//
// 		writer := ioutil.Discard
// 		hw, err := hashstream.NewHashWriter(writer, md5.New())
// 		if err != nil {
// 			log.Fatal(err)
// 		}
//
// 		_, err = hw.Write([]byte("hello"))
// 		if err != nil {
// 			log.Fatal(err)
// 		}
//
// 		fmt.Println(hex.EncodeToString(hw.Sum()))
// 	}
//
//      Output: 5d41402abc4b2a76b9719d911017c592
//
package hashstream

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"errors"
	"hash"
	"io"
)

func NewHashReader(r io.Reader, h hash.Hash) (*HashReader, error) {
	if r == nil || h == nil {
		return nil, errors.New("Reader or hash are nil: no allowed")
	}
	return &HashReader{
		reader: r,
		hash:   h,
	}, nil
}

func NewMD5Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, md5.New())
}

func NewSHA1Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha1.New())

}

func NewSHA224Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha256.New224())
}

func NewSHA256Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha256.New())
}

func NewSHA384Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha512.New384())
}

func NewSHA512Reader(r io.Reader) (*HashReader, error) {
	return NewHashReader(r, sha512.New())
}

type HashReader struct {
	reader io.Reader
	hash   hash.Hash
}

func (l *HashReader) Sum() []byte {
	return sum(l.hash)
}

func sum(h hash.Hash) []byte {
	return h.Sum(nil)
}

func (l *HashReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		n = -1
		err = errors.New("Buffer cannot be length=zero")
		return
	}

	n, err = l.reader.Read(p)

	if err != nil {
		return -1, err
	}
	if n > 0 {
		var buf []byte
		if n == len(p) {
			buf = p
		} else {
			buf = p[0:n]
		}
		n, err = l.hash.Write(buf)
		if err != nil {
			return -1, nil
		}
	}
	return
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2014 Ashley Jeffs

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 gabs implements a simplified wrapper around creating and parsing JSON.
package gabs

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"strings"
)

/*---------------------------------------------------------------------------------------------------
 */

/*
Container - an internal structure that holds a reference to the core interface map of the parsed
json. Use this container to move context.
*/
type Container struct {
	object interface{}
}

/*
Data - Return the contained data as an interface{}.
*/
func (g *Container) Data() interface{} {
	return g.object
}

/*---------------------------------------------------------------------------------------------------
 */

/*
Path - Search for a value using dot notation.
*/
func (g *Container) Path(path string) *Container {
	return g.Search(strings.Split(path, ".")...)
}

/*
Search - Attempt to find and return an object within the JSON structure by specifying the hierarchy
of field names to locate the target. If the search encounters an array and has not reached the end
target then it will iterate each object of the array for the target and return all of the results in
a JSON array.
*/
func (g *Container) Search(hierarchy ...string) *Container {
	var object interface{}

	object = g.object
	for target := 0; target < len(hierarchy); target++ {
		if mmap, ok := object.(map[string]interface{}); ok {
			object = mmap[hierarchy[target]]
		} else if marray, ok := object.([]interface{}); ok {
			tmpArray := []interface{}{}
			for _, val := range marray {
				tmpGabs := &Container{val}
				res := tmpGabs.Search(hierarchy[target:]...).Data()
				if res != nil {
					tmpArray = append(tmpArray, res)
				}
			}
			if len(tmpArray) == 0 {
				return &Container{nil}
			}
			return &Container{tmpArray}
		} else {
			return &Container{nil}
		}
	}
	return &Container{object}
}

/*
S - Shorthand method, does the same thing as Search.
*/
func (g *Container) S(hierarchy ...string) *Container {
	return g.Search(hierarchy...)
}

/*
Children - Return a slice of all the children of the array. This also works for objects, however, the
children returned for an object will NOT be in order and you lose the names of the returned objects
this way.
*/
func (g *Container) Children() ([]*Container, error) {
	if array, ok := g.Data().([]interface{}); ok {
		children := make([]*Container, len(array))
		for i := 0; i < len(array); i++ {
			children[i] = &Container{array[i]}
		}
		return children, nil
	}
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		children := []*Container{}
		for _, obj := range mmap {
			children = append(children, &Container{obj})
		}
		return children, nil
	}
	return nil, errors.New("parent was not a valid JSON object or array")
}

/*---------------------------------------------------------------------------------------------------
 */

/*
Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be
constructed, and if a collision occurs with a non object type whilst iterating the path an error is
returned.
*/
func (g *Container) Set(value interface{}, path ...string) (*Container, error) {
	var object interface{}

	if g.object == nil {
		g.object = map[string]interface{}{}
	}
	object = g.object
	for target := 0; target < len(path); target++ {
		if mmap, ok := object.(map[string]interface{}); ok {
			if target == len(path)-1 {
				mmap[path[target]] = value
			} else if mmap[path[target]] == nil {
				mmap[path[target]] = map[string]interface{}{}
			}
			object = mmap[path[target]]
		} else {
			return &Container{nil}, errors.New("encountered object collision whilst building path")
		}
	}
	return &Container{object}, nil
}

/*
SetP - Does the same as Set, but using a dot notation JSON path.
*/
func (g *Container) SetP(value interface{}, path string) (*Container, error) {
	return g.Set(value, strings.Split(path, ".")...)
}

/*
Object - Create a new JSON object at a path. Returns an error if the path contains a collision with
a non object type.
*/
func (g *Container) Object(path ...string) (*Container, error) {
	return g.Set(map[string]interface{}{}, path...)
}

/*
ObjectP - Does the same as Object, but using a dot notation JSON path.
*/
func (g *Container) ObjectP(path string) (*Container, error) {
	return g.Object(strings.Split(path, ".")...)
}

/*
Array - Create a new JSON array at a path. Returns an error if the path contains a collision with
a non object type.
*/
func (g *Container) Array(path ...string) (*Container, error) {
	return g.Set([]interface{}{}, path...)
}

/*
ArrayP - Does the same as Array, but using a dot notation JSON path.
*/
func (g *Container) ArrayP(path string) (*Container, error) {
	return g.Array(strings.Split(path, ".")...)
}

/*---------------------------------------------------------------------------------------------------
 */

/*
Array modification/search - Keeping these options simple right now, no need for anything more
complicated since you can just cast to []interface{}, modify and then reassign with Set.
*/

/*
ArrayAppend - Append a value onto a JSON array.
*/
func (g *Container) ArrayAppend(value interface{}, path ...string) error {
	array, ok := g.Search(path...).Data().([]interface{})
	if !ok {
		return errors.New("target object was not an array")
	}
	array = append(array, value)
	_, err := g.Set(array, path...)
	return err
}

/*
ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayAppendP(value interface{}, path string) error {
	return g.ArrayAppend(value, strings.Split(path, ".")...)
}

/*
ArrayRemove - Remove an element from a JSON array.
*/
func (g *Container) ArrayRemove(index int, path ...string) error {
	if index < 0 {
		return errors.New("target index out of bounds")
	}
	array, ok := g.Search(path...).Data().([]interface{})
	if !ok {
		return errors.New("target object was not an array")
	}
	if index < len(array) {
		array = append(array[:index], array[index+1:]...)
	} else {
		return errors.New("target index was out of bounds of array")
	}
	_, err := g.Set(array, path...)
	return err
}

/*
ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayRemoveP(index int, path string) error {
	return g.ArrayRemove(index, strings.Split(path, ".")...)
}

/*
ArrayElement - Access an element from a JSON array.
*/
func (g *Container) ArrayElement(index int, path ...string) (*Container, error) {
	if index < 0 {
		return &Container{nil}, errors.New("target index out of bounds")
	}
	array, ok := g.Search(path...).Data().([]interface{})
	if !ok {
		return &Container{nil}, errors.New("target object was not an array")
	}
	if index < len(array) {
		return &Container{array[index]}, nil
	}
	return &Container{nil}, errors.New("target index was out of bounds of array")
}

/*
ArrayElementP - Access an element from a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
	return g.ArrayElement(index, strings.Split(path, ".")...)
}

/*
ArrayCount - Count the number of elements in a JSON array.
*/
func (g *Container) ArrayCount(path ...string) (int, error) {
	if array, ok := g.Search(path...).Data().([]interface{}); ok {
		return len(array), nil
	}
	return 0, errors.New("target object was not an array")
}

/*
ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayCountP(path string) (int, error) {
	return g.ArrayCount(strings.Split(path, ".")...)
}

/*---------------------------------------------------------------------------------------------------
 */

/*
String - Converts the contained object back to a JSON formatted string.
*/
func (g *Container) String() string {
	if g.object != nil {
		if bytes, err := json.Marshal(g.object); err == nil {
			return string(bytes)
		}
	}
	return "{}"
}

/*
StringIndent - Converts the contained object back to a JSON formatted string with prefix and indent.
*/
func (g *Container) StringIndent(prefix string, indent string) string {
	if g.object != nil {
		if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil {
			return string(bytes)
		}
	}
	return "{}"
}

/*
New - Create a new gabs JSON object.
*/
func New() *Container {
	return &Container{map[string]interface{}{}}
}

/*
Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object.
*/
func Consume(root interface{}) (*Container, error) {
	if _, ok := root.(map[string]interface{}); ok {
		return &Container{root}, nil
	}
	return nil, errors.New("root was not a valid JSON object")
}

/*
ParseJSON - Convert a string into a representation of the parsed JSON.
*/
func ParseJSON(sample []byte) (*Container, error) {
	var gabs Container

	if err := json.Unmarshal(sample, &gabs.object); err != nil {
		return nil, err
	}
	if _, ok := gabs.object.(map[string]interface{}); ok {
		return &gabs, nil
	}
	return nil, errors.New("json appears to contain no data")
}

/*
ParseJSONFile - Read a file and convert into a representation of the parsed JSON.
*/
func ParseJSONFile(path string) (*Container, error) {
	if len(path) > 0 {
		cBytes, err := ioutil.ReadFile(path)
		if err != nil {
			container, err := ParseJSON(cBytes)
			if err != nil {
				return container, nil
			}
			return nil, err
		}
		return nil, err
	}
	return nil, errors.New("file path was invalid")
}

/*---------------------------------------------------------------------------------------------------
 */

// DEPRECATED METHODS

/*
Push - DEPRECATED: Push a value onto a JSON array.
*/
func (g *Container) Push(target string, value interface{}) error {
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			mmap[target] = append(array, value)
		} else {
			return errors.New("target object was not an array")
		}
	} else {
		return errors.New("parent was not a valid JSON object")
	}
	return nil
}

/*
RemoveElement - DEPRECATED: Remove a value from a JSON array.
*/
func (g *Container) RemoveElement(target string, index int) error {
	if index < 0 {
		return errors.New("target index out of bounds")
	}
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			if index < len(array) {
				mmap[target] = append(array[:index], array[index+1:]...)
			} else {
				return errors.New("target index was out of bounds of array")
			}
		} else {
			return errors.New("target object was not an array")
		}
	} else {
		return errors.New("parent was not a valid JSON object")
	}
	return nil
}

/*
GetElement - DEPRECATED: Get the desired element from a JSON array
*/
func (g *Container) GetElement(target string, index int) *Container {
	if index < 0 {
		return &Container{nil}
	}
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			if index < len(array) {
				return &Container{array[index]}
			}
		}
	}
	return &Container{nil}
}

/*
CountElements - DEPRECATED: Count the elements of a JSON array, returns -1 if the target is not an
array
*/
func (g *Container) CountElements(target string) int {
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			return len(array)
		}
	}
	return -1
}

/*---------------------------------------------------------------------------------------------------
 */
<commit_msg>Extract and share errors<commit_after>/*
Copyright (c) 2014 Ashley Jeffs

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 gabs implements a simplified wrapper around creating and parsing JSON.
package gabs

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"strings"
)

/*---------------------------------------------------------------------------------------------------
 */

var (
	// ErrOutOfBounds - Index out of bounds.
	ErrOutOfBounds = errors.New("out of bounds")

	// ErrNotObjOrArray - The target is not an object or array type.
	ErrNotObjOrArray = errors.New("not an object or array")

	// ErrNotObj - The target is not an object type.
	ErrNotObj = errors.New("not an object")

	// ErrNotArray - The target is not an array type.
	ErrNotArray = errors.New("not an array")

	// ErrPathCollision - Creating a path failed because an element collided with an existing value.
	ErrPathCollision = errors.New("encountered value collision whilst building path")

	// ErrInvalidInputObj - The input value was not a map[string]interface{}.
	ErrInvalidInputObj = errors.New("invalid input object")

	// ErrInvalidInputText - The input data could not be parsed.
	ErrInvalidInputText = errors.New("input text could not be parsed")

	// ErrInvalidPath - The filepath was not valid.
	ErrInvalidPath = errors.New("invalid file path")
)

/*---------------------------------------------------------------------------------------------------
 */

/*
Container - an internal structure that holds a reference to the core interface map of the parsed
json. Use this container to move context.
*/
type Container struct {
	object interface{}
}

/*
Data - Return the contained data as an interface{}.
*/
func (g *Container) Data() interface{} {
	return g.object
}

/*---------------------------------------------------------------------------------------------------
 */

/*
Path - Search for a value using dot notation.
*/
func (g *Container) Path(path string) *Container {
	return g.Search(strings.Split(path, ".")...)
}

/*
Search - Attempt to find and return an object within the JSON structure by specifying the hierarchy
of field names to locate the target. If the search encounters an array and has not reached the end
target then it will iterate each object of the array for the target and return all of the results in
a JSON array.
*/
func (g *Container) Search(hierarchy ...string) *Container {
	var object interface{}

	object = g.object
	for target := 0; target < len(hierarchy); target++ {
		if mmap, ok := object.(map[string]interface{}); ok {
			object = mmap[hierarchy[target]]
		} else if marray, ok := object.([]interface{}); ok {
			tmpArray := []interface{}{}
			for _, val := range marray {
				tmpGabs := &Container{val}
				res := tmpGabs.Search(hierarchy[target:]...).Data()
				if res != nil {
					tmpArray = append(tmpArray, res)
				}
			}
			if len(tmpArray) == 0 {
				return &Container{nil}
			}
			return &Container{tmpArray}
		} else {
			return &Container{nil}
		}
	}
	return &Container{object}
}

/*
S - Shorthand method, does the same thing as Search.
*/
func (g *Container) S(hierarchy ...string) *Container {
	return g.Search(hierarchy...)
}

/*
Children - Return a slice of all the children of the array. This also works for objects, however, the
children returned for an object will NOT be in order and you lose the names of the returned objects
this way.
*/
func (g *Container) Children() ([]*Container, error) {
	if array, ok := g.Data().([]interface{}); ok {
		children := make([]*Container, len(array))
		for i := 0; i < len(array); i++ {
			children[i] = &Container{array[i]}
		}
		return children, nil
	}
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		children := []*Container{}
		for _, obj := range mmap {
			children = append(children, &Container{obj})
		}
		return children, nil
	}
	return nil, ErrNotObjOrArray
}

/*---------------------------------------------------------------------------------------------------
 */

/*
Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be
constructed, and if a collision occurs with a non object type whilst iterating the path an error is
returned.
*/
func (g *Container) Set(value interface{}, path ...string) (*Container, error) {
	var object interface{}

	if g.object == nil {
		g.object = map[string]interface{}{}
	}
	object = g.object
	for target := 0; target < len(path); target++ {
		if mmap, ok := object.(map[string]interface{}); ok {
			if target == len(path)-1 {
				mmap[path[target]] = value
			} else if mmap[path[target]] == nil {
				mmap[path[target]] = map[string]interface{}{}
			}
			object = mmap[path[target]]
		} else {
			return &Container{nil}, ErrPathCollision
		}
	}
	return &Container{object}, nil
}

/*
SetP - Does the same as Set, but using a dot notation JSON path.
*/
func (g *Container) SetP(value interface{}, path string) (*Container, error) {
	return g.Set(value, strings.Split(path, ".")...)
}

/*
Object - Create a new JSON object at a path. Returns an error if the path contains a collision with
a non object type.
*/
func (g *Container) Object(path ...string) (*Container, error) {
	return g.Set(map[string]interface{}{}, path...)
}

/*
ObjectP - Does the same as Object, but using a dot notation JSON path.
*/
func (g *Container) ObjectP(path string) (*Container, error) {
	return g.Object(strings.Split(path, ".")...)
}

/*
Array - Create a new JSON array at a path. Returns an error if the path contains a collision with
a non object type.
*/
func (g *Container) Array(path ...string) (*Container, error) {
	return g.Set([]interface{}{}, path...)
}

/*
ArrayP - Does the same as Array, but using a dot notation JSON path.
*/
func (g *Container) ArrayP(path string) (*Container, error) {
	return g.Array(strings.Split(path, ".")...)
}

/*---------------------------------------------------------------------------------------------------
 */

/*
Array modification/search - Keeping these options simple right now, no need for anything more
complicated since you can just cast to []interface{}, modify and then reassign with Set.
*/

/*
ArrayAppend - Append a value onto a JSON array.
*/
func (g *Container) ArrayAppend(value interface{}, path ...string) error {
	array, ok := g.Search(path...).Data().([]interface{})
	if !ok {
		return ErrNotArray
	}
	array = append(array, value)
	_, err := g.Set(array, path...)
	return err
}

/*
ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayAppendP(value interface{}, path string) error {
	return g.ArrayAppend(value, strings.Split(path, ".")...)
}

/*
ArrayRemove - Remove an element from a JSON array.
*/
func (g *Container) ArrayRemove(index int, path ...string) error {
	if index < 0 {
		return ErrOutOfBounds
	}
	array, ok := g.Search(path...).Data().([]interface{})
	if !ok {
		return ErrNotArray
	}
	if index < len(array) {
		array = append(array[:index], array[index+1:]...)
	} else {
		return ErrOutOfBounds
	}
	_, err := g.Set(array, path...)
	return err
}

/*
ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayRemoveP(index int, path string) error {
	return g.ArrayRemove(index, strings.Split(path, ".")...)
}

/*
ArrayElement - Access an element from a JSON array.
*/
func (g *Container) ArrayElement(index int, path ...string) (*Container, error) {
	if index < 0 {
		return &Container{nil}, ErrOutOfBounds
	}
	array, ok := g.Search(path...).Data().([]interface{})
	if !ok {
		return &Container{nil}, ErrNotArray
	}
	if index < len(array) {
		return &Container{array[index]}, nil
	}
	return &Container{nil}, ErrOutOfBounds
}

/*
ArrayElementP - Access an element from a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
	return g.ArrayElement(index, strings.Split(path, ".")...)
}

/*
ArrayCount - Count the number of elements in a JSON array.
*/
func (g *Container) ArrayCount(path ...string) (int, error) {
	if array, ok := g.Search(path...).Data().([]interface{}); ok {
		return len(array), nil
	}
	return 0, ErrNotArray
}

/*
ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path.
*/
func (g *Container) ArrayCountP(path string) (int, error) {
	return g.ArrayCount(strings.Split(path, ".")...)
}

/*---------------------------------------------------------------------------------------------------
 */

/*
String - Converts the contained object back to a JSON formatted string.
*/
func (g *Container) String() string {
	if g.object != nil {
		if bytes, err := json.Marshal(g.object); err == nil {
			return string(bytes)
		}
	}
	return "{}"
}

/*
StringIndent - Converts the contained object back to a JSON formatted string with prefix and indent.
*/
func (g *Container) StringIndent(prefix string, indent string) string {
	if g.object != nil {
		if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil {
			return string(bytes)
		}
	}
	return "{}"
}

/*
New - Create a new gabs JSON object.
*/
func New() *Container {
	return &Container{map[string]interface{}{}}
}

/*
Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object.
*/
func Consume(root interface{}) (*Container, error) {
	if _, ok := root.(map[string]interface{}); ok {
		return &Container{root}, nil
	}
	return nil, ErrInvalidInputObj
}

/*
ParseJSON - Convert a string into a representation of the parsed JSON.
*/
func ParseJSON(sample []byte) (*Container, error) {
	var gabs Container

	if err := json.Unmarshal(sample, &gabs.object); err != nil {
		return nil, err
	}
	if _, ok := gabs.object.(map[string]interface{}); ok {
		return &gabs, nil
	}
	return nil, ErrInvalidInputText
}

/*
ParseJSONFile - Read a file and convert into a representation of the parsed JSON.
*/
func ParseJSONFile(path string) (*Container, error) {
	if len(path) > 0 {
		cBytes, err := ioutil.ReadFile(path)
		if err != nil {
			container, err := ParseJSON(cBytes)
			if err != nil {
				return container, nil
			}
			return nil, err
		}
		return nil, err
	}
	return nil, ErrInvalidPath
}

/*---------------------------------------------------------------------------------------------------
 */

// DEPRECATED METHODS

/*
Push - DEPRECATED: Push a value onto a JSON array.
*/
func (g *Container) Push(target string, value interface{}) error {
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			mmap[target] = append(array, value)
		} else {
			return ErrNotArray
		}
	} else {
		return ErrNotObj
	}
	return nil
}

/*
RemoveElement - DEPRECATED: Remove a value from a JSON array.
*/
func (g *Container) RemoveElement(target string, index int) error {
	if index < 0 {
		return ErrOutOfBounds
	}
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			if index < len(array) {
				mmap[target] = append(array[:index], array[index+1:]...)
			} else {
				return ErrOutOfBounds
			}
		} else {
			return ErrNotArray
		}
	} else {
		return ErrNotObj
	}
	return nil
}

/*
GetElement - DEPRECATED: Get the desired element from a JSON array
*/
func (g *Container) GetElement(target string, index int) *Container {
	if index < 0 {
		return &Container{nil}
	}
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			if index < len(array) {
				return &Container{array[index]}
			}
		}
	}
	return &Container{nil}
}

/*
CountElements - DEPRECATED: Count the elements of a JSON array, returns -1 if the target is not an
array
*/
func (g *Container) CountElements(target string) int {
	if mmap, ok := g.Data().(map[string]interface{}); ok {
		arrayTarget := mmap[target]
		if array, ok := arrayTarget.([]interface{}); ok {
			return len(array)
		}
	}
	return -1
}

/*---------------------------------------------------------------------------------------------------
 */
<|endoftext|>


<commit_before>package cryptica

import "bytes"

type Solution []Direction

func (solution *Solution) String() string {
	var buffer bytes.Buffer
	for i, dir := range *solution {
		switch {
		case i > 0:
			buffer.WriteRune(' ')
		default:
			buffer.WriteString(dir.String())
		}
	}
	return buffer.String()
}

type Game struct {
	State State
	Goal  Goal
	Steps int
}

type Solver interface {
	solve(game Game) Solution
}

type DepthFirstSolver struct {
}

func (solver DepthFirstSolver) solve(game Game) Solution {
	cache := make(map[uint64]int)
	f := func(state State, solution Solution, k interface{}) Solution {
		n := len(solution)
		if n > game.Steps {
			return nil
		}
		code := state.Board.Encode(state)
		if m, exists := cache[code]; exists && m <= n {
			return nil
		}
		cache[code] = n
		if state.Match(game.Goal) {
			return solution
		}
		f := k.(func(State, Solution, interface{}) Solution)
		for i := Up; i <= Right; i++ {
			dir := Direction(i)
			if result := f(state.Move(dir), append(solution, dir), k); result != nil {
				return result
			}
		}
		return nil
	}
	return f(game.State, make(Solution, 0), f)
}

type BreadthFirstSolver struct {
}

func (solver BreadthFirstSolver) solve(game Game) Solution {
	cache := make(map[uint64]Solution)
	var current, next []uint64
	{
		code := game.State.Board.Encode(game.State)
		cache[code] = make(Solution, 0)
		current = []uint64{code}
	}
	for depth := 0; depth < game.Steps && len(current) > 0; depth++ {
		next = make([]uint64, 0)
		for _, code := range current {
			solution := cache[code]
			state := game.State.Board.Decode(code)
			if state.Match(game.Goal) {
				return solution
			}
			for i := Up; i <= Right; i++ {
				dir := Direction(i)
				newstate := state.Move(dir)
				newcode := game.State.Board.Encode(newstate)
				if _, exists := cache[newcode]; !exists {
					newsolution := make(Solution, len(solution))
					copy(newsolution, solution)
					newsolution = append(newsolution, dir)
					cache[newcode] = newsolution
					next = append(next, newcode)
				}
			}
		}
		current = next
	}
	return nil
}
<commit_msg>fixed solution printing<commit_after>package cryptica

import "bytes"

type Solution []Direction

func (solution *Solution) String() string {
	var buffer bytes.Buffer
	for i, dir := range *solution {
		if i > 0 {
			buffer.WriteRune(' ')
		}
		buffer.WriteString(dir.String())
	}
	return buffer.String()
}

type Game struct {
	State State
	Goal  Goal
	Steps int
}

type Solver interface {
	solve(game Game) Solution
}

type DepthFirstSolver struct {
}

func (solver DepthFirstSolver) solve(game Game) Solution {
	cache := make(map[uint64]int)
	f := func(state State, solution Solution, k interface{}) Solution {
		n := len(solution)
		if n > game.Steps {
			return nil
		}
		code := state.Board.Encode(state)
		if m, exists := cache[code]; exists && m <= n {
			return nil
		}
		cache[code] = n
		if state.Match(game.Goal) {
			return solution
		}
		f := k.(func(State, Solution, interface{}) Solution)
		for i := Up; i <= Right; i++ {
			dir := Direction(i)
			if result := f(state.Move(dir), append(solution, dir), k); result != nil {
				return result
			}
		}
		return nil
	}
	return f(game.State, make(Solution, 0), f)
}

type BreadthFirstSolver struct {
}

func (solver BreadthFirstSolver) solve(game Game) Solution {
	cache := make(map[uint64]Solution)
	var current, next []uint64
	{
		code := game.State.Board.Encode(game.State)
		cache[code] = make(Solution, 0)
		current = []uint64{code}
	}
	for depth := 0; depth < game.Steps && len(current) > 0; depth++ {
		next = make([]uint64, 0)
		for _, code := range current {
			solution := cache[code]
			state := game.State.Board.Decode(code)
			if state.Match(game.Goal) {
				return solution
			}
			for i := Up; i <= Right; i++ {
				dir := Direction(i)
				newstate := state.Move(dir)
				newcode := game.State.Board.Encode(newstate)
				if _, exists := cache[newcode]; !exists {
					newsolution := make(Solution, len(solution))
					copy(newsolution, solution)
					newsolution = append(newsolution, dir)
					cache[newcode] = newsolution
					next = append(next, newcode)
				}
			}
		}
		current = next
	}
	return nil
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package gitd Implements Git Smart HTTP backend as a HTTP handler,
// using Git's C implementation as reference: https://github.com/git/git/blob/master/http-backend.c
package gitd

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

func init() {
	if !checkGitVersion(2, 2, 1) {
		log.Fatalln("Git >= v2.2.1 is required")
	}
}

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	reposPath string
}

// ReposPath allows to set the root path where the Git bare repos live.
func ReposPath(rpath string) option {
	return func(l *handler) {
		l.reposPath = rpath
	}
}

// Handler configures the handler and returns an HTTP handler function.
func Handler(h http.Handler, opts ...option) http.Handler {
	reposPath, err := ioutil.TempDir(os.TempDir(), "gitd")
	if err != nil {
		log.Fatalf("%v\n", err)
	}

	// Default configuration.
	handler := &handler{
		reposPath: reposPath,
	}

	// Sets users specified configurations, overriding default ones.
	for _, opt := range opts {
		opt(handler)
	}

	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		handlers := map[*regexp.Regexp]func(http.ResponseWriter, *http.Request, string, string){
			regexp.MustCompile("(.*?)/git-upload-pack$"):  UploadPack,
			regexp.MustCompile("(.*?)/git-receive-pack$"): ReceivePack,
			regexp.MustCompile("(.*?)/info/refs$"):        InfoRefs,
		}

		for re, fn := range handlers {
			if m := re.FindStringSubmatch(req.URL.Path); m != nil {
				repoPath := m[1]
				fn(w, req, handler.reposPath, repoPath)
				return
			}
		}
		h.ServeHTTP(w, req)
	})
}

// UploadPack runs git-upload-pack in a safe manner.
func UploadPack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-upload-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

// ReceivePack runs git-receive-pack in a safe manner.
func ReceivePack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-receive-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

// InfoRefs returns Git object refs.
func InfoRefs(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}

	process := req.URL.Query().Get("service")
	cwd := filepath.Join(reposPath, repoPath)

	if process != "git-receive-pack" && process != "git-upload-pack" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
		return
	}

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-advertisement", process))
	w.WriteHeader(http.StatusOK)

	w.Write(packetWrite(fmt.Sprintf("# service=%s\n", process)))
	w.Write(packetFlush())

	cmd := exec.Command(process, "--stateless-rpc", "--advertise-refs", ".")
	cmd.Dir = cwd
	runCommand(w, req.Body, cmd)
}

// runCommand executes a shell command and pipes its output to HTTP response writer.
// DO NOT expose this function directly to end users as it will create a security breach.
func runCommand(w io.Writer, r io.Reader, cmd *exec.Cmd) {
	if cmd.Dir != "" {
		cmd.Dir = sanitize(cmd.Dir)
	}

	log.Printf("[DEBUG] Running command from %s: %s %s ", cmd.Dir, cmd.Path, cmd.Args)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	if err := cmd.Start(); err != nil {
		log.Printf("[ERROR] %v", err)
	}

	io.Copy(stdin, r)
	io.Copy(w, stdout)
	cmd.Wait()
}

// packetWrite returns bytes of a git packet containing the given string
func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64((len(str) + 4)), 16)

	m := len(s) % 4
	if m != 0 {
		s = strings.Repeat("0", 4-m) + s
	}

	return []byte(s + str)
}

func packetFlush() []byte {
	return []byte("0000")
}

// sanitize Sanitizes name to avoid overwriting sensitive system files
// or executing forbidden binaries
func sanitize(name string) string {
	// Gets rid of volume drive label in Windows
	if len(name) > 1 && name[1] == ':' && runtime.GOOS == "windows" {
		name = name[2:]
	}

	name = filepath.Clean(name)
	name = filepath.ToSlash(name)
	for strings.HasPrefix(name, "../") {
		name = name[3:]
	}
	return name
}

// checkGitVersion checks a given Git version and returns whether or not
// the required version is installed in the system.
func checkGitVersion(major, minor, patch int) bool {
	git, err := exec.LookPath("git")
	if err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	cmd := exec.Command(git, "--version")
	var stdout string
	if stdout, _, err = runAndLog(cmd); err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	output := strings.Split(stdout, "\n")
	if len(output) < 2 {
		log.Printf("[DEBUG] git version output: %v", output)
		return false
	}

	parts := strings.Split(output[0], " ")
	if len(parts) < 3 {
		log.Printf("[DEBUG] git version parts: %v", parts)
		return false
	}

	version := strings.Split(parts[2], ".")
	major2, _ := strconv.Atoi(version[0])
	minor2, _ := strconv.Atoi(version[1])
	patch2, _ := strconv.Atoi(version[2])

	if major2 < major || minor2 < minor || patch2 < patch {
		log.Printf("[INFO] git version not supported: %d.%d.%d", major2, minor2, patch2)
		return false
	}

	return true
}

// Borrowed from https://github.com/mitchellh/packer/blob/master/builder/vmware/common/driver.go
// runAndLog executes Git commands and logs output.
func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("[GitD] Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		message := stderrString
		if message == "" {
			message = stdoutString
		}

		err = fmt.Errorf("[GitD] error: %s", message)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}
<commit_msg>Adds support for decompressing request body.<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package gitd Implements Git Smart HTTP backend as a HTTP handler,
// using Git's C implementation as reference: https://github.com/git/git/blob/master/http-backend.c
package gitd

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

func init() {
	if !checkGitVersion(2, 2, 1) {
		log.Fatalln("Git >= v2.2.1 is required")
	}
}

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	reposPath string
}

// ReposPath allows to set the root path where the Git bare repos live.
func ReposPath(rpath string) option {
	return func(l *handler) {
		l.reposPath = rpath
	}
}

// Handler configures the handler and returns an HTTP handler function.
func Handler(h http.Handler, opts ...option) http.Handler {
	reposPath, err := ioutil.TempDir(os.TempDir(), "gitd")
	if err != nil {
		log.Fatalf("%v\n", err)
	}

	// Default configuration.
	handler := &handler{
		reposPath: reposPath,
	}

	// Sets users specified configurations, overriding default ones.
	for _, opt := range opts {
		opt(handler)
	}

	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		handlers := map[*regexp.Regexp]func(http.ResponseWriter, *http.Request, string, string){
			regexp.MustCompile("(.*?)/git-upload-pack$"):  uploadPack,
			regexp.MustCompile("(.*?)/git-receive-pack$"): receivePack,
			regexp.MustCompile("(.*?)/info/refs$"):        infoRefs,
		}

		for re, fn := range handlers {
			if m := re.FindStringSubmatch(req.URL.Path); m != nil {
				repoPath := m[1]
				fn(w, req, handler.reposPath, repoPath)
				return
			}
		}
		h.ServeHTTP(w, req)
	})
}

// uploadPack runs git-upload-pack in a safe manner.
func uploadPack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-upload-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// receivePack runs git-receive-pack in a safe manner.
func receivePack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-receive-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// infoRefs returns Git object refs.
func infoRefs(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}

	process := req.URL.Query().Get("service")
	cwd := filepath.Join(reposPath, repoPath)

	if process != "git-receive-pack" && process != "git-upload-pack" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
		return
	}

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-advertisement", process))
	w.WriteHeader(http.StatusOK)

	w.Write(packetWrite(fmt.Sprintf("# service=%s\n", process)))
	w.Write(packetFlush())

	cmd := exec.Command(process, "--stateless-rpc", "--advertise-refs", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// runCommand executes a shell command and pipes its output to HTTP response writer.
// DO NOT expose this function directly to end users as it will create a security breach.
func runCommand(w io.Writer, r io.Reader, cmd *exec.Cmd) {
	if cmd.Dir != "" {
		cmd.Dir = sanitize(cmd.Dir)
	}

	log.Printf("[DEBUG] Running command from %s: %s %s ", cmd.Dir, cmd.Path, cmd.Args)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	if err := cmd.Start(); err != nil {
		log.Printf("[ERROR] %v", err)
	}

	io.Copy(stdin, r)
	io.Copy(w, stdout)
	cmd.Wait()
}

// packetWrite returns bytes of a git packet containing the given string
func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64((len(str) + 4)), 16)

	m := len(s) % 4
	if m != 0 {
		s = strings.Repeat("0", 4-m) + s
	}

	return []byte(s + str)
}

func packetFlush() []byte {
	return []byte("0000")
}

// decompress unzips request body if it is compressed by Git clients.
func decompress(r *http.Request) (io.Reader, error) {
	encoding := r.Header.Get("Content-Encoding")
	if encoding != "gzip" && encoding != "x-gzip" {
		return r.Body, nil
	}

	return gzip.NewReader(r.Body)
}

// sanitize Sanitizes name to avoid overwriting sensitive system files
// or executing forbidden binaries
func sanitize(name string) string {
	// Gets rid of volume drive label in Windows
	if len(name) > 1 && name[1] == ':' && runtime.GOOS == "windows" {
		name = name[2:]
	}

	name = filepath.Clean(name)
	name = filepath.ToSlash(name)
	for strings.HasPrefix(name, "../") {
		name = name[3:]
	}
	return name
}

// checkGitVersion checks a given Git version and returns whether or not
// the required version is installed in the system.
func checkGitVersion(major, minor, patch int) bool {
	git, err := exec.LookPath("git")
	if err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	cmd := exec.Command(git, "--version")
	var stdout string
	if stdout, _, err = runAndLog(cmd); err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	output := strings.Split(stdout, "\n")
	if len(output) < 2 {
		log.Printf("[DEBUG] git version output: %v", output)
		return false
	}

	parts := strings.Split(output[0], " ")
	if len(parts) < 3 {
		log.Printf("[DEBUG] git version parts: %v", parts)
		return false
	}

	version := strings.Split(parts[2], ".")
	major2, _ := strconv.Atoi(version[0])
	minor2, _ := strconv.Atoi(version[1])
	patch2, _ := strconv.Atoi(version[2])

	if major2 < major || minor2 < minor || patch2 < patch {
		log.Printf("[INFO] git version not supported: %d.%d.%d", major2, minor2, patch2)
		return false
	}

	return true
}

// Borrowed from https://github.com/mitchellh/packer/blob/master/builder/vmware/common/driver.go
// runAndLog executes Git commands and logs output.
func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("[GitD] Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		message := stderrString
		if message == "" {
			message = stdoutString
		}

		err = fmt.Errorf("[GitD] error: %s", message)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}
<|endoftext|>


<commit_before>package main

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

	"github.com/goby-lang/goby/compiler"
	"github.com/goby-lang/goby/compiler/parser"
	"github.com/goby-lang/goby/igb"
	"github.com/goby-lang/goby/vm"
	_ "github.com/goby-lang/goby/native/db"
	"github.com/pkg/profile"
)

const Version string = vm.Version

func main() {
	profileOptionPtr := flag.Bool("p", false, "Profile program execution")
	versionOptionPtr := flag.Bool("v", false, "Show current Goby version")
	interactiveOptionPtr := flag.Bool("i", false, "Run interactive goby")
	issueOptionPtr := flag.Bool("e", false, "Generate reporting format")

	flag.Parse()

	if *interactiveOptionPtr {
		igb.StartIgb(Version)
		os.Exit(0)
	}

	if *profileOptionPtr {
		defer profile.Start().Stop()
	}

	if *versionOptionPtr {
		fmt.Println(Version)
		os.Exit(0)
	}

	var fp string

	switch flag.Arg(0) {
	case "":
		flag.Usage()
		os.Exit(0)
	case "test":
		args := flag.Args()[1:]
		filePath := flag.Arg(1)
		fileInfo, err := os.Stat(filePath)
		reportErrorAndExit(err)

		dir := extractDirFromFilePath(filePath, fileInfo)
		v, err := vm.New(dir, args)

		if fileInfo.Mode().IsDir() {
			fileInfos, err := ioutil.ReadDir(filePath)
			reportErrorAndExit(err)

			for _, fileInfo := range fileInfos {
				fp := filepath.Join(dir, fileInfo.Name())
				reportErrorAndExit(err)

				err := runSpecFile(v, fp)
				reportErrorAndExit(err)
			}
		} else {
			err := runSpecFile(v, filePath)
			reportErrorAndExit(err)
		}

		instructionSets, err := compiler.CompileToInstructions("Spec.run", parser.NormalMode)
		v.ExecInstructions(instructionSets, filePath)
		return
	default:
		fp = flag.Arg(0)

		if !strings.Contains(fp, ".") {
			flag.Usage()
			os.Exit(0)
		}
	}

	// Execute files normally
	dir, _, fileExt := extractFileInfo(fp)
	file := readFile(fp)

	switch fileExt {
	case "gb", "rb":
		args := flag.Args()[1:]
		instructionSets, err := compiler.CompileToInstructions(string(file), parser.NormalMode)
		reportErrorAndExit(err)

		var v *vm.VM

		if *issueOptionPtr {
			fmt.Println("Will generate issue report on error...")
			v, err = vm.InitIssueReportVM(dir, args)
			defer vm.PrintError(v)
		} else {
			v, err = vm.New(dir, args)
		}
		reportErrorAndExit(err)

		fp, err := filepath.Abs(fp)
		reportErrorAndExit(err)

		v.ExecInstructions(instructionSets, fp)
	default:
		fmt.Printf("Unknown file extension: %s", fileExt)
	}
}

func extractFileInfo(fp string) (dir, filename, fileExt string) {
	dir, filename = filepath.Split(fp)
	dir, _ = filepath.Abs(dir)
	fileExt = filepath.Ext(fp)
	splited := strings.Split(filename, ".")
	filename, fileExt = splited[0], splited[1]
	return
}

func extractDirFromFilePath(filePath string, fileInfo os.FileInfo) string {
	if fileInfo.Mode().IsDir() {
		dir, err := filepath.Abs(filePath)
		reportErrorAndExit(err)
		return dir
	}

	filePath, err := filepath.Abs(filePath)
	reportErrorAndExit(err)
	dir, _, _ := extractFileInfo(filePath)
	return dir
}

func readFile(filepath string) (file []byte) {
	file, err := ioutil.ReadFile(filepath)
	reportErrorAndExit(err)
	return
}

func runSpecFile(v *vm.VM, fp string) (err error) {
	file := readFile(fp)
	instructionSets, err := compiler.CompileToInstructions(string(file), parser.NormalMode)

	if err != nil {
		return
	}

	v.ExecInstructions(instructionSets, fp)
	return
}

func reportErrorAndExit(err error) {
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}
<commit_msg>all: gofmt<commit_after>package main

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

	"github.com/goby-lang/goby/compiler"
	"github.com/goby-lang/goby/compiler/parser"
	"github.com/goby-lang/goby/igb"
	_ "github.com/goby-lang/goby/native/db"
	"github.com/goby-lang/goby/vm"
	"github.com/pkg/profile"
)

const Version string = vm.Version

func main() {
	profileOptionPtr := flag.Bool("p", false, "Profile program execution")
	versionOptionPtr := flag.Bool("v", false, "Show current Goby version")
	interactiveOptionPtr := flag.Bool("i", false, "Run interactive goby")
	issueOptionPtr := flag.Bool("e", false, "Generate reporting format")

	flag.Parse()

	if *interactiveOptionPtr {
		igb.StartIgb(Version)
		os.Exit(0)
	}

	if *profileOptionPtr {
		defer profile.Start().Stop()
	}

	if *versionOptionPtr {
		fmt.Println(Version)
		os.Exit(0)
	}

	var fp string

	switch flag.Arg(0) {
	case "":
		flag.Usage()
		os.Exit(0)
	case "test":
		args := flag.Args()[1:]
		filePath := flag.Arg(1)
		fileInfo, err := os.Stat(filePath)
		reportErrorAndExit(err)

		dir := extractDirFromFilePath(filePath, fileInfo)
		v, err := vm.New(dir, args)

		if fileInfo.Mode().IsDir() {
			fileInfos, err := ioutil.ReadDir(filePath)
			reportErrorAndExit(err)

			for _, fileInfo := range fileInfos {
				fp := filepath.Join(dir, fileInfo.Name())
				reportErrorAndExit(err)

				err := runSpecFile(v, fp)
				reportErrorAndExit(err)
			}
		} else {
			err := runSpecFile(v, filePath)
			reportErrorAndExit(err)
		}

		instructionSets, err := compiler.CompileToInstructions("Spec.run", parser.NormalMode)
		v.ExecInstructions(instructionSets, filePath)
		return
	default:
		fp = flag.Arg(0)

		if !strings.Contains(fp, ".") {
			flag.Usage()
			os.Exit(0)
		}
	}

	// Execute files normally
	dir, _, fileExt := extractFileInfo(fp)
	file := readFile(fp)

	switch fileExt {
	case "gb", "rb":
		args := flag.Args()[1:]
		instructionSets, err := compiler.CompileToInstructions(string(file), parser.NormalMode)
		reportErrorAndExit(err)

		var v *vm.VM

		if *issueOptionPtr {
			fmt.Println("Will generate issue report on error...")
			v, err = vm.InitIssueReportVM(dir, args)
			defer vm.PrintError(v)
		} else {
			v, err = vm.New(dir, args)
		}
		reportErrorAndExit(err)

		fp, err := filepath.Abs(fp)
		reportErrorAndExit(err)

		v.ExecInstructions(instructionSets, fp)
	default:
		fmt.Printf("Unknown file extension: %s", fileExt)
	}
}

func extractFileInfo(fp string) (dir, filename, fileExt string) {
	dir, filename = filepath.Split(fp)
	dir, _ = filepath.Abs(dir)
	fileExt = filepath.Ext(fp)
	splited := strings.Split(filename, ".")
	filename, fileExt = splited[0], splited[1]
	return
}

func extractDirFromFilePath(filePath string, fileInfo os.FileInfo) string {
	if fileInfo.Mode().IsDir() {
		dir, err := filepath.Abs(filePath)
		reportErrorAndExit(err)
		return dir
	}

	filePath, err := filepath.Abs(filePath)
	reportErrorAndExit(err)
	dir, _, _ := extractFileInfo(filePath)
	return dir
}

func readFile(filepath string) (file []byte) {
	file, err := ioutil.ReadFile(filepath)
	reportErrorAndExit(err)
	return
}

func runSpecFile(v *vm.VM, fp string) (err error) {
	file := readFile(fp)
	instructionSets, err := compiler.CompileToInstructions(string(file), parser.NormalMode)

	if err != nil {
		return
	}

	v.ExecInstructions(instructionSets, fp)
	return
}

func reportErrorAndExit(err error) {
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"fmt"
	"unicode/utf8"
)

const (
	// SesDelete is manipulaton type of deleting element in SES
	SesDelete SesType = iota
	// SesCommon is manipulaton type of same element in SES
	SesCommon
	// SesAdd is manipulaton type of adding element in SES
	SesAdd
)

// SesType is manipulaton type
type SesType int

// Point is coordinate in edit graph
type Point struct {
	x, y int
}

// PointWithRoute is coordinate in edit graph attached route
type PointWithRoute struct {
	x, y, r int
}

// SesElem is element of SES
type SesElem struct {
	e rune
	t SesType
}

// Diff is context for calculating difference between a and b
type Diff struct {
	a              []rune
	b              []rune
	m, n           int
	ed             int
	lcs            []rune
	ses            []SesElem
	reverse        bool
	path           []int
	onlyEd         bool
	pointWithRoute []PointWithRoute
}

func max(x, y int) int {
	if x < y {
		return y
	}
	return x
}

// New is initializer of Diff
func New(a string, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	diff.a, diff.b = []rune(a), []rune(b)
	diff.m, diff.n = m, n
	diff.reverse = false
	if m >= n {
		diff.a, diff.b = diff.b, diff.a
		diff.m, diff.n = n, m
		diff.reverse = true
	}
	diff.onlyEd = false
	return diff
}

// OnlyEd enables to calculate only edit distance
func (diff *Diff) OnlyEd() {
	diff.onlyEd = true
}

// Editdistance returns edit distance between a and b
func (diff *Diff) Editdistance() int {
	return diff.ed
}

// Lcs returns LCS (Longest Common Subsequence) between a and b
func (diff *Diff) Lcs() []rune {
	return diff.lcs
}

// Lcs returns LCS (Longest Common Subsequence) string between a and b
func (diff *Diff) LcsString() string {
	return string(diff.lcs)
}

// Ses return SES (Shortest Edit Script) between a and b
func (diff *Diff) Ses() []SesElem {
	return diff.ses
}

// PrintSes prints shortest edit script between a and b
func (diff *Diff) PrintSes() {
	for _, e := range diff.ses {
		switch e.t {
		case SesDelete:
			fmt.Printf("- %c\n", e.e)
		case SesAdd:
			fmt.Printf("+ %c\n", e.e)
		case SesCommon:
			fmt.Printf("  %c\n", e.e)
		}
	}
}

// Compose composes diff between a and b
func (diff *Diff) Compose() {
	fp := make([]int, diff.m+diff.n+3)
	diff.path = make([]int, diff.m+diff.n+3)
	diff.pointWithRoute = make([]PointWithRoute, 0)

	for i := range fp {
		fp[i] = -1
		diff.path[i] = -1
	}

	offset := diff.m + 1
	delta := diff.n - diff.m
	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.onlyEd {
		return
	}

	r := diff.path[delta+offset]
	epc := make([]Point, 0)
	for r != -1 {
		epc = append(epc, Point{x: diff.pointWithRoute[r].x, y: diff.pointWithRoute[r].y})
		r = diff.pointWithRoute[r].r
	}
	diff.recordSeq(epc)
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.path[k-1+offset]
	} else {
		r = diff.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.onlyEd {
		diff.path[k+offset] = len(diff.pointWithRoute)
		diff.pointWithRoute = append(diff.pointWithRoute, PointWithRoute{x: x, y: y, r: r})
	}

	return y
}

func (diff *Diff) recordSeq(epc []Point) {
	x, y := 1, 1
	px, py := 0, 0
	for i := len(epc) - 1; i >= 0; i-- {
		for (px < epc[i].x) || (py < epc[i].y) {
			if (epc[i].y - epc[i].x) > (py - px) {
				t := SesAdd
				if diff.reverse {
					t = SesDelete
				}
				diff.ses = append(diff.ses, SesElem{e: diff.b[py], t: t})
				y++
				py++
			} else if epc[i].y-epc[i].x < py-px {
				t := SesDelete
				if diff.reverse {
					t = SesAdd
				}
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: t})
				x++
				px++
			} else {
				diff.lcs = append(diff.lcs, diff.a[px])
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: SesCommon})
				x++
				y++
				px++
				py++
			}
		}
	}
}
<commit_msg>simplified.<commit_after>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"fmt"
	"unicode/utf8"
)

const (
	// SesDelete is manipulaton type of deleting element in SES
	SesDelete SesType = iota
	// SesCommon is manipulaton type of same element in SES
	SesCommon
	// SesAdd is manipulaton type of adding element in SES
	SesAdd
)

// SesType is manipulaton type
type SesType int

// Point is coordinate in edit graph
type Point struct {
	x, y int
}

// PointWithRoute is coordinate in edit graph attached route
type PointWithRoute struct {
	x, y, r int
}

// SesElem is element of SES
type SesElem struct {
	e rune
	t SesType
}

// Diff is context for calculating difference between a and b
type Diff struct {
	a              []rune
	b              []rune
	m, n           int
	ed             int
	lcs            []rune
	ses            []SesElem
	reverse        bool
	path           []int
	onlyEd         bool
	pointWithRoute []PointWithRoute
}

func max(x, y int) int {
	if x < y {
		return y
	}
	return x
}

// New is initializer of Diff
func New(a, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	diff.a, diff.b = []rune(a), []rune(b)
	diff.m, diff.n = m, n
	diff.reverse = false
	if m >= n {
		diff.a, diff.b = diff.b, diff.a
		diff.m, diff.n = n, m
		diff.reverse = true
	}
	diff.onlyEd = false
	return diff
}

// OnlyEd enables to calculate only edit distance
func (diff *Diff) OnlyEd() {
	diff.onlyEd = true
}

// Editdistance returns edit distance between a and b
func (diff *Diff) Editdistance() int {
	return diff.ed
}

// Lcs returns LCS (Longest Common Subsequence) between a and b
func (diff *Diff) Lcs() []rune {
	return diff.lcs
}

// Lcs returns LCS (Longest Common Subsequence) string between a and b
func (diff *Diff) LcsString() string {
	return string(diff.lcs)
}

// Ses return SES (Shortest Edit Script) between a and b
func (diff *Diff) Ses() []SesElem {
	return diff.ses
}

// PrintSes prints shortest edit script between a and b
func (diff *Diff) PrintSes() {
	for _, e := range diff.ses {
		switch e.t {
		case SesDelete:
			fmt.Printf("- %c\n", e.e)
		case SesAdd:
			fmt.Printf("+ %c\n", e.e)
		case SesCommon:
			fmt.Printf("  %c\n", e.e)
		}
	}
}

// Compose composes diff between a and b
func (diff *Diff) Compose() {
	fp := make([]int, diff.m+diff.n+3)
	diff.path = make([]int, diff.m+diff.n+3)
	diff.pointWithRoute = make([]PointWithRoute, 0)

	for i := range fp {
		fp[i] = -1
		diff.path[i] = -1
	}

	offset := diff.m + 1
	delta := diff.n - diff.m
	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.onlyEd {
		return
	}

	r := diff.path[delta+offset]
	epc := make([]Point, 0)
	for r != -1 {
		epc = append(epc, Point{x: diff.pointWithRoute[r].x, y: diff.pointWithRoute[r].y})
		r = diff.pointWithRoute[r].r
	}
	diff.recordSeq(epc)
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.path[k-1+offset]
	} else {
		r = diff.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.onlyEd {
		diff.path[k+offset] = len(diff.pointWithRoute)
		diff.pointWithRoute = append(diff.pointWithRoute, PointWithRoute{x: x, y: y, r: r})
	}

	return y
}

func (diff *Diff) recordSeq(epc []Point) {
	x, y := 1, 1
	px, py := 0, 0
	for i := len(epc) - 1; i >= 0; i-- {
		for (px < epc[i].x) || (py < epc[i].y) {
			if (epc[i].y - epc[i].x) > (py - px) {
				t := SesAdd
				if diff.reverse {
					t = SesDelete
				}
				diff.ses = append(diff.ses, SesElem{e: diff.b[py], t: t})
				y++
				py++
			} else if epc[i].y-epc[i].x < py-px {
				t := SesDelete
				if diff.reverse {
					t = SesAdd
				}
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: t})
				x++
				px++
			} else {
				diff.lcs = append(diff.lcs, diff.a[px])
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: SesCommon})
				x++
				y++
				px++
				py++
			}
		}
	}
}
<|endoftext|>