Stable APIs for Go. https://go.code.as
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 rivejä
7.6 KiB

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "html/template"
  6. "io/ioutil"
  7. "log"
  8. "net/http"
  9. "net/url"
  10. "os"
  11. "regexp"
  12. "sort"
  13. "sync"
  14. "time"
  15. )
  16. const packageTemplateString = `<!DOCTYPE html>
  17. <html >
  18. <head>
  19. <meta charset="utf-8">
  20. <title>{{.Repo.Name}}.{{.Repo.MajorVersion}}{{.Repo.SubPath}} - {{.Repo.GopkgPath}}</title>
  21. <link href='//fonts.googleapis.com/css?family=Ubuntu+Mono|Ubuntu' rel='stylesheet' >
  22. <link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet" >
  23. <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" >
  24. <style>
  25. html,
  26. body {
  27. height: 100%;
  28. }
  29. @media (min-width: 1200px) {
  30. .container {
  31. width: 970px;
  32. }
  33. }
  34. body {
  35. font-family: 'Ubuntu', sans-serif;
  36. }
  37. pre {
  38. font-family: 'Ubuntu Mono', sans-serif;
  39. }
  40. .main {
  41. padding-top: 20px;
  42. }
  43. .buttons a {
  44. width: 100%;
  45. text-align: left;
  46. margin-bottom: 5px;
  47. }
  48. .getting-started div {
  49. padding-top: 12px;
  50. }
  51. .getting-started p, .synopsis p {
  52. font-size: 1.3em;
  53. }
  54. .getting-started pre {
  55. font-size: 15px;
  56. }
  57. .versions {
  58. font-size: 1.3em;
  59. }
  60. .versions div {
  61. padding-top: 5px;
  62. }
  63. .versions a {
  64. font-weight: bold;
  65. }
  66. .versions a.current {
  67. color: black;
  68. font-decoration: none;
  69. }
  70. /* wrapper for page content to push down footer */
  71. #wrap {
  72. min-height: 100%;
  73. height: auto !important;
  74. height: 100%;
  75. /* negative indent footer by it's height */
  76. margin: 0 auto -40px;
  77. }
  78. /* footer styling */
  79. #footer {
  80. height: 40px;
  81. background-color: #eee;
  82. padding-top: 8px;
  83. text-align: center;
  84. }
  85. /* footer fixes for mobile devices */
  86. @media (max-width: 767px) {
  87. #footer {
  88. margin-left: -20px;
  89. margin-right: -20px;
  90. padding-left: 20px;
  91. padding-right: 20px;
  92. }
  93. }
  94. </style>
  95. </head>
  96. <body>
  97. <script type="text/javascript">
  98. // If there's a URL fragment, assume it's an attempt to read a specific documentation entry.
  99. if (window.location.hash.length > 1) {
  100. window.location = "http://godoc.org/{{.Repo.GopkgPath}}" + window.location.hash;
  101. }
  102. </script>
  103. <div id="wrap" >
  104. <div class="container" >
  105. <div class="row" >
  106. <div class="col-sm-12" >
  107. <div class="page-header">
  108. <h1>{{.Repo.GopkgPath}}</h1>
  109. {{.Synopsis}}
  110. </div>
  111. </div>
  112. </div>
  113. <div class="row" >
  114. <div class="col-sm-12" >
  115. <a class="btn btn-lg btn-info" href="https://{{.Repo.GitHubRoot}}/tree/{{if .Repo.AllVersions}}{{.FullVersion}}{{else}}master{{end}}{{.Repo.SubPath}}" ><i class="fa fa-github"></i> Source Code</a>
  116. <a class="btn btn-lg btn-info" href="http://godoc.org/{{.Repo.GopkgPath}}" ><i class="fa fa-info-circle"></i> API Documentation</a>
  117. </div>
  118. </div>
  119. <div class="row main" >
  120. <div class="col-sm-8 info" >
  121. <div class="getting-started" >
  122. <h2>Getting started</h2>
  123. <div>
  124. <p>To get the package, execute:</p>
  125. <pre>go get {{.Repo.GopkgPath}}</pre>
  126. </div>
  127. <div>
  128. <p>To import this package, add the following line to your code:</p>
  129. <pre>import "{{.Repo.GopkgPath}}"</pre>
  130. {{if .PackageName}}<p>Refer to it as <i>{{.PackageName}}</i>.{{end}}
  131. </div>
  132. <div>
  133. <p>For more details, see the API documentation.</p>
  134. </div>
  135. </div>
  136. </div>
  137. <div class="col-sm-3 col-sm-offset-1 versions" >
  138. <h2>Versions</h2>
  139. {{ if .LatestVersions }}
  140. {{ range .LatestVersions }}
  141. <div>
  142. <a href="//{{gopkgVersionRoot $.Repo .}}{{$.Repo.SubPath}}" {{if eq .Major $.Repo.MajorVersion.Major}}class="current"{{end}} >v{{.Major}}</a>
  143. &rarr;
  144. <span class="label label-default">{{.}}</span>
  145. </div>
  146. {{ end }}
  147. {{ else }}
  148. <div>
  149. <a href="//{{$.Repo.GopkgPath}}" class="current">v0</a>
  150. &rarr;
  151. <span class="label label-default">master</span>
  152. </div>
  153. {{ end }}
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. <div id="footer">
  159. <div class="container">
  160. <div class="row">
  161. <div class="col-sm-12">
  162. <p class="text-muted credit"><a href="https://gopkg.in">gopkg.in<a></p>
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. <!--<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>-->
  168. <!--<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>-->
  169. </body>
  170. </html>`
  171. var packageTemplate *template.Template
  172. func gopkgVersionRoot(repo *Repo, version Version) string {
  173. return repo.GopkgVersionRoot(version)
  174. }
  175. var packageFuncs = template.FuncMap{
  176. "gopkgVersionRoot": gopkgVersionRoot,
  177. }
  178. func init() {
  179. var err error
  180. packageTemplate, err = template.New("page").Funcs(packageFuncs).Parse(packageTemplateString)
  181. if err != nil {
  182. fmt.Fprintf(os.Stderr, "fatal: parsing package template failed: %s\n", err)
  183. os.Exit(1)
  184. }
  185. }
  186. type packageData struct {
  187. Repo *Repo
  188. LatestVersions VersionList // Contains only the latest version for each major
  189. FullVersion Version // Version that the major requested resolves to
  190. PackageName string // Actual package identifier as specified in http://golang.org/ref/spec#PackageClause
  191. Synopsis string
  192. }
  193. // SearchResults is used with the godoc.org search API
  194. type SearchResults struct {
  195. Results []struct {
  196. Path string `json:"path"`
  197. Synopsis string `json:"synopsis"`
  198. } `json:"results"`
  199. }
  200. var regexpPackageName = regexp.MustCompile(`<h2 id="pkg-overview">package ([\p{L}_][\p{L}\p{Nd}_]*)</h2>`)
  201. func renderPackagePage(resp http.ResponseWriter, req *http.Request, repo *Repo) {
  202. data := &packageData{
  203. Repo: repo,
  204. }
  205. // calculate version mapping
  206. latestVersionsMap := make(map[int]Version)
  207. for _, v := range repo.AllVersions {
  208. v2, exists := latestVersionsMap[v.Major]
  209. if !exists || v2.Less(v) {
  210. latestVersionsMap[v.Major] = v
  211. }
  212. }
  213. data.FullVersion = latestVersionsMap[repo.MajorVersion.Major]
  214. data.LatestVersions = make(VersionList, 0, len(latestVersionsMap))
  215. for _, v := range latestVersionsMap {
  216. data.LatestVersions = append(data.LatestVersions, v)
  217. }
  218. sort.Sort(sort.Reverse(data.LatestVersions))
  219. var dataMutex sync.Mutex
  220. wantResps := 2
  221. gotResp := make(chan bool, wantResps)
  222. go func() {
  223. // Retrieve package name from godoc.org. This should be on a proper API.
  224. godocResp, err := http.Get("http://godoc.org/" + repo.GopkgPath())
  225. if err == nil {
  226. godocRespBytes, err := ioutil.ReadAll(godocResp.Body)
  227. godocResp.Body.Close()
  228. if err == nil {
  229. matches := regexpPackageName.FindSubmatch(godocRespBytes)
  230. if matches != nil {
  231. dataMutex.Lock()
  232. data.PackageName = string(matches[1])
  233. dataMutex.Unlock()
  234. }
  235. }
  236. }
  237. gotResp <- true
  238. }()
  239. go func() {
  240. // Retrieve synopsis from godoc.org. This should be on a package path API
  241. // rather than a search.
  242. searchResp, err := http.Get("http://api.godoc.org/search?q=" + url.QueryEscape(repo.GopkgPath()))
  243. if err == nil {
  244. searchResults := &SearchResults{}
  245. err = json.NewDecoder(searchResp.Body).Decode(&searchResults)
  246. searchResp.Body.Close()
  247. if err == nil {
  248. gopkgPath := repo.GopkgPath()
  249. for _, result := range searchResults.Results {
  250. if result.Path == gopkgPath {
  251. dataMutex.Lock()
  252. data.Synopsis = result.Synopsis
  253. dataMutex.Unlock()
  254. break
  255. }
  256. }
  257. }
  258. }
  259. gotResp <- true
  260. }()
  261. r := 0
  262. for r < wantResps {
  263. select {
  264. case <-gotResp:
  265. r++
  266. case <-time.After(3 * time.Second):
  267. r = wantResps
  268. }
  269. }
  270. dataMutex.Lock()
  271. defer dataMutex.Unlock()
  272. err := packageTemplate.Execute(resp, data)
  273. if err != nil {
  274. log.Printf("error executing package page template: %v", err)
  275. }
  276. }