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

package fosite

import (
	"context"
	"net/http"
	"net/url"
	"time"
)

const MinParameterEntropy = 8

type TokenType string

const (
	AccessToken   TokenType = "access_token"
	RefreshToken  TokenType = "refresh_token"
	AuthorizeCode TokenType = "authorize_code"
	IDToken       TokenType = "id_token"
)

// OAuth2Provider is an interface that enables you to write OAuth2 handlers with only a few lines of code.
// Check fosite.Fosite for an implementation of this interface.
type OAuth2Provider interface {
	// NewAuthorizeRequest returns an AuthorizeRequest.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.1
	//	 Extension response types MAY contain a space-delimited (%x20) list of
	//	 values, where the order of values does not matter (e.g., response
	//	 type "a b" is the same as "b a").  The meaning of such composite
	//	 response types is defined by their respective specifications.
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2
	//   The redirection endpoint URI MUST be an absolute URI as defined by
	//   [RFC3986] Section 4.3.  The endpoint URI MAY include an
	//   "application/x-www-form-urlencoded" formatted (per Appendix B) query
	//   component ([RFC3986] Section 3.4), which MUST be retained when adding
	//   additional query parameters.  The endpoint URI MUST NOT include a
	//   fragment component.
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2.2 (everything MUST be implemented)
	NewAuthorizeRequest(ctx context.Context, req *http.Request) (AuthorizeRequester, error)

	// NewAuthorizeResponse iterates through all response type handlers and returns their result or
	// ErrUnsupportedResponseType if none of the handler's were able to handle it.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.1.1
	//	 Extension response types MAY contain a space-delimited (%x20) list of
	//	 values, where the order of values does not matter (e.g., response
	//	 type "a b" is the same as "b a").  The meaning of such composite
	//	 response types is defined by their respective specifications.
	//	 If an authorization request is missing the "response_type" parameter,
	//	 or if the response type is not understood, the authorization server
	//	 MUST return an error response as described in Section 4.1.2.1.
	NewAuthorizeResponse(ctx context.Context, requester AuthorizeRequester, session Session) (AuthorizeResponder, error)

	// WriteAuthorizeError returns the error codes to the redirection endpoint or shows the error to the user, if no valid
	// redirect uri was given. Implements rfc6749#section-4.1.2.1
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2
	//   The redirection endpoint URI MUST be an absolute URI as defined by
	//   [RFC3986] Section 4.3.  The endpoint URI MAY include an
	//   "application/x-www-form-urlencoded" formatted (per Appendix B) query
	//   component ([RFC3986] Section 3.4), which MUST be retained when adding
	//   additional query parameters.  The endpoint URI MUST NOT include a
	//   fragment component.
	// * https://tools.ietf.org/html/rfc6749#section-4.1.2.1 (everything)
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2.2 (everything MUST be implemented)
	WriteAuthorizeError(rw http.ResponseWriter, requester AuthorizeRequester, err error)

	// WriteAuthorizeResponse persists the AuthorizeSession in the store and redirects the user agent to the provided
	// redirect url or returns an error if storage failed.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#rfc6749#section-4.1.2.1
	//   After completing its interaction with the resource owner, the
	//   authorization server directs the resource owner's user-agent back to
	//   the client.  The authorization server redirects the user-agent to the
	//   client's redirection endpoint previously established with the
	//   authorization server during the client registration process or when
	//   making the authorization request.
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2.2 (everything MUST be implemented)
	WriteAuthorizeResponse(rw http.ResponseWriter, requester AuthorizeRequester, responder AuthorizeResponder)

	// NewAccessRequest creates a new access request object and validates
	// various parameters.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.2 (everything)
	// * https://tools.ietf.org/html/rfc6749#section-3.2.1 (everything)
	//
	// Furthermore the registered handlers should implement their specs accordingly.
	NewAccessRequest(ctx context.Context, req *http.Request, session Session) (AccessRequester, error)

	// NewAccessResponse creates a new access response and validates that access_token and token_type are set.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc6749#section-5.1
	NewAccessResponse(ctx context.Context, requester AccessRequester) (AccessResponder, error)

	// WriteAccessError writes an access request error response.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-5.2 (everything)
	WriteAccessError(rw http.ResponseWriter, requester AccessRequester, err error)

	// WriteAccessResponse writes the access response.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc6749#section-5.1
	WriteAccessResponse(rw http.ResponseWriter, requester AccessRequester, responder AccessResponder)

	// NewRevocationRequest handles incoming token revocation requests and validates various parameters.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc7009#section-2.1
	NewRevocationRequest(ctx context.Context, r *http.Request) error

	// WriteRevocationResponse writes the revoke response.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc7009#section-2.2
	WriteRevocationResponse(rw http.ResponseWriter, err error)

	// IntrospectToken returns token metadata, if the token is valid. Tokens generated by the authorization endpoint,
	// such as the authorization code, can not be introspected.
	IntrospectToken(ctx context.Context, token string, tokenType TokenType, session Session, scope ...string) (TokenType, AccessRequester, error)

	// NewIntrospectionRequest initiates token introspection as defined in
	// https://tools.ietf.org/search/rfc7662#section-2.1
	NewIntrospectionRequest(ctx context.Context, r *http.Request, session Session) (IntrospectionResponder, error)

	// WriteIntrospectionError responds with an error if token introspection failed as defined in
	// https://tools.ietf.org/search/rfc7662#section-2.3
	WriteIntrospectionError(rw http.ResponseWriter, err error)

	// WriteIntrospectionResponse responds with token metadata discovered by token introspection as defined in
	// https://tools.ietf.org/search/rfc7662#section-2.2
	WriteIntrospectionResponse(rw http.ResponseWriter, r IntrospectionResponder)
}

// IntrospectionResponder is the response object that will be returned when token introspection was successful,
// for example when the client is allowed to perform token introspection. Refer to
// https://tools.ietf.org/search/rfc7662#section-2.2 for more details.
type IntrospectionResponder interface {
	// IsActive returns true if the introspected token is active and false otherwise.
	IsActive() bool

	// AccessRequester returns nil when IsActive() is false and the original access request object otherwise.
	GetAccessRequester() AccessRequester

	// GetTokenType optionally returns the type of the token that was introspected. The could be "access_token", "refresh_token",
	// or if the type can not be determined an empty string.
	GetTokenType() TokenType
}

// Requester is an abstract interface for handling requests in Fosite.
type Requester interface {
	// SetID sets the unique identifier.
	SetID(id string)

	// GetID returns a unique identifier.
	GetID() string

	// GetRequestedAt returns the time the request was created.
	GetRequestedAt() (requestedAt time.Time)

	// GetClient returns the request's client.
	GetClient() (client Client)

	// GetRequestedScopes returns the request's scopes.
	GetRequestedScopes() (scopes Arguments)

	// GetRequestedAudience returns the requested audiences for this request.
	GetRequestedAudience() (audience Arguments)

	// SetRequestedScopes sets the request's scopes.
	SetRequestedScopes(scopes Arguments)

	// SetRequestedAudience sets the requested audience.
	SetRequestedAudience(audience Arguments)

	// AppendRequestedScope appends a scope to the request.
	AppendRequestedScope(scope string)

	// GetGrantScopes returns all granted scopes.
	GetGrantedScopes() (grantedScopes Arguments)

	// GetGrantedAudience returns all granted audiences.
	GetGrantedAudience() (grantedAudience Arguments)

	// GrantScope marks a request's scope as granted.
	GrantScope(scope string)

	// GrantAudience marks a request's audience as granted.
	GrantAudience(audience string)

	// GetSession returns a pointer to the request's session or nil if none is set.
	GetSession() (session Session)

	// GetSession sets the request's session pointer.
	SetSession(session Session)

	// GetRequestForm returns the request's form input.
	GetRequestForm() url.Values

	// Merge merges the argument into the method receiver.
	Merge(requester Requester)

	// Sanitize returns a sanitized clone of the request which can be used for storage.
	Sanitize(allowedParameters []string) Requester
}

// AccessRequester is a token endpoint's request context.
type AccessRequester interface {
	// GetGrantType returns the requests grant type.
	GetGrantTypes() (grantTypes Arguments)

	Requester
}

// AuthorizeRequester is an authorize endpoint's request context.
type AuthorizeRequester interface {
	// GetResponseTypes returns the requested response types
	GetResponseTypes() (responseTypes Arguments)

	// SetResponseTypeHandled marks a response_type (e.g. token or code) as handled indicating that the response type
	// is supported.
	SetResponseTypeHandled(responseType string)

	// DidHandleAllResponseTypes returns if all requested response types have been handled correctly
	DidHandleAllResponseTypes() (didHandle bool)

	// GetRedirectURI returns the requested redirect URI
	GetRedirectURI() (redirectURL *url.URL)

	// IsRedirectURIValid returns false if the redirect is not rfc-conform (i.e. missing client, not on white list,
	// or malformed)
	IsRedirectURIValid() (isValid bool)

	// GetState returns the request's state.
	GetState() (state string)

	Requester
}

// AccessResponder is a token endpoint's response.
type AccessResponder interface {
	// SetExtra sets a key value pair for the access response.
	SetExtra(key string, value interface{})

	// GetExtra returns a key's value.
	GetExtra(key string) interface{}

	SetExpiresIn(time.Duration)

	SetScopes(scopes Arguments)

	// SetAccessToken sets the responses mandatory access token.
	SetAccessToken(token string)

	// SetTokenType set's the responses mandatory token type
	SetTokenType(tokenType string)

	// SetAccessToken returns the responses access token.
	GetAccessToken() (token string)

	// GetTokenType returns the responses token type.
	GetTokenType() (token string)

	// ToMap converts the response to a map.
	ToMap() map[string]interface{}
}

