Full Code of issue9/cnregion for AI

master 692b4fb3ec65 cached
23 files
34.4 KB
12.1k tokens
82 symbols
1 requests
Download .txt
Repository: issue9/cnregion
Branch: master
Commit: 692b4fb3ec65
Files: 23
Total size: 34.4 KB

Directory structure:
gitextract_8k6qygrr/

├── .github/
│   └── workflows/
│       ├── codeql-analysis.yml
│       └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── cnregion.go
├── cnregion_test.go
├── data/
│   ├── embed.go
│   └── embed_test.go
├── db.go
├── db_test.go
├── districts.go
├── districts_test.go
├── go.mod
├── go.sum
├── id/
│   ├── id.go
│   └── id_test.go
├── region.go
├── region_test.go
├── search.go
├── search_test.go
└── version/
    ├── version.go
    └── version_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"

on:
  push:
    branches: [master]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [master]
  schedule:
    - cron: '0 16 * * 5'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        # Override automatic language detection by changing the below list
        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
        language: ['go']
        # Learn more...
        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      with:
        # We must fetch at least the immediate parents so that if this is
        # a pull request then we can checkout the head.
        fetch-depth: 2

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        # queries: ./path/to/local/query, your-org/your-repo/queries@main

    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v3

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 https://git.io/JvXDl

    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
    #    and modify them (or add more) to build your code if your project
    #    uses a compiled language

    #- run: |
    #   make bootstrap
    #   make release

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v3


================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on: [push, pull_request]

jobs:

  test:
    name: Test
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, macOS-latest, windows-latest]
        go: ['1.21.x', '1.23.x']

    steps:

      - name: Set git to use LF
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf

      - name: Check out code into the Go module directory
        uses: actions/checkout@v4

      - name: Set up Go ${{ matrix.go }}
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go }}
        id: go

      - name: Vet
        run: go vet -v ./...

      - name: Test
        run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./...

      - name: Upload Coverage report
        uses: codecov/codecov-action@v4
        with:
          token: ${{secrets.CODECOV_TOKEN}}
          file: ./coverage.txt


================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

.vscode/
.idea/
*.swp
.DS_Store


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 caixw

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# cnregion

