Copyright 2019 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 frontend

import (
	
	
	
	
	
	
	
	
	
	

	
	
	
	
	
)

const defaultSearchLimit = 10
SearchPage contains all of the data that the search template needs to populate.
SearchResult contains data needed to display a single search result.
fetchSearchPage fetches data matching the search query from the database and returns a SearchPage.
func ( context.Context,  *postgres.DB,  string,  paginationParams) (*SearchPage, error) {
	 := maxSearchOffset + .limit

	 := false
	if strings.HasPrefix(, "identifier:") {
		 = strings.TrimPrefix(, "identifier:")
		 = true
	}
	,  := .Search(, , .limit, .offset(), , )
	if  != nil {
		return nil, 
	}

	var  []*SearchResult
	for ,  := range  {
		 = append(, &SearchResult{
			Name:           .Name,
			PackagePath:    .PackagePath,
			ModulePath:     .ModulePath,
			Synopsis:       .Synopsis,
			DisplayVersion: displayVersion(.Version, .ModulePath),
			Licenses:       .Licenses,
			CommitTime:     elapsedTime(.CommitTime),
			NumImportedBy:  int(.NumImportedBy),
			SameModule:     packagePaths("Other packages in module "+.ModulePath+":", .SameModule, 5),
			LowerMajor:     modulePaths("Lower module versions:", .LowerMajor),
			Symbols:        symbolResults("Identifiers:", .PackagePath, .Symbols, 5),
		})
	}

	var (
		  int
		 bool
	)
	if len() > 0 {
		 = int([0].NumResults)
128 buckets corresponds to a standard error of 10%. http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf
			 = approximateNumber(, 0.1)
			 = true
		}
	}

	 := 0
Grouping will put some results inside others. Each result counts one for itself plus one for each sub-result in the SameModule list, because each of those is removed from the top-level slice. Results in the LowerMajor list are not removed from the top-level slice, so we don't add them up.
		 += 1 + len(.SameModule)
	}

	 := newPagination(, , )
	.Approximate = 
	return &SearchPage{
		Results:    ,
		Pagination: ,
	}, nil
}
approximateNumber returns an approximation of the estimate, calibrated by the statistical estimate of standard error. i.e., a number that isn't misleading when we say '1-10 of approximately N results', but that is still close to our estimate.
func ( int,  float64) int {
Compute the unit by rounding the error the logarithmically closest power of 10, so that 300->100, but 400->1000.
Now round the estimate to the nearest unit.
	return int( * math.Round(float64()/))
}

func ( string,  []*internal.SearchResult,  int) *subResult {
	if len() == 0 {
		return nil
	}
	var  []link
	for ,  := range  {
		if  >=  {
			break
		}
		 = append(, link{Href: .PackagePath, Body: internal.Suffix(.PackagePath, .ModulePath)})
	}
	 := ""
	if len() > len() {
		 = fmt.Sprintf("(and %d more)", len()-len())
	}
	return &subResult{
		Heading: ,
		Links:   ,
		Suffix:  ,
	}
}

func ( string,  []*internal.SearchResult) *subResult {
	if len() == 0 {
		return nil
	}
	 := map[string]bool{}
	for ,  := range  {
		[.ModulePath] = true
	}
	var  []string
	for  := range  {
		 = append(, )
	}
	sort.Slice(, func(,  int) bool {
		,  := internal.SeriesPathAndMajorVersion([])
		,  := internal.SeriesPathAndMajorVersion([])
		return  > 
	})
	 := make([]link, len())
	for ,  := range  {
		[] = link{Href: , Body: }
	}
	return &subResult{
		Heading: ,
		Links:   ,
	}
}