// AuthorizeResponder is an authorization endpoint's response.
type AuthorizeResponder interface {
	// GetCode returns the response's authorize code if set.
	GetCode() string

	// GetHeader returns the response's header
	GetHeader() (header http.Header)

	// AddHeader adds an header key value pair to the response
	AddHeader(key, value string)

	// GetQuery returns the response's query
	GetQuery() (query url.Values)

	// AddQuery adds an url query key value pair to the response
	AddQuery(key, value string)

	// GetHeader returns the response's url fragments
	GetFragment() (fragment url.Values)

	// AddHeader adds a key value pair to the response's url fragment
	AddFragment(key, value string)
}
<commit_msg>docs: update SetSession comment (#423)<commit_after>/*
 * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @copyright 	2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
 * @license 	Apache-2.0
 *
 */

package fosite

import (
	"context"
	"net/http"
	"net/url"
	"time"
)

const MinParameterEntropy = 8

type TokenType string

const (
	AccessToken   TokenType = "access_token"
	RefreshToken  TokenType = "refresh_token"
	AuthorizeCode TokenType = "authorize_code"
	IDToken       TokenType = "id_token"
)

// OAuth2Provider is an interface that enables you to write OAuth2 handlers with only a few lines of code.
// Check fosite.Fosite for an implementation of this interface.
type OAuth2Provider interface {
	// NewAuthorizeRequest returns an AuthorizeRequest.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.1
	//	 Extension response types MAY contain a space-delimited (%x20) list of
	//	 values, where the order of values does not matter (e.g., response
	//	 type "a b" is the same as "b a").  The meaning of such composite
	//	 response types is defined by their respective specifications.
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2
	//   The redirection endpoint URI MUST be an absolute URI as defined by
	//   [RFC3986] Section 4.3.  The endpoint URI MAY include an
	//   "application/x-www-form-urlencoded" formatted (per Appendix B) query
	//   component ([RFC3986] Section 3.4), which MUST be retained when adding
	//   additional query parameters.  The endpoint URI MUST NOT include a
	//   fragment component.
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2.2 (everything MUST be implemented)
	NewAuthorizeRequest(ctx context.Context, req *http.Request) (AuthorizeRequester, error)

	// NewAuthorizeResponse iterates through all response type handlers and returns their result or
	// ErrUnsupportedResponseType if none of the handler's were able to handle it.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.1.1
	//	 Extension response types MAY contain a space-delimited (%x20) list of
	//	 values, where the order of values does not matter (e.g., response
	//	 type "a b" is the same as "b a").  The meaning of such composite
	//	 response types is defined by their respective specifications.
	//	 If an authorization request is missing the "response_type" parameter,
	//	 or if the response type is not understood, the authorization server
	//	 MUST return an error response as described in Section 4.1.2.1.
	NewAuthorizeResponse(ctx context.Context, requester AuthorizeRequester, session Session) (AuthorizeResponder, error)

	// WriteAuthorizeError returns the error codes to the redirection endpoint or shows the error to the user, if no valid
	// redirect uri was given. Implements rfc6749#section-4.1.2.1
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2
	//   The redirection endpoint URI MUST be an absolute URI as defined by
	//   [RFC3986] Section 4.3.  The endpoint URI MAY include an
	//   "application/x-www-form-urlencoded" formatted (per Appendix B) query
	//   component ([RFC3986] Section 3.4), which MUST be retained when adding
	//   additional query parameters.  The endpoint URI MUST NOT include a
	//   fragment component.
	// * https://tools.ietf.org/html/rfc6749#section-4.1.2.1 (everything)
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2.2 (everything MUST be implemented)
	WriteAuthorizeError(rw http.ResponseWriter, requester AuthorizeRequester, err error)

	// WriteAuthorizeResponse persists the AuthorizeSession in the store and redirects the user agent to the provided
	// redirect url or returns an error if storage failed.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#rfc6749#section-4.1.2.1
	//   After completing its interaction with the resource owner, the
	//   authorization server directs the resource owner's user-agent back to
	//   the client.  The authorization server redirects the user-agent to the
	//   client's redirection endpoint previously established with the
	//   authorization server during the client registration process or when
	//   making the authorization request.
	// * https://tools.ietf.org/html/rfc6749#section-3.1.2.2 (everything MUST be implemented)
	WriteAuthorizeResponse(rw http.ResponseWriter, requester AuthorizeRequester, responder AuthorizeResponder)

	// NewAccessRequest creates a new access request object and validates
	// various parameters.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-3.2 (everything)
	// * https://tools.ietf.org/html/rfc6749#section-3.2.1 (everything)
	//
	// Furthermore the registered handlers should implement their specs accordingly.
	NewAccessRequest(ctx context.Context, req *http.Request, session Session) (AccessRequester, error)

	// NewAccessResponse creates a new access response and validates that access_token and token_type are set.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc6749#section-5.1
	NewAccessResponse(ctx context.Context, requester AccessRequester) (AccessResponder, error)

	// WriteAccessError writes an access request error response.
	//
	// The following specs must be considered in any implementation of this method:
	// * https://tools.ietf.org/html/rfc6749#section-5.2 (everything)
	WriteAccessError(rw http.ResponseWriter, requester AccessRequester, err error)

	// WriteAccessResponse writes the access response.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc6749#section-5.1
	WriteAccessResponse(rw http.ResponseWriter, requester AccessRequester, responder AccessResponder)

	// NewRevocationRequest handles incoming token revocation requests and validates various parameters.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc7009#section-2.1
	NewRevocationRequest(ctx context.Context, r *http.Request) error

	// WriteRevocationResponse writes the revoke response.
	//
	// The following specs must be considered in any implementation of this method:
	// https://tools.ietf.org/html/rfc7009#section-2.2
	WriteRevocationResponse(rw http.ResponseWriter, err error)

	// IntrospectToken returns token metadata, if the token is valid. Tokens generated by the authorization endpoint,
	// such as the authorization code, can not be introspected.
	IntrospectToken(ctx context.Context, token string, tokenType TokenType, session Session, scope ...string) (TokenType, AccessRequester, error)

	// NewIntrospectionRequest initiates token introspection as defined in
	// https://tools.ietf.org/search/rfc7662#section-2.1
	NewIntrospectionRequest(ctx context.Context, r *http.Request, session Session) (IntrospectionResponder, error)

	// WriteIntrospectionError responds with an error if token introspection failed as defined in
	// https://tools.ietf.org/search/rfc7662#section-2.3
	WriteIntrospectionError(rw http.ResponseWriter, err error)

	// WriteIntrospectionResponse responds with token metadata discovered by token introspection as defined in
	// https://tools.ietf.org/search/rfc7662#section-2.2
	WriteIntrospectionResponse(rw http.ResponseWriter, r IntrospectionResponder)
}

// IntrospectionResponder is the response object that will be returned when token introspection was successful,
// for example when the client is allowed to perform token introspection. Refer to
// https://tools.ietf.org/search/rfc7662#section-2.2 for more details.
type IntrospectionResponder interface {
	// IsActive returns true if the introspected token is active and false otherwise.
	IsActive() bool

	// AccessRequester returns nil when IsActive() is false and the original access request object otherwise.
	GetAccessRequester() AccessRequester

	// GetTokenType optionally returns the type of the token that was introspected. The could be "access_token", "refresh_token",
	// or if the type can not be determined an empty string.
	GetTokenType() TokenType
}

// Requester is an abstract interface for handling requests in Fosite.
type Requester interface {
	// SetID sets the unique identifier.
	SetID(id string)

	// GetID returns a unique identifier.
	GetID() string

	// GetRequestedAt returns the time the request was created.
	GetRequestedAt() (requestedAt time.Time)

	// GetClient returns the request's client.
	GetClient() (client Client)

	// GetRequestedScopes returns the request's scopes.
	GetRequestedScopes() (scopes Arguments)

	// GetRequestedAudience returns the requested audiences for this request.
	GetRequestedAudience() (audience Arguments)

	// SetRequestedScopes sets the request's scopes.
	SetRequestedScopes(scopes Arguments)

	// SetRequestedAudience sets the requested audience.
	SetRequestedAudience(audience Arguments)

	// AppendRequestedScope appends a scope to the request.
	AppendRequestedScope(scope string)

	// GetGrantScopes returns all granted scopes.
	GetGrantedScopes() (grantedScopes Arguments)

	// GetGrantedAudience returns all granted audiences.
	GetGrantedAudience() (grantedAudience Arguments)

	// GrantScope marks a request's scope as granted.
	GrantScope(scope string)

	// GrantAudience marks a request's audience as granted.
	GrantAudience(audience string)

	// GetSession returns a pointer to the request's session or nil if none is set.
	GetSession() (session Session)

	// SetSession sets the request's session pointer.
	SetSession(session Session)

	// GetRequestForm returns the request's form input.
	GetRequestForm() url.Values

	// Merge merges the argument into the method receiver.
	Merge(requester Requester)

	// Sanitize returns a sanitized clone of the request which can be used for storage.
	Sanitize(allowedParameters []string) Requester
}

// AccessRequester is a token endpoint's request context.
type AccessRequester interface {
	// GetGrantType returns the requests grant type.
	GetGrantTypes() (grantTypes Arguments)

	Requester
}

// AuthorizeRequester is an authorize endpoint's request context.
type AuthorizeRequester interface {
	// GetResponseTypes returns the requested response types
	GetResponseTypes() (responseTypes Arguments)

	// SetResponseTypeHandled marks a response_type (e.g. token or code) as handled indicating that the response type
	// is supported.
	SetResponseTypeHandled(responseType string)

	// DidHandleAllResponseTypes returns if all requested response types have been handled correctly
	DidHandleAllResponseTypes() (didHandle bool)

	// GetRedirectURI returns the requested redirect URI
	GetRedirectURI() (redirectURL *url.URL)

	// IsRedirectURIValid returns false if the redirect is not rfc-conform (i.e. missing client, not on white list,
	// or malformed)
	IsRedirectURIValid() (isValid bool)

	// GetState returns the request's state.
	GetState() (state string)

	Requester
}

// AccessResponder is a token endpoint's response.
type AccessResponder interface {
	// SetExtra sets a key value pair for the access response.
	SetExtra(key string, value interface{})

	// GetExtra returns a key's value.
	GetExtra(key string) interface{}

	SetExpiresIn(time.Duration)

	SetScopes(scopes Arguments)

	// SetAccessToken sets the responses mandatory access token.
	SetAccessToken(token string)

	// SetTokenType set's the responses mandatory token type
	SetTokenType(tokenType string)

	// SetAccessToken returns the responses access token.
	GetAccessToken() (token string)

	// GetTokenType returns the responses token type.
	GetTokenType() (token string)

	// ToMap converts the response to a map.
	ToMap() map[string]interface{}
}

// AuthorizeResponder is an authorization endpoint's response.
type AuthorizeResponder interface {
	// GetCode returns the response's authorize code if set.
	GetCode() string

	// GetHeader returns the response's header
	GetHeader() (header http.Header)

	// AddHeader adds an header key value pair to the response
	AddHeader(key, value string)

	// GetQuery returns the response's query
	GetQuery() (query url.Values)

	// AddQuery adds an url query key value pair to the response
	AddQuery(key, value string)

	// GetHeader returns the response's url fragments
	GetFragment() (fragment url.Values)

	// AddHeader adds a key value pair to the response's url fragment
	AddFragment(key, value string)
}
<|endoftext|>


<commit_before>package gitgo

import (
	"bufio"
	"bytes"
	"compress/zlib"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	RFC2822 = "Mon Jan 2 15:04:05 2006 -0700"
)

// GitObject represents a commit, tree, or blob.
// Under the hood, these may be objects stored directly
// or through packfiles
type GitObject interface {
	Type() string
	//Contents() string
}

type gitObject struct {
	Type string

	// Commit fields
	Tree      string
	Parents   []string
	Author    string
	Committer string
	Message   []byte
	Size      string

	// Tree
	Blobs []objectMeta
	Trees []objectMeta

	// Blob
	Contents []byte
}

// A Blob compresses content from a file
type Blob struct {
	_type    string
	size     string
	Contents []byte
	rawData  []byte
}

func (b Blob) Type() string {
	return b._type
}

type Commit struct {
	_type      string
	Name       SHA
	Tree       string
	Parents    []SHA
	Author     string
	AuthorDate time.Time
	Committer  string
	Message    []byte
	size       string
	rawData    []byte
}

func (c Commit) Type() string {
	return c._type
}

type Tree struct {
	_type string
	Blobs []objectMeta
	Trees []objectMeta
	size  string
}

func (t Tree) Type() string {
	return t._type
}

// objectMeta contains the metadata
// (hash, permissions, and filename)
// corresponding either to a blob (leaf) or another tree
type objectMeta struct {
	Hash     SHA
	Perms    string
	filename string
}

func NewObject(input SHA, basedir string) (obj GitObject, err error) {
	if path.Base(basedir) != ".git" {
		basedir = path.Join(basedir, ".git")
	}

	candidate := basedir
	for {
		_, err := os.Stat(candidate)
		if err == nil {
			basedir = candidate
			break
		}
		if !os.IsNotExist(err) {
			return nil, err
		}

		// This should not be the main condition of the for loop
		// just in case the filesystem root directory contains
		// a .git subdirectory
		// TODO check for mountpoint
		if candidate == "/" {
			return nil, fmt.Errorf("not a git repository (or any parent up to root /")
		}
		candidate = path.Join(candidate, "..", "..", ".git")
	}

	if len(input) < 4 {
		return nil, fmt.Errorf("input SHA must be at least 4 characters")
	}

	filename := path.Join(basedir, "objects", string(input[:2]), string(input[2:]))
	_, err = os.Stat(filename)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}

		// check the directory for a file with the SHA as a prefix
		_, err = os.Stat(path.Join(basedir, "objects", string(input[:2])))
		if err != nil {
			if !os.IsNotExist(err) {
				return nil, err
			}
		} else {
			dirname := path.Join(basedir, "objects", string(input[:2]))
			files, err := ioutil.ReadDir(dirname)
			if err != nil {
				return nil, err
			}
			for _, file := range files {
				if strings.HasPrefix(file.Name(), string(input[2:])) {
					return objectFromFile(path.Join(dirname, file.Name()), input, basedir)
				}
			}
		}

		// try the packfile
		obj, err := searchPacks(input, basedir)
		if err != nil {
			return nil, err
		}
		return obj.normalize(basedir)

	}

	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	r, err := zlib.NewReader(f)
	if err != nil {
		return nil, err
	}
	return parseObj(r, input, basedir)

}

