mirror of
https://github.com/Luzifer/go-holidays.git
synced 2024-12-26 05:41:18 +00:00
Add README page for / path
This commit is contained in:
parent
3c8fe7ceda
commit
9fe048b5d0
4 changed files with 300 additions and 0 deletions
|
@ -7,6 +7,8 @@ WORKDIR /go/src/github.com/Luzifer/go-holidays/cmd/holiday-api
|
||||||
|
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add --update git ca-certificates \
|
&& apk add --update git ca-certificates \
|
||||||
|
&& go get -u github.com/jteeuwen/go-bindata/... \
|
||||||
|
&& go generate \
|
||||||
&& go install -ldflags "-X main.version=$(git describe --tags || git rev-parse --short HEAD || echo dev)" \
|
&& go install -ldflags "-X main.version=$(git describe --tags || git rev-parse --short HEAD || echo dev)" \
|
||||||
&& apk del --purge git
|
&& apk del --purge git
|
||||||
|
|
||||||
|
|
192
cmd/holiday-api/assets.go
Normal file
192
cmd/holiday-api/assets.go
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
// Code generated by go-bindata.
|
||||||
|
// sources:
|
||||||
|
// index.html
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bindataRead reads the given file from disk. It returns an error on failure.
|
||||||
|
func bindataRead(path, name string) ([]byte, error) {
|
||||||
|
buf, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error reading asset %s at %s: %v", name, path, err)
|
||||||
|
}
|
||||||
|
return buf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type asset struct {
|
||||||
|
bytes []byte
|
||||||
|
info os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexHtml reads file data from disk. It returns an error on failure.
|
||||||
|
func indexHtml() (*asset, error) {
|
||||||
|
path := filepath.Join(rootDir, "index.html")
|
||||||
|
name := "index.html"
|
||||||
|
bytes, err := bindataRead(path, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &asset{bytes: bytes, info: fi}
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset loads and returns the asset for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func Asset(name string) ([]byte, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.bytes, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustAsset is like Asset but panics when Asset would return an error.
|
||||||
|
// It simplifies safe initialization of global variables.
|
||||||
|
func MustAsset(name string) []byte {
|
||||||
|
a, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
panic("asset: Asset(" + name + "): " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetInfo loads and returns the asset info for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func AssetInfo(name string) (os.FileInfo, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.info, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetNames returns the names of the assets.
|
||||||
|
func AssetNames() []string {
|
||||||
|
names := make([]string, 0, len(_bindata))
|
||||||
|
for name := range _bindata {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
|
var _bindata = map[string]func() (*asset, error){
|
||||||
|
"index.html": indexHtml,
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetDir returns the file names below a certain
|
||||||
|
// directory embedded in the file by go-bindata.
|
||||||
|
// For example if you run go-bindata on data/... and data contains the
|
||||||
|
// following hierarchy:
|
||||||
|
// data/
|
||||||
|
// foo.txt
|
||||||
|
// img/
|
||||||
|
// a.png
|
||||||
|
// b.png
|
||||||
|
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||||
|
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||||
|
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||||
|
// AssetDir("") will return []string{"data"}.
|
||||||
|
func AssetDir(name string) ([]string, error) {
|
||||||
|
node := _bintree
|
||||||
|
if len(name) != 0 {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
pathList := strings.Split(cannonicalName, "/")
|
||||||
|
for _, p := range pathList {
|
||||||
|
node = node.Children[p]
|
||||||
|
if node == nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Func != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
rv := make([]string, 0, len(node.Children))
|
||||||
|
for childName := range node.Children {
|
||||||
|
rv = append(rv, childName)
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bintree struct {
|
||||||
|
Func func() (*asset, error)
|
||||||
|
Children map[string]*bintree
|
||||||
|
}
|
||||||
|
var _bintree = &bintree{nil, map[string]*bintree{
|
||||||
|
"index.html": &bintree{indexHtml, map[string]*bintree{}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
// RestoreAsset restores an asset under the given directory
|
||||||
|
func RestoreAsset(dir, name string) error {
|
||||||
|
data, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := AssetInfo(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreAssets restores an asset under the given directory recursively
|
||||||
|
func RestoreAssets(dir, name string) error {
|
||||||
|
children, err := AssetDir(name)
|
||||||
|
// File
|
||||||
|
if err != nil {
|
||||||
|
return RestoreAsset(dir, name)
|
||||||
|
}
|
||||||
|
// Dir
|
||||||
|
for _, child := range children {
|
||||||
|
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _filePath(dir, name string) string {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||||
|
}
|
||||||
|
|
98
cmd/holiday-api/index.html
Normal file
98
cmd/holiday-api/index.html
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||||
|
<title>holiday-api Documentation</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap-theme.min.css"
|
||||||
|
integrity="sha256-ZT4HPpdCOt2lvDkXokHuhJfdOKSPFLzeAJik5U/Q+l4=" crossorigin="anonymous" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/superhero/bootstrap.min.css"
|
||||||
|
integrity="sha256-LEplvgQTKatd65f2Z/JThrYx/sdoKygi0dsC1h5sInE=" crossorigin="anonymous" />
|
||||||
|
|
||||||
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"
|
||||||
|
integrity="sha256-3Jy/GbSLrg0o9y5Z5n1uw0qxZECH7C6OQpVBgNFYa0g=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"
|
||||||
|
integrity="sha256-g6iAfvZp+nDQ2TdTR/VVKJf3bGro4ub5fvWSWVRi2NE=" crossorigin="anonymous"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="col-md-12" id="display">
|
||||||
|
# holiday-api Documentation
|
||||||
|
|
||||||
|
This instance of `holiday-api` is a convenient wrapper around my [go-holiday library](https://github.com/Luzifer/go-holidays) which contains holidays for different countries. With this wrapper you can request a JSON output for a specific country or state within a country using [ISO 3166-1 alpha-2 country codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). For some countries (for example Germany) the corresponding state code extension is supported.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The supported URL scheme is `/{country-code}/{year}` while `{year}` is optional and if not set the current year is used.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- [`/us`](/us) - Federal holidays for the United States for the current year
|
||||||
|
- [`/de/2017`](/de/2017) - National holidays in Germany for 2017
|
||||||
|
- [`/de-by/2017`](/de-by/2017) - Holidays for Bavaria for 2017 (Uses [ISO 3166-2:DE](https://en.wikipedia.org/wiki/ISO_3166-2:DE) codes)
|
||||||
|
|
||||||
|
No request limits are enforced so please play nice with the API.
|
||||||
|
|
||||||
|
## Response
|
||||||
|
|
||||||
|
The API will respond with
|
||||||
|
- `HTTP 200` and a JSON array if the query was successful
|
||||||
|
- `HTTP 404` if your URL is nvalid.
|
||||||
|
- `HTTP 500` if the requested country-code is not included / supported
|
||||||
|
|
||||||
|
### JSON format
|
||||||
|
|
||||||
|
Each entry consists of four keys:
|
||||||
|
- `name`: Name of the holiday in English language
|
||||||
|
- `localized_name`: A mapping of different localizations (key is an ISO 3166-1 alpha-2 country code)
|
||||||
|
- `date`: A string representation of the date in `YYYY-MM-DD` notation
|
||||||
|
- `parsed_date`: The date as a DateTime string with timezone of the server running the API
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "New Year's Day",
|
||||||
|
localized_name: {
|
||||||
|
de: "Neujahrstag"
|
||||||
|
},
|
||||||
|
date: "2017-01-01",
|
||||||
|
parsed_date: "2017-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Good Friday",
|
||||||
|
localized_name: {
|
||||||
|
de: "Karfreitag"
|
||||||
|
},
|
||||||
|
date: "2017-04-14",
|
||||||
|
parsed_date: "2017-04-14T00:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
If you are fluent in writing Go and know about holidays of a country not yet available inside the library please get in touch with me. Either using an issue or a pull-request in the [repository](https://github.com/Luzifer/go-holidays) or using any method on my website: [ahlers.me](https://ahlers.me/)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="https://github.com/Luzifer/go-holidays" class="github-corner" aria-label="View source on Github"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#70B7FD; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
|
||||||
|
|
||||||
|
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"
|
||||||
|
integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
|
||||||
|
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"
|
||||||
|
integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js"
|
||||||
|
integrity="sha256-mJAzKDq6kSoKqZKnA6UNLtPaIj8zT2mFnWu/GSouhgQ=" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
$(function(){ $('#display').html(marked($('#display').html())); });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,5 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
//go:generate go-bindata -pkg $GOPACKAGE -o assets.go index.html
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -38,6 +40,7 @@ func main() {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/{country-code:[a-z-]+}/{year:[0-9]{4}}", handleHolidays)
|
r.HandleFunc("/{country-code:[a-z-]+}/{year:[0-9]{4}}", handleHolidays)
|
||||||
r.HandleFunc("/{country-code:[a-z-]+}", handleHolidays)
|
r.HandleFunc("/{country-code:[a-z-]+}", handleHolidays)
|
||||||
|
r.HandleFunc("/", handleReadme)
|
||||||
http.ListenAndServe(cfg.Listen, r)
|
http.ListenAndServe(cfg.Listen, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,3 +69,8 @@ func handleHolidays(res http.ResponseWriter, r *http.Request) {
|
||||||
res.Header().Set("Content-Type", "application/json")
|
res.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(res).Encode(holidays)
|
json.NewEncoder(res).Encode(holidays)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleReadme(res http.ResponseWriter, r *http.Request) {
|
||||||
|
readme, _ := Asset("index.html")
|
||||||
|
res.Write(readme)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue