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.

page.go 8.0 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. {{ if .Repo.MajorVersion.Unstable }}
  114. <div class="col-sm-12 alert alert-danger">
  115. This is an <b><i>unstable</i></b> package and should <i>not</i> be used in released code.
  116. </div>
  117. {{ end }}
  118. <div class="row" >
  119. <div class="col-sm-12" >
  120. <a class="btn btn-lg btn-info" href="https://{{.Repo.GitHubRoot}}/{{.Repo.TreeDir}}/{{.Repo.GitHubTree}}{{.Repo.SubPath}}" ><i class="fa fa-github"></i> Source Code</a>
  121. <a class="btn btn-lg btn-info" href="http://godoc.org/{{.Repo.GopkgPath}}" ><i class="fa fa-info-circle"></i> API Documentation</a>
  122. </div>
  123. </div>
  124. <div class="row main" >
  125. <div class="col-sm-8 info" >
  126. <div class="getting-started" >
  127. <h2>Getting started</h2>
  128. <div>
  129. <p>To get the package, execute:</p>
  130. <pre>go get {{.Repo.GopkgPath}}</pre>
  131. </div>
  132. <div>
  133. <p>To import this package, add the following line to your code:</p>
  134. <pre>import "{{.Repo.GopkgPath}}"</pre>
  135. {{if .PackageName}}<p>Refer to it as <i>{{.PackageName}}</i>.{{end}}
  136. </div>
  137. <div>
  138. <p>For more details, see the API documentation.</p>
  139. </div>
  140. </div>
  141. </div>
  142. <div class="col-sm-3 col-sm-offset-1 versions" >
  143. <h2>Versions</h2>
  144. {{ if .LatestVersions }}
  145. {{ range .LatestVersions }}
  146. <div>
  147. <a href="//{{gopkgVersionRoot $.Repo .}}{{$.Repo.SubPath}}" {{if eq .Major $.Repo.MajorVersion.Major}}{{if eq .Unstable $.Repo.MajorVersion.Unstable}}class="current"{{end}}{{end}}>v{{.Major}}{{if .Unstable}}-unstable{{end}}</a>
  148. &rarr;
  149. <span class="label label-default">{{.}}</span>
  150. </div>
  151. {{ end }}
  152. {{ else }}
  153. <div>
  154. <a href="//{{$.Repo.GopkgPath}}" class="current">v0</a>
  155. &rarr;
  156. <span class="label label-default">master</span>
  157. </div>
  158. {{ end }}
  159. </div>
  160. </div>
  161. </div>
  162. </div>
  163. <div id="footer">
  164. <div class="container">
  165. <div class="row">
  166. <div class="col-sm-12">
  167. <p class="text-muted credit">Powered by a <a href="https://code.as/abunchtell/gopkg">Write.as fork</a> of <a href="https://gopkg.in">gopkg.in</a></p>
  168. </div>
  169. </div>
  170. </div>
  171. </div>
  172. <!--<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>-->
  173. <!--<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>-->
  174. </body>
  175. </html>`
  176. var packageTemplate *template.Template
  177. func gopkgVersionRoot(repo *Repo, version Version) string {
  178. return repo.GopkgVersionRoot(version)
  179. }
  180. var packageFuncs = template.FuncMap{
  181. "gopkgVersionRoot": gopkgVersionRoot,
  182. }
  183. func init() {
  184. var err error
  185. packageTemplate, err = template.New("page").Funcs(packageFuncs).Parse(packageTemplateString)
  186. if err != nil {
  187. fmt.Fprintf(os.Stderr, "fatal: parsing package template failed: %s\n", err)
  188. os.Exit(1)
  189. }
  190. }
  191. type packageData struct {
  192. Repo *Repo
  193. LatestVersions VersionList // Contains only the latest version for each major
  194. PackageName string // Actual package identifier as specified in http://golang.org/ref/spec#PackageClause
  195. Synopsis string
  196. GitTreeName string
  197. }
  198. // SearchResults is used with the godoc.org search API
  199. type SearchResults struct {
  200. Results []struct {
  201. Path string `json:"path"`
  202. Synopsis string `json:"synopsis"`
  203. } `json:"results"`
  204. }
  205. var regexpPackageName = regexp.MustCompile(`<h2 id="pkg-overview">package ([\p{L}_][\p{L}\p{Nd}_]*)</h2>`)
  206. func renderPackagePage(resp http.ResponseWriter, req *http.Request, repo *Repo) {
  207. data := &packageData{
  208. Repo: repo,
  209. }
  210. // Calculate the latest version for each major version, both stable and unstable.
  211. latestVersions := make(map[int]Version)
  212. for _, v := range repo.AllVersions {
  213. if v.Unstable {
  214. continue
  215. }
  216. v2, exists := latestVersions[v.Major]
  217. if !exists || v2.Less(v) {
  218. latestVersions[v.Major] = v
  219. }
  220. }
  221. data.LatestVersions = make(VersionList, 0, len(latestVersions))
  222. for _, v := range latestVersions {
  223. data.LatestVersions = append(data.LatestVersions, v)
  224. }
  225. sort.Sort(sort.Reverse(data.LatestVersions))
  226. if repo.FullVersion.Unstable {
  227. // Prepend post-sorting so it shows first.
  228. data.LatestVersions = append([]Version{repo.FullVersion}, data.LatestVersions...)
  229. }
  230. var dataMutex sync.Mutex
  231. wantResps := 2
  232. gotResp := make(chan bool, wantResps)
  233. go func() {
  234. // Retrieve package name from godoc.org. This should be on a proper API.
  235. godocResp, err := http.Get("http://godoc.org/" + repo.GopkgPath())
  236. if err == nil {
  237. godocRespBytes, err := ioutil.ReadAll(godocResp.Body)
  238. godocResp.Body.Close()
  239. if err == nil {
  240. matches := regexpPackageName.FindSubmatch(godocRespBytes)
  241. if matches != nil {
  242. dataMutex.Lock()
  243. data.PackageName = string(matches[1])
  244. dataMutex.Unlock()
  245. }
  246. }
  247. }
  248. gotResp <- true
  249. }()
  250. go func() {
  251. // Retrieve synopsis from godoc.org. This should be on a package path API
  252. // rather than a search.
  253. searchResp, err := http.Get("http://api.godoc.org/search?q=" + url.QueryEscape(repo.GopkgPath()))
  254. if err == nil {
  255. searchResults := &SearchResults{}
  256. err = json.NewDecoder(searchResp.Body).Decode(&searchResults)
  257. searchResp.Body.Close()
  258. if err == nil {
  259. gopkgPath := repo.GopkgPath()
  260. for _, result := range searchResults.Results {
  261. if result.Path == gopkgPath {
  262. dataMutex.Lock()
  263. data.Synopsis = result.Synopsis
  264. dataMutex.Unlock()
  265. break
  266. }
  267. }
  268. }
  269. }
  270. gotResp <- true
  271. }()
  272. r := 0
  273. for r < wantResps {
  274. select {
  275. case <-gotResp:
  276. r++
  277. case <-time.After(3 * time.Second):
  278. r = wantResps
  279. }
  280. }
  281. dataMutex.Lock()
  282. defer dataMutex.Unlock()
  283. err := packageTemplate.Execute(resp, data)
  284. if err != nil {
  285. log.Printf("error executing package page template: %v", err)
  286. }
  287. }