func objectFromFile(filename string, name SHA, basedir string) (GitObject, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	r, err := zlib.NewReader(f)
	if err != nil {
		return nil, err
	}
	return parseObj(r, name, basedir)
}

func normalizePerms(perms string) string {
	// TODO don't store permissions as a string
	for len(perms) < 6 {
		perms = "0" + perms
	}
	return perms
}

func parseObj(r io.Reader, name SHA, basedir string) (result GitObject, err error) {
	// TODO fixme
	bts, err := ioutil.ReadAll(r)
	if err != nil {
		return result, err
	}
	obj := bts

	parts := bytes.Split(obj, []byte("\x00"))
	parts = bytes.Fields(parts[0])
	resultType := string(parts[0])
	resultSize := string(parts[1])
	nullIndex := bytes.Index(obj, []byte("\x00"))

	switch resultType {
	case "commit":
		return parseCommit(bytes.NewReader(obj[nullIndex+1:]), resultSize, name)
	case "tree":
		return parseTree(bytes.NewReader(obj), resultSize, basedir)

	case "blob":
		return parseBlob(bytes.NewReader(obj[nullIndex+1:]), resultSize)
	default:
		err = fmt.Errorf("Received unknown object type %s", resultType)
	}

	return
}

// ScanNullLines is like bufio.ScanLines, except it uses the null character as the delimiter
// instead of a newline
func ScanNullLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexByte(data, '\x00'); i >= 0 {
		// We have a full null-terminated line.
		return i + 1, data[0:i], nil
	}
	// If we're at EOF, we have a final, non-terminated "line". Return it.
	if atEOF {
		return len(data), data, nil
	}
	// Request more data.
	return 0, nil, nil
}

func parseCommit(r io.Reader, resultSize string, name SHA) (Commit, error) {
	var commit = Commit{_type: "commit", size: resultSize}
	bts, err := ioutil.ReadAll(r)
	if err != nil {
		return commit, err
	}
	lines := bytes.Split(bts, []byte("\n"))
	for i, line := range lines {
		// The next line is the commit message
		if len(bytes.Fields(line)) == 0 {
			commit.Message = bytes.Join(lines[i+1:], []byte("\n"))
			break
		}
		parts := bytes.Fields(line)
		key := parts[0]
		switch keyType(key) {
		case treeKey:
			commit.Tree = string(parts[1])
		case parentKey:
			commit.Parents = append(commit.Parents, SHA(string(parts[1])))
		case authorKey:
			authorline := string(bytes.Join(parts[1:], []byte(" ")))
			author, date, err := parseAuthorString(authorline)
			if err != nil {
				return commit, err
			}
			commit.Author = author
			commit.AuthorDate = date
		case committerKey:
			commit.Committer = string(bytes.Join(parts[1:], []byte(" ")))
		default:
			err = fmt.Errorf("encountered unknown field in commit: %s", key)
			return commit, err
		}
	}
	commit.Name = name
	return commit, nil
}

func parseTree(r io.Reader, resultSize string, basedir string) (Tree, error) {
	var tree = Tree{_type: "tree", size: resultSize}

	scanner := bufio.NewScanner(r)
	scanner.Split(ScanNullLines)

	var tmp objectMeta

	var resultObjs []objectMeta

	for count := 0; ; count++ {
		done := !scanner.Scan()
		if done {
			break
		}

		txt := scanner.Text()

		if count == 0 {
			// the first time through, scanner.Text() will be
			// "tree <size>"
			continue
		}
		if count == 1 {
			// the second time through, scanner.Text() will be
			// <perms> <filename>
			// separated by a space
			fields := strings.Fields(txt)
			tmp.Perms = normalizePerms(fields[0])
			tmp.filename = fields[1]
			continue
		}

		// after the second time through, scanner.Text() will be
		// <sha><perms2> <file2>
		// where perms2 and file2 refer to the permissions and filename (respectively)
		// of the NEXT object, and <sha> is the first 20 bytes exactly.
		// If there is no next object (this is the last object)
		// then scanner.Text() will yield exactly 20 bytes.

		// decode the next 20 bytes to get the SHA
		tmp.Hash = SHA(hex.EncodeToString([]byte(txt[:20])))
		resultObjs = append(resultObjs, tmp)
		if len(txt) <= 20 {
			// We've read the last line
			break
		}

		// Now, tmp points to the next object in the tree listing
		tmp = objectMeta{}
		remainder := txt[20:]
		fields := strings.Fields(remainder)
		tmp.Perms = normalizePerms(fields[0])
		tmp.filename = fields[1]
	}

	if err := scanner.Err(); err != nil && err != io.EOF {
		return tree, err
	}

	for _, part := range resultObjs {
		obj, err := NewObject(part.Hash, basedir)
		if err != nil {
			return tree, err
		}

		if o, ok := obj.(*packObject); ok {
			obj, err = o.normalize(basedir)
			if err != nil {
				return tree, err
			}
		}

		switch obj.Type() {
		case "tree":
			tree.Trees = append(tree.Trees, part)
		case "blob":
			tree.Blobs = append(tree.Blobs, part)
		default:
			return tree, fmt.Errorf("Unknown type found: %s", obj.Type())
		}
	}
	return tree, nil
}

func parseBlob(r io.Reader, resultSize string) (Blob, error) {
	var blob = Blob{_type: "blob", size: resultSize}
	bts, err := ioutil.ReadAll(r)
	blob.Contents = bts
	return blob, err
}

func findFromPrefix(prefix SHA, basedir string) (GitObject, error) {
	objectsDir := path.Join(basedir, "objects", string(prefix[:2]))
	files, err := ioutil.ReadDir(objectsDir)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
	}

	file, err := findUniquePrefix(prefix, files)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}

		// Try the packfile
		obj, err := searchPacks(prefix, basedir)
		if err != nil {
			return nil, err
		}
		return obj.normalize(basedir)
	}
	f, err := os.Open(file.Name())
	defer f.Close()
	r, err := zlib.NewReader(f)
	if err != nil {
		return nil, err
	}
	return parseObj(r, prefix, basedir)

}

func findUniquePrefix(prefix SHA, files []os.FileInfo) (os.FileInfo, error) {
	var result os.FileInfo
	for _, file := range files {
		if file.IsDir() {
			continue
		}
		if strings.HasPrefix(file.Name(), string(prefix)) {
			if result != nil {
				return nil, fmt.Errorf("prefix is not unique: %s", prefix)
			}
			result = file
		}
	}
	if result == nil {
		return nil, os.ErrNotExist
	}
	return result, nil
}

func parseAuthorString(str string) (author string, date time.Time, err error) {
	const layout = "Mon Jan _2 15:04:05 2006 -0700"
	var authorW bytes.Buffer
	var dateW bytes.Buffer

	s := bufio.NewScanner(strings.NewReader(str))
	s.Split(bufio.ScanBytes)

	// git will ignore '<' if it appears in an author's name
	// so we can safely use it as a delimiter
	for s.Scan() {
		authorW.Write(s.Bytes())
		if s.Text() == ">" {
			break
		}
	}
	for s.Scan() {
		dateW.Write(s.Bytes())
	}
	if s.Err() != nil {
		err = s.Err()
		return
	}

	timestamp, err := strconv.Atoi(strings.Fields(dateW.String())[0])
	if err != nil {
		return
	}
	timezone := strings.Fields(dateW.String())[1]
	t := time.Unix(int64(timestamp), 0)
	date, err = time.Parse(layout, fmt.Sprintf("%s %s", t.Format(time.ANSIC), timezone))

	return strings.TrimSpace(authorW.String()), date, err
}
<commit_msg>Fix timezone and locale issue<commit_after>package gitgo

import (
	"bufio"
	"bytes"
	"compress/zlib"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	RFC2822 = "Mon Jan 2 15:04:05 2006 -0700"
)

// GitObject represents a commit, tree, or blob.
// Under the hood, these may be objects stored directly
// or through packfiles
type GitObject interface {
	Type() string
	//Contents() string
}

type gitObject struct {
	Type string

	// Commit fields
	Tree      string
	Parents   []string
	Author    string
	Committer string
	Message   []byte
	Size      string

	// Tree
	Blobs []objectMeta
	Trees []objectMeta

	// Blob
	Contents []byte
}

// A Blob compresses content from a file
type Blob struct {
	_type    string
	size     string
	Contents []byte
	rawData  []byte
}

func (b Blob) Type() string {
	return b._type
}

type Commit struct {
	_type      string
	Name       SHA
	Tree       string
	Parents    []SHA
	Author     string
	AuthorDate time.Time
	Committer  string
	Message    []byte
	size       string
	rawData    []byte
}

func (c Commit) Type() string {
	return c._type
}

type Tree struct {
	_type string
	Blobs []objectMeta
	Trees []objectMeta
	size  string
}

func (t Tree) Type() string {
	return t._type
}

// objectMeta contains the metadata
// (hash, permissions, and filename)
// corresponding either to a blob (leaf) or another tree
type objectMeta struct {
	Hash     SHA
	Perms    string
	filename string
}

func NewObject(input SHA, basedir string) (obj GitObject, err error) {
	if path.Base(basedir) != ".git" {
		basedir = path.Join(basedir, ".git")
	}

	candidate := basedir
	for {
		_, err := os.Stat(candidate)
		if err == nil {
			basedir = candidate
			break
		}
		if !os.IsNotExist(err) {
			return nil, err
		}

		// This should not be the main condition of the for loop
		// just in case the filesystem root directory contains
		// a .git subdirectory
		// TODO check for mountpoint
		if candidate == "/" {
			return nil, fmt.Errorf("not a git repository (or any parent up to root /")
		}
		candidate = path.Join(candidate, "..", "..", ".git")
	}

	if len(input) < 4 {
		return nil, fmt.Errorf("input SHA must be at least 4 characters")
	}

	filename := path.Join(basedir, "objects", string(input[:2]), string(input[2:]))
	_, err = os.Stat(filename)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}

		// check the directory for a file with the SHA as a prefix
		_, err = os.Stat(path.Join(basedir, "objects", string(input[:2])))
		if err != nil {
			if !os.IsNotExist(err) {
				return nil, err
			}
		} else {
			dirname := path.Join(basedir, "objects", string(input[:2]))
			files, err := ioutil.ReadDir(dirname)
			if err != nil {
				return nil, err
			}
			for _, file := range files {
				if strings.HasPrefix(file.Name(), string(input[2:])) {
					return objectFromFile(path.Join(dirname, file.Name()), input, basedir)
				}
			}
		}

		// try the packfile
		obj, err := searchPacks(input, basedir)
		if err != nil {
			return nil, err
		}
		return obj.normalize(basedir)

	}

	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	r, err := zlib.NewReader(f)
	if err != nil {
		return nil, err
	}
	return parseObj(r, input, basedir)

}

