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.

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