func (,  string,  []string,  int) *subResult {
	if len() == 0 {
		return nil
	}
	var  []link
	for ,  := range  {
		if  >=  {
			break
		}
		 := symbolLink(, , internal.BuildContexts)
		 = append(, link{Href: , Body: })
	}
	 := ""
	if len() > len() {
		 = fmt.Sprintf("(and %d more)", len()-len())
	}
	return &subResult{
		Heading: ,
		Links:   ,
		Suffix:  ,
	}
}
Search constraints.
maxSearchQueryLength represents the max number of characters that a search query can be. For PostgreSQL 11, there is a max length of 2K bytes: https://www.postgresql.org/docs/11/textsearch-limitations.html. No valid searches on pkg.go.dev will need more than the maxSearchQueryLength.
maxSearchOffset is the maximum allowed offset into the search results. This prevents some very CPU-intensive queries from running.
maxSearchPageSize is the maximum allowed limit for search results.
serveSearch applies database data to the search template. Handles endpoint /search?q=<query>. If <query> is an exact match for a package path, the user will be redirected to the details page.
The proxydatasource does not support the imported by page.
		return proxydatasourceNotSupportedErr()
	}

	 := .Context()
	 := searchQuery()
	if !utf8.ValidString() {
		return &serverError{status: http.StatusBadRequest}
	}
	if len() > maxSearchQueryLength {
		return &serverError{
			status: http.StatusBadRequest,
			epage: &errorPage{
				messageTemplate: template.MakeTrustedTemplate(
					`<h3 class="Error-message">Search query too long.</h3>`),
			},
		}
	}
	if  == "" {
		http.Redirect(, , "/", http.StatusFound)
		return nil
	}
	 := newPaginationParams(, defaultSearchLimit)
	if .offset() > maxSearchOffset {
		return &serverError{
			status: http.StatusBadRequest,
			epage: &errorPage{
				messageTemplate: template.MakeTrustedTemplate(
					`<h3 class="Error-message">Search page number too large.</h3>`),
			},
		}
	}
	if .limit > maxSearchPageSize {
		return &serverError{
			status: http.StatusBadRequest,
			epage: &errorPage{
				messageTemplate: template.MakeTrustedTemplate(
					`<h3 class="Error-message">Search page size too large.</h3>`),
			},
		}
	}

	if  := searchRequestRedirectPath(, , );  != "" {
		http.Redirect(, , , http.StatusFound)
		return nil
	}
	,  := fetchSearchPage(, , , )
	if  != nil {
		return fmt.Errorf("fetchSearchPage(ctx, db, %q): %v", , )
	}
	.basePage = .newBasePage(, fmt.Sprintf("%s - Search Results", ))
	.servePage(, , "search.tmpl", )
	return nil
}
searchRequestRedirectPath returns the path that a search request should be redirected to, or the empty string if there is no such path. If the user types an existing package path into the search bar, we will redirect the user to the details page. Standard library packages that only contain one element (such as fmt, errors, etc.) will not redirect, to allow users to search by those terms.
func ( context.Context,  internal.DataSource,  string) string {
	 := strings.Index(, "://")
	if  > -1 {
		 = [+3:]
	}
	 := path.Clean()
	if !strings.Contains(, "/") {
		return ""
	}
	,  := .GetUnitMeta(, , internal.UnknownModulePath, internal.LatestVersion)
	if  != nil {
		if !errors.Is(, derrors.NotFound) {
			log.Errorf(, "searchRequestRedirectPath(%q): %v", , )
		}
		return ""
	}
	return fmt.Sprintf("/%s", )
}
searchQuery extracts a search query from the request.
elapsedTime takes a date and returns returns human-readable, relative timestamps based on the following rules: (1) 'X hours ago' when X < 6 (2) 'today' between 6 hours and 1 day ago (3) 'Y days ago' when Y < 6 (4) A date formatted like "Jan 2, 2006" for anything further back
func ( time.Time) string {
	 := int(time.Since().Hours())
	if  == 1 {
		return "1 hour ago"
	} else if  < 6 {
		return fmt.Sprintf("%d hours ago", )
	}

	 :=  / 24
	if  < 1 {
		return "today"
	} else if  == 1 {
		return "1 day ago"
	} else if  < 6 {
		return fmt.Sprintf("%d days ago", )
	}

	return absoluteTime()