func objectFromFile(filename string, name SHA, basedir string) (GitObject, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	r, err := zlib.NewReader(f)
	if err != nil {
		return nil, err
	}
	return parseObj(r, name, basedir)
}

func normalizePerms(perms string) string {
	// TODO don't store permissions as a string
	for len(perms) < 6 {
		perms = "0" + perms
	}
	return perms
}

func parseObj(r io.Reader, name SHA, basedir string) (result GitObject, err error) {
	// TODO fixme
	bts, err := ioutil.ReadAll(r)
	if err != nil {
		return result, err
	}
	obj := bts

	parts := bytes.Split(obj, []byte("\x00"))
	parts = bytes.Fields(parts[0])
	resultType := string(parts[0])
	resultSize := string(parts[1])
	nullIndex := bytes.Index(obj, []byte("\x00"))

	switch resultType {
	case "commit":
		return parseCommit(bytes.NewReader(obj[nullIndex+1:]), resultSize, name)
	case "tree":
		return parseTree(bytes.NewReader(obj), resultSize, basedir)

	case "blob":
		return parseBlob(bytes.NewReader(obj[nullIndex+1:]), resultSize)
	default:
		err = fmt.Errorf("Received unknown object type %s", resultType)
	}

	return
}

// ScanNullLines is like bufio.ScanLines, except it uses the null character as the delimiter
// instead of a newline
func ScanNullLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexByte(data, '\x00'); i >= 0 {
		// We have a full null-terminated line.
		return i + 1, data[0:i], nil
	}
	// If we're at EOF, we have a final, non-terminated "line". Return it.
	if atEOF {
		return len(data), data, nil
	}
	// Request more data.
	return 0, nil, nil
}

func parseCommit(r io.Reader, resultSize string, name SHA) (Commit, error) {
	var commit = Commit{_type: "commit", size: resultSize}
	bts, err := ioutil.ReadAll(r)
	if err != nil {
		return commit, err
	}
	lines := bytes.Split(bts, []byte("\n"))
	for i, line := range lines {
		// The next line is the commit message
		if len(bytes.Fields(line)) == 0 {
			commit.Message = bytes.Join(lines[i+1:], []byte("\n"))
			break
		}
		parts := bytes.Fields(line)
		key := parts[0]
		switch keyType(key) {
		case treeKey:
			commit.Tree = string(parts[1])
		case parentKey:
			commit.Parents = append(commit.Parents, SHA(string(parts[1])))
		case authorKey:
			authorline := string(bytes.Join(parts[1:], []byte(" ")))
			author, date, err := parseAuthorString(authorline)
			if err != nil {
				return commit, err
			}
			commit.Author = author
			commit.AuthorDate = date
		case committerKey:
			commit.Committer = string(bytes.Join(parts[1:], []byte(" ")))
		default:
			err = fmt.Errorf("encountered unknown field in commit: %s", key)
			return commit, err
		}
	}
	commit.Name = name
	return commit, nil
}

func parseTree(r io.Reader, resultSize string, basedir string) (Tree, error) {
	var tree = Tree{_type: "tree", size: resultSize}

	scanner := bufio.NewScanner(r)
	scanner.Split(ScanNullLines)

	var tmp objectMeta

	var resultObjs []objectMeta

	for count := 0; ; count++ {
		done := !scanner.Scan()
		if done {
			break
		}

		txt := scanner.Text()

		if count == 0 {
			// the first time through, scanner.Text() will be
			// "tree <size>"
			continue
		}
		if count == 1 {
			// the second time through, scanner.Text() will be
			// <perms> <filename>
			// separated by a space
			fields := strings.Fields(txt)
			tmp.Perms = normalizePerms(fields[0])
			tmp.filename = fields[1]
			continue
		}

		// after the second time through, scanner.Text() will be
		// <sha><perms2> <file2>
		// where perms2 and file2 refer to the permissions and filename (respectively)
		// of the NEXT object, and <sha> is the first 20 bytes exactly.
		// If there is no next object (this is the last object)
		// then scanner.Text() will yield exactly 20 bytes.

		// decode the next 20 bytes to get the SHA
		tmp.Hash = SHA(hex.EncodeToString([]byte(txt[:20])))
		resultObjs = append(resultObjs, tmp)
		if len(txt) <= 20 {
			// We've read the last line
			break
		}

		// Now, tmp points to the next object in the tree listing
		tmp = objectMeta{}
		remainder := txt[20:]
		fields := strings.Fields(remainder)
		tmp.Perms = normalizePerms(fields[0])
		tmp.filename = fields[1]
	}

	if err := scanner.Err(); err != nil && err != io.EOF {
		return tree, err
	}

	for _, part := range resultObjs {
		obj, err := NewObject(part.Hash, basedir)
		if err != nil {
			return tree, err
		}

		if o, ok := obj.(*packObject); ok {
			obj, err = o.normalize(basedir)
			if err != nil {
				return tree, err
			}
		}

		switch obj.Type() {
		case "tree":
			tree.Trees = append(tree.Trees, part)
		case "blob":
			tree.Blobs = append(tree.Blobs, part)
		default:
			return tree, fmt.Errorf("Unknown type found: %s", obj.Type())
		}
	}
	return tree, nil
}

func parseBlob(r io.Reader, resultSize string) (Blob, error) {
	var blob = Blob{_type: "blob", size: resultSize}
	bts, err := ioutil.ReadAll(r)
	blob.Contents = bts
	return blob, err
}

func findFromPrefix(prefix SHA, basedir string) (GitObject, error) {
	objectsDir := path.Join(basedir, "objects", string(prefix[:2]))
	files, err := ioutil.ReadDir(objectsDir)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
	}

	file, err := findUniquePrefix(prefix, files)
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}

		// Try the packfile
		obj, err := searchPacks(prefix, basedir)
		if err != nil {
			return nil, err
		}
		return obj.normalize(basedir)
	}
	f, err := os.Open(file.Name())
	defer f.Close()
	r, err := zlib.NewReader(f)
	if err != nil {
		return nil, err
	}
	return parseObj(r, prefix, basedir)

}

func findUniquePrefix(prefix SHA, files []os.FileInfo) (os.FileInfo, error) {
	var result os.FileInfo
	for _, file := range files {
		if file.IsDir() {
			continue
		}
		if strings.HasPrefix(file.Name(), string(prefix)) {
			if result != nil {
				return nil, fmt.Errorf("prefix is not unique: %s", prefix)
			}
			result = file
		}
	}
	if result == nil {
		return nil, os.ErrNotExist
	}
	return result, nil
}

func parseAuthorString(str string) (author string, date time.Time, err error) {
	const layout = "Mon Jan _2 15:04:05 2006 -0700"
	const layout2 = "Mon Jan _2 15:04:05 2006"
	var authorW bytes.Buffer
	var dateW bytes.Buffer

	s := bufio.NewScanner(strings.NewReader(str))
	s.Split(bufio.ScanBytes)

	// git will ignore '<' if it appears in an author's name
	// so we can safely use it as a delimiter
	for s.Scan() {
		authorW.Write(s.Bytes())
		if s.Text() == ">" {
			break
		}
	}
	for s.Scan() {
		dateW.Write(s.Bytes())
	}
	if s.Err() != nil {
		err = s.Err()
		return
	}

	timestamp, err := strconv.Atoi(strings.Fields(dateW.String())[0])
	if err != nil {
		return
	}

	timezone := strings.Fields(dateW.String())[1]

    hours, err := strconv.Atoi(timezone)
    if err != nil{
        return
    }
	t := time.Unix(int64(timestamp), 0).In(time.FixedZone("", hours* 60 * 60 / 100))
	date, err = time.Parse(layout, fmt.Sprintf("%s %s", t.Format(layout2), timezone))

	return strings.TrimSpace(authorW.String()), date, err
}
<|endoftext|>


<commit_before>package orm

import (
	"fmt"

	"github.com/lfq7413/tomato/config"
	"github.com/lfq7413/tomato/types"
	"gopkg.in/mgo.v2"
)

var (
	// TomatoDB ...
	TomatoDB *Database
)

func init() {
}

// Database ...
type Database struct {
	Session        *mgo.Session
	Database       *mgo.Database
	collectionList []string
}

// NewDatabase ...
func NewDatabase(url string) *Database {
	session, err := mgo.Dial(url)
	if err != nil {
		panic(err)
	}
	fmt.Println("connect success")
	session.SetMode(mgo.Monotonic, true)
	database := session.DB("")
	db := &Database{
		Session:        session,
		Database:       database,
		collectionList: []string{},
	}
	return db
}

// OpenDB ...
func OpenDB() {
	TomatoDB = NewDatabase(config.TConfig.DatabaseURI)
}

// CloseDB ...
func CloseDB() {
	TomatoDB.Session.Close()
}

// Insert ...
func (d *Database) Insert(collection string, docs interface{}) error {
	err := d.Database.C(collection).Insert(docs)
	return err
}

// FindOne ...
func (d *Database) FindOne(collection string, query interface{}) (types.M, error) {
	var result types.M
	err := d.Database.C(collection).Find(query).One(&result)
	return result, err
}

// Update ...
func (d *Database) Update(collection string, selector interface{}, update interface{}) error {
	err := d.Database.C(collection).Update(selector, update)
	return err
}

// Find ...
func (d *Database) Find(collection string, query interface{}) ([]types.M, error) {
	var result []types.M
	err := d.Database.C(collection).Find(query).All(&result)
	return result, err
}

// Remove ...
func (d *Database) Remove(collection string, selector interface{}) error {
	err := d.Database.C(collection).Remove(selector)
	return err
}

func (d *Database) collectionExists(className string) bool {
	// 先在内存中查询
	for _, v := range d.collectionList {
		if v == className {
			return true
		}
	}
	// 内存中不存在，则去数据库中查询一次，更新到内存中
	d.collectionList = d.getCollectionNames()
	if d.collectionList == nil {
		d.collectionList = []string{}
	}
	for _, v := range d.collectionList {
		if v == className {
			return true
		}
	}
	return false
}

func (d *Database) getCollectionNames() []string {
	names, err := d.Database.CollectionNames()
	if err == nil && names != nil {
		return names
	}
	return []string{}
}
<commit_msg>重构 db.go<commit_after>package orm

import (
	"fmt"

	"github.com/lfq7413/tomato/config"
	"gopkg.in/mgo.v2"
)

var (
	// TomatoDB 全局可访问的数据库操作结构体
	TomatoDB *Database
)

func init() {
}

// Database 封装 mongo 数据库对象
type Database struct {
	Session        *mgo.Session
	Database       *mgo.Database
	collectionList []string
}

// NewDatabase 创建数据库结构体
func NewDatabase(url string) *Database {
	session, err := mgo.Dial(url)
	if err != nil {
		panic(err)
	}
	fmt.Println("connect success")
	session.SetMode(mgo.Monotonic, true)
	database := session.DB("")
	db := &Database{
		Session:        session,
		Database:       database,
		collectionList: []string{},
	}
	return db
}

// OpenDB 打开数据库
func OpenDB() {
	TomatoDB = NewDatabase(config.TConfig.DatabaseURI)
}

// CloseDB 关闭数据库
func CloseDB() {
	TomatoDB.Session.Close()
}

// getCollectionNames 获取数据库中当前已经存在的表名
func (d *Database) getCollectionNames() []string {
	names, err := d.Database.CollectionNames()
	if err == nil && names != nil {
		return names
	}
	return []string{}
}
<|endoftext|>


