[+] initial version
This commit is contained in:
parent
0763f9dcd5
commit
fc3b66e2c8
5
.gitignore
vendored
5
.gitignore
vendored
@ -46,4 +46,7 @@ go.work
|
|||||||
go.work.sum
|
go.work.sum
|
||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# geofeed file
|
||||||
|
geofeed.csv
|
||||||
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
![geofeed-validator](https://socialify.git.ci/realSunyz/geofeed-validator/image?description=1&descriptionEditable=&font=Jost&language=1&name=1&owner=1&pattern=Circuit%20Board&theme=Auto)
|
||||||
|
|
||||||
|
This utility is used to validate the format of geofeed files.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The project is still under development. The code on the main branch is stable and usable.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Compile
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/realSunyz/geofeed-validator.git
|
||||||
|
|
||||||
|
# Navigate to the directory
|
||||||
|
cd geofeed-validator/src
|
||||||
|
|
||||||
|
# Synchronize and clean up dependencies
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
# Build the executable file
|
||||||
|
go build -o geofeed-validator
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run
|
||||||
|
```bash
|
||||||
|
./geofeed-validator <path_to_geofeed.csv>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Issues and Pull Requests are definitely welcome!
|
||||||
|
|
||||||
|
Please make sure you have tested your code locally before submitting a PR.
|
||||||
|
|
||||||
|
## License
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](https://github.com/realSunyz/geofeed-validator/blob/main/LICENSE) file for details.
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3
src/go.mod
Normal file
3
src/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/realsunyz/geofeed-validator
|
||||||
|
|
||||||
|
go 1.23.2
|
164
src/validator.go
Normal file
164
src/validator.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// validator.go
|
||||||
|
// author: Yanzheng SUN (realSunyz)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"embed"
|
||||||
|
"encoding/csv"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Reset = "\033[0m"
|
||||||
|
Red = "\033[31m"
|
||||||
|
Green = "\033[32m"
|
||||||
|
Yellow = "\033[33m"
|
||||||
|
Magenta = "\033[35m"
|
||||||
|
Cyan = "\033[36m"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Country struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subdivision struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubdivisionMap map[string][]Subdivision
|
||||||
|
|
||||||
|
//go:embed data/*.json
|
||||||
|
var dataFS embed.FS
|
||||||
|
|
||||||
|
func readJSON(filename string, v interface{}) error {
|
||||||
|
data, err := dataFS.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateGeofeed(geofeedFile string, countries []Country, subdivisions SubdivisionMap) []string {
|
||||||
|
|
||||||
|
countryMap := make(map[string]bool)
|
||||||
|
for _, country := range countries {
|
||||||
|
countryMap[country.Code] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(geofeedFile)
|
||||||
|
if err != nil {
|
||||||
|
return []string{fmt.Sprintf("Failed to open geofeed file: %v", err)}
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
lineNumber := 0
|
||||||
|
var errors []string
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
lineNumber++
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
// Skip blank lines
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip comment lines
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := csv.NewReader(strings.NewReader(line))
|
||||||
|
reader.FieldsPerRecord = -1
|
||||||
|
record, _ := reader.Read()
|
||||||
|
|
||||||
|
// Validate geofeed format (prefix, country_code, subdivision_code, city, postal_code)
|
||||||
|
if len(record) < 5 {
|
||||||
|
errors = append(errors, fmt.Sprintf("Line %d: invalid "+Magenta+"geofeed "+Reset+"format", lineNumber))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prefix, countryCode, subdivisionCode := record[0], record[1], record[2]
|
||||||
|
|
||||||
|
// Validate prefix format
|
||||||
|
_, _, prefixErr := net.ParseCIDR(prefix)
|
||||||
|
if prefixErr != nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("Line %d: invalid "+Magenta+"prefix "+Reset+"format ("+Cyan+"%s"+Reset+")", lineNumber, prefix))
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty country code
|
||||||
|
if countryCode == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate country code
|
||||||
|
if !countryMap[countryCode] {
|
||||||
|
errors = append(errors, fmt.Sprintf("Line %d: invalid "+Magenta+"country code "+Reset+"("+Cyan+"%s"+Reset+")", lineNumber, countryCode))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip empty subdivision code
|
||||||
|
if subdivisionCode == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate subdivision code
|
||||||
|
validSubdivision := false
|
||||||
|
for _, subdivision := range subdivisions[countryCode] {
|
||||||
|
if subdivision.Code == subdivisionCode {
|
||||||
|
validSubdivision = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !validSubdivision {
|
||||||
|
errors = append(errors, fmt.Sprintf("Line %d: invalid "+Magenta+"subdivision code "+Reset+"("+Cyan+"%s"+Reset+") "+"for country ("+Cyan+"%s"+Reset+")", lineNumber, subdivisionCode, countryCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
countryFile := "data/iso3166-1.json"
|
||||||
|
subdivisionFile := "data/iso3166-2.json"
|
||||||
|
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fmt.Println("Usage: ./geofeed-validator <path_to_geofeed.csv>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
geofeedFile := os.Args[1]
|
||||||
|
|
||||||
|
var countries []Country
|
||||||
|
var subdivisions SubdivisionMap
|
||||||
|
|
||||||
|
if err := readJSON(countryFile, &countries); err != nil {
|
||||||
|
fmt.Printf("Failed to load countries: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := readJSON(subdivisionFile, &subdivisions); err != nil {
|
||||||
|
fmt.Printf("Failed to load subdivisions: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := validateGeofeed(geofeedFile, countries, subdivisions)
|
||||||
|
|
||||||
|
if len(errors) == 0 {
|
||||||
|
fmt.Println("Congratulations! Your geofeed file is " + Green + "VALID" + Reset + ".")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Your geofeed file is " + Red + "INVALID" + Reset + ":")
|
||||||
|
for _, err := range errors {
|
||||||
|
fmt.Println("- " + err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user