Compare commits

...

34 Commits

Author SHA1 Message Date
raul 2cf128e4ce Merge pull request 'Main project' (#1) from testing into main
Reviewed-on: #1
2024-06-14 07:49:33 +02:00
raul 4cdc2b0a18 Different colors for cold/hot temperatures 2024-05-31 09:32:49 +02:00
raul f78c66fdc3 Add page for selecting province to pull weather from 2024-05-31 08:57:52 +02:00
raul 8148f756d7 Styling improvements and selecting locality manually 2024-05-30 09:29:47 +02:00
raul 7ebc68a9a4 Add styling template 2024-05-30 08:26:08 +02:00
raul 6fe7383b36 Configure HTML table for storing weather data 2024-05-29 11:21:29 +02:00
raul 5c08a756c0 Figure out templating range/ifelse logic 2024-05-29 10:16:27 +02:00
raul 3c5e1615a0 Formatting tweaks 2024-05-29 08:59:15 +02:00
raul 6468294478 Allow pulling raw json for debugging 2024-05-27 09:03:42 +02:00
raul 0863a258c6 Clean up codebase 2024-05-27 08:44:59 +02:00
raul 7d5c8a26aa Configure Gin to use embedded templates 2024-05-27 08:28:32 +02:00
raul 915c5f0e0d Add HTML template sample 2024-05-27 08:28:10 +02:00
raul 7cfafda526 Add template embedding functions 2024-05-27 08:27:54 +02:00
raul 88d10f976c Remove windows support 2024-05-23 10:45:42 +02:00
raul 843cf7ab94 Improve error handling 2024-05-21 10:38:28 +02:00
raul faad6a72fb Add requesting weather from individual days 2024-05-21 09:22:19 +02:00
raul a8cb384800 Add backup of functional goxml2kjson fork
Again, credits to riccardomanfrin for his fork which saved my life, I'm
only including this here incase the fork ever goes missing
(https://github.com/riccardomanfrin/goxml2json).
2024-05-15 12:37:58 +02:00
raul 421f37ed62 Update dependencies 2024-05-15 12:37:31 +02:00
raul e0a8c745d5 Fix JSON unmarshalling breaking on XML arrays being rendered as objects
Due to a problem in the upstream goxml2json repo, I had to switch to a
fork that allowed me to specify which children should be forcefully
rendered as arrays, as some in some instances the AEMET's XML would
contain completely individual values that are usually arrays, resulting
in the JSON translator interpreting them as objects instead of arrays.

The amount of effort I had to put into fixing this problem was
mind-boggling.
2024-05-15 12:27:24 +02:00
raul 8f9239e392 Update README.md 2024-05-15 12:26:21 +02:00
raul e70106932d Use dictionaries for requesting locality weather
This is the first time in my life I've found a practical use for
dictionaries myself
2024-05-15 08:35:00 +02:00
raul 281e194bc5 Expand struct and remove client() 2024-05-15 08:00:18 +02:00
raul bb2a3adb83 Expand JSON struct 2024-05-13 08:26:11 +02:00
raul bef2a46c41 Remove workflow file 2024-05-13 07:56:40 +02:00
raul 4336c0068b Properly handle unmarshalling errors 2024-05-09 10:07:00 +02:00
raul 212462be3d Communicate upstream errors to client 2024-05-08 11:14:30 +02:00
raul cc49c07b1b Improve error handling and include User-Agent 2024-05-08 11:14:11 +02:00
raul 53e4b1acf9 Fix gin only listening on localhost 2024-05-08 10:47:41 +02:00
raul 6354a824f9 Properly render JSON 2024-05-08 09:29:58 +02:00
raul 8e04491386 Add basic server functionality 2024-05-08 09:16:05 +02:00
raul 08fb32b4b2 Transition project to Cobra 2024-05-08 09:08:36 +02:00
raul acd0b74fca Update dependencies 2024-05-08 08:57:57 +02:00
raul 75053b0ec5 Minor formatting changes 2024-05-05 12:46:07 +02:00
raul 3f9fb89b6d Configure CI/CD 2024-05-03 09:26:49 +02:00
14 changed files with 801 additions and 72 deletions

51
.goreleaser.yaml Normal file
View File

@ -0,0 +1,51 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 1
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
gitea_urls:
api: https://git.bulgariu.xyz/api/v1
download: https://git.bulgariu.xyz
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
#- windows
goarch:
- amd64
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"

View File

@ -1,3 +1,9 @@
# aemet # aemet
CLI tool to check Valencian weather API Server that fetches spanish weather data from the [AEMET](https://www.aemet.es/en/portada) formatted in XML and translates it
to JSON, optionally allowing to render the obtained data in a web page.
-----
Credits and thanks to [riccardomanfrin](https://github.com/riccardomanfrin) for his fork of [goxml2json, which allowed
this entire project to work properly](https://github.com/riccardomanfrin/goxml2json) despite the AEMET's inconsistent XML formatting.

BIN
backup-goxml2json.tar.gz Normal file

Binary file not shown.

99
cmd/clientFunc.go Normal file
View File

@ -0,0 +1,99 @@
package cmd
import (
"fmt"
xj "github.com/riccardomanfrin/goxml2json"
"io"
"net/http"
"strings"
)
type root struct {
Base struct {
Nombre string `json:"nombre"`
Prediccion struct {
Dia []struct {
Fecha string `json:"-fecha"`
UV string `json:"uv_max"`
Temperatura struct {
Maxima string `json:"maxima"`
Minima string `json:"minima"`
Dato []struct {
Valor string `json:"#content"`
Hora string `json:"-hora"`
} `json:"dato"`
} `json:"temperatura"`
Sens_Termica struct {
Maxima string `json:"maxima"`
Minima string `json:"minima"`
Dato []struct {
Valor string `json:"#content"`
Hora string `json:"-hora"`
} `json:"dato"`
} `json:"sens_termica"`
Humedad_Relativa struct {
Maxima string `json:"maxima"`
Minima string `json:"minima"`
} `json:"humedad_relativa"`
Estado_Cielo []struct {
Test string `json:"#content"`
Periodo string `json:"-periodo"`
Descripcion string `json:"-descripcion"`
} `json:"estado_cielo"`
// Prob_Precipitacion []struct {
// Probabilidad string `json:"#content"`
// Periodo string `json:"-periodo"`
// } `json:"prob_precipitacion"`
} `json:"dia"`
} `json:"prediccion"`
} `json:"root"`
}
func getJSON(codPostal string) (string, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://www.aemet.es/xml/municipios/localidad_"+codPostal+".xml", nil)
if err != nil {
e := fmt.Errorf("Error occurred creating GET request: %v\n", err)
return "", e
}
req.Header.Set("User-Agent", "AEMET-Client/1.0 (https://git.bulgariu.xyz/raul/aemet)")
resp, err := client.Do(req)
if err != nil {
e := fmt.Errorf("Error occurred executing GET request: %v\n", err)
return "", e
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
e := fmt.Errorf("Error occurred accessing upstream website: %v\n", resp.Status)
return "", e
}
data, err := io.ReadAll(resp.Body)
if err != nil {
e := fmt.Errorf("Error occurred reading data: %v\n", err)
return "", e
}
xml := strings.NewReader(string(data))
// I am in tremendous pain after what I had to go through to get this damn thing working,
json, err := xj.Convert(xml, xj.WithNodes(
xj.NodePlugin("root.prediccion.dia.estado_cielo", xj.ToArray()),
xj.NodePlugin("root.prediccion.dia.prob_precipitacion", xj.ToArray()),
))
if err != nil {
e := fmt.Errorf("Error occurred converting XML to JSON: %v\n", err)
return "", e
}
return json.String(), nil
}

39
cmd/embedTemplates.go Normal file
View File

@ -0,0 +1,39 @@
package cmd
import (
"embed"
"github.com/gin-gonic/gin"
"html/template"
"io/fs"
"regexp"
"strings"
)
func LoadHTMLFromEmbedFS(engine *gin.Engine, embedFS embed.FS, pattern string) {
root := template.New("")
tmpl := template.Must(root, LoadAndAddToRoot(engine.FuncMap, root, embedFS, pattern))
engine.SetHTMLTemplate(tmpl)
}
func LoadAndAddToRoot(funcMap template.FuncMap, rootTemplate *template.Template, embedFS embed.FS, pattern string) error {
pattern = strings.ReplaceAll(pattern, ".", "\\.")
pattern = strings.ReplaceAll(pattern, "*", ".*")
err := fs.WalkDir(embedFS, ".", func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if matched, _ := regexp.MatchString(pattern, path); !d.IsDir() && matched {
data, readErr := embedFS.ReadFile(path)
if readErr != nil {
return readErr
}
t := rootTemplate.New(path).Funcs(funcMap)
if _, parseErr := t.Parse(string(data)); parseErr != nil {
return parseErr
}
}
return nil
})
return err
}

30
cmd/root.go Normal file
View File

@ -0,0 +1,30 @@
/*
Copyright © 2024 raul <raul@bulgariu.xyz>
*/
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "aemet",
Short: "API Server that fetches spanish weather data from the AEMET",
Long: `API Server that fetches spanish weather data from the AEMET formatted
in XML and translates it to JSON, optionally allowing to render the
obtained data in a web page.`,
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

38
cmd/server.go Normal file
View File

@ -0,0 +1,38 @@
/*
Copyright © 2024 raul
*/
package cmd
import (
"github.com/spf13/cobra"
"log"
)
var serverCmd = &cobra.Command{
Use: "server",
Short: "Start aemet webserver",
Long: `Start aemet webserver`,
Run: func(cmd *cobra.Command, args []string) {
if err := setServerParameters(cmd); err != nil {
log.Fatalf("Error happened trying to set parameters: %v\n", err)
}
server()
},
}
func init() {
rootCmd.AddCommand(serverCmd)
serverCmd.PersistentFlags().StringP("port", "p", "1302", "Port to use for listening")
}
func setServerParameters(cmd *cobra.Command) error {
parameterPort, err := cmd.Flags().GetString("port")
if err != nil {
return err
}
if parameterPort != "" {
listenPort = parameterPort
}
return nil
}

127
cmd/serverFunc.go Normal file
View File

@ -0,0 +1,127 @@
package cmd
import (
"embed"
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
var (
listenPort string = "1302"
localidades = map[string]string{
"valencia": "46250",
"madrid": "28079",
"barcelona": "08019",
}
)
//go:embed templates/*
var templatesFolder embed.FS
func server() {
//gin.SetMode(gin.ReleaseMode)
r := gin.Default()
LoadHTMLFromEmbedFS(r, templatesFolder, "templates/*.html")
r.StaticFileFS("/style.css", "./templates/style.css", http.FS(templatesFolder))
r.GET("/", chooseProvince)
r.GET("/:local", returnProvince)
r.GET("/api/:localidad", returnAPIWeather)
r.GET("/api/:localidad/:dia", returnAPIWeather)
fmt.Printf("Listening on port %v...\n", listenPort)
r.Run(":" + listenPort)
}
func chooseProvince(c *gin.Context) {
c.HTML(http.StatusOK, "templates/redirect.html", gin.H{
"Localidades": localidades,
})
}
func returnProvince(c *gin.Context) {
localidad := c.Param("local")
if isAv := localityIsAvailable(localidad); isAv != true {
c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported, sorry\n")
return
}
aemetRequest, _, err := pullWeatherInfo(localidad)
if err != nil {
e := fmt.Sprint(err)
c.String(http.StatusInternalServerError, e)
return
}
c.HTML(http.StatusOK, "templates/index.html", gin.H{
"Base": aemetRequest.Base,
"Localidades": localidades,
})
}
func pullWeatherInfo(localidad string) (root, string, error) {
jsonData, err := getJSON(localidades[localidad])
if err != nil {
return root{}, jsonData, err
}
textBytes := []byte(jsonData)
aemetRequest := root{}
err = json.Unmarshal(textBytes, &aemetRequest)
if err != nil {
return root{}, jsonData, err
}
return aemetRequest, jsonData, nil
}
func localityIsAvailable(localidad string) bool {
isAvailable := false
for k := range localidades {
if localidad == k {
isAvailable = true
}
}
if isAvailable == false {
return false
}
return true
}
func returnAPIWeather(c *gin.Context) {
c.Writer.Header().Set("Federal-Agents", "Outside my home")
var n int
var err error
localidad := c.Param("localidad")
numDias := c.Param("dia")
if isAv := localityIsAvailable(localidad); isAv != true {
c.String(http.StatusNotFound, "The locality doesn't exist or is currently not supported, sorry\n")
return
}
aemetRequest, _, err := pullWeatherInfo(localidad)
if err != nil {
e := fmt.Sprint(err)
c.String(http.StatusInternalServerError, e)
//c.String(500, jsonData)
return
}
if numDias != "" {
n, err = strconv.Atoi(numDias)
if err != nil || n > len(aemetRequest.Base.Prediccion.Dia)-1 || n < 0 {
c.String(http.StatusNotAcceptable, "Invalid day identifier")
return
}
}
if numDias != "" {
c.IndentedJSON(http.StatusOK, aemetRequest.Base.Prediccion.Dia[n])
} else {
c.IndentedJSON(http.StatusOK, aemetRequest)
}
//c.JSON(http.StatusOK, aemetRequest)
}

89
cmd/templates/index.html Normal file
View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>AEMET Client</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="style.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div id="main">
<b>
<ul>
<li class="hor">
<p><a href="https://git.bulgariu.xyz/raul/aemet" target="_blank">Source code</a></p>
</li>
<li class="hor">
<p><a href="https://git.bulgariu.xyz/raul/aemet" target="_blank">API Docs</a></p>
</li>
<li class="hor">
<p><a href="https://git.bulgariu.xyz/raul/aemet" target="_blank">But why though?</a></p>
</li>
<li class="hor">
<p>
<select class="optionCap"
onchange="this.options[this.selectedIndex].value && (window.location = this.options[this.selectedIndex].value);">
<option value="">Select...</option>
{{ range $key, $value := .Localidades }}
<option value="{{ $key }}" class="optionCap">{{ $key }}</option>
{{ end }}
</select>
</p>
</li>
</ul>
</b>
<table border=1>
<th colspan="100%">{{ .Base.Nombre }}</th>
<tr>
{{ range .Base.Prediccion.Dia}}
<td class="date">{{ .Fecha }}</td>
{{ end }}
</tr>
<tr>
<td class="left" colspan="100%">Temperatura mínima y máxima (°C)</td>
</tr>
<tr>
{{ range .Base.Prediccion.Dia}}
<td>
<cold>{{ .Temperatura.Minima }}</cold> / <hot>{{ .Temperatura.Maxima }}</hot>
</td>
{{ end }}
</tr>
<tr>
<td class="left" colspan="100%">Sens. térmica mínima y máxima (°C)</td>
</tr>
<tr>
{{ range .Base.Prediccion.Dia }}
<td>
<cold>{{ .Sens_Termica.Minima }}</cold> / <hot>{{ .Sens_Termica.Maxima }}</hot>
</td>
{{ end }}
</tr>
<tr>
<td class="left" colspan="100%">Índice UV</td>
</tr>
<tr>
{{ range .Base.Prediccion.Dia}}
<td>
{{ if eq .UV "" }}
No disponible!
{{ else }}
{{ .UV }}
{{ end }}
</td>
{{ end }}
</tr>
</table>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>AEMET Client</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="style.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div id="main">
<b>
<ul>
{{ range $key, $value := .Localidades }}
<li>
<p><a href="{{ $key }}" class="optionCap">{{ $key }}</a></p>
</li>
{{ end }}
</ul>
</b>
</div>
</div>
</body>
</html>

164
cmd/templates/style.css Normal file
View File

@ -0,0 +1,164 @@
* {
font-family: arial;
}
table {
border: 1px solid black;
border-collapse: collapse;
margin-left: auto;
margin-right: auto;
}
.optionCap {
text-transform: capitalize;
}
ul {
text-align: center;
list-style: none;
top: 0;
margin: 0;
padding: 0;
}
li.hor {
display: inline-flex;
margin-right: 20px;
}
td.left {
text-align: left;
padding-left: 5px;
}
td.date {
text-align: center;
padding-left: 5px;
padding-right: 5px;
}
cold {
color: darkcyan;
font-weight: 500;
}
hot {
color: darkorange;
font-weight: 500;
}
table * {
text-align: center;
}
input {
margin-bottom: 10px;
}
#but {
background-color: #eee;
border: 2px black solid;
}
.centered {
text-align: center;
}
img {
text-align: center;
border: 2px solid #ff6e00;
border-radius: 50%;
padding: 1%;
}
button {
background-color: #eee;
border: 2px black solid;
margin-bottom: 10px;
}
#but:hover {
background-color: #ff6e00;
border: 2px black solid;
}
body {
background-color: #aaa;
}
h1 {
text-align: center;
bottom: 0%;
}
h3 {
text-align: center;
}
form {
text-align: center;
}
a {
color: #ff6e00;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
progress::-moz-progress-bar {
background-color: #ff6e00;
}
progress::-webkit-progress-value {
background-color: #ff6e00;
}
progress {
color: #ff6e00;
}
footer {
top: 100%;
}
#main {
background-color: #eee;
width: 80vw;
height: 80vh;
padding: 10px;
outline: solid 2px #ff6e00;
overflow: scroll;
}
@media (max-width: 600px) {
#main {
width: 60vw;
}
}
.container {
display: flex;
position: fixed;
margin: 1px;
padding: 1px;
top: 1px;
left: 1px;
outline: solid 1px black;
justify-content: center;
align-items: center;
height: 99vh;
width: 99vw;
background-color: #aaa;
/* width: 100%; */
/* height: 100%; */
/* margin: 50px; */
/* outline: solid 1px black; */
/* display: flex; */
/* justify-content: center; */
/* align-items: center; */
}

37
go.mod
View File

@ -2,11 +2,40 @@ module aemet
go 1.22.2 go 1.22.2
require github.com/basgys/goxml2json v1.1.0 require (
github.com/gin-gonic/gin v1.10.0
github.com/riccardomanfrin/goxml2json v1.1.1-0.20190801150129-10f0ac9a1fe4
github.com/spf13/cobra v1.8.0
)
require ( require (
github.com/bitly/go-simplejson v0.5.1 // indirect github.com/bitly/go-simplejson v0.5.1 // indirect
github.com/stretchr/testify v1.9.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect
golang.org/x/net v0.24.0 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
golang.org/x/text v0.14.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

97
go.sum
View File

@ -1,16 +1,101 @@
github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw=
github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw=
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/riccardomanfrin/goxml2json v1.1.1-0.20190801150129-10f0ac9a1fe4 h1:rCNXLdPiD69nJU1XiMyJ+ia0A40wEx2RXxKzhnmGGyw=
github.com/riccardomanfrin/goxml2json v1.1.1-0.20190801150129-10f0ac9a1fe4/go.mod h1:RLEQpK1Wk5+A0V1SfATfoPrwDiZWbbVMChJ0+8m1dvY=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

67
main.go
View File

@ -1,66 +1,11 @@
/*
Copyright © 2024 raul
*/
package main package main
import ( import "aemet/cmd"
"encoding/json"
"fmt"
xj "github.com/basgys/goxml2json"
"io"
"log"
"net/http"
"strings"
)
type root struct {
Base struct {
Nombre string `json:"nombre"`
Prediccion struct {
Dia []struct {
Fecha string `json:"-fecha"`
Temperatura struct {
Maxima string `json:"maxima"`
Minima string `json:"minima"`
}
}
}
} `json:"root"`
}
func main() { func main() {
jsonData := getJSON() cmd.Execute()
textBytes := []byte(jsonData)
aemetRequest := root{}
err := json.Unmarshal(textBytes, &aemetRequest)
if err != nil {
log.Fatalf("Error occurred unmarshalling data: %v\n", err)
}
fmt.Println(aemetRequest.Base.Nombre)
fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Fecha)
fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Temperatura.Maxima)
fmt.Println(aemetRequest.Base.Prediccion.Dia[0].Temperatura.Minima)
}
func getJSON() string {
resp, err := http.Get("https://www.aemet.es/xml/municipios/localidad_46250.xml")
if err != nil {
log.Fatalf("Error occurred pulling data: %v\n", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("Error occurred accessing website: %v\n", err)
}
data, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error occurred reading data: %v\n", err)
}
xml := strings.NewReader(string(data))
json, err := xj.Convert(xml)
if err != nil {
log.Fatalf("Error occurred converting XML to JSON: %v\n", err)
}
return json.String()
} }