<commit_before>package utee

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	MAX_HTTP_CLIENT_CONCURRENT = 1000

	ContentTypeForm = "application/x-www-form-urlencoded"
	ContentTypeJson = "application/json; charset=utf-8"
)

var (
	httpClientThrottle = NewThrottle(MAX_HTTP_CLIENT_CONCURRENT)
	insecureClient     = &http.Client{
		Timeout:   15 * time.Second,
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
	}
	ErrEmptyHeaderName = errors.New("header name must not be empty")
)

type BasicAuth struct {
	Username string
	Password string
}

type HttpOpt struct {
	Headers   map[string]string
	BasicAuth *BasicAuth
}

func HttpPost2(postUrl string, contentType string, body io.Reader, opt *HttpOpt) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	contentType = strings.TrimSpace(contentType)
	if contentType == "" {
		contentType = ContentTypeForm
	}

	var resp *http.Response
	var err error
	req, err := http.NewRequest("POST", postUrl, body)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}
	req.Header.Set("Content-Type", contentType)

	if opt != nil {
		for k, v := range opt.Headers {
			k := strings.ToLower(strings.TrimSpace(k))
			if k == "" {
				return nil, ErrEmptyHeaderName
			}
			if k == "content-type" {
				continue
			}
			req.Header.Set(strings.Title(k), v)
		}

		if opt.BasicAuth != nil {
			req.SetBasicAuth(opt.BasicAuth.Username, opt.BasicAuth.Password)
		}
	}

	resp, err = insecureClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}

	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http] status err %s, %d", postUrl, resp.StatusCode)
	}

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[http] read err %s, %s", postUrl, err)
	}

	return b, nil
}

func HttpGet2(getUrl string, contentType string, opt *HttpOpt) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	var resp *http.Response
	var err error
	req, err := http.NewRequest("GET", getUrl, nil)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s\n", getUrl, err)
	}

	contentType = strings.TrimSpace(contentType)
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}

	if opt != nil {
		for k, v := range opt.Headers {
			k := strings.ToLower(strings.TrimSpace(k))
			if k == "" {
				return nil, ErrEmptyHeaderName
			}
			if k == "content-type" {
				continue
			}
			req.Header.Set(strings.Title(k), v)
		}

		if opt.BasicAuth != nil {
			req.SetBasicAuth(opt.BasicAuth.Username, opt.BasicAuth.Password)
		}
	}

	resp, err = insecureClient.Do(req)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http get] status err %s, %d\n", getUrl, resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}

func HttpPost(postUrl string, q url.Values, credential ...string) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	var resp *http.Response
	var err error
	req, err := http.NewRequest("POST", postUrl, strings.NewReader(q.Encode()))
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if len(credential) == 2 {
		req.SetBasicAuth(credential[0], credential[1])
	}

	resp, err = insecureClient.Do(req)

	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http] status err %s, %d", postUrl, resp.StatusCode)
	}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[http] read err %s, %s", postUrl, err)
	}
	return b, nil
}

func HttpGet(getUrl string, credential ...string) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	var resp *http.Response
	var err error
	req, err := http.NewRequest("GET", getUrl, nil)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s\n", getUrl, err)
	}
	if len(credential) == 2 {
		req.SetBasicAuth(credential[0], credential[1])
	}

	resp, err = insecureClient.Do(req)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http get] status err %s, %d\n", getUrl, resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}
<commit_msg>add body param for http get2<commit_after>package utee

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	MAX_HTTP_CLIENT_CONCURRENT = 1000

	ContentTypeForm = "application/x-www-form-urlencoded"
	ContentTypeJson = "application/json; charset=utf-8"
)

var (
	httpClientThrottle = NewThrottle(MAX_HTTP_CLIENT_CONCURRENT)
	insecureClient     = &http.Client{
		Timeout:   15 * time.Second,
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
	}
	ErrEmptyHeaderName = errors.New("header name must not be empty")
)

type BasicAuth struct {
	Username string
	Password string
}

type HttpOpt struct {
	Headers   map[string]string
	BasicAuth *BasicAuth
}

func HttpPost2(postUrl string, contentType string, body io.Reader, opt *HttpOpt) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	contentType = strings.TrimSpace(contentType)
	if contentType == "" {
		contentType = ContentTypeForm
	}

	var resp *http.Response
	var err error
	req, err := http.NewRequest("POST", postUrl, body)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}
	req.Header.Set("Content-Type", contentType)

	if opt != nil {
		for k, v := range opt.Headers {
			k := strings.ToLower(strings.TrimSpace(k))
			if k == "" {
				return nil, ErrEmptyHeaderName
			}
			if k == "content-type" {
				continue
			}
			req.Header.Set(strings.Title(k), v)
		}

		if opt.BasicAuth != nil {
			req.SetBasicAuth(opt.BasicAuth.Username, opt.BasicAuth.Password)
		}
	}

	resp, err = insecureClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}

	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http] status err %s, %d", postUrl, resp.StatusCode)
	}

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[http] read err %s, %s", postUrl, err)
	}

	return b, nil
}

func HttpGet2(getUrl string, contentType string, body io.Reader, opt *HttpOpt) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	var resp *http.Response
	var err error
	req, err := http.NewRequest("GET", getUrl, body)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s\n", getUrl, err)
	}

	contentType = strings.TrimSpace(contentType)
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}

	if opt != nil {
		for k, v := range opt.Headers {
			k := strings.ToLower(strings.TrimSpace(k))
			if k == "" {
				return nil, ErrEmptyHeaderName
			}
			if k == "content-type" {
				continue
			}
			req.Header.Set(strings.Title(k), v)
		}

		if opt.BasicAuth != nil {
			req.SetBasicAuth(opt.BasicAuth.Username, opt.BasicAuth.Password)
		}
	}

	resp, err = insecureClient.Do(req)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http get] status err %s, %d\n", getUrl, resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}

func HttpPost(postUrl string, q url.Values, credential ...string) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	var resp *http.Response
	var err error
	req, err := http.NewRequest("POST", postUrl, strings.NewReader(q.Encode()))
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	if len(credential) == 2 {
		req.SetBasicAuth(credential[0], credential[1])
	}

	resp, err = insecureClient.Do(req)

	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s", postUrl, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http] status err %s, %d", postUrl, resp.StatusCode)
	}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("[http] read err %s, %s", postUrl, err)
	}
	return b, nil
}