[![Test](https://github.com/issue9/cnregion/workflows/Test/badge.svg)](https://github.com/issue9/cnregion/actions?query=workflow%3ATest)
[![Go version](https://img.shields.io/github/go-mod/go-version/issue9/cnregion)](https://golang.org)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/issue9/cnregion)](https://pkg.go.dev/github.com/issue9/cnregion/v2)
[![codecov](https://codecov.io/gh/issue9/cnregion/branch/master/graph/badge.svg)](https://codecov.io/gh/issue9/cnregion)
![License](https://img.shields.io/github/license/issue9/cnregion)

历年统计用区域和城乡划分代码,数据来源于 <https://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/>。
符合国家标准 GB/T 2260 与 GB/T 10114。

关于版本号,主版本号代码不兼容性更改,次版本号代码最后一次生成的数据年份,BUG 修正和兼容性的功能增加则增加修订版本号。

```go
v, err := cnregion.LoadFile("./data/regions.db", "-", 2020)

p := v.Provinces() // 返回所有省列表
cities := p[0].Items() // 返回该省下的所有市
counties := cities[0].Items() // 返回该市下的所有县
towns := counties[0].Items() // 返回所有镇
villages := towns[0].Items() // 所有村和街道信息

d := v.Districts() // 按以前的行政大区进行划分
provinces := d[0].Items() // 该大区下的所有省份

list := v.Search(&SearchOptions{Text: "温州"}) // 按索地名中带温州的区域列表
```

对采集的数据进行了一定的加工,以减少文件的体积,文件保存在 `./data/regions.db` 中。

## 安装

```shell
go get github.com/issue9/cnregion/v2
```

## 版权

本项目采用 [MIT](https://opensource.org/licenses/MIT) 开源授权许可证,完整的授权说明可在 [LICENSE](LICENSE) 文件中找到。


================================================
FILE: cnregion.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

// Package cnregion 中国区域划分代码
//
// 中国行政区域五级划分代码,包含了省、市、县、乡和村五个级别。
// [数据规则]以及[数据来源]。
//
// [数据规则]: http://www.stats.gov.cn/tjsj/tjbz/200911/t20091125_8667.html
// [数据来源]: http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/
package cnregion

import (
	"bytes"
	"compress/gzip"
	"io"
	"io/fs"
	"os"
)

// LoadFS 从数据文件加载数据
func LoadFS(f fs.FS, file, separator string, compress bool, version ...int) (*DB, error) {
	data, err := fs.ReadFile(f, file)
	if err != nil {
		return nil, err
	}
	return Load(data, separator, compress, version...)
}

// Load 将数据内容加载至 DB 对象
//
// version 仅加载指定年份的数据,如果为空,则加载所有数据;
func Load(data []byte, separator string, compress bool, version ...int) (*DB, error) {
	if compress {
		rd, err := gzip.NewReader(bytes.NewReader(data))
		if err != nil {
			return nil, err
		}

		data, err = io.ReadAll(rd)
		if err != nil {
			return nil, err
		}
	}

	db := &DB{
		fullNameSeparator: separator,
		filters:           version,
	}
	if err := db.unmarshal(data); err != nil {
		return nil, err
	}

	db.initDistricts()

	return db, nil
}

// LoadFile 从数据文件加载数据
func LoadFile(file, separator string, compress bool, version ...int) (*DB, error) {
	data, err := os.ReadFile(file)
	if err != nil {
		return nil, err
	}
	return Load(data, separator, compress, version...)
}

// Dump 输出到文件
func (db *DB) Dump(file string, compress bool) error {
	data, err := db.marshal()
	if err != nil {
		return err
	}

	if compress {
		buf := new(bytes.Buffer)
		w := gzip.NewWriter(buf)
		if _, err = w.Write(data); err != nil {
			return err
		}
		if err = w.Close(); err != nil {
			return err
		}

		data = buf.Bytes()
	}

	return os.WriteFile(file, data, os.ModePerm)
}


================================================
FILE: cnregion_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/issue9/assert/v4"

	"github.com/issue9/cnregion/v2/id"
	"github.com/issue9/cnregion/v2/version"
)

func TestLoad(t *testing.T) {
	a := assert.New(t, false)

	o1, err := Load(data, "-", false)
	a.NotError(err).
		Equal(o1.fullNameSeparator, obj.fullNameSeparator).
		Equal(o1.versions, obj.versions).
		Equal(len(o1.root.items), len(obj.root.items)).
		Equal(o1.root.items[0].id, obj.root.items[0].id).
		Equal(o1.root.items[0].fullID, obj.root.items[0].fullID).
		Equal(o1.root.items[0].items[0].id, obj.root.items[0].items[0].id).
		Equal(o1.root.items[1].items[0].id, obj.root.items[1].items[0].id).
		Equal(o1.root.items[1].items[0].fullID, obj.root.items[1].items[0].fullID).
		Equal(o1.root.items[1].items[1].fullID, obj.root.items[1].items[1].fullID).
		NotEqual(o1.root.items[1].items[1].fullID, obj.root.items[1].items[0].fullID)

	d1, err := obj.marshal()
	a.NotError(err).NotNil(d1)
	a.Equal(string(d1), string(data))

	_, err = Load([]byte("100:[2020]:::1:0{}"), "-", false)
	a.Equal(err, ErrIncompatible)

	o1, err = Load(data, "-", false, 2019)
	a.NotError(err).
		Equal(0, len(o1.root.items))
}

func TestDB_LoadDump(t *testing.T) {
	a := assert.New(t, false)

	path := filepath.Join(os.TempDir(), "cnregion_db.dict")
	a.NotError(obj.Dump(path, false))
	d, err := LoadFile(path, "-", false)
	a.NotError(err).NotNil(d)

	path = filepath.Join(os.TempDir(), "cnregion_db_compress.dict")
	a.NotError(obj.Dump(path, true))
	d, err = LoadFile(path, "-", true)
	a.NotError(err).NotNil(d)
}

func TestLoadFS(t *testing.T) {
	a := assert.New(t, false)

	obj, err := LoadFS(os.DirFS("./data"), "regions.db", "-", true)
	a.NotError(err).NotNil(obj)
	a.Equal(obj.versions, version.All()).
		Equal(obj.fullNameSeparator, "-").
		True(len(obj.root.items) > 0).
		Equal(obj.root.items[0].level, id.Province).
		Equal(obj.root.items[0].items[0].level, id.City).
		Equal(obj.root.items[0].items[0].items[0].level, id.County).
		Equal(obj.root.items[1].level, id.Province).
		Equal(obj.root.items[2].items[0].level, id.City)
}


================================================
FILE: data/embed.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package data

import (
	"embed"

	"github.com/issue9/cnregion/v2"
)

//go:embed regions.db
var data embed.FS

// Embed 将 regions.db 的内容嵌入到程序中
//
// 这样可以让程序不依赖外部文件,但同时也会增加编译后程序的大小。
func Embed(separator string, version ...int) (*cnregion.DB, error) {
	return cnregion.LoadFS(data, "regions.db", separator, true, version...)
}


================================================
FILE: data/embed_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package data

import (
	"testing"

	"github.com/issue9/assert/v4"
)

func TestEmbed(t *testing.T) {
	a := assert.New(t, false)

	v, err := Embed(">", 2021)
	a.NotError(err).NotNil(v)
	r := v.Find("330305000000")
	a.NotNil(r).
		Equal(r.ID(), "05").
		Equal(r.FullID(), "330305000000").
		Equal(r.Name(), "洞头区").
		Equal(r.FullName(), "浙江省>温州市>洞头区")
}


================================================
FILE: db.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"bytes"
	"errors"
	"fmt"
	"slices"
	"strconv"
	"strings"

	"github.com/issue9/errwrap"

	"github.com/issue9/cnregion/v2/id"
)

// Version 数据文件的版本号
const Version = 1

// ErrIncompatible 数据文件版本不兼容
//
// 当数据文件中指定的版本号与当前的 Version 不相等时,返回此错误。
var ErrIncompatible = errors.New("数据文件版本不兼容")

// DB 区域数据库信息
//
// 数据格式:
//
//	1:[versions]:{id:name:yearIndex:size{}}
//
//	- 1 表示数据格式的版本,采用当前包的 Version 常量;
//	- versions 表示当前数据文件中的数据支持的年份列表,以逗号分隔;
//	- id 当前区域的 ID;
//	- name 当前区域的名称;
//	- yearIndex 此条数据支持的年份列表,每一个位表示一个年份在 versions 中的索引值;
//	- size 表示子元素的数量;
type DB struct {
	root     *Region
	versions []int // 支持的版本

	// 以下数据不会写入数据文件中

	fullNameSeparator string
	districts         []*Region

	// Load 指定的过滤版本,仅在 unmarshal 过程中使用,
	// 在完成 unmarshal 之的清空。
	filters []int
}

// NewDB 返回空的 [DB] 对象
func NewDB() *DB {
	db := &DB{versions: []int{}}
	db.root = &Region{db: db}
	return db
}

// Version 当前这份数据支持的年份列表
func (db *DB) Versions() []int { return db.versions }

// AddVersion 添加新的版本号
func (db *DB) AddVersion(ver int) (ok bool) {
	if slices.Index(db.versions, ver) > -1 { // 检测 ver 是否已经存在
		return false
	}

	db.versions = append(db.versions, ver)
	return true
}

// Find 查找指定 ID 对应的信息
func (db *DB) Find(regionID string) *Region { return db.root.findItem(id.SplitFilter(regionID)...) }

var levelIndex = []id.Level{id.Province, id.City, id.County, id.Town, id.Village}

// AddItem 添加一条子项
func (db *DB) AddItem(regionID, name string, ver int) error {
	list := id.SplitFilter(regionID)
	item := db.root.findItem(list...)

	if item == nil {
		items := list[:len(list)-1] // 上一级
		item = db.root.findItem(items...)
		level := levelIndex[len(items)]
		return item.addItem(list[len(list)-1], name, level, ver)
	}

	return item.setSupported(ver)
}

func (db *DB) marshal() ([]byte, error) {
	versions := make([]string, 0, len(db.versions))
	for _, v := range db.versions {
		versions = append(versions, strconv.Itoa(v))
	}

	buf := errwrap.Buffer{Buffer: bytes.Buffer{}}
	buf.WString(strconv.Itoa(Version)).WByte(':')

	buf.WByte('[')
	buf.WString(strings.Join(versions, ","))
	buf.WByte(']').WByte(':')

	err := db.root.marshal(&buf)
	if err != nil {
		return nil, err
	}

	if buf.Err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func (db *DB) unmarshal(data []byte) error {
	data, val := indexBytes(data, ':')
	ver, err := strconv.Atoi(val)
	if err != nil {
		return err
	}
	if ver != Version {
		return ErrIncompatible
	}

	data, val = indexBytes(data, ':')
	versions := strings.Split(strings.Trim(val, "[]"), ",")
	db.versions = make([]int, 0, len(versions))
	for _, version := range versions {
		v, err := strconv.Atoi(version)
		if err != nil {
			return err
		}
		db.versions = append(db.versions, v)
	}

	if len(db.filters) == 0 {
		db.filters = db.versions
	} else {
	LOOP:
		for _, v := range db.filters {
			for _, v2 := range db.versions {
				if v2 == v {
					continue LOOP
				}
			}
			return fmt.Errorf("当前数据文件没有 %d 年份的数据", v)
		}
	}

	defer func() {
		db.versions = db.filters
		db.filters = db.filters[:0]
	}()

	db.root = &Region{db: db}
	return db.root.unmarshal(data, "", "", 0)
}

func (db *DB) filterVersions(versions []int) []int {
	vers := make([]int, 0, len(versions))
LOOP:
	for _, v := range versions {
		for _, v2 := range db.filters {
			if v2 == v {
				vers = append(vers, v)
				continue LOOP
			}
		}
	}

	return vers
}


================================================
FILE: db_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"testing"

	"github.com/issue9/assert/v4"

	"github.com/issue9/cnregion/v2/id"
	"github.com/issue9/cnregion/v2/version"
)

var data = []byte(`1:[2020,2019]:::1:2{33:浙江:1:1{01:温州:3:0{}}34:安徽:1:3{01:合肥:3:0{}02:芜湖:1:0{}03:芜湖-2:1:0{}}}`)

var obj = &DB{
	versions:          []int{2020, 2019},
	fullNameSeparator: "-",
	root: &Region{
		name:     "",
		versions: []int{2020},
		items: []*Region{
			{
				id:       "33",
				name:     "浙江",
				versions: []int{2020},
				fullName: "浙江",
				fullID:   "330000000000",
				level:    id.Province,
				items: []*Region{
					{
						id:       "01",
						name:     "温州",
						versions: []int{2020, 2019},
						fullName: "浙江-温州",
						fullID:   "330100000000",
						level:    id.City,
					},
				},
			},
			{
				id:       "34",
				name:     "安徽",
				fullName: "安徽",
				fullID:   "340000000000",
				versions: []int{2020},
				level:    id.Province,
				items: []*Region{
					{
						id:       "01",
						name:     "合肥",
						versions: []int{2020, 2019},
						fullName: "安徽-合肥",
						fullID:   "340100000000",
						level:    id.City,
					},
					{
						id:       "02",
						name:     "芜湖",
						versions: []int{2020},
						fullName: "安徽-芜湖",
						fullID:   "340200000000",
						level:    id.City,
					},
					{
						id:       "03",
						name:     "芜湖-2",
						versions: []int{2020},
						fullName: "安徽-芜湖-2",
						fullID:   "340300000000",
						level:    id.City,
					},
				},
			},
		},
	},
}

func init() {
	setRegionDB(obj.root, obj)
}

func setRegionDB(r *Region, db *DB) {
	r.db = db
	for _, i := range r.items {
		setRegionDB(i, db)
	}
}

func TestDB_Find(t *testing.T) {
	a := assert.New(t, false)

	// 2020
	db, err := LoadFile("./data/regions.db", ">", true, 2020)
	a.NotError(err).NotNil(db)
	r := db.Find("330305000000")
	a.NotNil(r).
		Equal(r.ID(), "05").
		Equal(r.FullID(), "330305000000").
		Equal(r.Name(), "洞头区").
		Equal(r.FullName(), "浙江省>温州市>洞头区").
		Equal(r.Versions(), []int{2020})
	r = db.Find("330322000000") // 洞头县,已改为洞头区
	a.Nil(r)

	// 2009
	db, err = LoadFile("./data/regions.db", ">", true, 2009)
	a.NotError(err).NotNil(db)
	r = db.Find("330322000000")
	a.NotNil(r).
		Equal(r.ID(), "22").
		Equal(r.FullID(), "330322000000").
		Equal(r.Name(), "洞头县").
		Equal(r.FullName(), "浙江省>温州市>洞头县").
		Equal(r.Versions(), []int{2009})
	r = db.Find("330305000000")
	a.Nil(r)

	// 所有年份的数据
	db, err = LoadFile("./data/regions.db", ">", true, version.Range(2009, 2020)...)
	a.NotError(err).NotNil(db)
	r = db.Find("330322000000")
	a.NotNil(r).
		Equal(r.ID(), "22").
		Equal(r.Versions(), []int{2014, 2013, 2012, 2011, 2010, 2009})
	r = db.Find("330305000000")
	a.NotNil(r).
		Equal(r.ID(), "05").
		Contains(r.Versions(), []int{2018, 2017, 2016, 2015})
}


================================================
FILE: districts.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import "github.com/issue9/cnregion/v2/id"

// Districts 按行政大区划分
//
// NOTE: 大区划分并不统一,按照各个省份的第一个数字进行划分。
func (db *DB) Districts() []*Region { return db.districts }

func (db *DB) initDistricts() {
	db.districts = make([]*Region, 0, len(districtsMap))

	for index, name := range districtsMap {
		items := make([]*Region, 0, 10)
		for _, p := range db.Provinces() {
			if p.ID()[0] == index {
				items = append(items, p)
			}
		}

		db.districts = append(db.districts, &Region{
			id:       string(index),
			fullID:   id.Fill(string(index), id.Village),
			name:     name,
			fullName: name,
			items:    items,
		})
	}

}

var districtsMap = map[byte]string{
	'1': "华北地区",
	'2': "东北地区",
	'3': "华东地区",
	'4': "中南地区",
	'5': "西南地区",
	'6': "西北地区",
}


================================================
FILE: districts_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"testing"

	"github.com/issue9/assert/v4"
)

func TestDB_Districts(t *testing.T) {
	a := assert.New(t, false)

	db, err := LoadFile("./data/regions.db", ">", true, 2020)
	a.NotError(err).NotNil(db)
	a.Length(db.Districts(), len(districtsMap))

	for _, d := range db.Districts() {
		if d.ID() == "1" {
			a.Equal(d.Name(), "华北地区").Equal(5, len(d.Items()))
		}
	}
}


================================================
FILE: go.mod
================================================
module github.com/issue9/cnregion/v2

go 1.21

require (
	github.com/issue9/assert/v4 v4.3.1
	github.com/issue9/errwrap v0.3.2
)


================================================
FILE: go.sum
================================================
github.com/issue9/assert/v4 v4.1.1/go.mod h1:v7qDRXi7AsaZZNh8eAK2rkLJg5/clztqQGA1DRv9Lv4=
github.com/issue9/assert/v4 v4.3.1 h1:dHYODk1yV7j/1baIB6K6UggI4r1Hfuljqic7PaDbwLg=
github.com/issue9/assert/v4 v4.3.1/go.mod h1:v7qDRXi7AsaZZNh8eAK2rkLJg5/clztqQGA1DRv9Lv4=
github.com/issue9/errwrap v0.3.2 h1:7KEme9Pfe75M+sIMcPCn/DV90wjnOcRbO4DXVAHj3Fw=
github.com/issue9/errwrap v0.3.2/go.mod h1:KcCLuUGiffjooLCUjL89r1cyO8/HT/VRcQrneO53N3A=


================================================
FILE: id/id.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

// Package id 针对 ID 的一些操作函数
package id

import (
	"fmt"
	"strings"
)

// Level 表示区域的级别
type Level uint8

// 对区域级别的定义
const (
	Village Level = 1 << iota
	Town
	County
	City
	Province

	AllLevel = Village + Town + County + City + Province
)

var lengths = map[Level]int{
	Village:  12,
	Town:     9,
	County:   6,
	City:     4,
	Province: 2,
}

// Length 获取各个类型 ID 的有效果长度
func Length(level Level) int {
	if _, found := lengths[level]; !found {
		panic("无效的 level 参数")
	}

	return lengths[level]
}

// Split 将一个区域 ID 按区域进行划分
func Split(id string) (province, city, county, town, village string) {
	if len(id) != Length(Village) {
		panic(fmt.Sprintf("id 的长度只能为 %d,当前为 %s", Length(Village), id))
	}

	return id[:Length(Province)],
		id[Length(Province):Length(City)],
		id[Length(City):Length(County)],
		id[Length(County):Length(Town)],
		id[Length(Town):Length(Village)]
}

// SplitFilter 将 id 按区域进行划分且过滤掉零值的区域
//
//	330312123000 => 33 03 12 123
//
// 如果传递的是零值,则返回空数组。
func SplitFilter(id string) []string {
	province, city, county, town, village := Split(id)
	return filterZero(province, city, county, town, village)
}

func filterZero(id ...string) []string {
	for index, i := range id { // 过滤掉数组中的零值
		if isZero(i) {
			id = id[:index]
			break
		}
	}
	return id
}

// Parent 获取 id 的上一级行政区域的 ID
//
//	330312123456 => 330312123
func Parent(id string) string {
	list := SplitFilter(id)
	return strings.Join(list[:len(list)-1], "")
}

// Prefix 获取 ID 的非零前缀
//
//	330312123456 => 330312123456
//	330312123000 => 330312123
func Prefix(id string) string {
	return strings.Join(SplitFilter(id), "")
}

// Fill 为 id 填充后缀的 0
//
// id 为原始值;
// level 为需要达到的行政级别,最终的长度为 Length(level)。
func Fill(id string, level Level) string {
	rem := Length(level) - len(id)
	switch {
	case rem == 0:
		return id
	case rem > Length(level) || rem < 2:
		panic(fmt.Sprintf("无效的 id %s,无法为其填充 0", id))
	default:
		return id + strings.Repeat("0", rem)
	}
}

// isZero 判断一组字符串是否都由 0 组成
func isZero(id string) bool {
	for _, r := range id {
		if r != '0' {
			return false
		}
	}
	return true
}


================================================
FILE: id/id_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package id

import (
	"testing"

	"github.com/issue9/assert/v4"
)

func TestSplit(t *testing.T) {
	a := assert.New(t, false)

	province, city, county, town, village := Split("330203103233")
	a.Equal(province, "33").
		Equal(city, "02").
		Equal(county, "03").
		Equal(town, "103").
		Equal(village, "233")

	a.Panic(func() {
		Split("3303")
	})
}

func TestSplitFilter(t *testing.T) {
	a := assert.New(t, false)

	list := SplitFilter("330203103000")
	a.Equal(4, len(list)).
		Equal(list[0], "33").
		Equal(list[1], "02").
		Equal(list[2], "03").
		Equal(list[3], "103")

	// 碰到第一个零值,即结果后续的判断
	list = SplitFilter("330003103000")
	a.Equal(1, len(list)).Equal(list[0], "33")

	list = SplitFilter("000000000000")
	a.Empty(list)
}

func TestParent(t *testing.T) {
	a := assert.New(t, false)

	a.Equal(Parent("330300000000"), "33")
	a.Equal(Parent("330302111000"), "330302")
}

func TestPrefix(t *testing.T) {
	a := assert.New(t, false)

	a.Equal(Prefix("330301001001"), "330301001001")
	a.Equal(Prefix("330300000000"), "3303")
	a.Equal(Prefix("330302000000"), "330302")
}

func TestFill(t *testing.T) {
	a := assert.New(t, false)

	a.Equal(Fill("34", Village), "340000000000")
	a.Equal(Fill("3", Village), "300000000000")
	a.Equal(Fill("34", Province), "34")
	a.Equal(Fill("34", City), "3400")
	a.Equal(Fill("341234666777", Village), "341234666777")
	a.Panic(func() {
		Fill("34112233444332", Village)
	})
}

func TestIsZero(t *testing.T) {
	a := assert.New(t, false)

	a.True(isZero("000"))
	a.False(isZero("00x"))
	a.True(isZero(""))
}


================================================
FILE: region.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"bytes"
	"errors"
	"fmt"
	"slices"
	"strconv"

	"github.com/issue9/errwrap"

	"github.com/issue9/cnregion/v2/id"
)

// Region 表示单个区域
type Region struct {
	id       string
	name     string
	items    []*Region
	versions []int // 支持的版本号列表

	// 以下数据不会写入数据文件中

	fullName string // 全名
	fullID   string
	db       *DB
	level    id.Level
}

// Provinces 省份列表
func (db *DB) Provinces() []*Region { return db.root.items }

func (r *Region) ID() string       { return r.id }       // 区域的 ID,不包括后缀 0 和上一级的 ID
func (r *Region) Name() string     { return r.name }     // 区域的名称
func (r *Region) FullName() string { return r.fullName } // 区域的全称,包括上一级的名称
func (r *Region) FullID() string   { return r.fullID }   // 区域的 ID,包括后缀的 0 以及上一级的 ID,长度为 12
func (r *Region) Versions() []int  { return r.versions } // 支持的年份版本
func (r *Region) Items() []*Region { return r.items }    // 子项

// IsSupported 当前数据是否支持该年份
func (r *Region) IsSupported(ver int) bool { return slices.Index(r.versions, ver) > -1 }

func (reg *Region) addItem(id, name string, level id.Level, ver int) error {
	if slices.Index(reg.db.versions, ver) == -1 {
		return fmt.Errorf("不支持该年份 %d 的数据", ver)
	}

	for _, item := range reg.items {
		if item.id == id {
			return fmt.Errorf("已经存在相同 ID 的数据项:%s", id)
		}
	}

	reg.items = append(reg.items, &Region{
		id:       id,
		name:     name,
		db:       reg.db,
		level:    level,
		versions: []int{ver},
	})
	return nil
}

func (reg *Region) setSupported(ver int) error {
	if slices.Index(reg.db.versions, ver) == -1 {
		return fmt.Errorf("不存在该年份 %d 的数据", ver)
	}

	if !reg.IsSupported(ver) {
		reg.versions = append(reg.versions, ver)
	}
	return nil
}

func (reg *Region) findItem(regionID ...string) *Region {
	if len(regionID) == 0 {
		return reg
	}

	for _, item := range reg.items {
		if item.id == regionID[0] {
			return item.findItem(regionID[1:]...)
		}
	}

	return nil
}

func (reg *Region) marshal(buf *errwrap.Buffer) error {
	supported := 0
	for _, ver := range reg.versions {
		index := slices.Index(reg.db.versions, ver)
		if index == -1 {
			return fmt.Errorf("无效的年份 %d 位于 %s", ver, reg.fullName)
		}
		supported += 1 << index
	}
	buf.Printf("%s:%s:%d:%d{", reg.id, reg.name, supported, len(reg.items))
	for _, item := range reg.items {
		err := item.marshal(buf)
		if err != nil {
			return err
		}
	}
	buf.WByte('}')

	return nil
}

func (reg *Region) unmarshal(data []byte, parentName, parentID string, level id.Level) error {
	reg.level = level

	data, reg.id = indexBytes(data, ':')

	data, reg.name = indexBytes(data, ':')
	reg.fullName = reg.name
	if parentName != "" {
		reg.fullName = parentName + reg.db.fullNameSeparator + reg.name
	}
	parentID += reg.id
	reg.fullID = id.Fill(parentID, id.Village)

	// Versions
	data, val := indexBytes(data, ':')
	supported, err := strconv.Atoi(val)
	if err != nil {
		return err
	}
	versions := make([]int, 0, len(reg.db.versions))
	for i, v := range reg.db.versions {
		if flag := 1 << i; flag&supported == flag {
			versions = append(versions, v)
		}
	}
	reg.versions = reg.db.filterVersions(versions)

	data, val = indexBytes(data, '{')
	size, err := strconv.Atoi(val)
	if err != nil {
		return err
	}

	if size > 0 {
		for i := 0; i < size; i++ {
			index := findEnd(data)
			if index < 0 {
				return errors.New("未找到结束符号 }")
			}

			// 下一级的 Level
			var next id.Level
			if level == 0 {
				next = id.Province
			} else {
				next = level >> 1
			}

			item := &Region{db: reg.db}
			if err := item.unmarshal(data[:index], reg.fullName, parentID, next); err != nil {
				return err
			}
			if len(item.versions) > 0 { // 表示该条数据不支持所有的年份
				reg.items = append(reg.items, item)
			}
			data = data[index+1:]
		}
	}

	return nil
}

func indexBytes(data []byte, b byte) ([]byte, string) {
	index := bytes.IndexByte(data, b)
	if index == -1 {
		panic(fmt.Sprintf("在%s未找到:%s", string(data), string(b)))
	}

	return data[index+1:], string(data[:index])
}

func findEnd(data []byte) int {
	deep := 0
	for i, b := range data {
		switch b {
		case '{':
			deep++
		case '}':
			deep--
			if deep == 0 {
				return i
			}
		}
	}

	return 0
}


================================================
FILE: region_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"testing"

	"github.com/issue9/assert/v4"

	"github.com/issue9/cnregion/v2/id"
)

func TestRegion_IsSupported(t *testing.T) {
	a := assert.New(t, false)

	obj := &DB{versions: []int{2020, 2019, 2018}}
	obj.root = &Region{items: []*Region{
		{versions: []int{2020, 2019}, name: "test", db: obj},
	}, db: obj}

	a.True(obj.root.items[0].IsSupported(2020))
	a.True(obj.root.items[0].IsSupported(2019))
	a.False(obj.root.items[0].IsSupported(2018)) // 不支持
	a.False(obj.root.items[0].IsSupported(2009)) // 不存在于 db
}

func TestRegion_addItem(t *testing.T) {
	a := assert.New(t, false)

	obj := &DB{versions: []int{2020, 2019, 2018}}
	obj.root = &Region{items: []*Region{}, db: obj}

	a.ErrorString(obj.root.addItem("33", "浙江", id.Province, 2001), "不支持该年份")

	a.NotError(obj.root.addItem("44", "广东", id.Province, 2020))
	a.Equal(obj.root.items[0].id, "44").
		NotNil(obj.root.items[0].db).
		True(obj.root.items[0].IsSupported(2020)).
		False(obj.root.items[0].IsSupported(2019))

	a.ErrorString(obj.root.addItem("44", "广东", id.Province, 2020), "存在相同")
}

func TestRegion_SetSupported(t *testing.T) {
	a := assert.New(t, false)

	obj := &DB{versions: []int{2020, 2019, 2018}}
	obj.root = &Region{items: []*Region{{db: obj}}, db: obj}

	a.NotError(obj.root.addItem("33", "浙江", id.Province, 2020))
	a.NotError(obj.root.items[0].setSupported(2020)).
		Equal(obj.root.items[0].versions, []int{2020})
	a.NotError(obj.root.items[0].setSupported(2019)).
		Equal(obj.root.items[0].versions, []int{2020, 2019})
	a.ErrorString(obj.root.items[0].setSupported(2001), "不存在该年份")
}

func TestFindEnd(t *testing.T) {
	a := assert.New(t, false)

	data := []byte("0123{56}")
	a.Equal(findEnd(data), 7)
}

func TestDB_Provinces(t *testing.T) {
	a := assert.New(t, false)

	v, err := LoadFile("./data/regions.db", ">", true, 2020)
	a.NotError(err).NotNil(v)

	for _, p := range v.Provinces() {
		if p.ID() == "33" {
			a.Equal(p.Name(), "浙江省")
		}
	}
}

func TestRegion_Items(t *testing.T) {
	a := assert.New(t, false)

	// 2020
	var x05, x22 bool
	v, err := LoadFile("./data/regions.db", ">", true, 2020)
	a.NotError(err).NotNil(v)
	r := v.Find("330300000000")
	for _, item := range r.Items() {
		if item.ID() == "05" {
			x05 = true
		}
		if item.ID() == "22" {
			x22 = true
		}
	}
	a.True(x05).False(x22)

	// 2009
	x05 = false
	x22 = false
	v, err = LoadFile("./data/regions.db", ">", true, 2009)
	a.NotError(err).NotNil(v)
	r = v.Find("330300000000")
	for _, item := range r.Items() {
		if item.ID() == "05" {
			x05 = true
		}
		if item.ID() == "22" {
			x22 = true
		}
	}
	a.False(x05).True(x22)

	//2020 + 2009
	x05 = false
	x22 = false
	v, err = LoadFile("./data/regions.db", ">", true, 2009, 2020)
	a.NotError(err).NotNil(v)
	r = v.Find("330300000000")
	for _, item := range r.Items() {
		if item.ID() == "05" {
			x05 = true
		}
		if item.ID() == "22" {
			x22 = true
		}
	}
	a.True(x05).True(x22)
}

func TestRegion_findItem(t *testing.T) {
	a := assert.New(t, false)

	r := obj.root.findItem("34", "01")
	a.NotNil(r).Equal(r.name, "合肥").Equal(r.fullName, "安徽-合肥")

	r = obj.root.findItem("34", "01", "00")
	a.Nil(r)

	r = obj.root.findItem("34")
	a.NotNil(r).Equal(r.name, "安徽").Equal(r.fullName, "安徽")

	r = obj.root.findItem()
	a.NotNil(r).Equal(r.name, "").Equal(r.fullName, "").Equal(2, len(r.items))

	// 不存在于 obj
	a.Nil(obj.root.findItem("99"))
	a.Nil(obj.root.findItem(""))
}


================================================
FILE: search.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"strings"

	"github.com/issue9/cnregion/v2/id"
)

// Options 搜索选项
type Options struct {
	// 表示你需要搜索的地名需要包含的内容
	//
	// 不能是多个名称的组合,比如"浙江温州",直接写"温州"就可以。
	// 也不要提供类似于"居委会"这种无实际意义的地名;
	Text string

	// 上一级的区域 ID
	//
	// 为空表示不限制。
	Parent string

	// 搜索的城市类型
	//
	// 该值取值于 github.com/issue9/cnregion/v2/id.Level 类型。 多个值可以通过或运算叠加。
	// 0 表示所有类型。
	Level id.Level

	// 最大的搜索数量。0 表示不限制数量。
	Max       int
	unlimited bool
}

func (o *Options) isEmpty() bool {
	return o.Text == "" &&
		(o.Parent == "" || o.Parent == "000000000000") &&
		o.Level == 0 &&
		o.Max == 0
}

// Search 简单的搜索功能
func (db *DB) Search(opt *Options) []*Region {
	if opt == nil || opt.isEmpty() {
		panic("参数 opt 不能为空值")
	}

	r := db.root
	if opt.Parent != "" {
		r = db.Find(opt.Parent)
	}
	if r == nil { // 不存在 opt.Parent 指定的数据
		return nil
	}

	if opt.Level == 0 {
		opt.Level = id.AllLevel
	}

	opt.unlimited = opt.Max == 0
	size := 100
	if !opt.unlimited {
		size = opt.Max
	}
	list := make([]*Region, 0, size)

	return r.search(opt, list)
}

func (reg *Region) search(opt *Options, list []*Region) []*Region {
	if strings.Contains(reg.name, opt.Text) &&
		(reg.level&opt.Level == reg.level) && reg.level != 0 { // level == 0 只有根元素才有
		list = append(list, reg)
		opt.Max--
	}

	if !opt.unlimited && opt.Max <= 0 {
		return list
	}

	for _, item := range reg.items {
		list = item.search(opt, list)
	}

	return list
}


================================================
FILE: search_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package cnregion

import (
	"os"
	"strings"
	"testing"

	"github.com/issue9/assert/v4"

	"github.com/issue9/cnregion/v2/id"
)

func TestDB_Search(t *testing.T) {
	a := assert.New(t, false)

	rs := obj.Search(&Options{Text: "合肥"})
	a.Equal(1, len(rs)).
		Equal(rs[0].name, "合肥")

	rs = obj.Search(&Options{Parent: "340000000000", Text: "合肥"})
	a.Equal(1, len(rs)).
		Equal(rs[0].name, "合肥")

	rs = obj.Search(&Options{Parent: "000000000000", Text: "合肥"})
	a.Equal(1, len(rs)).
		Equal(rs[0].name, "合肥")

	// 限定 level 只能是省以及 parent 为 34 开头
	rs = obj.Search(&Options{Parent: "340000000000", Level: id.Province, Text: "合肥"})
	a.Equal(0, len(rs))

	// 未限定 parent 且 level 正确
	rs = obj.Search(&Options{Level: id.City, Text: "合肥"})
	a.Equal(1, len(rs))

	rs = obj.Search(&Options{Level: id.City, Text: "湖"})
	a.Equal(2, len(rs))

	rs = obj.Search(&Options{Level: id.City, Parent: "340000000000", Text: "湖"})
	a.Equal(2, len(rs))

	// parent = 浙江
	rs = obj.Search(&Options{Parent: "330000000000", Text: "合肥"})
	a.Equal(0, len(rs))

	// parent 不存在
	rs = obj.Search(&Options{Parent: "110000000000", Text: "合肥"})
	a.Equal(0, len(rs))

	// 只有 Level
	rs = obj.Search(&Options{Level: id.City})
	a.Equal(4, len(rs))
	for _, r := range rs {
		a.True(strings.HasSuffix(r.fullID, "00000000"))
	}

	// 只有 Level
	rs = obj.Search(&Options{Level: id.City + id.Town})
	a.Equal(4, len(rs))

	// 只有 Level
	rs = obj.Search(&Options{Level: id.City + id.Province})
	a.Equal(6, len(rs))
}

func TestDB_SearchWithData(t *testing.T) {
	a := assert.New(t, false)

	obj, err := LoadFS(os.DirFS("./data"), "regions.db", "-", true)
	a.NotError(err).NotNil(obj)
	got := obj.Search(&Options{Text: "温州"})
	a.NotEmpty(got)

	// Level 不匹配
	got = obj.Search(&Options{Text: "温州", Level: id.Province})
	a.Empty(got)
}


================================================
FILE: version/version.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

// Package version 提供版本的相关信息
//
// 依据 https://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/ 提供的数据,
// 以年作为单位进行更新,同时也以四位的年份作为版本号。
package version

import "fmt"

// ErrInvalidYear 无效的年份版本
//
// 年份只能介于 [2009, 当前年份-1) 的区间之间。
var ErrInvalidYear = fmt.Errorf("无效的版本号,必须是介于 [%d,%d] 之间的整数", start, latest)

// 起始版本号,即提供的数据的起始年份。
const start = 2009

// 最新的有效年份,每次更新数据之后,需要手动更新此值。
var latest = 2023

// All 返回支持的版本号列表
func All() []int { return Range(start, latest) }

// IsValid 验证年份是否为一个有效的版本号
func IsValid(year int) bool { return year >= start && year <= latest }

// BeginWith 从 begin 开始直到最新年份
func BeginWith(begin int) []int { return Range(begin, latest) }

// Range 获取指定范围内的版本号
func Range(begin, end int) []int {
	if !IsValid(begin) {
		panic(ErrInvalidYear)
	}

	if !IsValid(end) {
		panic(ErrInvalidYear)
	}

	if begin > end {
		panic(ErrInvalidYear)
	}

	years := make([]int, 0, end-begin+1)
	for year := end; year >= begin; year-- {
		years = append(years, year)
	}
	return years
}


================================================
FILE: version/version_test.go
================================================
// SPDX-FileCopyrightText: 2021-2024 caixw
//
// SPDX-License-Identifier: MIT

package version

import (
	"testing"

	"github.com/issue9/assert/v4"
)

func TestAll(t *testing.T) {
	a := assert.New(t, false)

	all := All()
	// 保证从大到小
	a.Equal(all[0], latest).
		Equal(all[len(all)-1], start)
}

func TestBeginWith(t *testing.T) {
	a := assert.New(t, false)

	list := BeginWith(latest)
	a.Equal(1, len(list)).Equal(list[0], latest)

	a.Panic(func() {
		BeginWith(start - 1)
	})
}
Download .txt
gitextract_8k6qygrr/

├── .github/
│   └── workflows/
│       ├── codeql-analysis.yml
│       └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── cnregion.go
├── cnregion_test.go
├── data/
│   ├── embed.go
│   └── embed_test.go
├── db.go
├── db_test.go
├── districts.go
├── districts_test.go
├── go.mod
├── go.sum
├── id/
│   ├── id.go
│   └── id_test.go
├── region.go
├── region_test.go
├── search.go
├── search_test.go
└── version/
    ├── version.go
    └── version_test.go
Download .txt
SYMBOL INDEX (82 symbols across 16 files)

FILE: cnregion.go
  function LoadFS (line 23) | func LoadFS(f fs.FS, file, separator string, compress bool, version ...i...
  function Load (line 34) | func Load(data []byte, separator string, compress bool, version ...int) ...
  function LoadFile (line 61) | func LoadFile(file, separator string, compress bool, version ...int) (*D...
  method Dump (line 70) | func (db *DB) Dump(file string, compress bool) error {

FILE: cnregion_test.go
  function TestLoad (line 18) | func TestLoad(t *testing.T) {
  function TestDB_LoadDump (line 46) | func TestDB_LoadDump(t *testing.T) {
  function TestLoadFS (line 60) | func TestLoadFS(t *testing.T) {

FILE: data/embed.go
  function Embed (line 19) | func Embed(separator string, version ...int) (*cnregion.DB, error) {

FILE: data/embed_test.go
  function TestEmbed (line 13) | func TestEmbed(t *testing.T) {

FILE: db.go
  constant Version (line 21) | Version = 1
  type DB (line 40) | type DB struct
    method Versions (line 62) | func (db *DB) Versions() []int { return db.versions }
    method AddVersion (line 65) | func (db *DB) AddVersion(ver int) (ok bool) {
    method Find (line 75) | func (db *DB) Find(regionID string) *Region { return db.root.findItem(...
    method AddItem (line 80) | func (db *DB) AddItem(regionID, name string, ver int) error {
    method marshal (line 94) | func (db *DB) marshal() ([]byte, error) {
    method unmarshal (line 118) | func (db *DB) unmarshal(data []byte) error {
    method filterVersions (line 162) | func (db *DB) filterVersions(versions []int) []int {
  function NewDB (line 55) | func NewDB() *DB {

FILE: db_test.go
  function init (line 81) | func init() {
  function setRegionDB (line 85) | func setRegionDB(r *Region, db *DB) {
  function TestDB_Find (line 92) | func TestDB_Find(t *testing.T) {

FILE: districts.go
  method Districts (line 12) | func (db *DB) Districts() []*Region { return db.districts }
  method initDistricts (line 14) | func (db *DB) initDistricts() {

FILE: districts_test.go
  function TestDB_Districts (line 13) | func TestDB_Districts(t *testing.T) {

FILE: id/id.go
  type Level (line 14) | type Level
  constant Village (line 18) | Village Level = 1 << iota
  constant Town (line 19) | Town
  constant County (line 20) | County
  constant City (line 21) | City
  constant Province (line 22) | Province
  constant AllLevel (line 24) | AllLevel = Village + Town + County + City + Province
  function Length (line 36) | func Length(level Level) int {
  function Split (line 45) | func Split(id string) (province, city, county, town, village string) {
  function SplitFilter (line 62) | func SplitFilter(id string) []string {
  function filterZero (line 67) | func filterZero(id ...string) []string {
  function Parent (line 80) | func Parent(id string) string {
  function Prefix (line 89) | func Prefix(id string) string {
  function Fill (line 97) | func Fill(id string, level Level) string {
  function isZero (line 110) | func isZero(id string) bool {

FILE: id/id_test.go
  function TestSplit (line 13) | func TestSplit(t *testing.T) {
  function TestSplitFilter (line 28) | func TestSplitFilter(t *testing.T) {
  function TestParent (line 46) | func TestParent(t *testing.T) {
  function TestPrefix (line 53) | func TestPrefix(t *testing.T) {
  function TestFill (line 61) | func TestFill(t *testing.T) {
  function TestIsZero (line 74) | func TestIsZero(t *testing.T) {

FILE: region.go
  type Region (line 20) | type Region struct
    method ID (line 37) | func (r *Region) ID() string       { return r.id }
    method Name (line 38) | func (r *Region) Name() string     { return r.name }
    method FullName (line 39) | func (r *Region) FullName() string { return r.fullName }
    method FullID (line 40) | func (r *Region) FullID() string   { return r.fullID }
    method Versions (line 41) | func (r *Region) Versions() []int  { return r.versions }
    method Items (line 42) | func (r *Region) Items() []*Region { return r.items }
    method IsSupported (line 45) | func (r *Region) IsSupported(ver int) bool { return slices.Index(r.ver...
    method addItem (line 47) | func (reg *Region) addItem(id, name string, level id.Level, ver int) e...
    method setSupported (line 68) | func (reg *Region) setSupported(ver int) error {
    method findItem (line 79) | func (reg *Region) findItem(regionID ...string) *Region {
    method marshal (line 93) | func (reg *Region) marshal(buf *errwrap.Buffer) error {
    method unmarshal (line 114) | func (reg *Region) unmarshal(data []byte, parentName, parentID string,...
  method Provinces (line 35) | func (db *DB) Provinces() []*Region { return db.root.items }
  function indexBytes (line 176) | func indexBytes(data []byte, b byte) ([]byte, string) {
  function findEnd (line 185) | func findEnd(data []byte) int {

FILE: region_test.go
  function TestRegion_IsSupported (line 15) | func TestRegion_IsSupported(t *testing.T) {
  function TestRegion_addItem (line 29) | func TestRegion_addItem(t *testing.T) {
  function TestRegion_SetSupported (line 46) | func TestRegion_SetSupported(t *testing.T) {
  function TestFindEnd (line 60) | func TestFindEnd(t *testing.T) {
  function TestDB_Provinces (line 67) | func TestDB_Provinces(t *testing.T) {
  function TestRegion_Items (line 80) | func TestRegion_Items(t *testing.T) {
  function TestRegion_findItem (line 131) | func TestRegion_findItem(t *testing.T) {

FILE: search.go
  type Options (line 14) | type Options struct
    method isEmpty (line 37) | func (o *Options) isEmpty() bool {
  method Search (line 45) | func (db *DB) Search(opt *Options) []*Region {
  method search (line 72) | func (reg *Region) search(opt *Options, list []*Region) []*Region {

FILE: search_test.go
  function TestDB_Search (line 17) | func TestDB_Search(t *testing.T) {
  function TestDB_SearchWithData (line 70) | func TestDB_SearchWithData(t *testing.T) {

FILE: version/version.go
  constant start (line 19) | start = 2009
  function All (line 25) | func All() []int { return Range(start, latest) }
  function IsValid (line 28) | func IsValid(year int) bool { return year >= start && year <= latest }
  function BeginWith (line 31) | func BeginWith(begin int) []int { return Range(begin, latest) }
  function Range (line 34) | func Range(begin, end int) []int {

FILE: version/version_test.go
  function TestAll (line 13) | func TestAll(t *testing.T) {
  function TestBeginWith (line 22) | func TestBeginWith(t *testing.T) {
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (43K chars).
[
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 2350,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 918,
    "preview": "name: Test\non: [push, pull_request]\n\njobs:\n\n  test:\n    name: Test\n    runs-on: ${{ matrix.os }}\n\n    strategy:\n      ma"
  },
  {
    "path": ".gitignore",
    "chars": 302,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Ou"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2021 caixw\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "README.md",
    "chars": 1333,
    "preview": "# cnregion\n\n[![Test](https://github.com/issue9/cnregion/workflows/Test/badge.svg)](https://github.com/issue9/cnregion/ac"
  },
  {
    "path": "cnregion.go",
    "chars": 1747,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\n// Package cnregion 中国区域划分代码\n//\n// 中国行政区域"
  },
  {
    "path": "cnregion_test.go",
    "chars": 2177,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"os\"\n\t\"path/f"
  },
  {
    "path": "data/embed.go",
    "chars": 403,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage data\n\nimport (\n\t\"embed\"\n\n\t\"github"
  },
  {
    "path": "data/embed_test.go",
    "chars": 430,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage data\n\nimport (\n\t\"testing\"\n\n\t\"gith"
  },
  {
    "path": "db.go",
    "chars": 3463,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"bytes\"\n\t\"err"
  },
  {
    "path": "db_test.go",
    "chars": 2852,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"testing\"\n\n\t\""
  },
  {
    "path": "districts.go",
    "chars": 842,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport \"github.com/issu"
  },
  {
    "path": "districts_test.go",
    "chars": 471,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"testing\"\n\n\t\""
  },
  {
    "path": "go.mod",
    "chars": 129,
    "preview": "module github.com/issue9/cnregion/v2\n\ngo 1.21\n\nrequire (\n\tgithub.com/issue9/assert/v4 v4.3.1\n\tgithub.com/issue9/errwrap "
  },
  {
    "path": "go.sum",
    "chars": 432,
    "preview": "github.com/issue9/assert/v4 v4.1.1/go.mod h1:v7qDRXi7AsaZZNh8eAK2rkLJg5/clztqQGA1DRv9Lv4=\ngithub.com/issue9/assert/v4 v4"
  },
  {
    "path": "id/id.go",
    "chars": 2140,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\n// Package id 针对 ID 的一些操作函数\npackage id\n\ni"
  },
  {
    "path": "id/id_test.go",
    "chars": 1612,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage id\n\nimport (\n\t\"testing\"\n\n\t\"github"
  },
  {
    "path": "region.go",
    "chars": 4198,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"bytes\"\n\t\"err"
  },
  {
    "path": "region_test.go",
    "chars": 3477,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"testing\"\n\n\t\""
  },
  {
    "path": "search.go",
    "chars": 1487,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"strings\"\n\n\t\""
  },
  {
    "path": "search_test.go",
    "chars": 1853,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage cnregion\n\nimport (\n\t\"os\"\n\t\"string"
  },
  {
    "path": "version/version.go",
    "chars": 1055,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\n// Package version 提供版本的相关信息\n//\n// 依据 htt"
  },
  {
    "path": "version/version_test.go",
    "chars": 478,
    "preview": "// SPDX-FileCopyrightText: 2021-2024 caixw\n//\n// SPDX-License-Identifier: MIT\n\npackage version\n\nimport (\n\t\"testing\"\n\n\t\"g"
  }
]

About this extraction

This page contains the full source code of the issue9/cnregion GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (34.4 KB), approximately 12.1k tokens, and a symbol index with 82 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!