func HttpGet(getUrl string, credential ...string) ([]byte, error) {
	httpClientThrottle.Acquire()
	defer httpClientThrottle.Release()

	var resp *http.Response
	var err error
	req, err := http.NewRequest("GET", getUrl, nil)
	if err != nil {
		return nil, fmt.Errorf("[http] err %s, %s\n", getUrl, err)
	}
	if len(credential) == 2 {
		req.SetBasicAuth(credential[0], credential[1])
	}

	resp, err = insecureClient.Do(req)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("[http get] status err %s, %d\n", getUrl, resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "net/http"
    "os"
    "os/exec"
    "io"
    "encoding/json"
    "log"
    //"time"
    "strings"
    //"math/rand"
    "regexp"
)

var HEADER_CONTENT_TYPES map[string]string = map[string]string{
    "json": "javascript/json;charset=utf-8",
    "text": "text/plain;charset=utf-8",
    "xml": "text/xml;charset=utf-8",
}

var IGNORE_OPTIONS map[string]bool = map[string]bool{
    "-e": true,
    "-c": true,
    "-d": true,
}

const DEFAULT_FORMAT string = "text"

type Config struct {
    Host string
    Port int
    Reg_filter string
    Mystem_path string
    Mystem_options []string
    Mystem_workers int
    Mystem_answer_size int
    Channel_buffer int
    Max_word_length int
    Max_words int
    format string
    is_d bool
}

type Answer struct {
    data string
    err error
}

type Data struct {
    word string
    channel chan Answer
}

var for_process chan Data
var reg_filter *regexp.Regexp
var config Config

func makePanic(msg string) {
    log.Fatal(msg)
    panic(msg)
}

func processMystemOptions(in_opts []string) (out_opts []string, format string, err error) {
    var opts_arr []string
    var approved bool = false
    var exists bool = false
    var n_exists = false
    for _, opt := range in_opts {
        approved = false
        opt = strings.TrimSpace(opt)
        opt = strings.ToLower(opt)
        if strings.Index(opt, " ") == -1 {
            opts_arr = make([]string, 1)
            opts_arr[0] = opt
            if !bool(IGNORE_OPTIONS[opt]) {
                approved = true
            }
            if (!n_exists) && (opt == "-n") {
                n_exists = true
            }
        } else {
            opts_arr = make([]string, 2)
            opts_arr = strings.Split(opt, " ")
            if opts_arr[0] == "--format" {
                _, exists = HEADER_CONTENT_TYPES[opts_arr[1]]
                if exists {
                    format = opts_arr[1]
                } else {
                    format = DEFAULT_FORMAT
                    opts_arr[1] = format
                    log.Printf("Mystem option '%v' was replaced by default value. Because contain error", opt)
                }
                approved = true
            } else if !bool(IGNORE_OPTIONS[opts_arr[0]]) {
                approved = true
            }
        }
        if approved {
            out_opts = append(out_opts, opts_arr...)
        } else {
            log.Printf("Mystem option '%v' was ignored or replaced by default value. Because wrong or unnecessary", opt)
        }
    }
    if !n_exists {
        out_opts = append(out_opts, "-n")
        log.Print("Added mystem option '-n'. It need for normal work.\n")
    }
    if format == "" {
        format = DEFAULT_FORMAT
    }
    return out_opts, format, err
}

func loadConfig() (cfg Config, err error) {
    var f_name string = "http_mystem.json"
    if (len(os.Args) > 1) {
        f_name = os.Args[1]
    }
    file, err := os.Open(f_name)
    defer file.Close()
    if err == nil {
        f_stat, _ := file.Stat()
        raw_json := make([]byte, f_stat.Size())
        _, err = file.Read(raw_json)
        if err == nil {
            err = json.Unmarshal(raw_json, &cfg)
            if err == nil {
                cfg.Mystem_options, cfg.format, err = processMystemOptions(cfg.Mystem_options)
                //cfg.Mystem_answer_size = cfg.Max_word_length * 500
            }
        }
    }
    return cfg, err
}

func writeStringToPipe(for_write string, pipe io.WriteCloser) (n int, err error) {
    defer func() {
        result := recover()
        if result != nil {
            log.Fatalf("Recover say: %v", result)
        }
    }()
    n, err = pipe.Write([]byte(for_write + "\n"))
    if err != nil {
        log.Panicf("Can't send word to mystem: %v", err)
    }
    return n, err
}

func readStringFromPipe(for_read *string, pipe io.ReadCloser) (n int, err error) {
    defer func() {
        result := recover()
        if result != nil {
            log.Fatalf("Recover say: %v", result)
        }
    }()
    buf := make([]byte, config.Mystem_answer_size)
    n, err = pipe.Read(buf)
    if err == nil {
        *for_read = strings.TrimSpace(string(buf[:n]))
    } else {
        log.Panicf("Can't send word to mystem: %v", err)
    }
    return n, err
}

func readXMLTrash(number int, pipe io.ReadCloser) {
    var data string = ""
    var err error
    for i := 0; i < number; i++ {
        for data == "" {
            _, err = readStringFromPipe(&data, pipe)
            if err != nil {
                makePanic(fmt.Sprintf("Can't start: %v", err))
            }
        }
        data = ""
    }
}

func workerMystem(for_process chan Data, mystem_path string) {
    //name := rand.Int()
    //log.Print("Mystem started ", name)
    mystem := exec.Command(mystem_path, config.Mystem_options...)
    mystem_writer, err_w := mystem.StdinPipe()
    mystem_reader, err_r := mystem.StdoutPipe()
    err := mystem.Start()
    if (err_w != nil) || (err_r != nil) {
        makePanic(fmt.Sprintf("Can't start: \n%v\n%v", err_w, err_r))
    }
    if err != nil {
        makePanic(fmt.Sprintf("Can't start: %v", err))
    } else {
        var data Data
        var answer Answer
        if config.format == "xml" {
            readXMLTrash(2, mystem_reader)
            _, err = writeStringToPipe("тест", mystem_writer)
            if err != nil {
                panic("")
            }
            readXMLTrash(2, mystem_reader)
        } else {
            _, err = writeStringToPipe("тест", mystem_writer)
            if err != nil {
                panic("")
            }
            _, err = readStringFromPipe(&answer.data, mystem_reader)
            if err != nil {
                panic("")
            }
        }
        for {
            data = <- for_process
            _, err = writeStringToPipe(data.word, mystem_writer)
            answer.err = err
            if config.format != "xml" {
                _, err = readStringFromPipe(&answer.data, mystem_reader)
                if err != nil {
                    answer.err = err
                    break
                }
            } else {
                for answer.data == "" {
                    _, err = readStringFromPipe(&answer.data, mystem_reader)
                    if err != nil {
                        answer.err = err
                        break
                    }
                }
            }
            data.channel <- answer
            answer.data = ""
            answer.err = nil
        }
    }
}

func checkInput(req *http.Request, words *[]string, ) (code int) {
    var words_count int = len(req.Form["words[]"])
    if words_count == 0 {
        code = http.StatusNotAcceptable
    } else if words_count > int(config.Max_words) {
        code = http.StatusRequestEntityTooLarge
    } else {
        var w string = ""
        for _, word := range req.Form["words[]"] {
            w = strings.TrimSpace(word)
            if len(w) > int(config.Max_word_length) {
                w = word[:int(config.Max_word_length)]
            }
            if w != "" {
                *words = append(*words, w)
            }
        }
    }
    return code
}

func processWords(resp http.ResponseWriter, req *http.Request) {
    var data Data
    var answer Answer
    var answers []string
    var request_answer string = ""
    req.ParseForm()
    var words []string
    var tmp_answer string = ""
    status_code := checkInput(req, &words)
    var words_count int = len(words)
    resp.Header().Set("Content-Type", HEADER_CONTENT_TYPES[config.format])
    //resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
    if words_count > 0 {
        var err error
        var local_channel chan Answer = make(chan Answer, words_count)
        data.channel = local_channel
        var start int = 0
        for _, word := range words {
            err = nil
            if reg_filter.MatchString(word) {
                data.word = word
                for_process <- data
            } else {
                start++
                switch config.format {
                    case "json": {
                        tmp_answer = fmt.Sprintf(`{"analysis":[], "text":"%v"}`, word)
                    }
                    case "xml": {
                        tmp_answer = "<w>" + word + "</w>"
                    }
                    case "text": {
                        tmp_answer = word
                    }
                }
                answers = append(answers, tmp_answer)
            }
        }
        for i := start; i < words_count; i++ {
            answer = <- local_channel
            if answer.err != nil {
                err = answer.err
            }
            answers = append(answers, answer.data)
        }
        if err == nil {
            switch config.format {
                case "json": {
                    request_answer = "[" + strings.Join(answers, ",") + "]"
                }
                case "xml": {
                    request_answer = `<?xml version="1.0" encoding="utf-8"?><html><body><se>` + strings.Join(answers, "\n") + "</se>"
                }
                case "text": {
                    request_answer = strings.Join(answers, "\n")
                }
            }
        } else {
            //request_answer = fmt.Sprintf(`{"status": %v, "reason": "%v"}`, status_code, http.StatusText(http.StatusInternalServerError)
            resp.WriteHeader(http.StatusInternalServerError)
        }
        close(local_channel)
    } else {
        //request_answer = fmt.Sprintf(`{"status": %v, "reason": "%v"}`, status_code, http.StatusText(status_code))
        resp.WriteHeader(status_code)
    }
    resp.Write([]byte(request_answer))
}

func main() {
    var err error = nil
    config, err = loadConfig()
    if err != nil {
        makePanic(fmt.Sprintf("Can't load config: %v", err))
    } else {
        reg_filter, err = regexp.Compile(config.Reg_filter)
        if err != nil {
            makePanic(fmt.Sprintf("Can`t compile regular expression: %v", err))
        }
        _, err = os.Open(config.Mystem_path)
        if  err != nil {
            makePanic(fmt.Sprintf("Can't find mystem: %v", err))
        }
        var i int
        if config.Channel_buffer > 0 {
            for_process = make(chan Data, int(config.Channel_buffer))
        } else {
            for_process = make(chan Data)
        }
        log.Print("Config load successful")
        for i = 0; i < int(config.Mystem_workers); i++ {
            go workerMystem(for_process, config.Mystem_path)
        }
        http.HandleFunc("/", processWords)
        log.Printf("Server start on: %v:%v", config.Host, config.Port)
        err = http.ListenAndServe(fmt.Sprintf("%v:%v", config.Host, config.Port), nil)
        if err != nil {
            makePanic(fmt.Sprintf("Can't start http server: %v", err))
        }
    }
}<commit_msg>if number of workers 0, it wil be set number of cpu cores<commit_after>package main

import (
    "fmt"
    "net/http"
    "os"
    "os/exec"
    "io"
    "encoding/json"
    "log"
    //"time"
    "strings"
    //"math/rand"
    "regexp"
    "runtime"
)

var HEADER_CONTENT_TYPES map[string]string = map[string]string{
    "json": "javascript/json;charset=utf-8",
    "text": "text/plain;charset=utf-8",
    "xml": "text/xml;charset=utf-8",
}

var IGNORE_OPTIONS map[string]bool = map[string]bool{
    "-e": true,
    "-c": true,
    "-d": true,
}

const DEFAULT_FORMAT string = "text"

type Config struct {
    Host string
    Port int
    Reg_filter string
    Mystem_path string
    Mystem_options []string
    Mystem_workers int
    Mystem_answer_size int
    Channel_buffer int
    Max_word_length int
    Max_words int
    format string
    is_d bool
}

type Answer struct {
    data string
    err error
}

type Data struct {
    word string
    channel chan Answer
}

var for_process chan Data
var reg_filter *regexp.Regexp
var config Config

func makePanic(msg string) {
    log.Fatal(msg)
    panic(msg)
}

func processMystemOptions(in_opts []string) (out_opts []string, format string, err error) {
    var opts_arr []string
    var approved bool = false
    var exists bool = false
    var n_exists = false
    for _, opt := range in_opts {
        approved = false
        opt = strings.TrimSpace(opt)
        opt = strings.ToLower(opt)
        if strings.Index(opt, " ") == -1 {
            opts_arr = make([]string, 1)
            opts_arr[0] = opt
            if !bool(IGNORE_OPTIONS[opt]) {
                approved = true
            }
            if (!n_exists) && (opt == "-n") {
                n_exists = true
            }
        } else {
            opts_arr = make([]string, 2)
            opts_arr = strings.Split(opt, " ")
            if opts_arr[0] == "--format" {
                _, exists = HEADER_CONTENT_TYPES[opts_arr[1]]
                if exists {
                    format = opts_arr[1]
                } else {
                    format = DEFAULT_FORMAT
                    opts_arr[1] = format
                    log.Printf("Mystem option '%v' was replaced by default value. Because contain error", opt)
                }
                approved = true
            } else if !bool(IGNORE_OPTIONS[opts_arr[0]]) {
                approved = true
            }
        }
        if approved {
            out_opts = append(out_opts, opts_arr...)
        } else {
            log.Printf("Mystem option '%v' was ignored or replaced by default value. Because wrong or unnecessary", opt)
        }
    }
    if !n_exists {
        out_opts = append(out_opts, "-n")
        log.Print("Added mystem option '-n'. It need for normal work.\n")
    }
    if format == "" {
        format = DEFAULT_FORMAT
    }
    return out_opts, format, err
}

func loadConfig() (cfg Config, err error) {
    var f_name string = "http_mystem.json"
    if (len(os.Args) > 1) {
        f_name = os.Args[1]
    }
    file, err := os.Open(f_name)
    defer file.Close()
    if err == nil {
        f_stat, _ := file.Stat()
        raw_json := make([]byte, f_stat.Size())
        _, err = file.Read(raw_json)
        if err == nil {
            err = json.Unmarshal(raw_json, &cfg)
            if err == nil {
                cfg.Mystem_options, cfg.format, err = processMystemOptions(cfg.Mystem_options)
                //cfg.Mystem_answer_size = cfg.Max_word_length * 500
            }
        }
    }
    return cfg, err
}

func writeStringToPipe(for_write string, pipe io.WriteCloser) (n int, err error) {
    defer func() {
        result := recover()
        if result != nil {
            log.Fatalf("Recover say: %v", result)
        }
    }()
    n, err = pipe.Write([]byte(for_write + "\n"))
    if err != nil {
        log.Panicf("Can't send word to mystem: %v", err)
    }
    return n, err
}

func readStringFromPipe(for_read *string, pipe io.ReadCloser) (n int, err error) {
    defer func() {
        result := recover()
        if result != nil {
            log.Fatalf("Recover say: %v", result)
        }
    }()
    buf := make([]byte, config.Mystem_answer_size)
    n, err = pipe.Read(buf)
    if err == nil {
        *for_read = strings.TrimSpace(string(buf[:n]))
    } else {
        log.Panicf("Can't send word to mystem: %v", err)
    }
    return n, err
}

func readXMLTrash(number int, pipe io.ReadCloser) {
    var data string = ""
    var err error
    for i := 0; i < number; i++ {
        for data == "" {
            _, err = readStringFromPipe(&data, pipe)
            if err != nil {
                makePanic(fmt.Sprintf("Can't start: %v", err))
            }
        }
        data = ""
    }
}

func workerMystem(for_process chan Data, mystem_path string) {
    //name := rand.Int()
    //log.Print("Mystem started ", name)
    mystem := exec.Command(mystem_path, config.Mystem_options...)
    mystem_writer, err_w := mystem.StdinPipe()
    mystem_reader, err_r := mystem.StdoutPipe()
    err := mystem.Start()
    if (err_w != nil) || (err_r != nil) {
        makePanic(fmt.Sprintf("Can't start: \n%v\n%v", err_w, err_r))
    }
    if err != nil {
        makePanic(fmt.Sprintf("Can't start: %v", err))
    } else {
        var data Data
        var answer Answer
        if config.format == "xml" {
            readXMLTrash(2, mystem_reader)
            _, err = writeStringToPipe("тест", mystem_writer)
            if err != nil {
                panic("")
            }
            readXMLTrash(2, mystem_reader)
        } else {
            _, err = writeStringToPipe("тест", mystem_writer)
            if err != nil {
                panic("")
            }
            _, err = readStringFromPipe(&answer.data, mystem_reader)
            if err != nil {
                panic("")
            }
        }
        for {
            data = <- for_process
            _, err = writeStringToPipe(data.word, mystem_writer)
            answer.err = err
            if config.format != "xml" {
                _, err = readStringFromPipe(&answer.data, mystem_reader)
                if err != nil {
                    answer.err = err
                    break
                }
            } else {
                for answer.data == "" {
                    _, err = readStringFromPipe(&answer.data, mystem_reader)
                    if err != nil {
                        answer.err = err
                        break
                    }
                }
            }
            data.channel <- answer
            answer.data = ""
            answer.err = nil
        }
    }
}

func checkInput(req *http.Request, words *[]string, ) (code int) {
    var words_count int = len(req.Form["words[]"])
    if words_count == 0 {
        code = http.StatusNotAcceptable
    } else if words_count > int(config.Max_words) {
        code = http.StatusRequestEntityTooLarge
    } else {
        var w string = ""
        for _, word := range req.Form["words[]"] {
            w = strings.TrimSpace(word)
            if len(w) > int(config.Max_word_length) {
                w = word[:int(config.Max_word_length)]
            }
            if w != "" {
                *words = append(*words, w)
            }
        }
    }
    return code
}

func processWords(resp http.ResponseWriter, req *http.Request) {
    var data Data
    var answer Answer
    var answers []string
    var request_answer string = ""
    req.ParseForm()
    var words []string
    var tmp_answer string = ""
    status_code := checkInput(req, &words)
    var words_count int = len(words)
    resp.Header().Set("Content-Type", HEADER_CONTENT_TYPES[config.format])
    //resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
    if words_count > 0 {
        var err error
        var local_channel chan Answer = make(chan Answer, words_count)
        data.channel = local_channel
        var start int = 0
        for _, word := range words {
            err = nil
            if reg_filter.MatchString(word) {
                data.word = word
                for_process <- data
            } else {
                start++
                switch config.format {
                    case "json": {
                        tmp_answer = fmt.Sprintf(`{"analysis":[], "text":"%v"}`, word)
                    }
                    case "xml": {
                        tmp_answer = "<w>" + word + "</w>"
                    }
                    case "text": {
                        tmp_answer = word
                    }
                }
                answers = append(answers, tmp_answer)
            }
        }
        for i := start; i < words_count; i++ {
            answer = <- local_channel
            if answer.err != nil {
                err = answer.err
            }
            answers = append(answers, answer.data)
        }
        if err == nil {
            switch config.format {
                case "json": {
                    request_answer = "[" + strings.Join(answers, ",") + "]"
                }
                case "xml": {
                    request_answer = `<?xml version="1.0" encoding="utf-8"?><html><body><se>` + strings.Join(answers, "\n") + "</se>"
                }
                case "text": {
                    request_answer = strings.Join(answers, "\n")
                }
            }
        } else {
            //request_answer = fmt.Sprintf(`{"status": %v, "reason": "%v"}`, status_code, http.StatusText(http.StatusInternalServerError)
            resp.WriteHeader(http.StatusInternalServerError)
        }
        close(local_channel)
    } else {
        //request_answer = fmt.Sprintf(`{"status": %v, "reason": "%v"}`, status_code, http.StatusText(status_code))
        resp.WriteHeader(status_code)
    }
    resp.Write([]byte(request_answer))
}

func main() {
    var err error = nil
    config, err = loadConfig()
    if err != nil {
        makePanic(fmt.Sprintf("Can't load config: %v", err))
    } else {
        reg_filter, err = regexp.Compile(config.Reg_filter)
        if err != nil {
            makePanic(fmt.Sprintf("Can`t compile regular expression: %v", err))
        }
        _, err = os.Open(config.Mystem_path)
        if  err != nil {
            makePanic(fmt.Sprintf("Can't find mystem: %v", err))
        }
        var i int
        if config.Channel_buffer > 0 {
            for_process = make(chan Data, int(config.Channel_buffer))
        } else {
            for_process = make(chan Data)
        }
        log.Print("Config load successful")
        if config.Mystem_workers == 0 {
            var num_cpu int = runtime.NumCPU()
            runtime.GOMAXPROCS(num_cpu)
            config.Mystem_workers = num_cpu
            log.Printf("Found %[1]v cores of CPU. Number of workers will be %[1]v", num_cpu)
        }
        for i = 0; i < int(config.Mystem_workers); i++ {
            go workerMystem(for_process, config.Mystem_path)
        }
        http.HandleFunc("/", processWords)
        log.Printf("Server start on: %v:%v", config.Host, config.Port)
        err = http.ListenAndServe(fmt.Sprintf("%v:%v", config.Host, config.Port), nil)
        if err != nil {
            makePanic(fmt.Sprintf("Can't start http server: %v", err))
        }
    }
}<|endoftext|>


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

package config

import (
	"fmt"
)

// Return an error if the supplied config data is invalid in some way.
func Validate(c *Config) error {
	return nil, fmt.Errorf("TODO: Implement config.Validate.")
}

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

package config

import (
	"fmt"
)

// Return an error if the supplied config data is invalid in some way.
func Validate(c *Config) error {
	return fmt.Errorf("TODO: Implement config.Validate.")
}

<|endoftext|>


<commit_before>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/printer"
	"go/token"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"reflect"
	"sort"
	"sync"

	"golang.org/x/tools/go/loader"
)

var fset = token.NewFileSet()

type Edit struct {
	Pos, End int
}

type editsByPos []Edit

func (e editsByPos) Len() int           { return len(e) }
func (e editsByPos) Less(i, j int) bool { return e[i].Pos < e[j].Pos }
func (e editsByPos) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }

func apply(file string, edits []Edit) {
	if len(edits) == 0 {
		return
	}

	sort.Sort(editsByPos(edits))

	// Check for overlap.
	for i := 1; i < len(edits); i++ {
		if edits[i-1].End > edits[i].Pos {
			log.Fatal("overlap")
		}
	}

	buf, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatal(err)
	}

	for i := len(edits) - 1; i >= 0; i-- {
		copy(buf[edits[i].Pos:], buf[edits[i].End:])
		buf = buf[:len(buf)-(edits[i].End-edits[i].Pos)]
	}

	buf, err = format.Source(buf)
	if err != nil {
		log.Fatal(err)
	}

	err = ioutil.WriteFile(file, buf, 0)
	if err != nil {
		log.Fatal(err)
	}
}

var (
	flagAll   = flag.Bool("all", false, "type check all GOOS and GOARCH combinations")
	flagApply = flag.Bool("apply", false, "apply edits")
	flagGob   = flag.Bool("gob", false, "dump edits to stdout as gob")
)

func main() {
	flag.Parse()

	var m map[string][]Edit
	if *flagAll {
		m = mergeEdits()
	} else {
		m = computeEdits()
	}

	if *flagGob {
		err := gob.NewEncoder(os.Stdout).Encode(m)
		if err != nil {
			log.Fatal(err)
		}
		return
	} else if *flagApply {
		for f, e := range m {
			apply(f, e)
		}
	} else {
		err := json.NewEncoder(os.Stdout).Encode(m)
		if err != nil {
			log.Fatal(err)
		}
		return
	}
}

var plats = [...]struct {
	goos, goarch string
}{
	{"linux", "386"},
	{"linux", "amd64"},
	{"linux", "arm"},
	{"linux", "arm64"},
	{"linux", "ppc64"},
	{"linux", "ppc64le"},
	{"nacl", "386"},
	{"nacl", "amd64p32"},
	{"nacl", "arm"},
	{"darwin", "386"},
	{"darwin", "amd64"},
	{"dragonfly", "amd64"},
	{"freebsd", "386"},
	{"freebsd", "amd64"},
	{"freebsd", "arm"},
	{"netbsd", "386"},
	{"netbsd", "amd64"},
	{"netbsd", "arm"},
	{"openbsd", "386"},
	{"openbsd", "amd64"},
	{"openbsd", "arm"},
	{"plan9", "386"},
	{"plan9", "amd64"},
	{"solaris", "amd64"},
	{"windows", "386"},
	{"windows", "amd64"},
}

func mergeEdits() map[string][]Edit {
	ch := make(chan map[string][]Edit)
	var wg sync.WaitGroup
	for _, plat := range plats {
		wg.Add(1)
		go func(goos, goarch string) {
			defer wg.Done()
			ch <- doSub(goos, goarch)
		}(plat.goos, plat.goarch)
	}
	go func() {
		wg.Wait()
		close(ch)
	}()

	m := make(map[string][]Edit)
	for m1 := range ch {
		for f, e := range m1 {
			if e0, ok := m[f]; ok {
				m[f] = intersect(e0, e)
			} else {
				m[f] = e
			}
		}
	}
	return m
}

func intersect(e1, e2 []Edit) []Edit {
	if len(e1) == 0 || len(e2) == 0 {
		return nil
	}

	set := make(map[Edit]bool, len(e1))
	for _, e := range e1 {
		set[e] = true
	}

	var res []Edit
	for _, e := range e2 {
		if set[e] {
			res = append(res, e)
		}
	}
	return res
}

func doSub(goos, goarch string) map[string][]Edit {
	var m map[string][]Edit
	pr, pw := io.Pipe()
	ch := make(chan error)
	go func() {
		ch <- gob.NewDecoder(pr).Decode(&m)
	}()
	cmd := exec.Command("./unconvert", append([]string{"-gob"}, flag.Args()...)...)
	cmd.Stdout = pw
	cmd.Stderr = os.Stderr
	cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)
	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	if err := <-ch; err != nil {
		log.Fatal(err)
	}
	return m
}

func computeEdits() map[string][]Edit {
	var conf loader.Config
	conf.Fset = fset
	for _, arg := range flag.Args() {
		conf.Import(arg)
	}
	prog, err := conf.Load()
	if err != nil {
		log.Fatal(err)
	}

	type res struct {
		file  string
		edits []Edit
	}
	ch := make(chan res)
	var wg sync.WaitGroup
	for _, pkg := range prog.InitialPackages() {
		for _, file := range pkg.Files {
			wg.Add(1)
			go func(pkg *loader.PackageInfo, file *ast.File) {
				defer wg.Done()
				v := visitor{pkg: pkg, file: fset.File(file.Package)}
				ast.Walk(&v, file)
				sort.Sort(editsByPos(v.edits))
				ch <- res{v.file.Name(), v.edits}
			}(pkg, file)
		}
	}
	go func() {
		wg.Wait()
		close(ch)
	}()

	m := make(map[string][]Edit)
	for r := range ch {
		m[r.file] = r.edits
	}
	return m
}

type visitor struct {
	pkg   *loader.PackageInfo
	file  *token.File
	edits []Edit
	nodes []ast.Node
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	if node != nil {
		v.nodes = append(v.nodes, node)
	} else {
		v.nodes = v.nodes[:len(v.nodes)-1]
	}
	if call, ok := node.(*ast.CallExpr); ok {
		v.unconvert(call)
	}
	return v
}

func (v *visitor) unconvert(call *ast.CallExpr) {
	// TODO(mdempsky): Handle useless multi-conversions.

	// Conversions have exactly one argument.
	if len(call.Args) != 1 || call.Ellipsis != token.NoPos {
		return
	}
	ft, ok := v.pkg.Types[call.Fun]
	if !ok {
		fmt.Println("Missing type for function")
		return
	}
	if !ft.IsType() {
		// Not a conversion.
		return
	}
	at, ok := v.pkg.Types[call.Args[0]]
	if !ok {
		fmt.Println("Missing type for argument")
	}
	if ft.Type != at.Type {
		// A real conversion.
		return
	}
	if at.Value != nil || hasUntypedValue(call.Args[0]) {
		// As a workaround for golang.org/issue/13061,
		// skip conversions that contain an untyped value.
		return
	}

	outer := v.nodes[len(v.nodes)-2]
	if keepParen(outer, call, call.Args[0]) {
		v.remove(call.Fun.Pos(), call.Lparen)
	} else {
		v.remove(call.Fun.Pos(), call.Lparen+1)
		v.remove(call.Rparen, call.Rparen+1)
	}
}

func (v *visitor) remove(pos, end token.Pos) {
	v.edits = append(v.edits, Edit{v.file.Offset(pos), v.file.Offset(end)})
}

func keepParen(a ast.Node, b, c ast.Expr) bool {
	// 1. Find the value in a that points to b.
	bp := findExprField(a, b)

	// 2. Try printing a with s/b/c/ and with s/b/(c)/.
	var buf1 bytes.Buffer
	*bp = c
	printer.Fprint(&buf1, fset, a)

	var buf2 bytes.Buffer
	*bp = &ast.ParenExpr{X: c}
	printer.Fprint(&buf2, fset, a)

	*bp = b

	// 3. Return whether they print the same (i.e., the parentheses are necessary).
	return buf1.String() == buf2.String()
}

func findExprField(a ast.Node, b ast.Expr) *ast.Expr {
	v := reflect.ValueOf(a).Elem()
	for i, n := 0, v.NumField(); i < n; i++ {
		// Interesting fields are either ast.Expr or []ast.Expr.
		switch f := v.Field(i).Interface().(type) {
		case ast.Expr:
			if f == b {
				return v.Field(i).Addr().Interface().(*ast.Expr)
			}
		case []ast.Expr:
			for i, e := range f {
				if e == b {
					return &f[i]
				}
			}
		}
	}
	log.Fatal("Failed to find b in a")
	return nil
}

func hasUntypedValue(n ast.Expr) bool {
	var v uvVisitor
	ast.Walk(&v, n)
	return v.found
}

type uvVisitor struct {
	found bool
}

func (v *uvVisitor) Visit(node ast.Node) ast.Visitor {
	// Short circuit.
	if v.found {
		return nil
	}

	switch node := node.(type) {
	case *ast.BinaryExpr:
		switch node.Op {
		case token.SHL, token.SHR, token.EQL, token.NEQ, token.LSS, token.GTR, token.LEQ, token.GEQ:
			// Shifts yield an untyped value if their LHS is untyped.
			// Comparisons yield an untyped boolean value.
			v.found = true
		}
	case *ast.Ident:
		if node.Name == "nil" {
			// Probably the universal untyped zero value.
			v.found = true
		}
	}

	if v.found {
		return nil
	}
	return v
}
<commit_msg>unconvert: faster edit application and better type equivalency<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/printer"
	"go/token"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"reflect"
	"sort"
	"sync"

	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/types"
)

var fset = token.NewFileSet()

type Edit struct {
	Pos, End int
}

type editsByPos []Edit

func (e editsByPos) Len() int           { return len(e) }
func (e editsByPos) Less(i, j int) bool { return e[i].Pos < e[j].Pos }
func (e editsByPos) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }

func apply(file string, edits []Edit) {
	if len(edits) == 0 {
		return
	}

	sort.Sort(editsByPos(edits))

	// Check for overlap.
	// TODO(mdempsky): Overlap can legally happen in bizarro expressions like
	// "(*[unsafe.Sizeof(int8(int8(0)))]byte)(*[1]byte)(nil)".
	for i := 1; i < len(edits); i++ {
		if edits[i-1].End > edits[i].Pos {
			log.Fatal("overlap")
		}
	}

	buf, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatal(err)
	}

	n := edits[0].Pos
	for i := 1; i < len(edits); i++ {
		n += copy(buf[n:], buf[edits[i-1].End:edits[i].Pos])
	}
	n += copy(buf[n:], buf[edits[len(edits)-1].End:])
	buf = buf[:n]

	buf, err = format.Source(buf)
	if err != nil {
		log.Fatal(err)
	}

	err = ioutil.WriteFile(file, buf, 0)
	if err != nil {
		log.Fatal(err)
	}
}

var (
	flagAll   = flag.Bool("all", false, "type check all GOOS and GOARCH combinations")
	flagApply = flag.Bool("apply", false, "apply edits")
	flagGob   = flag.Bool("gob", false, "dump edits to stdout as gob")
)

func main() {
	flag.Parse()

	var m map[string][]Edit
	if *flagAll {
		m = mergeEdits()
	} else {
		m = computeEdits()
	}

	if *flagGob {
		err := gob.NewEncoder(os.Stdout).Encode(m)
		if err != nil {
			log.Fatal(err)
		}
		return
	} else if *flagApply {
		for f, e := range m {
			apply(f, e)
		}
	} else {
		err := json.NewEncoder(os.Stdout).Encode(m)
		if err != nil {
			log.Fatal(err)
		}
		return
	}
}

var plats = [...]struct {
	goos, goarch string
}{
	{"linux", "386"},
	{"linux", "amd64"},
	{"linux", "arm"},
	{"linux", "arm64"},
	{"linux", "ppc64"},
	{"linux", "ppc64le"},
	{"nacl", "386"},
	{"nacl", "amd64p32"},
	{"nacl", "arm"},
	{"darwin", "386"},
	{"darwin", "amd64"},
	{"dragonfly", "amd64"},
	{"freebsd", "386"},
	{"freebsd", "amd64"},
	{"freebsd", "arm"},
	{"netbsd", "386"},
	{"netbsd", "amd64"},
	{"netbsd", "arm"},
	{"openbsd", "386"},
	{"openbsd", "amd64"},
	{"openbsd", "arm"},
	{"plan9", "386"},
	{"plan9", "amd64"},
	{"solaris", "amd64"},
	{"windows", "386"},
	{"windows", "amd64"},
}

func mergeEdits() map[string][]Edit {
	ch := make(chan map[string][]Edit)
	var wg sync.WaitGroup
	for _, plat := range plats {
		wg.Add(1)
		go func(goos, goarch string) {
			defer wg.Done()
			ch <- doSub(goos, goarch)
		}(plat.goos, plat.goarch)
	}
	go func() {
		wg.Wait()
		close(ch)
	}()

	m := make(map[string][]Edit)
	for m1 := range ch {
		for f, e := range m1 {
			if e0, ok := m[f]; ok {
				m[f] = intersect(e0, e)
			} else {
				m[f] = e
			}
		}
	}
	return m
}

func intersect(e1, e2 []Edit) []Edit {
	if len(e1) == 0 || len(e2) == 0 {
		return nil
	}

	set := make(map[Edit]bool, len(e1))
	for _, e := range e1 {
		set[e] = true
	}

	var res []Edit
	for _, e := range e2 {
		if set[e] {
			res = append(res, e)
		}
	}
	return res
}

func doSub(goos, goarch string) map[string][]Edit {
	var m map[string][]Edit
	pr, pw := io.Pipe()
	ch := make(chan error)
	go func() {
		ch <- gob.NewDecoder(pr).Decode(&m)
	}()
	cmd := exec.Command("./unconvert", append([]string{"-gob"}, flag.Args()...)...)
	cmd.Stdout = pw
	cmd.Stderr = os.Stderr
	cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)
	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	if err := <-ch; err != nil {
		log.Fatal(err)
	}
	return m
}

func computeEdits() map[string][]Edit {
	var conf loader.Config
	conf.Fset = fset
	for _, arg := range flag.Args() {
		conf.Import(arg)
	}
	prog, err := conf.Load()
	if err != nil {
		log.Fatal(err)
	}

	type res struct {
		file  string
		edits []Edit
	}
	ch := make(chan res)
	var wg sync.WaitGroup
	for _, pkg := range prog.InitialPackages() {
		for _, file := range pkg.Files {
			wg.Add(1)
			go func(pkg *loader.PackageInfo, file *ast.File) {
				defer wg.Done()
				v := visitor{pkg: pkg, file: fset.File(file.Package)}
				ast.Walk(&v, file)
				sort.Sort(editsByPos(v.edits))
				ch <- res{v.file.Name(), v.edits}
			}(pkg, file)
		}
	}
	go func() {
		wg.Wait()
		close(ch)
	}()

	m := make(map[string][]Edit)
	for r := range ch {
		m[r.file] = r.edits
	}
	return m
}

type visitor struct {
	pkg   *loader.PackageInfo
	file  *token.File
	edits []Edit
	nodes []ast.Node
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	if node != nil {
		v.nodes = append(v.nodes, node)
	} else {
		v.nodes = v.nodes[:len(v.nodes)-1]
	}
	if call, ok := node.(*ast.CallExpr); ok {
		v.unconvert(call)
	}
	return v
}

func (v *visitor) unconvert(call *ast.CallExpr) {
	// TODO(mdempsky): Handle useless multi-conversions.

	// Conversions have exactly one argument.
	if len(call.Args) != 1 || call.Ellipsis != token.NoPos {
		return
	}
	ft, ok := v.pkg.Types[call.Fun]
	if !ok {
		fmt.Println("Missing type for function")
		return
	}
	if !ft.IsType() {
		// Not a conversion.
		return
	}
	at, ok := v.pkg.Types[call.Args[0]]
	if !ok {
		fmt.Println("Missing type for argument")
	}
	if !types.Identical(ft.Type, at.Type) {
		// A real conversion.
		return
	}
	if at.Value != nil || hasUntypedValue(call.Args[0]) {
		// As a workaround for golang.org/issue/13061,
		// skip conversions that contain an untyped value.
		return
	}

	outer := v.nodes[len(v.nodes)-2]
	if keepParen(outer, call, call.Args[0]) {
		v.remove(call.Fun.Pos(), call.Lparen)
	} else {
		v.remove(call.Fun.Pos(), call.Lparen+1)
		v.remove(call.Rparen, call.Rparen+1)
	}
}

func (v *visitor) remove(pos, end token.Pos) {
	v.edits = append(v.edits, Edit{v.file.Offset(pos), v.file.Offset(end)})
}

func keepParen(a ast.Node, b, c ast.Expr) bool {
	// 1. Find the value in a that points to b.
	bp := findExprField(a, b)

	// 2. Try printing a with s/b/c/ and with s/b/(c)/.
	var buf1 bytes.Buffer
	*bp = c
	printer.Fprint(&buf1, fset, a)

	var buf2 bytes.Buffer
	*bp = &ast.ParenExpr{X: c}
	printer.Fprint(&buf2, fset, a)

	*bp = b

	// 3. Return whether they print the same (i.e., the parentheses are necessary).
	return buf1.String() == buf2.String()
}

func findExprField(a ast.Node, b ast.Expr) *ast.Expr {
	v := reflect.ValueOf(a).Elem()
	for i, n := 0, v.NumField(); i < n; i++ {
		// Interesting fields are either ast.Expr or []ast.Expr.
		switch f := v.Field(i).Interface().(type) {
		case ast.Expr:
			if f == b {
				return v.Field(i).Addr().Interface().(*ast.Expr)
			}
		case []ast.Expr:
			for i, e := range f {
				if e == b {
					return &f[i]
				}
			}
		}
	}
	log.Fatal("Failed to find b in a")
	return nil
}

func hasUntypedValue(n ast.Expr) bool {
	var v uvVisitor
	ast.Walk(&v, n)
	return v.found
}

type uvVisitor struct {
	found bool
}

func (v *uvVisitor) Visit(node ast.Node) ast.Visitor {
	// Short circuit.
	if v.found {
		return nil
	}

	switch node := node.(type) {
	case *ast.BinaryExpr:
		switch node.Op {
		case token.SHL, token.SHR, token.EQL, token.NEQ, token.LSS, token.GTR, token.LEQ, token.GEQ:
			// Shifts yield an untyped value if their LHS is untyped.
			// Comparisons yield an untyped boolean value.
			v.found = true
		}
	case *ast.Ident:
		if node.Name == "nil" {
			// Probably the universal untyped zero value.
			v.found = true
		}
	}

	if v.found {
		return nil
	}
	return v
}
<|endoftext|>