mirror of
https://gitee.com/johng/gf
synced 2026-06-08 10:37:44 +08:00
Compare commits
63 Commits
copilot/fi
...
v2.9.8
| Author | SHA1 | Date | |
|---|---|---|---|
| c9641ea115 | |||
| d8a173d9f0 | |||
| 5d1712b4ab | |||
| a4f98c2490 | |||
| d1cd30c9b4 | |||
| 5979261584 | |||
| df463d75bc | |||
| de9d3c2b3c | |||
| ce3599a672 | |||
| cd6fd247e2 | |||
| be91c4889e | |||
| 6219da7a76 | |||
| c600f3aae8 | |||
| 9dd43cd331 | |||
| 3e73e2d2cc | |||
| 1ed4e0267a | |||
| 3120a8bc22 | |||
| 13524a36bc | |||
| cb26931378 | |||
| 40f4d9f8ec | |||
| caea7ea4b8 | |||
| a6485d53af | |||
| db9f47d942 | |||
| c5778127b1 | |||
| 8f826edc43 | |||
| d148e0ea62 | |||
| d091547212 | |||
| 6334ee1958 | |||
| 24939eb0d6 | |||
| dd62b18877 | |||
| cb4681ce3e | |||
| 7daf916032 | |||
| c82da1e57c | |||
| 90564f9fb0 | |||
| 4d6c7e3d3a | |||
| 18e77de02f | |||
| bf6238e178 | |||
| 887a776441 | |||
| 7274a7399a | |||
| b59824e9dc | |||
| 5cbe421aaa | |||
| 852c3dda62 | |||
| d8b857f930 | |||
| d353bf0fbc | |||
| baf30a0e99 | |||
| 6e0ba551f9 | |||
| 1650aab340 | |||
| bb9133ab9d | |||
| 48845c3473 | |||
| ea956189bf | |||
| 3912d97811 | |||
| 50fb349bc9 | |||
| 777d7aabb5 | |||
| 5a67aac85d | |||
| 132a5ab9a3 | |||
| 8575f01273 | |||
| ac75026716 | |||
| 485a9637cc | |||
| b57b49ecca | |||
| cdead46c79 | |||
| a4883e6e3d | |||
| fe8ba5e35f | |||
| 54b7c249fd |
29
.github/workflows/ci-main.yml
vendored
29
.github/workflows/ci-main.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
||||
# Service containers to run with `code-test`
|
||||
services:
|
||||
# Etcd service.
|
||||
# docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
|
||||
# docker run -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
|
||||
etcd:
|
||||
image: bitnamilegacy/etcd:3.4.24
|
||||
env:
|
||||
@ -75,7 +75,7 @@ jobs:
|
||||
- 6379:6379
|
||||
|
||||
# MySQL backend server.
|
||||
# docker run -d --name mysql \
|
||||
# docker run \
|
||||
# -p 3306:3306 \
|
||||
# -e MYSQL_DATABASE=test \
|
||||
# -e MYSQL_ROOT_PASSWORD=12345678 \
|
||||
@ -89,7 +89,7 @@ jobs:
|
||||
- 3306:3306
|
||||
|
||||
# MariaDb backend server.
|
||||
# docker run -d --name mariadb \
|
||||
# docker run \
|
||||
# -p 3307:3306 \
|
||||
# -e MYSQL_DATABASE=test \
|
||||
# -e MYSQL_ROOT_PASSWORD=12345678 \
|
||||
@ -103,7 +103,7 @@ jobs:
|
||||
- 3307:3306
|
||||
|
||||
# PostgreSQL backend server.
|
||||
# docker run -d --name postgres \
|
||||
# docker run \
|
||||
# -p 5432:5432 \
|
||||
# -e POSTGRES_PASSWORD=12345678 \
|
||||
# -e POSTGRES_USER=postgres \
|
||||
@ -150,7 +150,7 @@ jobs:
|
||||
--health-retries 10
|
||||
|
||||
# ClickHouse backend server.
|
||||
# docker run -d --name clickhouse \
|
||||
# docker run \
|
||||
# -p 9000:9000 -p 8123:8123 -p 9001:9001 \
|
||||
# clickhouse/clickhouse-server:24.11.1.2557-alpine
|
||||
clickhouse-server:
|
||||
@ -161,7 +161,7 @@ jobs:
|
||||
- 9001:9001
|
||||
|
||||
# Polaris backend server.
|
||||
# docker run -d --name polaris \
|
||||
# docker run \
|
||||
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
|
||||
# polarismesh/polaris-standalone:v1.17.2
|
||||
polaris:
|
||||
@ -198,6 +198,17 @@ jobs:
|
||||
ports:
|
||||
- 5236:5236
|
||||
|
||||
# openGauss server
|
||||
# docker run --privileged=true -e GS_PASSWORD=UTpass@1234 -p 9950:5432 opengauss/opengauss:7.0.0-RC1.B023
|
||||
gaussdb:
|
||||
image: opengauss/opengauss:7.0.0-RC1.B023
|
||||
env:
|
||||
GS_PASSWORD: UTpass@1234
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- 9950:5432
|
||||
|
||||
|
||||
zookeeper:
|
||||
image: zookeeper:3.8
|
||||
ports:
|
||||
@ -221,6 +232,12 @@ jobs:
|
||||
detached: true
|
||||
limit-access-to-actor: false
|
||||
|
||||
- name: Free Disk Space
|
||||
run: |
|
||||
df -h /
|
||||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/Python || true
|
||||
df -h /
|
||||
|
||||
- name: Start Apollo Containers
|
||||
run: docker compose -f ".github/workflows/apollo/docker-compose.yml" up -d --build
|
||||
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -17,11 +17,12 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Github Code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
|
||||
- name: Set Up Golang Environment
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.25
|
||||
cache: false
|
||||
|
||||
- name: Build CLI Binary
|
||||
run: |
|
||||
|
||||
0
.github/workflows/scripts/before_script.sh
vendored
Normal file → Executable file
0
.github/workflows/scripts/before_script.sh
vendored
Normal file → Executable file
250
.github/workflows/scripts/ci-main-clean.sh
vendored
Executable file
250
.github/workflows/scripts/ci-main-clean.sh
vendored
Executable file
@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
dirpath=$1
|
||||
|
||||
# Extract the base directory name for pattern matching
|
||||
if [ -n "$dirpath" ]; then
|
||||
dirname=$(basename "$dirpath")
|
||||
echo "Cleaning Docker resources for path: $dirpath (pattern: $dirname)"
|
||||
df -h /
|
||||
|
||||
# Process containers and images based on the directory
|
||||
case "$dirname" in
|
||||
# "mysql")
|
||||
# echo "Cleaning mysql resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing mysql containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q mysql 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
"mssql")
|
||||
echo "Cleaning mssql resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing mssql containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q mcr.microsoft.com/mssql/server 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"pgsql")
|
||||
echo "Cleaning postgres resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing postgres containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q postgres 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"oracle")
|
||||
echo "Cleaning oracle resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing oracle containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q loads/oracle-xe-11g-r2 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"dm")
|
||||
echo "Cleaning dm resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing dm containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q loads/dm 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
"clickhouse")
|
||||
echo "Cleaning clickhouse resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing clickhouse containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q clickhouse/clickhouse-server 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
# "redis")
|
||||
# echo "Cleaning redis resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing redis containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q redis loads/redis loads/redis-sentinel 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
"etcd")
|
||||
echo "Cleaning etcd resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing etcd containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q bitnamilegacy/etcd 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
# "consul")
|
||||
# echo "Cleaning consul resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing consul containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q consul 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
# "nacos")
|
||||
# echo "Cleaning nacos resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing nacos containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q nacos/nacos-server 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
# "polaris")
|
||||
# echo "Cleaning polaris resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing polaris containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q polarismesh/polaris-standalone 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
"zookeeper")
|
||||
echo "Cleaning zookeeper resources..."
|
||||
containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
if [ -n "$containers" ]; then
|
||||
echo "Stopping and removing zookeeper containers..."
|
||||
docker stop $containers 2>/dev/null || true
|
||||
docker rm -f $containers 2>/dev/null || true
|
||||
fi
|
||||
docker rmi -f $(docker images -q zookeeper 2>/dev/null) 2>/dev/null || true
|
||||
;;
|
||||
# "apollo")
|
||||
# echo "Cleaning apollo resources..."
|
||||
# containers=$(docker ps -aq --filter "name=$dirname" 2>/dev/null)
|
||||
# if [ -n "$containers" ]; then
|
||||
# echo "Stopping and removing apollo containers..."
|
||||
# docker stop $containers 2>/dev/null || true
|
||||
# docker rm -f $containers 2>/dev/null || true
|
||||
# fi
|
||||
# docker rmi -f $(docker images -q loads/apollo-quick-start 2>/dev/null) 2>/dev/null || true
|
||||
# ;;
|
||||
*)
|
||||
# No matching pattern, skip cleanup
|
||||
echo "No specific Docker cleanup rule for '$dirname', skipping cleanup"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove dangling images and volumes to free up space
|
||||
echo "Removing dangling images and unused volumes..."
|
||||
docker image prune -f 2>/dev/null || true
|
||||
docker volume prune -f 2>/dev/null || true
|
||||
|
||||
echo "Docker cleanup completed for $dirname"
|
||||
docker system df
|
||||
df -h /
|
||||
fi
|
||||
|
||||
# df -h /
|
||||
# Filesystem Size Used Avail Use% Mounted on
|
||||
# /dev/root 72G 67G 5.4G 93% /
|
||||
# tmpfs 7.9G 84K 7.9G 1% /dev/shm
|
||||
# tmpfs 3.2G 2.6M 3.2G 1% /run
|
||||
# tmpfs 5.0M 0 5.0M 0% /run/lock
|
||||
# /dev/sdb16 881M 62M 758M 8% /boot
|
||||
# /dev/sdb15 105M 6.2M 99M 6% /boot/efi
|
||||
# /dev/sda1 74G 4.1G 66G 6% /mnt
|
||||
# tmpfs 1.6G 12K 1.6G 1% /run/user/1001
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ docker system df
|
||||
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||
# Images 18 11 8.326GB 1.644GB (19%)
|
||||
# Containers 11 11 2.692GB 0B (0%)
|
||||
# Local Volumes 11 8 665.7MB 211.9MB (31%)
|
||||
# Build Cache 0 0 0B 0B
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ docker images
|
||||
# REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
# alpine/curl latest 99fd43792a61 2 days ago 13.5MB
|
||||
# postgres 17-alpine b6bf692a8125 9 days ago 278MB
|
||||
# zookeeper 3.8 2f26c02b94ca 10 days ago 306MB
|
||||
# mariadb 11.4 063fb6684f96 10 days ago 332MB
|
||||
# mcr.microsoft.com/mssql/server 2022-latest a2fbff321505 4 weeks ago 1.61GB
|
||||
# clickhouse/clickhouse-server 24.11.1.2557-alpine 2eee9fd3ae74 12 months ago 539MB
|
||||
# redis 7.0 7705dd2858c1 18 months ago 109MB
|
||||
# consul 1.15 686495461132 20 months ago 155MB
|
||||
# mysql 5.7 5107333e08a8 23 months ago 501MB
|
||||
# polarismesh/polaris-standalone v1.17.2 b7a8cf0a8438 2 years ago 545MB
|
||||
# bitnamilegacy/etcd 3.4.24 74ae5e205ac5 2 years ago 134MB
|
||||
# nacos/nacos-server v2.1.2 a978644d9246 2 years ago 1.06GB
|
||||
# loads/redis 7.0-sentinel 6f12d40540ba 3 years ago 114MB
|
||||
# loads/dm v8.1.2.128_ent_x86_64_ctm_pack4 ccb727ce9dce 3 years ago 432MB
|
||||
# loads/redis-sentinel 7.0 6818c626f5ca 3 years ago 104MB
|
||||
# loads/apollo-quick-start latest 8490de672148 3 years ago 190MB
|
||||
# alpine 3.8 c8bccc0af957 5 years ago 4.41MB
|
||||
# loads/oracle-xe-11g-r2 11.2.0 0d19fd2e072e 6 years ago 2.1GB
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ docker ps -s
|
||||
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
|
||||
# 8214f83420c6 zookeeper:3.8 "/docker-entrypoint.…" 6 minutes ago Up 6 minutes 2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp, [::]:2181->2181/tcp, 8080/tcp d66bac92ae9646f688f70ed4b5176f14_zookeeper38_3a22ef 33kB (virtual 306MB)
|
||||
# 8938d73842e8 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4 "/bin/bash /opt/star…" 6 minutes ago Up 6 minutes 0.0.0.0:5236->5236/tcp, [::]:5236->5236/tcp ca280fbdb86f40c2acf86d7d526c6285_loadsdmv812128_ent_x86_64_ctm_pack4_770a59 844MB (virtual 1.28GB)
|
||||
# 0d3a653fe1f2 loads/oracle-xe-11g-r2:11.2.0 "/bin/sh -c '/usr/sb…" 6 minutes ago Up 6 minutes 22/tcp, 8080/tcp, 0.0.0.0:1521->1521/tcp, [::]:1521->1521/tcp 2048856d428c4967b1c35193eb8c9192_loadsoraclexe11gr21120_295d54 1.3GB (virtual 3.4GB)
|
||||
# ca3936189166 polarismesh/polaris-standalone:v1.17.2 "/bin/bash run.sh" 6 minutes ago Up 6 minutes 0.0.0.0:8090-8091->8090-8091/tcp, [::]:8090-8091->8090-8091/tcp, 8080/tcp, 8100-8101/tcp, 0.0.0.0:8093->8093/tcp, [::]:8093->8093/tcp, 8761/tcp, 15010/tcp, 0.0.0.0:9090-9091->9090-9091/tcp, [::]:9090-9091->9090-9091/tcp cbd43dceef754e2d8aab507e33167be7_polarismeshpolarisstandalonev1172_ca40b6 299MB (virtual 844MB)
|
||||
# 26169dad485e clickhouse/clickhouse-server:24.11.1.2557-alpine "/entrypoint.sh" 6 minutes ago Up 6 minutes 0.0.0.0:8123->8123/tcp, [::]:8123->8123/tcp, 0.0.0.0:9000-9001->9000-9001/tcp, [::]:9000-9001->9000-9001/tcp, 9009/tcp f1c7766fbe36401792a6f735d7acf123_clickhouseclickhouseserver241112557alpine_cfc034 338kB (virtual 539MB)
|
||||
# 04689a1d581f mcr.microsoft.com/mssql/server:2022-latest "/opt/mssql/bin/laun…" 6 minutes ago Up 6 minutes (healthy) 0.0.0.0:1433->1433/tcp, [::]:1433->1433/tcp 41d685349a7640b28230db8d0f60efe7_mcrmicrosoftcommssqlserver2022latest_fe29fb 108MB (virtual 1.72GB)
|
||||
# d5fbc5f811af postgres:17-alpine "docker-entrypoint.s…" 6 minutes ago Up 6 minutes (healthy) 0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp 2783be71b5ce417ab9a31428e7b4d8f2_postgres17alpine_c60840 63B (virtual 278MB)
|
||||
# da96a7ad7a01 mariadb:11.4 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp 45eed646fa6c4a698893ee11cda95a4c_mariadb114_3a9cd6 2B (virtual 332MB)
|
||||
# 27ba1904ba3a mysql:5.7 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp ea6d7a4c207d427a95b5ae0db91fdf56_mysql57_c21053 4B (virtual 501MB)
|
||||
# 518e785d1bb6 redis:7.0 "docker-entrypoint.s…" 7 minutes ago Up 7 minutes (healthy) 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp af6044fc849e441bbc6c48f7a5ec5fec_redis70_b11994 0B (virtual 109MB)
|
||||
# 7495ec2cd8e3 bitnamilegacy/etcd:3.4.24 "/opt/bitnami/script…" 7 minutes ago Up 7 minutes 0.0.0.0:2379->2379/tcp, [::]:2379->2379/tcp, 2380/tcp 49f2a2a6bf3a4fae842cc950bbc3658a_bitnamilegacyetcd3424_1265e1 145MB (virtual 279MB)
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ du -ah --max-depth=1 /usr | sort -n
|
||||
# 4.0K /usr/games
|
||||
# 4.0K /usr/lib64
|
||||
# 6.6G /usr/lib
|
||||
# 9.3G /usr/share
|
||||
# 15M /usr/lib32
|
||||
# 24G /usr/local
|
||||
# 41G /usr
|
||||
# 95M /usr/sbin
|
||||
# 156M /usr/include
|
||||
# 158M /usr/src
|
||||
# 402M /usr/libexec
|
||||
# 841M /usr/bin
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ du -ah --max-depth=1 /opt | sort -n
|
||||
# 4.0K /opt/pipx_bin
|
||||
# 5.8G /opt/hostedtoolcache
|
||||
# 8.5G /opt
|
||||
# 12K /opt/containerd
|
||||
# 14M /opt/hca
|
||||
# 16K /opt/post-generation
|
||||
# 217M /opt/runner-cache
|
||||
# 243M /opt/actionarchivecache
|
||||
# 374M /opt/google
|
||||
# 515M /opt/pipx
|
||||
# 655M /opt/az
|
||||
# 783M /opt/microsoft
|
||||
|
||||
# runner@runnervmg1sw1:~/work/gf/gf$ du -ah --max-depth=1 /opt/hostedtoolcache/ | sort -n
|
||||
# 1.1G /opt/hostedtoolcache/go
|
||||
# 1.6G /opt/hostedtoolcache/CodeQL
|
||||
# 1.9G /opt/hostedtoolcache/Python
|
||||
# 5.8G /opt/hostedtoolcache/
|
||||
# 9.9M /opt/hostedtoolcache/protoc
|
||||
# 24K /opt/hostedtoolcache/Java_Temurin-Hotspot_jdk
|
||||
# 217M /opt/hostedtoolcache/Ruby
|
||||
# 520M /opt/hostedtoolcache/PyPy
|
||||
# 574M /opt/hostedtoolcache/node
|
||||
|
||||
54
.github/workflows/scripts/ci-main.sh
vendored
Normal file → Executable file
54
.github/workflows/scripts/ci-main.sh
vendored
Normal file → Executable file
@ -6,56 +6,54 @@ coverage=$1
|
||||
for file in `find . -name go.mod`; do
|
||||
dirpath=$(dirname $file)
|
||||
echo $dirpath
|
||||
|
||||
# ignore mssql tests as its docker service failed
|
||||
# TODO remove this ignoring codes after the mssql docker service OK
|
||||
if [ "mssql" = $(basename $dirpath) ]; then
|
||||
continue 1
|
||||
fi
|
||||
|
||||
|
||||
# package kubecm was moved to sub ci procedure.
|
||||
if [ "kubecm" = $(basename $dirpath) ]; then
|
||||
continue 1
|
||||
fi
|
||||
|
||||
|
||||
# examples directory was moved to sub ci procedure.
|
||||
if [[ $dirpath =~ "/examples/" ]]; then
|
||||
continue 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ $file =~ "/testdata/" ]]; then
|
||||
echo "ignore testdata path $file"
|
||||
continue 1
|
||||
fi
|
||||
|
||||
# Check if it's a contrib directory
|
||||
if [[ $dirpath =~ "/contrib/" ]]; then
|
||||
# Check if go version meets the requirement
|
||||
if ! go version | grep -qE "go${LATEST_GO_VERSION}"; then
|
||||
echo "ignore path $dirpath as go version is not ${LATEST_GO_VERSION}: $(go version)"
|
||||
# clean docker containers and images to free disk space
|
||||
# bash .github/workflows/scripts/ci-main-clean.sh "$dirpath"
|
||||
continue 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $file =~ "/testdata/" ]]; then
|
||||
echo "ignore testdata path $file"
|
||||
continue 1
|
||||
fi
|
||||
|
||||
if [[ $dirpath = "." ]]; then
|
||||
# No space left on device error sometimes occurs in CI pipelines, so clean the cache before tests.
|
||||
go clean -cache
|
||||
fi
|
||||
|
||||
|
||||
# if [[ $dirpath = "." ]]; then
|
||||
# # No space left on device error sometimes occurs in CI pipelines, so clean the cache before tests.
|
||||
# go clean -cache
|
||||
# fi
|
||||
|
||||
cd $dirpath
|
||||
go mod tidy
|
||||
go build ./...
|
||||
|
||||
|
||||
# test with coverage
|
||||
if [ "${coverage}" = "coverage" ]; then
|
||||
go test ./... -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1
|
||||
|
||||
if grep -q "/gogf/gf/.*/v2" go.mod; then
|
||||
sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out
|
||||
fi
|
||||
go test ./... -count=1 -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1
|
||||
|
||||
if grep -q "/gogf/gf/.*/v2" go.mod; then
|
||||
sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out
|
||||
fi
|
||||
else
|
||||
go test ./... -race || exit 1
|
||||
go test ./... -count=1 -race || exit 1
|
||||
fi
|
||||
|
||||
|
||||
cd -
|
||||
# clean docker containers and images to free disk space
|
||||
# bash .github/workflows/scripts/ci-main-clean.sh "$dirpath"
|
||||
done
|
||||
|
||||
0
.github/workflows/scripts/ci-sub.sh
vendored
Normal file → Executable file
0
.github/workflows/scripts/ci-sub.sh
vendored
Normal file → Executable file
785
.github/workflows/scripts/docker-services.sh
vendored
Executable file
785
.github/workflows/scripts/docker-services.sh
vendored
Executable file
@ -0,0 +1,785 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# GoFrame Docker Services Manager
|
||||
# For managing Docker services used in local development and testing
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Container name prefix
|
||||
PREFIX="goframe"
|
||||
|
||||
# Color definitions
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Service definitions
|
||||
declare -A SERVICES
|
||||
declare -A SERVICE_PORTS
|
||||
declare -A SERVICE_ENVS
|
||||
declare -A SERVICE_OPTS
|
||||
|
||||
# Basic services
|
||||
SERVICES["etcd"]="bitnamilegacy/etcd:3.4.24"
|
||||
SERVICE_PORTS["etcd"]="2379:2379"
|
||||
SERVICE_ENVS["etcd"]="-e ALLOW_NONE_AUTHENTICATION=yes"
|
||||
|
||||
SERVICES["redis"]="redis:7.0"
|
||||
SERVICE_PORTS["redis"]="6379:6379"
|
||||
SERVICE_OPTS["redis"]="--health-cmd 'redis-cli ping' --health-interval 10s --health-timeout 5s --health-retries 5"
|
||||
|
||||
SERVICES["mysql"]="mysql:5.7"
|
||||
SERVICE_PORTS["mysql"]="3306:3306"
|
||||
SERVICE_ENVS["mysql"]="-e MYSQL_DATABASE=test -e MYSQL_ROOT_PASSWORD=12345678"
|
||||
|
||||
SERVICES["mariadb"]="mariadb:11.4"
|
||||
SERVICE_PORTS["mariadb"]="3307:3306"
|
||||
SERVICE_ENVS["mariadb"]="-e MARIADB_DATABASE=test -e MARIADB_ROOT_PASSWORD=12345678"
|
||||
|
||||
SERVICES["postgres"]="postgres:17-alpine"
|
||||
SERVICE_PORTS["postgres"]="5432:5432"
|
||||
SERVICE_ENVS["postgres"]="-e POSTGRES_PASSWORD=12345678 -e POSTGRES_USER=postgres -e POSTGRES_DB=test -e TZ=Asia/Shanghai"
|
||||
SERVICE_OPTS["postgres"]="--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5"
|
||||
|
||||
SERVICES["mssql"]="mcr.microsoft.com/mssql/server:2022-latest"
|
||||
SERVICE_PORTS["mssql"]="1433:1433"
|
||||
SERVICE_ENVS["mssql"]="-e TZ=Asia/Shanghai -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD=LoremIpsum86"
|
||||
|
||||
SERVICES["clickhouse"]="clickhouse/clickhouse-server:24.11.1.2557-alpine"
|
||||
SERVICE_PORTS["clickhouse"]="9000:9000 -p 8123:8123 -p 9001:9001"
|
||||
|
||||
SERVICES["polaris"]="polarismesh/polaris-standalone:v1.17.2"
|
||||
SERVICE_PORTS["polaris"]="8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091"
|
||||
|
||||
SERVICES["oracle"]="loads/oracle-xe-11g-r2:11.2.0"
|
||||
SERVICE_PORTS["oracle"]="1521:1521"
|
||||
SERVICE_ENVS["oracle"]="-e ORACLE_ALLOW_REMOTE=true -e ORACLE_SID=XE -e ORACLE_DB_USER_NAME=system -e ORACLE_DB_PASSWORD=oracle"
|
||||
|
||||
SERVICES["dm"]="loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4"
|
||||
SERVICE_PORTS["dm"]="5236:5236"
|
||||
|
||||
SERVICES["gaussdb"]="opengauss/opengauss:7.0.0-RC1.B023"
|
||||
SERVICE_PORTS["gaussdb"]="9950:5432"
|
||||
SERVICE_ENVS["gaussdb"]="-e GS_PASSWORD=UTpass@1234 -e TZ=Asia/Shanghai"
|
||||
SERVICE_OPTS["gaussdb"]="--privileged=true"
|
||||
|
||||
SERVICES["zookeeper"]="zookeeper:3.8"
|
||||
SERVICE_PORTS["zookeeper"]="2181:2181"
|
||||
|
||||
# Service groups
|
||||
GROUP_DB="mysql mariadb postgres mssql oracle dm gaussdb clickhouse"
|
||||
GROUP_CACHE="redis etcd"
|
||||
GROUP_REGISTRY="polaris zookeeper"
|
||||
GROUP_ALL="etcd redis mysql mariadb postgres mssql clickhouse polaris oracle dm gaussdb zookeeper"
|
||||
|
||||
# Working directories
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
WORKFLOW_DIR="$PROJECT_ROOT/.github/workflows"
|
||||
|
||||
# Print colored messages
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[OK]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if Docker is available
|
||||
check_docker() {
|
||||
if ! command -v docker &> /dev/null; then
|
||||
print_error "Docker is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
if ! docker info &> /dev/null; then
|
||||
print_error "Docker service is not running"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Get container name
|
||||
get_container_name() {
|
||||
echo "${PREFIX}-$1"
|
||||
}
|
||||
|
||||
# Start a single service
|
||||
start_service() {
|
||||
local service=$1
|
||||
local container_name=$(get_container_name "$service")
|
||||
local image="${SERVICES[$service]}"
|
||||
local ports="${SERVICE_PORTS[$service]}"
|
||||
local envs="${SERVICE_ENVS[$service]}"
|
||||
local opts="${SERVICE_OPTS[$service]}"
|
||||
|
||||
if [ -z "$image" ]; then
|
||||
print_error "Unknown service: $service"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if container already exists
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
||||
print_warning "$service is already running"
|
||||
return 0
|
||||
else
|
||||
print_info "Starting existing container $service..."
|
||||
docker start "$container_name" > /dev/null
|
||||
print_success "$service started"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
print_info "Starting $service..."
|
||||
|
||||
# Build docker run command
|
||||
local cmd="docker run -d --name $container_name"
|
||||
|
||||
# Add port mappings
|
||||
for port in $ports; do
|
||||
cmd="$cmd -p $port"
|
||||
done
|
||||
|
||||
# Add environment variables
|
||||
if [ -n "$envs" ]; then
|
||||
cmd="$cmd $envs"
|
||||
fi
|
||||
|
||||
# Add other options
|
||||
if [ -n "$opts" ]; then
|
||||
cmd="$cmd $opts"
|
||||
fi
|
||||
|
||||
cmd="$cmd $image"
|
||||
|
||||
if eval "$cmd" > /dev/null 2>&1; then
|
||||
print_success "$service started (container: $container_name)"
|
||||
else
|
||||
print_error "Failed to start $service"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop a single service
|
||||
stop_service() {
|
||||
local service=$1
|
||||
local container_name=$(get_container_name "$service")
|
||||
|
||||
if docker ps --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
||||
print_info "Stopping $service..."
|
||||
docker stop "$container_name" > /dev/null
|
||||
print_success "$service stopped"
|
||||
else
|
||||
print_warning "$service is not running"
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove a single service
|
||||
remove_service() {
|
||||
local service=$1
|
||||
local container_name=$(get_container_name "$service")
|
||||
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
||||
print_info "Removing $service..."
|
||||
docker rm -f "$container_name" > /dev/null
|
||||
print_success "$service removed"
|
||||
else
|
||||
print_warning "$service container does not exist"
|
||||
fi
|
||||
}
|
||||
|
||||
# View service logs
|
||||
logs_service() {
|
||||
local service=$1
|
||||
local container_name=$(get_container_name "$service")
|
||||
local lines=${2:-100}
|
||||
|
||||
if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
||||
docker logs --tail "$lines" -f "$container_name"
|
||||
else
|
||||
print_error "$service container does not exist"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Start docker-compose service
|
||||
start_compose_service() {
|
||||
local service=$1
|
||||
local compose_file=""
|
||||
|
||||
case $service in
|
||||
apollo)
|
||||
compose_file="$WORKFLOW_DIR/apollo/docker-compose.yml"
|
||||
;;
|
||||
nacos)
|
||||
compose_file="$WORKFLOW_DIR/nacos/docker-compose.yml"
|
||||
;;
|
||||
redis-cluster)
|
||||
compose_file="$WORKFLOW_DIR/redis/docker-compose.yml"
|
||||
;;
|
||||
consul)
|
||||
compose_file="$WORKFLOW_DIR/consul/docker-compose.yml"
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown compose service: $service"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -f "$compose_file" ]; then
|
||||
print_info "Starting $service (docker-compose)..."
|
||||
docker compose -f "$compose_file" up -d
|
||||
print_success "$service started"
|
||||
else
|
||||
print_error "Compose file does not exist: $compose_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Stop docker-compose service
|
||||
stop_compose_service() {
|
||||
local service=$1
|
||||
local compose_file=""
|
||||
|
||||
case $service in
|
||||
apollo)
|
||||
compose_file="$WORKFLOW_DIR/apollo/docker-compose.yml"
|
||||
;;
|
||||
nacos)
|
||||
compose_file="$WORKFLOW_DIR/nacos/docker-compose.yml"
|
||||
;;
|
||||
redis-cluster)
|
||||
compose_file="$WORKFLOW_DIR/redis/docker-compose.yml"
|
||||
;;
|
||||
consul)
|
||||
compose_file="$WORKFLOW_DIR/consul/docker-compose.yml"
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown compose service: $service"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -f "$compose_file" ]; then
|
||||
print_info "Stopping $service (docker-compose)..."
|
||||
docker compose -f "$compose_file" down
|
||||
print_success "$service stopped"
|
||||
else
|
||||
print_error "Compose file does not exist: $compose_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Show service status
|
||||
show_status() {
|
||||
echo ""
|
||||
echo -e "${CYAN}========== GoFrame Docker Services Status ==========${NC}"
|
||||
echo ""
|
||||
printf "%-15s %-12s %-30s %s\n" "SERVICE" "STATUS" "CONTAINER" "PORTS"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
for service in $GROUP_ALL; do
|
||||
local container_name=$(get_container_name "$service")
|
||||
local status="stopped"
|
||||
local ports="-"
|
||||
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${container_name}$"; then
|
||||
status="${GREEN}running${NC}"
|
||||
ports=$(docker port "$container_name" 2>/dev/null | tr '\n' ' ' || echo "-")
|
||||
elif docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${container_name}$"; then
|
||||
status="${YELLOW}stopped${NC}"
|
||||
else
|
||||
status="${RED}not created${NC}"
|
||||
fi
|
||||
|
||||
printf "%-15s %-22b %-30s %s\n" "$service" "$status" "$container_name" "$ports"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}========== Compose Services ==========${NC}"
|
||||
echo ""
|
||||
|
||||
for compose_svc in apollo nacos redis-cluster consul; do
|
||||
local running=0
|
||||
case $compose_svc in
|
||||
apollo)
|
||||
running=$(docker ps --filter "name=apollo" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
nacos)
|
||||
running=$(docker ps --filter "name=nacos" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
redis-cluster)
|
||||
running=$(docker ps --filter "name=redis-" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
consul)
|
||||
running=$(docker ps --filter "name=consul" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$running" -gt 0 ]; then
|
||||
printf "%-15s ${GREEN}running${NC} (%d containers)\n" "$compose_svc" "$running"
|
||||
else
|
||||
printf "%-15s ${RED}stopped${NC}\n" "$compose_svc"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Show service information
|
||||
show_service_info() {
|
||||
echo ""
|
||||
echo -e "${CYAN}========== Available Services ==========${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Basic Services (standalone containers):${NC}"
|
||||
echo ""
|
||||
printf "%-15s %-50s %s\n" "SERVICE" "IMAGE" "PORTS"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
for service in $GROUP_ALL; do
|
||||
printf "%-15s %-50s %s\n" "$service" "${SERVICES[$service]}" "${SERVICE_PORTS[$service]}"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}Compose Services (multi-container):${NC}"
|
||||
echo " apollo - Apollo Config Center (8080, 8070, 8060, 13306)"
|
||||
echo " nacos - Nacos Registry (8848, 9848, 9555)"
|
||||
echo " redis-cluster - Redis Primary-Replica + Sentinel Cluster (6380-6382, 26379-26381)"
|
||||
echo " consul - Consul Service Discovery (8500, 8600)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Service Groups:${NC}"
|
||||
echo " db - Databases: $GROUP_DB"
|
||||
echo " cache - Cache: $GROUP_CACHE"
|
||||
echo " registry - Registry: $GROUP_REGISTRY"
|
||||
echo " all - All basic services"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Show help
|
||||
show_help() {
|
||||
echo ""
|
||||
echo -e "${CYAN}GoFrame Docker Services Manager${NC}"
|
||||
echo ""
|
||||
echo "Usage: $0 <command> [service|group] [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start <service|group> Start service or service group"
|
||||
echo " stop <service|group> Stop service or service group"
|
||||
echo " restart <service|group> Restart service or service group"
|
||||
echo " remove <service|group> Remove service container"
|
||||
echo " logs <service> [lines] View service logs (default 100 lines)"
|
||||
echo " status Show all service status"
|
||||
echo " info Show available service information"
|
||||
echo " clean Remove all goframe containers"
|
||||
echo " pull [service] Pull images"
|
||||
echo ""
|
||||
echo "Services:"
|
||||
echo " Basic: etcd, redis, mysql, mariadb, postgres, mssql,"
|
||||
echo " clickhouse, polaris, oracle, dm, gaussdb, zookeeper"
|
||||
echo " Compose: apollo, nacos, redis-cluster, consul"
|
||||
echo ""
|
||||
echo "Service Groups:"
|
||||
echo " db - All database services"
|
||||
echo " cache - Cache services (redis, etcd)"
|
||||
echo " registry - Registry services (polaris, zookeeper)"
|
||||
echo " all - All basic services"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 start mysql # Start MySQL"
|
||||
echo " $0 start db # Start all databases"
|
||||
echo " $0 start all # Start all basic services"
|
||||
echo " $0 start apollo # Start Apollo (compose)"
|
||||
echo " $0 stop all # Stop all basic services"
|
||||
echo " $0 logs mysql 50 # View last 50 lines of MySQL logs"
|
||||
echo " $0 status # View service status"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Parse service groups
|
||||
parse_services() {
|
||||
local input=$1
|
||||
case $input in
|
||||
db)
|
||||
echo "$GROUP_DB"
|
||||
;;
|
||||
cache)
|
||||
echo "$GROUP_CACHE"
|
||||
;;
|
||||
registry)
|
||||
echo "$GROUP_REGISTRY"
|
||||
;;
|
||||
all)
|
||||
echo "$GROUP_ALL"
|
||||
;;
|
||||
*)
|
||||
echo "$input"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check if it's a compose service
|
||||
is_compose_service() {
|
||||
local service=$1
|
||||
case $service in
|
||||
apollo|nacos|redis-cluster|consul)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Pull images
|
||||
pull_images() {
|
||||
local services=$1
|
||||
|
||||
if [ -z "$services" ]; then
|
||||
services="$GROUP_ALL"
|
||||
fi
|
||||
|
||||
for service in $services; do
|
||||
if [ -n "${SERVICES[$service]}" ]; then
|
||||
print_info "Pulling image: ${SERVICES[$service]}"
|
||||
docker pull "${SERVICES[$service]}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Clean all goframe containers
|
||||
clean_all() {
|
||||
print_info "Removing all $PREFIX containers..."
|
||||
local containers=$(docker ps -a --filter "name=$PREFIX" --format '{{.Names}}')
|
||||
|
||||
if [ -n "$containers" ]; then
|
||||
for container in $containers; do
|
||||
docker rm -f "$container" > /dev/null
|
||||
print_success "Removed: $container"
|
||||
done
|
||||
else
|
||||
print_info "No $PREFIX containers found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get service status mark
|
||||
get_service_status_mark() {
|
||||
local service=$1
|
||||
local container_name=$(get_container_name "$service")
|
||||
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${container_name}$"; then
|
||||
echo -e "${GREEN}*${NC}"
|
||||
else
|
||||
echo " "
|
||||
fi
|
||||
}
|
||||
|
||||
# Get compose service status mark
|
||||
get_compose_status_mark() {
|
||||
local service=$1
|
||||
local running=0
|
||||
|
||||
case $service in
|
||||
apollo)
|
||||
running=$(docker ps --filter "name=apollo" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
nacos)
|
||||
running=$(docker ps --filter "name=nacos" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
redis-cluster)
|
||||
running=$(docker ps --filter "name=redis-" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
consul)
|
||||
running=$(docker ps --filter "name=consul" --format '{{.Names}}' 2>/dev/null | wc -l)
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$running" -gt 0 ]; then
|
||||
echo -e "${GREEN}*${NC}"
|
||||
else
|
||||
echo " "
|
||||
fi
|
||||
}
|
||||
|
||||
# Service selection menu
|
||||
select_service_menu() {
|
||||
local action=$1
|
||||
local action_name=$2
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}========== Select Service to ${action_name} ==========${NC}"
|
||||
|
||||
# Show running status for stop/restart/logs operations
|
||||
if [[ "$action" == "stop" || "$action" == "restart" || "$action" == "logs" ]]; then
|
||||
echo -e " (${GREEN}*${NC} indicates running)"
|
||||
fi
|
||||
echo ""
|
||||
echo -e "${YELLOW}Basic Services:${NC}"
|
||||
printf " %b1) etcd %b2) redis %b3) mysql\n" \
|
||||
"$(get_service_status_mark etcd)" "$(get_service_status_mark redis)" "$(get_service_status_mark mysql)"
|
||||
printf " %b4) mariadb %b5) postgres %b6) mssql\n" \
|
||||
"$(get_service_status_mark mariadb)" "$(get_service_status_mark postgres)" "$(get_service_status_mark mssql)"
|
||||
printf " %b7) clickhouse %b8) polaris %b9) oracle\n" \
|
||||
"$(get_service_status_mark clickhouse)" "$(get_service_status_mark polaris)" "$(get_service_status_mark oracle)"
|
||||
printf " %b10) dm %b11) gaussdb %b12) zookeeper\n" \
|
||||
"$(get_service_status_mark dm)" "$(get_service_status_mark gaussdb)" "$(get_service_status_mark zookeeper)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Compose Services:${NC}"
|
||||
printf " %b13) apollo %b14) nacos %b15) redis-cluster\n" \
|
||||
"$(get_compose_status_mark apollo)" "$(get_compose_status_mark nacos)" "$(get_compose_status_mark redis-cluster)"
|
||||
printf " %b16) consul\n" "$(get_compose_status_mark consul)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Service Groups:${NC}"
|
||||
echo " 17) db (all databases) 18) cache (cache services)"
|
||||
echo " 19) registry (registry services) 20) all (all basic services)"
|
||||
echo ""
|
||||
echo " 0) Back to main menu"
|
||||
echo ""
|
||||
read -p "Select [0-20]: " svc_choice
|
||||
|
||||
local svc=""
|
||||
case $svc_choice in
|
||||
1) svc="etcd" ;;
|
||||
2) svc="redis" ;;
|
||||
3) svc="mysql" ;;
|
||||
4) svc="mariadb" ;;
|
||||
5) svc="postgres" ;;
|
||||
6) svc="mssql" ;;
|
||||
7) svc="clickhouse" ;;
|
||||
8) svc="polaris" ;;
|
||||
9) svc="oracle" ;;
|
||||
10) svc="dm" ;;
|
||||
11) svc="gaussdb" ;;
|
||||
12) svc="zookeeper" ;;
|
||||
13) svc="apollo" ;;
|
||||
14) svc="nacos" ;;
|
||||
15) svc="redis-cluster" ;;
|
||||
16) svc="consul" ;;
|
||||
17) svc="db" ;;
|
||||
18) svc="cache" ;;
|
||||
19) svc="registry" ;;
|
||||
20) svc="all" ;;
|
||||
0) return ;;
|
||||
*)
|
||||
print_error "Invalid selection"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case $action in
|
||||
start)
|
||||
if is_compose_service "$svc"; then
|
||||
start_compose_service "$svc"
|
||||
else
|
||||
for s in $(parse_services "$svc"); do
|
||||
start_service "$s"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_compose_service "$svc"; then
|
||||
stop_compose_service "$svc"
|
||||
else
|
||||
for s in $(parse_services "$svc"); do
|
||||
stop_service "$s"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
if is_compose_service "$svc"; then
|
||||
stop_compose_service "$svc"
|
||||
start_compose_service "$svc"
|
||||
else
|
||||
for s in $(parse_services "$svc"); do
|
||||
stop_service "$s"
|
||||
start_service "$s"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
remove)
|
||||
for s in $(parse_services "$svc"); do
|
||||
remove_service "$s"
|
||||
done
|
||||
;;
|
||||
logs)
|
||||
if is_compose_service "$svc"; then
|
||||
print_error "For Compose services, please use 'docker compose logs'"
|
||||
else
|
||||
read -p "Number of lines (default 100): " lines
|
||||
lines=${lines:-100}
|
||||
logs_service "$svc" "$lines"
|
||||
fi
|
||||
;;
|
||||
pull)
|
||||
pull_images "$(parse_services "$svc")"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Interactive menu
|
||||
interactive_menu() {
|
||||
while true; do
|
||||
echo ""
|
||||
echo -e "${CYAN}========== GoFrame Docker Services Manager ==========${NC}"
|
||||
echo ""
|
||||
echo " 1) Start Service"
|
||||
echo " 2) Stop Service"
|
||||
echo " 3) Restart Service"
|
||||
echo " 4) Remove Service"
|
||||
echo " 5) View Logs"
|
||||
echo " 6) View Status"
|
||||
echo " 7) Service Info"
|
||||
echo " 8) Clean All Containers"
|
||||
echo " 9) Pull Images"
|
||||
echo " 0) Exit"
|
||||
echo ""
|
||||
read -p "Select operation [0-9]: " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
select_service_menu "start" "Start"
|
||||
;;
|
||||
2)
|
||||
select_service_menu "stop" "Stop"
|
||||
;;
|
||||
3)
|
||||
select_service_menu "restart" "Restart"
|
||||
;;
|
||||
4)
|
||||
select_service_menu "remove" "Remove"
|
||||
;;
|
||||
5)
|
||||
select_service_menu "logs" "View Logs"
|
||||
;;
|
||||
6)
|
||||
show_status
|
||||
;;
|
||||
7)
|
||||
show_service_info
|
||||
;;
|
||||
8)
|
||||
read -p "Confirm removing all goframe containers? [y/N]: " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
clean_all
|
||||
fi
|
||||
;;
|
||||
9)
|
||||
select_service_menu "pull" "Pull Images"
|
||||
;;
|
||||
0)
|
||||
echo "Goodbye!"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid selection"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
check_docker
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
interactive_menu
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local command=$1
|
||||
local target=$2
|
||||
local extra=$3
|
||||
|
||||
case $command in
|
||||
start)
|
||||
if [ -z "$target" ]; then
|
||||
print_error "Please specify service name or service group"
|
||||
exit 1
|
||||
fi
|
||||
if is_compose_service "$target"; then
|
||||
start_compose_service "$target"
|
||||
else
|
||||
for service in $(parse_services "$target"); do
|
||||
start_service "$service"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ -z "$target" ]; then
|
||||
print_error "Please specify service name or service group"
|
||||
exit 1
|
||||
fi
|
||||
if is_compose_service "$target"; then
|
||||
stop_compose_service "$target"
|
||||
else
|
||||
for service in $(parse_services "$target"); do
|
||||
stop_service "$service"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
if [ -z "$target" ]; then
|
||||
print_error "Please specify service name or service group"
|
||||
exit 1
|
||||
fi
|
||||
if is_compose_service "$target"; then
|
||||
stop_compose_service "$target"
|
||||
start_compose_service "$target"
|
||||
else
|
||||
for service in $(parse_services "$target"); do
|
||||
stop_service "$service"
|
||||
start_service "$service"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
remove|rm)
|
||||
if [ -z "$target" ]; then
|
||||
print_error "Please specify service name or service group"
|
||||
exit 1
|
||||
fi
|
||||
for service in $(parse_services "$target"); do
|
||||
remove_service "$service"
|
||||
done
|
||||
;;
|
||||
logs)
|
||||
if [ -z "$target" ]; then
|
||||
print_error "Please specify service name"
|
||||
exit 1
|
||||
fi
|
||||
logs_service "$target" "${extra:-100}"
|
||||
;;
|
||||
status|ps)
|
||||
show_status
|
||||
;;
|
||||
info|list)
|
||||
show_service_info
|
||||
;;
|
||||
clean)
|
||||
clean_all
|
||||
;;
|
||||
pull)
|
||||
pull_images "$target"
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown command: $command"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "examples"]
|
||||
path = examples
|
||||
url = git@github.com:gogf/examples.git
|
||||
|
||||
11
Makefile
11
Makefile
@ -6,6 +6,7 @@ tidy:
|
||||
./.make_tidy.sh
|
||||
|
||||
# execute "golangci-lint" to check code style
|
||||
# go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run -c .golangci.yml
|
||||
@ -75,3 +76,13 @@ subsync: subup
|
||||
git push origin; \
|
||||
fi; \
|
||||
cd ..;
|
||||
|
||||
# manage docker services for local development
|
||||
# usage: make docker or make docker cmd=start svc=mysql
|
||||
.PHONY: docker
|
||||
docker:
|
||||
@if [ -z "$(cmd)" ]; then \
|
||||
./.github/workflows/scripts/docker-services.sh; \
|
||||
else \
|
||||
./.github/workflows/scripts/docker-services.sh $(cmd) $(svc) $(extra); \
|
||||
fi
|
||||
|
||||
10
README.MD
10
README.MD
@ -1,3 +1,4 @@
|
||||
English | [简体中文](README.zh_CN.MD)
|
||||
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/img/logo_full.png" width="300" alt="goframe gf logo"/>
|
||||
@ -23,6 +24,12 @@
|
||||
|
||||
A powerful framework for faster, easier, and more efficient project development.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get -u github.com/gogf/gf/v2
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- Official Site: [https://goframe.org](https://goframe.org)
|
||||
@ -31,13 +38,14 @@ A powerful framework for faster, easier, and more efficient project development.
|
||||
- Mirror Site: [Github Pages](https://pages.goframe.org)
|
||||
- Mirror Site: [Offline Docs](https://github.com/gogf/goframe.org-pdf?tab=readme-ov-file#%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC)
|
||||
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
|
||||
- Doc Source: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site)
|
||||
|
||||
## Contributors
|
||||
|
||||
💖 [Thanks to all the contributors who made GoFrame possible](https://github.com/gogf/gf/graphs/contributors) 💖
|
||||
|
||||
<a href="https://github.com/gogf/gf/graphs/contributors">
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.5" alt="goframe contributors"/>
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.8" alt="goframe contributors"/>
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
53
README.zh_CN.MD
Normal file
53
README.zh_CN.MD
Normal file
@ -0,0 +1,53 @@
|
||||
[English](README.MD) | 简体中文
|
||||
|
||||
<div align=center>
|
||||
<img src="https://goframe.org/img/logo_full.png" width="300" alt="goframe gf logo"/>
|
||||
|
||||
[](https://pkg.go.dev/github.com/gogf/gf/v2)
|
||||
[](https://github.com/gogf/gf/actions/workflows/ci-main.yml)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/gogf/gf)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/9233)
|
||||
[](https://goreportcard.com/report/github.com/gogf/gf/v2)
|
||||
[](https://codecov.io/gh/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
|
||||
[](https://github.com/gogf/gf/releases)
|
||||
[](https://github.com/gogf/gf/pulls)
|
||||
[](https://github.com/gogf/gf/pulls?q=is%3Apr+is%3Aclosed)
|
||||
[](https://github.com/gogf/gf/issues)
|
||||
[](https://github.com/gogf/gf/issues?q=is%3Aissue+is%3Aclosed)
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
一个强大的框架,为了更快、更轻松、更高效的项目开发。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
go get -u github.com/gogf/gf/v2
|
||||
```
|
||||
|
||||
## 文档
|
||||
|
||||
- 官方网站: [https://goframe.org](https://goframe.org)
|
||||
- 官方网站(en): [https://goframe.org/en](https://goframe.org/en)
|
||||
- 国内镜像: [https://goframe.org.cn](https://goframe.org.cn)
|
||||
- 镜像网站: [Github Pages](https://pages.goframe.org)
|
||||
- 镜像网站: [离线文档](https://github.com/gogf/goframe.org-pdf?tab=readme-ov-file#%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC)
|
||||
- Go包文档: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
|
||||
- 文档源码: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site)
|
||||
|
||||
## 贡献者
|
||||
|
||||
💖 [感谢所有使 GoFrame 成为可能的贡献者](https://github.com/gogf/gf/graphs/contributors) 💖
|
||||
|
||||
<a href="https://github.com/gogf/gf/graphs/contributors">
|
||||
<img src="https://goframe.org/img/contributors.svg?version=v2.9.5" alt="goframe contributors"/>
|
||||
</a>
|
||||
|
||||
## 许可证
|
||||
|
||||
`GoFrame` 采用 [MIT License](LICENSE) 许可,100% 免费和开源,永久保持。
|
||||
@ -1,3 +1,5 @@
|
||||
English | [简体中文](README.zh_CN.MD)
|
||||
|
||||
# gf
|
||||
|
||||
`gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience.
|
||||
@ -21,18 +23,18 @@ You can also install `gf` tool using pre-built binaries: <https://github.com/gog
|
||||
|
||||
3. Database support
|
||||
|
||||
| DB | builtin support | remarks |
|
||||
|:----------:|:---------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||
| mysql | yes | - |
|
||||
| mariadb | yes | - |
|
||||
| tidb | yes | - |
|
||||
| mssql | yes | - |
|
||||
| oracle | yes | - |
|
||||
| pgsql | yes | - |
|
||||
| sqlite | yes | - |
|
||||
| sqlitecgo | no | to support sqlite database on 32bit architecture systems, manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| clickhouse | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| dm | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| DB | builtin support | remarks |
|
||||
| :--------: | :-------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| mysql | yes | - |
|
||||
| mariadb | yes | - |
|
||||
| tidb | yes | - |
|
||||
| mssql | yes | - |
|
||||
| oracle | yes | - |
|
||||
| pgsql | yes | - |
|
||||
| sqlite | yes | - |
|
||||
| sqlitecgo | no | to support sqlite database on 32bit architecture systems, manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
| clickhouse | yes | - |
|
||||
| dm | no | manually add package import to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
|
||||
|
||||
## 2) Manually Install
|
||||
|
||||
@ -43,30 +45,31 @@ go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 # certain version(should be >= v2
|
||||
|
||||
## 2. Commands
|
||||
|
||||
```html
|
||||
$ gf
|
||||
```shell
|
||||
$ gf -h
|
||||
USAGE
|
||||
gf COMMAND [OPTION]
|
||||
|
||||
COMMAND
|
||||
up upgrade GoFrame version/tool to latest one in current project
|
||||
env show current Golang environment variables
|
||||
fix auto fixing codes after upgrading to new GoFrame version
|
||||
run running go codes with hot-compiled-like feature
|
||||
gen automatically generate go files for dao/do/entity/pb/pbentity
|
||||
tpl template parsing and building commands
|
||||
init create and initialize an empty GoFrame project
|
||||
pack packing any file/directory to a resource file, or a go file
|
||||
build cross-building go project for lots of platforms
|
||||
docker build docker image for current GoFrame project
|
||||
install install gf binary to system (might need root/admin permission)
|
||||
version show version information of current binary
|
||||
up upgrade GoFrame version/tool to latest one in current project
|
||||
env show current Golang environment variables
|
||||
fix auto fixing codes after upgrading to new GoFrame version
|
||||
run running go codes with hot-compiled-like feature
|
||||
gen automatically generate go files for dao/do/entity/pb/pbentity
|
||||
tpl template parsing and building commands
|
||||
init create and initialize an empty GoFrame project
|
||||
pack packing any file/directory to a resource file, or a go file
|
||||
build cross-building go project for lots of platforms
|
||||
docker build docker image for current GoFrame project
|
||||
install install gf binary to system (might need root/admin permission)
|
||||
version show version information of current binary
|
||||
doc download https://pages.goframe.org/ to run locally
|
||||
|
||||
OPTION
|
||||
-y, --yes all yes for all command without prompt ask
|
||||
-v, --version show version information of current binary
|
||||
-d, --debug show internal detailed debugging information
|
||||
-h, --help more information about this command
|
||||
-y, --yes all yes for all command without prompt ask
|
||||
-v, --version show version information of current binary
|
||||
-d, --debug show internal detailed debugging information
|
||||
-h, --help more information about this command
|
||||
|
||||
ADDITIONAL
|
||||
Use "gf COMMAND -h" for details about a command.
|
||||
|
||||
82
cmd/gf/README.zh_CN.MD
Normal file
82
cmd/gf/README.zh_CN.MD
Normal file
@ -0,0 +1,82 @@
|
||||
[English](README.MD) | 简体中文
|
||||
|
||||
# gf
|
||||
|
||||
`gf` 是一个强大的 CLI 工具,用于便捷地构建 [GoFrame](https://goframe.org) 应用程序。
|
||||
|
||||
## 1. 安装
|
||||
|
||||
## 1) 预编译二进制文件
|
||||
|
||||
您也可以使用预构建的二进制文件安装 `gf` 工具:<https://github.com/gogf/gf/releases>
|
||||
|
||||
1. `Mac` & `Linux`
|
||||
|
||||
```shell
|
||||
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf
|
||||
```
|
||||
|
||||
> 如果您使用 `zsh`,您可能需要通过命令 `alias gf=gf` 重命名别名以解决 `gf` 和 `git fetch` 之间的冲突。
|
||||
|
||||
2. `Windows`
|
||||
手动下载,在命令行中执行,然后按照说明操作。
|
||||
|
||||
3. 数据库支持
|
||||
|
||||
| 数据库 | 内置支持 | 说明 |
|
||||
| :--------: | :-------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| mysql | 是 | - |
|
||||
| mariadb | 是 | - |
|
||||
| tidb | 是 | - |
|
||||
| mssql | 是 | - |
|
||||
| oracle | 是 | - |
|
||||
| pgsql | 是 | - |
|
||||
| sqlite | 是 | - |
|
||||
| sqlitecgo | 否 | 要在 32 位架构系统上支持 sqlite 数据库,请手动向[源代码](./internal/cmd/cmd_gen_dao.go)添加包导入并进行构建。 |
|
||||
| clickhouse | 是 | - |
|
||||
| dm | 否 | 手动向[源代码](./internal/cmd/cmd_gen_dao.go)添加包导入并进行构建。 |
|
||||
|
||||
## 2) 手动安装
|
||||
|
||||
```shell
|
||||
go install github.com/gogf/gf/cmd/gf/v2@latest # 最新版本
|
||||
go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 # 特定版本(应该 >= v2.5.5)
|
||||
```
|
||||
|
||||
## 2. 命令
|
||||
|
||||
```shell
|
||||
$ gf -h
|
||||
用法
|
||||
gf 命令 [选项]
|
||||
|
||||
命令
|
||||
up 升级项目中的 GoFrame 版本/工具到最新版本
|
||||
env 显示当前 Golang 环境变量
|
||||
fix 升级到新 GoFrame 版本后自动修复代码
|
||||
run 运行 go 代码,具有热编译功能
|
||||
gen 自动生成 dao/do/entity/pb/pbentity 的 go 文件
|
||||
tpl 模板解析和构建命令
|
||||
init 创建并初始化一个空的 GoFrame 项目
|
||||
pack 将任何文件/目录打包到资源文件或 go 文件
|
||||
build 为多个平台交叉编译 go 项目
|
||||
docker 为当前 GoFrame 项目构建 docker 镜像
|
||||
install 将 gf 二进制文件安装到系统(可能需要 root/admin 权限)
|
||||
version 显示当前二进制文件的版本信息
|
||||
doc 下载 https://pages.goframe.org/ 本地运行
|
||||
|
||||
选项
|
||||
-y, --yes 对所有命令都使用 yes,不再提示
|
||||
-v, --version 显示当前二进制文件的版本信息
|
||||
-d, --debug 显示内部详细的调试信息
|
||||
-h, --help 显示此命令的更多信息
|
||||
|
||||
附加信息
|
||||
使用 "gf 命令 -h" 获取有关命令的详细信息。
|
||||
```
|
||||
|
||||
## 3. 常见问题
|
||||
|
||||
### 1). 命令 `gf run` 返回 `pipe: too many open files`
|
||||
|
||||
请使用 `ulimit -n 65535` 扩大系统配置以增加当前终端 shell 会话的最大打开文件数,然后再运行 `gf run`。
|
||||
@ -3,13 +3,13 @@ module github.com/gogf/gf/cmd/gf/v2
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5
|
||||
github.com/gogf/gf/v2 v2.9.5
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.8
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.8
|
||||
github.com/gogf/gf/v2 v2.9.8
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
|
||||
github.com/olekukonko/tablewriter v1.1.0
|
||||
github.com/schollz/progressbar/v3 v3.15.0
|
||||
@ -23,7 +23,7 @@ require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.0.15 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
|
||||
@ -27,8 +27,8 @@ 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
@ -46,20 +46,6 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5 h1:WTbuQvbtOSddi2GtiobM/FCRUr5god7yzlEQP7WGOM8=
|
||||
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5/go.mod h1:/lnvHd9+8VmjDLdgIczzRTJA1tZYY5AA8bdcaexi/ao=
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5 h1:hgkgzbi6j8tDf+UpjG01fLqnAchD3913RZ37ruY9TqE=
|
||||
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5/go.mod h1:Sy0DQNJ/xEL4snJyWqcflmYGr2jE8Tn9mynxqbe2Dds=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5 h1:0+ZBYhi4sqwxXwL+hIBpp06a7G4m5nmjskQ3NNb8qYc=
|
||||
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5/go.mod h1:vyB7J/uJcLCrHD5lfFBzxhEEMkePIRzfhd33EcsuLa0=
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5 h1:eCPD7oteF/gurCU5ZfAhFBU7E1vI85cnoHKRdckn9Vc=
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5/go.mod h1:e5sxdxw3OrAFB+4VdYt3Wbm0G7LP5wofNRKj3JHIots=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5 h1:FCgvTLBuhPX7HE6YyR/dbNASoiKv72jpVS5SqoYYJTw=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5/go.mod h1:/Lz1qzhYN3ogt5aMFoIfjp82SbeuzjpXCqQhFu4Z3MI=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5 h1:MooWqn5qLMfB105PlBvd2Z7J3KdVBsjYxULtb42u308=
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5/go.mod h1:pr68Q85xhRnJ5PhyGte/LiFJ/m+998RPjW+Jn+w+xYo=
|
||||
github.com/gogf/gf/v2 v2.9.5 h1:1scfOdHbMP854oQaiLejl+eL+c4xfuvtWmmZiDJxbKs=
|
||||
github.com/gogf/gf/v2 v2.9.5/go.mod h1:VUb5eyJKpvW77O/dXsbbLNO/Kjrg0UycIiq0lRiBjjo=
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f h1:7xfXR/BhG3JDqO1s45n65Oyx9t4E/UqDOXep6jXdLCM=
|
||||
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f/go.mod h1:HnYoio6S7VaFJdryKcD/r9HgX+4QzYfr00XiXUo/xz0=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
|
||||
@ -14,5 +14,9 @@ replace (
|
||||
github.com/gogf/gf/contrib/drivers/oracle/v2 => ../../contrib/drivers/oracle
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 => ../../contrib/drivers/pgsql
|
||||
github.com/gogf/gf/contrib/drivers/sqlite/v2 => ../../contrib/drivers/sqlite
|
||||
github.com/gogf/gf/contrib/drivers/mariadb/v2 => ../../contrib/drivers/mariadb
|
||||
github.com/gogf/gf/contrib/drivers/tidb/v2 => ../../contrib/drivers/tidb
|
||||
github.com/gogf/gf/contrib/drivers/oceanbase/v2 => ../../contrib/drivers/oceanbase
|
||||
github.com/gogf/gf/contrib/drivers/gaussdb/v2 => ../../contrib/drivers/gaussdb
|
||||
github.com/gogf/gf/v2 => ../../
|
||||
)
|
||||
|
||||
@ -7,9 +7,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@ -20,6 +22,7 @@ import (
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gtag"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/geninit"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/allyes"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
|
||||
@ -44,6 +47,13 @@ const (
|
||||
gf init my-project
|
||||
gf init my-mono-repo -m
|
||||
gf init my-mono-repo -a
|
||||
gf init my-project -u
|
||||
gf init my-project -g "github.com/myorg/myproject"
|
||||
gf init -r github.com/gogf/template-single my-project
|
||||
gf init -r github.com/gogf/examples/httpserver/jwt my-jwt
|
||||
gf init -r github.com/gogf/gf/cmd/gf/v2@v2.9.7 mygf
|
||||
gf init -r github.com/gogf/gf/cmd/gf/v2 mygf -s
|
||||
gf init -i
|
||||
`
|
||||
cInitNameBrief = `
|
||||
name for the project. It will create a folder with NAME in current directory.
|
||||
@ -55,6 +65,16 @@ The NAME will also be the module name for the project.
|
||||
cInitGitignore = ".gitignore"
|
||||
)
|
||||
|
||||
// defaultTemplates is the list of predefined templates for interactive selection
|
||||
var defaultTemplates = []struct {
|
||||
Name string
|
||||
Repo string
|
||||
Desc string
|
||||
}{
|
||||
{"template-single", "github.com/gogf/template-single", "Single project template"},
|
||||
{"template-mono", "github.com/gogf/template-mono", "Mono-repo project template"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cInitBrief`: cInitBrief,
|
||||
@ -64,17 +84,86 @@ func init() {
|
||||
}
|
||||
|
||||
type cInitInput struct {
|
||||
g.Meta `name:"init"`
|
||||
Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
|
||||
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
|
||||
MonoApp bool `name:"monoApp" short:"a" brief:"initialize a mono-repo-app instead a single-repo" orphan:"true"`
|
||||
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
|
||||
Module string `name:"module" short:"g" brief:"custom go module"`
|
||||
g.Meta `name:"init"`
|
||||
Name string `name:"NAME" arg:"true" brief:"{cInitNameBrief}"`
|
||||
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
|
||||
MonoApp bool `name:"monoApp" short:"a" brief:"initialize a mono-repo-app instead a single-repo" orphan:"true"`
|
||||
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
|
||||
Module string `name:"module" short:"g" brief:"custom go module"`
|
||||
Repo string `name:"repo" short:"r" brief:"remote repository URL for template download"`
|
||||
SelectVer bool `name:"select" short:"s" brief:"enable interactive version selection for remote template" orphan:"true"`
|
||||
Interactive bool `name:"interactive" short:"i" brief:"enable interactive mode to select template" orphan:"true"`
|
||||
}
|
||||
|
||||
type cInitOutput struct{}
|
||||
|
||||
func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
// Check if using remote template mode
|
||||
if in.Repo != "" || in.Interactive {
|
||||
return c.initFromRemote(ctx, in)
|
||||
}
|
||||
|
||||
// If no name provided and no remote mode, enter interactive mode
|
||||
if in.Name == "" {
|
||||
return c.initInteractive(ctx, in)
|
||||
}
|
||||
|
||||
// Default: use built-in template
|
||||
return c.initFromBuiltin(ctx, in)
|
||||
}
|
||||
|
||||
// initFromRemote initializes project from remote repository
|
||||
func (c cInit) initFromRemote(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
repo := in.Repo
|
||||
name := in.Name
|
||||
|
||||
// If interactive mode and no repo specified, let user select
|
||||
if in.Interactive && repo == "" {
|
||||
var modPath string
|
||||
var upgradeDeps bool
|
||||
repo, name, modPath, upgradeDeps, err = interactiveSelectTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if modPath != "" {
|
||||
in.Module = modPath
|
||||
}
|
||||
if upgradeDeps {
|
||||
in.Update = true
|
||||
}
|
||||
}
|
||||
|
||||
if repo == "" {
|
||||
return nil, fmt.Errorf("repository URL is required for remote template mode")
|
||||
}
|
||||
|
||||
// Default name to repo basename if empty
|
||||
if name == "" {
|
||||
name = gfile.Basename(repo)
|
||||
mlog.Printf("Using repository basename as project name: %s", name)
|
||||
}
|
||||
|
||||
mlog.Print("initializing from remote template...")
|
||||
|
||||
opts := &geninit.ProcessOptions{
|
||||
SelectVersion: in.SelectVer,
|
||||
ModulePath: in.Module,
|
||||
UpgradeDeps: in.Update,
|
||||
}
|
||||
|
||||
if err = geninit.Process(ctx, repo, name, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mlog.Print("initialization done!")
|
||||
if name != "" && name != "." {
|
||||
mlog.Printf(`you can now run "cd %s && gf run main.go" to start your journey, enjoy!`, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initFromBuiltin initializes project from built-in template
|
||||
func (c cInit) initFromBuiltin(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
var overwrote = false
|
||||
if !gfile.IsEmpty(in.Name) && !allyes.Check() {
|
||||
s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name)
|
||||
@ -149,6 +238,9 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
||||
return
|
||||
}
|
||||
|
||||
// Format the generated Go files.
|
||||
utils.GoFmt(in.Name)
|
||||
|
||||
// Update the GoFrame version.
|
||||
if in.Update {
|
||||
mlog.Print("update goframe...")
|
||||
@ -180,3 +272,170 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initInteractive enters interactive mode when no arguments provided
|
||||
func (c cInit) initInteractive(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
// Ask user which mode to use
|
||||
fmt.Println("\nPlease select initialization mode:")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
fmt.Println(" [1] Built-in template (default)")
|
||||
fmt.Println(" [2] Remote template")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
fmt.Print("Select mode [1-2] (default: 1): ")
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
if input == "2" {
|
||||
in.Interactive = true
|
||||
return c.initFromRemote(ctx, in)
|
||||
}
|
||||
|
||||
// Built-in template mode
|
||||
fmt.Println("\nPlease select project type:")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
fmt.Println(" [1] Single project (default)")
|
||||
fmt.Println(" [2] Mono-repo project")
|
||||
fmt.Println(" [3] Mono-repo app")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
fmt.Print("Select type [1-3] (default: 1): ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
switch input {
|
||||
case "2":
|
||||
in.Mono = true
|
||||
case "3":
|
||||
in.MonoApp = true
|
||||
}
|
||||
|
||||
// Get project name
|
||||
for {
|
||||
fmt.Print("Enter project name: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
in.Name = strings.TrimSpace(input)
|
||||
if in.Name != "" {
|
||||
break
|
||||
}
|
||||
fmt.Println("Project name cannot be empty")
|
||||
}
|
||||
|
||||
// Get module path (optional)
|
||||
fmt.Printf("Enter Go module path (leave empty to use \"%s\"): ", in.Name)
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
in.Module = strings.TrimSpace(input)
|
||||
|
||||
// Ask about update
|
||||
fmt.Print("Update to latest GoFrame version? [y/N]: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
mlog.Fatalf("failed to read input: %v", err)
|
||||
return
|
||||
}
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
in.Update = input == "y" || input == "yes"
|
||||
|
||||
fmt.Println()
|
||||
return c.initFromBuiltin(ctx, in)
|
||||
}
|
||||
|
||||
// interactiveSelectTemplate prompts user to select a template interactively
|
||||
func interactiveSelectTemplate() (repo, name, modPath string, upgradeDeps bool, err error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
// 1. Select template
|
||||
fmt.Println("\nPlease select a project template:")
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
for i, t := range defaultTemplates {
|
||||
fmt.Printf(" [%d] %s - %s\n", i+1, t.Name, t.Desc)
|
||||
}
|
||||
fmt.Printf(" [%d] Custom repository URL\n", len(defaultTemplates)+1)
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
for {
|
||||
fmt.Printf("Select template [1-%d]: ", len(defaultTemplates)+1)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read template selection: %w", err)
|
||||
}
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
idx, e := strconv.Atoi(input)
|
||||
if e != nil || idx < 1 || idx > len(defaultTemplates)+1 {
|
||||
fmt.Printf("Invalid selection, please enter a number between 1-%d\n", len(defaultTemplates)+1)
|
||||
continue
|
||||
}
|
||||
|
||||
if idx <= len(defaultTemplates) {
|
||||
repo = defaultTemplates[idx-1].Repo
|
||||
fmt.Printf("Selected: %s\n\n", repo)
|
||||
} else {
|
||||
// Custom URL
|
||||
fmt.Print("Enter repository URL: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read repository URL: %w", err)
|
||||
}
|
||||
repo = strings.TrimSpace(input)
|
||||
if repo == "" {
|
||||
fmt.Println("Repository URL cannot be empty")
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 2. Enter project name
|
||||
for {
|
||||
fmt.Print("Enter project name: ")
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read project name: %w", err)
|
||||
}
|
||||
name = strings.TrimSpace(input)
|
||||
if name == "" {
|
||||
fmt.Println("Project name cannot be empty")
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 3. Enter module path (optional)
|
||||
fmt.Printf("Enter Go module path (leave empty to use \"%s\"): ", name)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read module path: %w", err)
|
||||
}
|
||||
modPath = strings.TrimSpace(input)
|
||||
|
||||
// 4. Ask about upgrade
|
||||
fmt.Print("Upgrade dependencies to latest (go get -u)? [y/N]: ")
|
||||
input, err = reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", "", "", false, fmt.Errorf("failed to read upgrade confirmation: %w", err)
|
||||
}
|
||||
input = strings.TrimSpace(strings.ToLower(input))
|
||||
upgradeDeps = input == "y" || input == "yes"
|
||||
|
||||
fmt.Println()
|
||||
return repo, name, modPath, upgradeDeps, nil
|
||||
}
|
||||
|
||||
@ -33,12 +33,18 @@ type cRun struct {
|
||||
g.Meta `name:"run" usage:"{cRunUsage}" brief:"{cRunBrief}" eg:"{cRunEg}" dc:"{cRunDc}"`
|
||||
}
|
||||
|
||||
type watchPath struct {
|
||||
Path string
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
type cRunApp struct {
|
||||
File string // Go run file name.
|
||||
Path string // Directory storing built binary.
|
||||
Options string // Extra "go run" options.
|
||||
Args string // Custom arguments.
|
||||
WatchPaths []string // Watch paths for live reload.
|
||||
File string // Go run file name.
|
||||
Path string // Directory storing built binary.
|
||||
Options string // Extra "go run" options.
|
||||
Args string // Custom arguments.
|
||||
WatchPaths []string // Watch paths for live reload.
|
||||
IgnorePatterns []string // Custom ignore patterns.
|
||||
}
|
||||
|
||||
const (
|
||||
@ -48,43 +54,47 @@ const (
|
||||
gf run main.go
|
||||
gf run main.go --args "server -p 8080"
|
||||
gf run main.go -mod=vendor
|
||||
gf run main.go -w "manifest/config/*.yaml"
|
||||
gf run main.go -w internal,api
|
||||
gf run main.go -i ".git,node_modules"
|
||||
`
|
||||
cRunDc = `
|
||||
The "run" command is used for running go codes with hot-compiled-like feature,
|
||||
which compiles and runs the go codes asynchronously when codes change.
|
||||
`
|
||||
cRunFileBrief = `building file path.`
|
||||
cRunPathBrief = `output directory path for built binary file. it's "./" in default`
|
||||
cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
|
||||
cRunArgsBrief = `custom arguments for your process`
|
||||
cRunWatchPathsBrief = `watch additional paths for live reload, separated by ",". i.e. "manifest/config/*.yaml"`
|
||||
cRunFileBrief = `building file path.`
|
||||
cRunPathBrief = `output directory path for built binary file. it's "./" in default`
|
||||
cRunExtraBrief = `the same options as "go run"/"go build" except some options as follows defined`
|
||||
cRunArgsBrief = `custom arguments for your process`
|
||||
cRunWatchPathsBrief = `watch additional paths for live reload, separated by ",". i.e. "internal,api"`
|
||||
cRunIgnorePatternBrief = `custom ignore patterns for watch, separated by ",". i.e. ".git,node_modules". default patterns: node_modules, vendor, .*, _*. Glob syntax: "*" matches any chars, "?" matches single char, "[abc]" matches char class. Note: patterns match directory names only, not paths`
|
||||
)
|
||||
|
||||
var process *gproc.Process
|
||||
|
||||
func init() {
|
||||
gtag.Sets(g.MapStrStr{
|
||||
`cRunUsage`: cRunUsage,
|
||||
`cRunBrief`: cRunBrief,
|
||||
`cRunEg`: cRunEg,
|
||||
`cRunDc`: cRunDc,
|
||||
`cRunFileBrief`: cRunFileBrief,
|
||||
`cRunPathBrief`: cRunPathBrief,
|
||||
`cRunExtraBrief`: cRunExtraBrief,
|
||||
`cRunArgsBrief`: cRunArgsBrief,
|
||||
`cRunWatchPathsBrief`: cRunWatchPathsBrief,
|
||||
`cRunUsage`: cRunUsage,
|
||||
`cRunBrief`: cRunBrief,
|
||||
`cRunEg`: cRunEg,
|
||||
`cRunDc`: cRunDc,
|
||||
`cRunFileBrief`: cRunFileBrief,
|
||||
`cRunPathBrief`: cRunPathBrief,
|
||||
`cRunExtraBrief`: cRunExtraBrief,
|
||||
`cRunArgsBrief`: cRunArgsBrief,
|
||||
`cRunWatchPathsBrief`: cRunWatchPathsBrief,
|
||||
`cRunIgnorePatternBrief`: cRunIgnorePatternBrief,
|
||||
})
|
||||
}
|
||||
|
||||
type (
|
||||
cRunInput struct {
|
||||
g.Meta `name:"run" config:"gfcli.run"`
|
||||
File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
|
||||
Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
|
||||
Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
|
||||
Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
|
||||
WatchPaths []string `name:"watchPaths" short:"w" brief:"{cRunWatchPathsBrief}"`
|
||||
g.Meta `name:"run" config:"gfcli.run"`
|
||||
File string `name:"FILE" arg:"true" brief:"{cRunFileBrief}" v:"required"`
|
||||
Path string `name:"path" short:"p" brief:"{cRunPathBrief}" d:"./"`
|
||||
Extra string `name:"extra" short:"e" brief:"{cRunExtraBrief}"`
|
||||
Args string `name:"args" short:"a" brief:"{cRunArgsBrief}"`
|
||||
WatchPaths []string `name:"watchPaths" short:"w" brief:"{cRunWatchPathsBrief}"`
|
||||
IgnorePatterns []string `name:"ignorePatterns" short:"i" brief:"{cRunIgnorePatternBrief}"`
|
||||
}
|
||||
cRunOutput struct{}
|
||||
)
|
||||
@ -101,17 +111,25 @@ func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err err
|
||||
mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
|
||||
}
|
||||
|
||||
if len(in.WatchPaths) == 1 {
|
||||
in.WatchPaths = strings.Split(in.WatchPaths[0], ",")
|
||||
// Parse comma-separated values in WatchPaths
|
||||
if len(in.WatchPaths) > 0 {
|
||||
in.WatchPaths = parseCommaSeparatedArgs(in.WatchPaths)
|
||||
mlog.Printf("watchPaths: %v", in.WatchPaths)
|
||||
}
|
||||
|
||||
// Parse comma-separated values in IgnorePatterns
|
||||
if len(in.IgnorePatterns) > 0 {
|
||||
in.IgnorePatterns = parseCommaSeparatedArgs(in.IgnorePatterns)
|
||||
mlog.Printf("ignorePatterns: %v", in.IgnorePatterns)
|
||||
}
|
||||
|
||||
app := &cRunApp{
|
||||
File: in.File,
|
||||
Path: filepath.FromSlash(in.Path),
|
||||
Options: in.Extra,
|
||||
Args: in.Args,
|
||||
WatchPaths: in.WatchPaths,
|
||||
File: in.File,
|
||||
Path: filepath.FromSlash(in.Path),
|
||||
Options: in.Extra,
|
||||
Args: in.Args,
|
||||
WatchPaths: in.WatchPaths,
|
||||
IgnorePatterns: in.IgnorePatterns,
|
||||
}
|
||||
dirty := gtype.NewBool()
|
||||
|
||||
@ -121,6 +139,7 @@ func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err err
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the file extension is 'go'.
|
||||
if gfile.ExtName(event.Path) != "go" {
|
||||
return
|
||||
}
|
||||
@ -138,15 +157,11 @@ func (c cRun) Index(ctx context.Context, in cRunInput) (out *cRunOutput, err err
|
||||
})
|
||||
}
|
||||
|
||||
if len(app.WatchPaths) > 0 {
|
||||
for _, path := range app.WatchPaths {
|
||||
_, err = gfsnotify.Add(gfile.RealPath(path), callbackFunc)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err = gfsnotify.Add(gfile.RealPath("."), callbackFunc)
|
||||
// Get directories to watch (recursive or non-recursive monitoring).
|
||||
watchPaths := app.getWatchPaths()
|
||||
for _, wp := range watchPaths {
|
||||
option := gfsnotify.WatchOption{NoRecursive: !wp.Recursive}
|
||||
_, err = gfsnotify.Add(wp.Path, callbackFunc, option)
|
||||
if err != nil {
|
||||
mlog.Fatal(err)
|
||||
}
|
||||
@ -249,35 +264,181 @@ func (app *cRunApp) End(ctx context.Context, sig os.Signal, outputPath string) {
|
||||
}
|
||||
|
||||
func (app *cRunApp) genOutputPath() (outputPath string) {
|
||||
var renamePath string
|
||||
outputPath = gfile.Join(app.Path, gfile.Name(app.File))
|
||||
if runtime.GOOS == "windows" {
|
||||
outputPath += ".exe"
|
||||
if gfile.Exists(outputPath) {
|
||||
renamePath = outputPath + "~"
|
||||
renamePath := outputPath + "~"
|
||||
if err := gfile.Rename(outputPath, renamePath); err != nil {
|
||||
mlog.Print(err)
|
||||
}
|
||||
// Clean up the renamed old binary file
|
||||
defer func() {
|
||||
if gfile.Exists(renamePath) {
|
||||
_ = gfile.Remove(renamePath)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
return filepath.FromSlash(outputPath)
|
||||
}
|
||||
|
||||
func matchWatchPaths(watchPaths []string, eventPath string) bool {
|
||||
for _, path := range watchPaths {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
|
||||
// getWatchPaths uses DFS to find the minimal set of directories to watch.
|
||||
// Rule: if a directory and all its descendants have no ignored subdirectories, watch it;
|
||||
// otherwise, recurse into valid children and watch the current directory non-recursively.
|
||||
func (app *cRunApp) getWatchPaths() []watchPath {
|
||||
roots := []string{"."}
|
||||
if len(app.WatchPaths) > 0 {
|
||||
roots = app.WatchPaths
|
||||
}
|
||||
|
||||
// Use custom ignore patterns if provided, otherwise use default.
|
||||
ignorePatterns := defaultIgnorePatterns
|
||||
if len(app.IgnorePatterns) > 0 {
|
||||
ignorePatterns = app.IgnorePatterns
|
||||
}
|
||||
|
||||
var watchPaths []watchPath
|
||||
|
||||
for _, root := range roots {
|
||||
absRoot := gfile.RealPath(root)
|
||||
if absRoot == "" {
|
||||
mlog.Printf("watch path '%s' not found, skipping", root)
|
||||
continue
|
||||
}
|
||||
matched, err := filepath.Match(absPath, eventPath)
|
||||
if err != nil {
|
||||
mlog.Printf("match watchPath '%s' error: %s", path, err.Error())
|
||||
if isIgnoredDirName(absRoot, ignorePatterns) {
|
||||
continue
|
||||
}
|
||||
if matched {
|
||||
app.collectWatchPaths(absRoot, ignorePatterns, &watchPaths)
|
||||
}
|
||||
|
||||
if len(watchPaths) == 0 {
|
||||
mlog.Printf("no directories to watch, using current directory")
|
||||
if absCur := gfile.RealPath("."); absCur != "" {
|
||||
return []watchPath{{Path: absCur, Recursive: true}}
|
||||
}
|
||||
return []watchPath{{Path: ".", Recursive: true}}
|
||||
}
|
||||
|
||||
mlog.Printf("watching %d paths", len(watchPaths))
|
||||
for _, wp := range watchPaths {
|
||||
recursiveStr := "recursive"
|
||||
if !wp.Recursive {
|
||||
recursiveStr = "non-recursive"
|
||||
}
|
||||
mlog.Debugf(" - %s (%s)", wp.Path, recursiveStr)
|
||||
}
|
||||
return watchPaths
|
||||
}
|
||||
|
||||
// collectWatchPaths performs a DFS traversal to collect the minimal set of directories to watch.
|
||||
// Returns true if the directory or any of its descendants contains ignored directories.
|
||||
// Rule: if a directory has no ignored descendants at any depth, watch it recursively;
|
||||
// otherwise, watch it non-recursively and recurse into valid children.
|
||||
func (app *cRunApp) collectWatchPaths(dir string, ignorePatterns []string, watchPaths *[]watchPath) bool {
|
||||
entries, err := gfile.ScanDir(dir, "*", false)
|
||||
if err != nil {
|
||||
mlog.Printf("scan directory '%s' error: %s", dir, err.Error())
|
||||
// If we can't scan the directory, add it to watch list as fallback
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: true})
|
||||
return false
|
||||
}
|
||||
|
||||
// First pass: identify valid subdirectories and check for directly ignored children
|
||||
var validSubDirs []string
|
||||
hasIgnoredChild := false
|
||||
for _, entry := range entries {
|
||||
if !gfile.IsDir(entry) {
|
||||
continue
|
||||
}
|
||||
if isIgnoredDirName(entry, ignorePatterns) {
|
||||
hasIgnoredChild = true
|
||||
} else {
|
||||
validSubDirs = append(validSubDirs, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// If already has ignored child, we know this dir needs non-recursive watch
|
||||
if hasIgnoredChild {
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: false})
|
||||
for _, subDir := range validSubDirs {
|
||||
app.collectWatchPaths(subDir, ignorePatterns, watchPaths)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// No ignored children, but need to check descendants recursively
|
||||
// Collect results from all subdirectories first
|
||||
subResults := make([]bool, len(validSubDirs))
|
||||
subWatchPaths := make([][]watchPath, len(validSubDirs))
|
||||
hasIgnoredDescendant := false
|
||||
|
||||
for i, subDir := range validSubDirs {
|
||||
var subPaths []watchPath
|
||||
subResults[i] = app.collectWatchPaths(subDir, ignorePatterns, &subPaths)
|
||||
subWatchPaths[i] = subPaths
|
||||
if subResults[i] {
|
||||
hasIgnoredDescendant = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasIgnoredDescendant {
|
||||
// No ignored descendants at any depth, watch this directory recursively
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: true})
|
||||
return false
|
||||
}
|
||||
|
||||
// Has ignored descendants, watch current directory non-recursively
|
||||
// and add all collected subdirectory watch paths
|
||||
*watchPaths = append(*watchPaths, watchPath{Path: dir, Recursive: false})
|
||||
for _, subPaths := range subWatchPaths {
|
||||
*watchPaths = append(*watchPaths, subPaths...)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// defaultIgnorePatterns contains glob patterns for directory names that should be ignored when watching.
|
||||
// These directories typically contain third-party code or non-source files.
|
||||
// Supported glob syntax (filepath.Match):
|
||||
// - "*" matches any sequence of non-separator characters
|
||||
// - "?" matches any single non-separator character
|
||||
// - "[abc]" matches any character in the bracket
|
||||
// - "[a-z]" matches any character in the range
|
||||
// - "[^abc]" or "[!abc]" matches any character not in the bracket
|
||||
//
|
||||
// Note: patterns match directory base names only, not full paths (no "/" or path separators allowed).
|
||||
var defaultIgnorePatterns = []string{
|
||||
"node_modules",
|
||||
"vendor",
|
||||
".*", // All hidden directories (covers .git, .svn, .hg, .idea, .vscode, etc.)
|
||||
"_*", // Directories starting with underscore
|
||||
}
|
||||
|
||||
// isIgnoredDirName checks if a directory name matches any ignored pattern.
|
||||
// It accepts either a full path or just the directory name, but only matches against the base name.
|
||||
// Note: patterns should not contain "/" as they only match directory names, not paths.
|
||||
func isIgnoredDirName(name string, ignorePatterns []string) bool {
|
||||
baseName := gfile.Basename(name)
|
||||
for _, pattern := range ignorePatterns {
|
||||
if matched, _ := filepath.Match(pattern, baseName); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseCommaSeparatedArgs parses command line arguments that may contain comma-separated values.
|
||||
// It handles both single argument with commas (e.g., "a,b,c") and multiple arguments.
|
||||
func parseCommaSeparatedArgs(args []string) []string {
|
||||
var result []string
|
||||
for _, arg := range args {
|
||||
parts := strings.Split(arg, ",")
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ func Test_Gen_Ctrl_Default(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
path = gfile.Temp(guid.S())
|
||||
apiFolder = gtest.DataPath("genctrl", "api")
|
||||
apiFolder = gtest.DataPath("genctrl", "default", "api")
|
||||
in = genctrl.CGenCtrlInput{
|
||||
SrcFolder: apiFolder,
|
||||
DstFolder: path,
|
||||
@ -39,7 +39,7 @@ func Test_Gen_Ctrl_Default(t *testing.T) {
|
||||
|
||||
err = gfile.Mkdir(path)
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(path)
|
||||
defer gfile.RemoveAll(path)
|
||||
|
||||
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
|
||||
t.AssertNil(err)
|
||||
@ -49,7 +49,7 @@ func Test_Gen_Ctrl_Default(t *testing.T) {
|
||||
genApi = apiFolder + filepath.FromSlash("/article/article.go")
|
||||
genApiExpect = apiFolder + filepath.FromSlash("/article/article_expect.go")
|
||||
)
|
||||
defer gfile.Remove(genApi)
|
||||
defer gfile.RemoveAll(genApi)
|
||||
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
|
||||
|
||||
// files
|
||||
@ -67,7 +67,7 @@ func Test_Gen_Ctrl_Default(t *testing.T) {
|
||||
})
|
||||
|
||||
// content
|
||||
testPath := gtest.DataPath("genctrl", "controller")
|
||||
testPath := gtest.DataPath("genctrl", "default", "controller")
|
||||
expectFiles := []string{
|
||||
testPath + filepath.FromSlash("/article/article.go"),
|
||||
testPath + filepath.FromSlash("/article/article_new.go"),
|
||||
@ -84,6 +84,104 @@ func Test_Gen_Ctrl_Default(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Gen_Ctrl_Default_Multi(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
path = gfile.Temp(guid.S())
|
||||
apiFolder = gtest.DataPath("genctrl", "multi", "api")
|
||||
in = genctrl.CGenCtrlInput{
|
||||
SrcFolder: apiFolder,
|
||||
DstFolder: path,
|
||||
WatchFile: "",
|
||||
SdkPath: "",
|
||||
SdkStdVersion: false,
|
||||
SdkNoV1: false,
|
||||
Clear: false,
|
||||
Merge: false,
|
||||
}
|
||||
)
|
||||
|
||||
err := gutil.FillStructWithDefault(&in)
|
||||
t.AssertNil(err)
|
||||
|
||||
err = gfile.Mkdir(path)
|
||||
t.AssertNil(err)
|
||||
defer gfile.RemoveAll(path)
|
||||
|
||||
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
|
||||
t.AssertNil(err)
|
||||
|
||||
// apiInterface file
|
||||
var (
|
||||
genApiSlice = []string{
|
||||
apiFolder + filepath.FromSlash("/admin/article/article.go"),
|
||||
apiFolder + filepath.FromSlash("/admin/user/user.go"),
|
||||
apiFolder + filepath.FromSlash("/app/user/user.go"),
|
||||
apiFolder + filepath.FromSlash("/app/user/user_ext/user_ext.go"),
|
||||
}
|
||||
genApiSliceExpect = []string{
|
||||
apiFolder + filepath.FromSlash("/admin/article/article_expect.go"),
|
||||
apiFolder + filepath.FromSlash("/admin/user/user_expect.go"),
|
||||
apiFolder + filepath.FromSlash("/app/user/user_expect.go"),
|
||||
apiFolder + filepath.FromSlash("/app/user/user_ext/user_ext_expect.go"),
|
||||
}
|
||||
)
|
||||
|
||||
for i := range genApiSlice {
|
||||
t.Assert(gfile.GetContents(genApiSlice[i]), gfile.GetContents(genApiSliceExpect[i]))
|
||||
gfile.RemoveAll(genApiSlice[i])
|
||||
}
|
||||
|
||||
// files
|
||||
files, err := gfile.ScanDir(path, "*.go", true)
|
||||
t.AssertNil(err)
|
||||
t.Assert(files, []string{
|
||||
path + filepath.FromSlash("/admin/article/article.go"),
|
||||
path + filepath.FromSlash("/admin/article/article_new.go"),
|
||||
path + filepath.FromSlash("/admin/article/article_v1_create.go"),
|
||||
|
||||
path + filepath.FromSlash("/admin/user/user.go"),
|
||||
path + filepath.FromSlash("/admin/user/user_new.go"),
|
||||
path + filepath.FromSlash("/admin/user/user_v1_create.go"),
|
||||
|
||||
path + filepath.FromSlash("/app/user/user.go"),
|
||||
path + filepath.FromSlash("/app/user/user_ext/user_ext.go"),
|
||||
path + filepath.FromSlash("/app/user/user_ext/user_ext_new.go"),
|
||||
path + filepath.FromSlash("/app/user/user_ext/user_ext_v1_create.go"),
|
||||
path + filepath.FromSlash("/app/user/user_ext/user_ext_v1_update.go"),
|
||||
|
||||
path + filepath.FromSlash("/app/user/user_new.go"),
|
||||
path + filepath.FromSlash("/app/user/user_v1_create.go"),
|
||||
path + filepath.FromSlash("/app/user/user_v1_update.go"),
|
||||
})
|
||||
|
||||
// content
|
||||
testPath := gtest.DataPath("genctrl", "multi", "controller")
|
||||
expectFiles := []string{
|
||||
testPath + filepath.FromSlash("/admin/article/article.go"),
|
||||
testPath + filepath.FromSlash("/admin/article/article_new.go"),
|
||||
testPath + filepath.FromSlash("/admin/article/article_v1_create.go"),
|
||||
|
||||
testPath + filepath.FromSlash("/admin/user/user.go"),
|
||||
testPath + filepath.FromSlash("/admin/user/user_new.go"),
|
||||
testPath + filepath.FromSlash("/admin/user/user_v1_create.go"),
|
||||
|
||||
testPath + filepath.FromSlash("/app/user/user.go"),
|
||||
testPath + filepath.FromSlash("/app/user/user_ext/user_ext.go"),
|
||||
testPath + filepath.FromSlash("/app/user/user_ext/user_ext_new.go"),
|
||||
testPath + filepath.FromSlash("/app/user/user_ext/user_ext_v1_create.go"),
|
||||
testPath + filepath.FromSlash("/app/user/user_ext/user_ext_v1_update.go"),
|
||||
|
||||
testPath + filepath.FromSlash("/app/user/user_new.go"),
|
||||
testPath + filepath.FromSlash("/app/user/user_v1_create.go"),
|
||||
testPath + filepath.FromSlash("/app/user/user_v1_update.go"),
|
||||
}
|
||||
for i := range files {
|
||||
t.Assert(gfile.GetContents(files[i]), gfile.GetContents(expectFiles[i]))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func expectFilesContent(t *gtest.T, paths []string, expectPaths []string) {
|
||||
for i, expectFile := range expectPaths {
|
||||
val := gfile.GetContents(paths[i])
|
||||
@ -98,8 +196,8 @@ func Test_Gen_Ctrl_UseMerge_AddNewFile(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
ctrlPath = gfile.Temp(guid.S())
|
||||
//ctrlPath = gtest.DataPath("issue", "3460", "controller")
|
||||
apiFolder = gtest.DataPath("genctrl-merge", "add_new_file", "api")
|
||||
// ctrlPath = gtest.DataPath("issue", "3460", "controller")
|
||||
apiFolder = gtest.DataPath("genctrl", "merge", "add_new_file", "api")
|
||||
in = genctrl.CGenCtrlInput{
|
||||
SrcFolder: apiFolder,
|
||||
DstFolder: ctrlPath,
|
||||
@ -118,7 +216,7 @@ type DictTypeAddRes struct {
|
||||
|
||||
err := gfile.Mkdir(ctrlPath)
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(ctrlPath)
|
||||
defer gfile.RemoveAll(ctrlPath)
|
||||
|
||||
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
|
||||
t.AssertNil(err)
|
||||
@ -127,7 +225,7 @@ type DictTypeAddRes struct {
|
||||
genApi = filepath.Join(apiFolder, "/dict/dict.go")
|
||||
genApiExpect = filepath.Join(apiFolder, "/dict/dict_expect.go")
|
||||
)
|
||||
defer gfile.Remove(genApi)
|
||||
defer gfile.RemoveAll(genApi)
|
||||
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
|
||||
|
||||
genCtrlFiles, err := gfile.ScanDir(ctrlPath, "*.go", true)
|
||||
@ -138,7 +236,7 @@ type DictTypeAddRes struct {
|
||||
filepath.Join(ctrlPath, "/dict/dict_v1_dict_type.go"),
|
||||
})
|
||||
|
||||
expectCtrlPath := gtest.DataPath("genctrl-merge", "add_new_file", "controller")
|
||||
expectCtrlPath := gtest.DataPath("genctrl", "merge", "add_new_file", "controller")
|
||||
expectFiles := []string{
|
||||
filepath.Join(expectCtrlPath, "/dict/dict.go"),
|
||||
filepath.Join(expectCtrlPath, "/dict/dict_new.go"),
|
||||
@ -152,7 +250,7 @@ type DictTypeAddRes struct {
|
||||
newApiFilePath := filepath.Join(apiFolder, "/dict/v1/test_new.go")
|
||||
err = gfile.PutContents(newApiFilePath, testNewApiFile)
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(newApiFilePath)
|
||||
defer gfile.RemoveAll(newApiFilePath)
|
||||
|
||||
// Then execute the command
|
||||
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
|
||||
@ -179,8 +277,8 @@ func Test_Gen_Ctrl_UseMerge_AddNewCtrl(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
ctrlPath = gfile.Temp(guid.S())
|
||||
//ctrlPath = gtest.DataPath("issue", "3460", "controller")
|
||||
apiFolder = gtest.DataPath("genctrl-merge", "add_new_ctrl", "api")
|
||||
// ctrlPath = gtest.DataPath("issue", "3460", "controller")
|
||||
apiFolder = gtest.DataPath("genctrl", "merge", "add_new_ctrl", "api")
|
||||
in = genctrl.CGenCtrlInput{
|
||||
SrcFolder: apiFolder,
|
||||
DstFolder: ctrlPath,
|
||||
@ -190,7 +288,7 @@ func Test_Gen_Ctrl_UseMerge_AddNewCtrl(t *testing.T) {
|
||||
|
||||
err := gfile.Mkdir(ctrlPath)
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(ctrlPath)
|
||||
defer gfile.RemoveAll(ctrlPath)
|
||||
|
||||
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
|
||||
t.AssertNil(err)
|
||||
@ -199,7 +297,7 @@ func Test_Gen_Ctrl_UseMerge_AddNewCtrl(t *testing.T) {
|
||||
genApi = filepath.Join(apiFolder, "/dict/dict.go")
|
||||
genApiExpect = filepath.Join(apiFolder, "/dict/dict_expect.go")
|
||||
)
|
||||
defer gfile.Remove(genApi)
|
||||
defer gfile.RemoveAll(genApi)
|
||||
t.Assert(gfile.GetContents(genApi), gfile.GetContents(genApiExpect))
|
||||
|
||||
genCtrlFiles, err := gfile.ScanDir(ctrlPath, "*.go", true)
|
||||
@ -210,7 +308,7 @@ func Test_Gen_Ctrl_UseMerge_AddNewCtrl(t *testing.T) {
|
||||
filepath.Join(ctrlPath, "/dict/dict_v1_dict_type.go"),
|
||||
})
|
||||
|
||||
expectCtrlPath := gtest.DataPath("genctrl-merge", "add_new_ctrl", "controller")
|
||||
expectCtrlPath := gtest.DataPath("genctrl", "merge", "add_new_ctrl", "controller")
|
||||
expectFiles := []string{
|
||||
filepath.Join(expectCtrlPath, "/dict/dict.go"),
|
||||
filepath.Join(expectCtrlPath, "/dict/dict_new.go"),
|
||||
@ -236,7 +334,7 @@ type DictTypeAddRes struct {
|
||||
err = gfile.PutContentsAppend(dictModuleFileName, testNewApiFile)
|
||||
t.AssertNil(err)
|
||||
|
||||
//==================================
|
||||
// ==================================
|
||||
// Then execute the command
|
||||
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
|
||||
t.AssertNil(err)
|
||||
@ -262,7 +360,7 @@ func Test_Gen_Ctrl_UseMerge_Issue3460(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
ctrlPath = gfile.Temp(guid.S())
|
||||
//ctrlPath = gtest.DataPath("issue", "3460", "controller")
|
||||
// ctrlPath = gtest.DataPath("issue", "3460", "controller")
|
||||
apiFolder = gtest.DataPath("issue", "3460", "api")
|
||||
in = genctrl.CGenCtrlInput{
|
||||
SrcFolder: apiFolder,
|
||||
@ -278,7 +376,7 @@ func Test_Gen_Ctrl_UseMerge_Issue3460(t *testing.T) {
|
||||
|
||||
err := gfile.Mkdir(ctrlPath)
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(ctrlPath)
|
||||
defer gfile.RemoveAll(ctrlPath)
|
||||
|
||||
_, err = genctrl.CGenCtrl{}.Ctrl(ctx, in)
|
||||
t.AssertNil(err)
|
||||
|
||||
336
cmd/gf/internal/cmd/cmd_z_unit_run_test.go
Normal file
336
cmd/gf/internal/cmd/cmd_z_unit_run_test.go
Normal file
@ -0,0 +1,336 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
func Test_cRunApp_getWatchPaths_Basic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{"."},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
for _, v := range watchPaths {
|
||||
t.Log(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_EmptyWatchPaths(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should default to current directory "."
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_CustomIgnorePattern(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{"testdata"},
|
||||
IgnorePatterns: []string{"2572"},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Ensure the "2572" directory is not watched directly.
|
||||
for _, wp := range watchPaths {
|
||||
t.Log("watch path:", wp)
|
||||
t.Assert(strings.HasSuffix(wp.Path, "2572"), false)
|
||||
}
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_WithIgnoredDirectories(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure for testing
|
||||
tempDir := gfile.Temp("gf_run_test")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure:
|
||||
// tempDir/
|
||||
// ├── src/
|
||||
// │ ├── api/
|
||||
// │ └── internal/
|
||||
// ├── vendor/ <-- ignored
|
||||
// └── node_modules/ <-- ignored
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "api"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "internal"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "vendor"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "node_modules"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch tempDir non-recursively (to catch top-level files) and src recursively
|
||||
t.Assert(len(watchPaths), 2)
|
||||
// First path is tempDir (non-recursive)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, false)
|
||||
// Second path is src (recursive, since it has no ignored descendants)
|
||||
t.Assert(watchPaths[1].Path, filepath.Join(tempDir, "src"))
|
||||
t.Assert(watchPaths[1].Recursive, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_NoIgnoredDirectories(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure without ignored directories
|
||||
tempDir := gfile.Temp("gf_run_test_no_ignore")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure without ignored patterns:
|
||||
// tempDir/
|
||||
// ├── src/
|
||||
// │ ├── api/
|
||||
// │ └── internal/
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "api"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "internal"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch the root directory recursively since no ignored directories exist
|
||||
t.Assert(len(watchPaths), 1)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_CustomIgnorePatterns(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure
|
||||
tempDir := gfile.Temp("gf_run_test_custom_ignore")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure:
|
||||
// tempDir/
|
||||
// ├── src/
|
||||
// │ ├── api/
|
||||
// │ └── internal/
|
||||
// ├── build/ <-- ignored
|
||||
// └── dist/ <-- ignored
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "api"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "src", "internal"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "build"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "dist"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
IgnorePatterns: []string{"build", "dist"},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch tempDir non-recursively and src recursively
|
||||
t.Assert(len(watchPaths), 2)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, false)
|
||||
t.Assert(watchPaths[1].Path, filepath.Join(tempDir, "src"))
|
||||
t.Assert(watchPaths[1].Recursive, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_DeepNestedStructure(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a deep nested directory structure
|
||||
tempDir := gfile.Temp("gf_run_test_deep")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create deep directory structure:
|
||||
// tempDir/
|
||||
// ├── a/
|
||||
// │ ├── b/
|
||||
// │ │ └── c/
|
||||
// │ └── vendor/ <-- ignored
|
||||
// └── d/
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "b", "c"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "vendor"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "d"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch individual valid directories due to ignored vendor directory
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
|
||||
// Verify that vendor directory is not in watch list
|
||||
for _, wp := range watchPaths {
|
||||
t.Assert(strings.Contains(wp.Path, "vendor"), false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_MultipleRoots(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create multiple temporary directories
|
||||
tempDir1 := gfile.Temp("gf_run_test_multi1")
|
||||
tempDir2 := gfile.Temp("gf_run_test_multi2")
|
||||
defer gfile.Remove(tempDir1)
|
||||
defer gfile.Remove(tempDir2)
|
||||
|
||||
gfile.Mkdir(filepath.Join(tempDir1, "src"))
|
||||
gfile.Mkdir(filepath.Join(tempDir2, "api"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir1, tempDir2},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should watch both root directories recursively
|
||||
t.Assert(len(watchPaths), 2)
|
||||
|
||||
// Both directories should be in the watch list
|
||||
foundDir1, foundDir2 := false, false
|
||||
for _, wp := range watchPaths {
|
||||
if wp.Path == tempDir1 {
|
||||
foundDir1 = true
|
||||
t.Assert(wp.Recursive, true)
|
||||
}
|
||||
if wp.Path == tempDir2 {
|
||||
foundDir2 = true
|
||||
t.Assert(wp.Recursive, true)
|
||||
}
|
||||
}
|
||||
t.Assert(foundDir1, true)
|
||||
t.Assert(foundDir2, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_NonExistentDirectory(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{"/non/existent/path"},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Should fall back to current directory when no valid paths found
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
|
||||
// Should contain current directory
|
||||
currentDir, _ := os.Getwd()
|
||||
foundCurrentDir := false
|
||||
for _, wp := range watchPaths {
|
||||
if wp.Path == currentDir {
|
||||
foundCurrentDir = true
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Assert(foundCurrentDir, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_isIgnoredDirName(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Test default ignore patterns
|
||||
t.Assert(isIgnoredDirName("node_modules", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName("vendor", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName(".git", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName("_private", defaultIgnorePatterns), true)
|
||||
t.Assert(isIgnoredDirName("src", defaultIgnorePatterns), false)
|
||||
t.Assert(isIgnoredDirName("api", defaultIgnorePatterns), false)
|
||||
|
||||
// Test custom ignore patterns
|
||||
customPatterns := []string{"build", "dist", "*.tmp"}
|
||||
t.Assert(isIgnoredDirName("build", customPatterns), true)
|
||||
t.Assert(isIgnoredDirName("dist", customPatterns), true)
|
||||
t.Assert(isIgnoredDirName("test.tmp", customPatterns), true)
|
||||
t.Assert(isIgnoredDirName("src", customPatterns), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_DeeplyNestedIgnore(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create a temporary directory structure with deeply nested ignored directory
|
||||
tempDir := gfile.Temp("gf_run_test_deeply_nested")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
// Create directory structure:
|
||||
// tempDir/
|
||||
// ├── a/
|
||||
// │ ├── b/
|
||||
// │ │ ├── c/
|
||||
// │ │ │ └── vendor/ <-- deeply nested ignored (4 levels)
|
||||
// │ │ └── d/
|
||||
// │ └── e/
|
||||
// └── f/
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "b", "c", "vendor"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "b", "d"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "a", "e"))
|
||||
gfile.Mkdir(filepath.Join(tempDir, "f"))
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Expected watch paths:
|
||||
// 1. tempDir (non-recursive) - has ignored descendant
|
||||
// 2. a (non-recursive) - has ignored descendant in b/c/vendor
|
||||
// 3. b (non-recursive) - has ignored descendant in c/vendor
|
||||
// 4. c (non-recursive) - has ignored child vendor
|
||||
// 5. d (recursive) - no ignored descendants
|
||||
// 6. e (recursive) - no ignored descendants
|
||||
// 7. f (recursive) - no ignored descendants
|
||||
|
||||
t.AssertGT(len(watchPaths), 0)
|
||||
|
||||
// Verify vendor is not in watch paths
|
||||
for _, wp := range watchPaths {
|
||||
t.Assert(strings.Contains(wp.Path, "vendor"), false)
|
||||
}
|
||||
|
||||
// Find specific paths and verify their recursive flags
|
||||
foundF := false
|
||||
for _, wp := range watchPaths {
|
||||
if wp.Path == filepath.Join(tempDir, "f") {
|
||||
foundF = true
|
||||
t.Assert(wp.Recursive, true) // f should be recursive (no ignored descendants)
|
||||
}
|
||||
}
|
||||
t.Assert(foundF, true)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_cRunApp_getWatchPaths_EmptyDirectory(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// Create an empty temporary directory
|
||||
tempDir := gfile.Temp("gf_run_test_empty")
|
||||
defer gfile.Remove(tempDir)
|
||||
|
||||
gfile.Mkdir(tempDir)
|
||||
|
||||
app := &cRunApp{
|
||||
WatchPaths: []string{tempDir},
|
||||
}
|
||||
watchPaths := app.getWatchPaths()
|
||||
|
||||
// Empty directory should be watched recursively (no ignored descendants)
|
||||
t.Assert(len(watchPaths), 1)
|
||||
t.Assert(watchPaths[0].Path, tempDir)
|
||||
t.Assert(watchPaths[0].Recursive, true)
|
||||
})
|
||||
}
|
||||
@ -89,28 +89,11 @@ func (c CGenCtrl) Ctrl(ctx context.Context, in CGenCtrlInput) (out *CGenCtrlOutp
|
||||
if !gfile.Exists(in.SrcFolder) {
|
||||
mlog.Fatalf(`source folder path "%s" does not exist`, in.SrcFolder)
|
||||
}
|
||||
// retrieve all api modules.
|
||||
apiModuleFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
|
||||
|
||||
err = c.generateByModules(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, apiModuleFolderPath := range apiModuleFolderPaths {
|
||||
if !gfile.IsDir(apiModuleFolderPath) {
|
||||
continue
|
||||
}
|
||||
// generate go files by api module.
|
||||
var (
|
||||
module = gfile.Basename(apiModuleFolderPath)
|
||||
dstModuleFolderPath = gfile.Join(in.DstFolder, module)
|
||||
)
|
||||
err = c.generateByModule(
|
||||
apiModuleFolderPath, dstModuleFolderPath, in.SdkPath,
|
||||
in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Print(`done!`)
|
||||
return
|
||||
@ -163,6 +146,56 @@ func (c CGenCtrl) generateByWatchFile(watchFile, sdkPath string, sdkStdVersion,
|
||||
)
|
||||
}
|
||||
|
||||
// generateByModules recursively calls generateByModule for multi-level modules generation.
|
||||
func (c CGenCtrl) generateByModules(in CGenCtrlInput) (err error) {
|
||||
// read root folder, example: api/user or api/app
|
||||
moduleFolderPaths, err := gfile.ScanDir(in.SrcFolder, "*", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, moduleFolder := range moduleFolderPaths {
|
||||
if !gfile.IsDir(moduleFolder) {
|
||||
continue
|
||||
}
|
||||
|
||||
// read children folder, example: api/user/v1 or api/app/user
|
||||
childrenFolderPaths, err := gfile.ScanDir(moduleFolder, "*", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, childrenFolderPath := range childrenFolderPaths {
|
||||
if !gfile.IsDir(childrenFolderPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
inCopy = in
|
||||
module = gfile.Basename(moduleFolder)
|
||||
)
|
||||
inCopy.SrcFolder = gfile.Join(in.SrcFolder, module)
|
||||
inCopy.DstFolder = gfile.Join(in.DstFolder, module)
|
||||
err = c.generateByModules(inCopy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// generate go files by api module.
|
||||
var (
|
||||
module = gfile.Basename(moduleFolder)
|
||||
dstModuleFolderPath = gfile.Join(in.DstFolder, module)
|
||||
)
|
||||
err = c.generateByModule(
|
||||
moduleFolder, dstModuleFolderPath, in.SdkPath,
|
||||
in.SdkStdVersion, in.SdkNoV1, in.Clear, in.Merge,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parseApiModule parses certain api and generate associated go files by certain module, not all api modules.
|
||||
func (c CGenCtrl) generateByModule(
|
||||
apiModuleFolderPath, dstModuleFolderPath, sdkPath string,
|
||||
|
||||
@ -8,6 +8,9 @@ package genctrl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -144,8 +147,8 @@ func (c *controllerGenerator) doGenerateCtrlItem(dstModuleFolderPath string, ite
|
||||
"{MethodName}": item.MethodName,
|
||||
"{MethodComment}": item.GetComment(),
|
||||
})
|
||||
|
||||
if gstr.Contains(gfile.GetContents(methodFilePath), fmt.Sprintf(`func (c *%v) %v(`, ctrlName, item.MethodName)) {
|
||||
// Use AST-based checking for more accurate method detection
|
||||
if methodExists(methodFilePath, ctrlName, item.MethodName) {
|
||||
return
|
||||
}
|
||||
if err = gfile.PutContentsAppend(methodFilePath, gstr.TrimLeft(content)); err != nil {
|
||||
@ -170,7 +173,6 @@ func (c *controllerGenerator) doGenerateCtrlItem(dstModuleFolderPath string, ite
|
||||
|
||||
// use -merge
|
||||
func (c *controllerGenerator) doGenerateCtrlMergeItem(dstModuleFolderPath string, apiItems []apiItem, doneApiSet *gset.StrSet) (err error) {
|
||||
|
||||
type controllerFileItem struct {
|
||||
module string
|
||||
version string
|
||||
@ -193,13 +195,23 @@ func (c *controllerGenerator) doGenerateCtrlMergeItem(dstModuleFolderPath string
|
||||
ctrlFileItemMap[api.FileName] = ctrlFileItem
|
||||
}
|
||||
|
||||
ctrlName := fmt.Sprintf(`Controller%s`, gstr.UcFirst(api.Version))
|
||||
ctrl := gstr.TrimLeft(gstr.ReplaceByMap(consts.TemplateGenCtrlControllerMethodFuncMerge, g.MapStrStr{
|
||||
"{Module}": api.Module,
|
||||
"{CtrlName}": fmt.Sprintf(`Controller%s`, gstr.UcFirst(api.Version)),
|
||||
"{CtrlName}": ctrlName,
|
||||
"{Version}": api.Version,
|
||||
"{MethodName}": api.MethodName,
|
||||
"{MethodComment}": api.GetComment(),
|
||||
}))
|
||||
|
||||
ctrlFilePath := gfile.Join(dstModuleFolderPath, fmt.Sprintf(
|
||||
`%s_%s_%s.go`, ctrlFileItem.module, ctrlFileItem.version, api.FileName,
|
||||
))
|
||||
// Use AST-based checking for more accurate method detection
|
||||
if methodExists(ctrlFilePath, ctrlName, api.MethodName) {
|
||||
return
|
||||
}
|
||||
|
||||
ctrlFileItem.controllers.WriteString(ctrl)
|
||||
doneApiSet.Add(api.String())
|
||||
}
|
||||
@ -229,3 +241,41 @@ func (c *controllerGenerator) doGenerateCtrlMergeItem(dstModuleFolderPath string
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// methodExists checks if a method with the given receiver type and name exists in the file.
|
||||
// It uses AST parsing to accurately detect method definitions regardless of formatting.
|
||||
// This handles various code formatting styles including multi-line method signatures.
|
||||
func methodExists(filePath, ctrlName, methodName string) bool {
|
||||
fset := token.NewFileSet()
|
||||
node, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
// If parsing fails (e.g., file doesn't exist or invalid syntax), return false
|
||||
return false
|
||||
}
|
||||
for _, decl := range node.Decls {
|
||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Check if it's a method (has receiver)
|
||||
if funcDecl.Recv != nil && len(funcDecl.Recv.List) > 0 {
|
||||
// Extract receiver type name
|
||||
// Handle both *T and T patterns
|
||||
recvType := ""
|
||||
switch t := funcDecl.Recv.List[0].Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
if ident, ok := t.X.(*ast.Ident); ok {
|
||||
recvType = ident.Name
|
||||
}
|
||||
case *ast.Ident:
|
||||
recvType = t.Name
|
||||
}
|
||||
|
||||
// Check if both receiver type and method name match
|
||||
if recvType == ctrlName && funcDecl.Name.Name == methodName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -104,6 +104,10 @@ var (
|
||||
"smallmoney": {
|
||||
Type: "float64",
|
||||
},
|
||||
"uuid": {
|
||||
Type: "uuid.UUID",
|
||||
Import: "github.com/google/uuid",
|
||||
},
|
||||
}
|
||||
|
||||
// tablewriter Options
|
||||
|
||||
@ -98,7 +98,6 @@ func generateStructFieldDefinition(
|
||||
err error
|
||||
localTypeName gdb.LocalType
|
||||
localTypeNameStr string
|
||||
jsonTag = gstr.CaseConvert(field.Name, gstr.CaseTypeMatch(in.JsonCase))
|
||||
)
|
||||
|
||||
if in.TypeMapping != nil && len(in.TypeMapping) > 0 {
|
||||
@ -156,6 +155,8 @@ func generateStructFieldDefinition(
|
||||
" #" + formatFieldName(newFiledName, FieldNameCaseCamel),
|
||||
" #" + localTypeNameStr,
|
||||
}
|
||||
|
||||
jsonTag := gstr.CaseConvert(newFiledName, gstr.CaseTypeMatch(in.JsonCase))
|
||||
attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag))
|
||||
// orm tag
|
||||
if !in.IsDo {
|
||||
|
||||
269
cmd/gf/internal/cmd/geninit/geninit.go
Normal file
269
cmd/gf/internal/cmd/geninit/geninit.go
Normal file
@ -0,0 +1,269 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// ProcessOptions contains options for the Process function
|
||||
type ProcessOptions struct {
|
||||
SelectVersion bool // Enable interactive version selection
|
||||
ModulePath string // Custom go.mod module path (e.g., github.com/xxx/xxx)
|
||||
UpgradeDeps bool // Upgrade dependencies to latest (go get -u ./...)
|
||||
}
|
||||
|
||||
// Process handles the template generation flow from remote repository
|
||||
func Process(ctx context.Context, repo, name string, opts *ProcessOptions) error {
|
||||
if opts == nil {
|
||||
opts = &ProcessOptions{}
|
||||
}
|
||||
|
||||
// 0. Check Go environment first
|
||||
mlog.Print("Checking Go environment...")
|
||||
goEnv, err := CheckGoEnv(ctx)
|
||||
if err != nil {
|
||||
mlog.Printf("Go environment check failed: %v", err)
|
||||
return err
|
||||
}
|
||||
mlog.Printf("Go environment OK (version: %s)", goEnv.GOVERSION)
|
||||
|
||||
// Check if this is a git subdirectory URL
|
||||
if IsSubdirRepo(repo) {
|
||||
return processGitSubdir(ctx, repo, name, opts)
|
||||
}
|
||||
|
||||
// Try Go module download first, fallback to git subdirectory if it fails
|
||||
// This handles edge cases where the heuristic may be incorrect
|
||||
err = processGoModule(ctx, repo, name, opts)
|
||||
if err != nil {
|
||||
mlog.Printf("Go module download failed, trying git subdirectory mode: %v", err)
|
||||
mlog.Print("Note: If this is a git subdirectory, you can force git mode by using a full git URL")
|
||||
|
||||
// If Go module download fails, try git subdirectory as fallback
|
||||
// This handles cases where the heuristic incorrectly classified a git subdir as Go module
|
||||
if IsSubdirRepo(repo) {
|
||||
mlog.Print("Falling back to git subdirectory download...")
|
||||
return processGitSubdir(ctx, repo, name, opts)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// processGoModule handles standard Go module download via go get
|
||||
func processGoModule(ctx context.Context, repo, name string, opts *ProcessOptions) error {
|
||||
// Extract module path (without version)
|
||||
modulePath := repo
|
||||
specifiedVersion := ""
|
||||
if gstr.Contains(repo, "@") {
|
||||
parts := gstr.Split(repo, "@")
|
||||
modulePath = parts[0]
|
||||
specifiedVersion = parts[1]
|
||||
}
|
||||
|
||||
// Default name to repo basename if empty
|
||||
if name == "" {
|
||||
name = filepath.Base(modulePath)
|
||||
}
|
||||
|
||||
// Determine the target module path for go.mod
|
||||
targetModulePath := name
|
||||
if opts.ModulePath != "" {
|
||||
targetModulePath = opts.ModulePath
|
||||
}
|
||||
|
||||
// 1. Determine version to use
|
||||
var targetVersion string
|
||||
if specifiedVersion != "" {
|
||||
// User specified version, try to use it first
|
||||
targetVersion = specifiedVersion
|
||||
mlog.Printf("Using specified version: %s", targetVersion)
|
||||
} else if opts.SelectVersion {
|
||||
// Interactive version selection
|
||||
mlog.Print("Fetching available versions...")
|
||||
versionInfo, err := GetModuleVersions(ctx, modulePath)
|
||||
if err != nil {
|
||||
mlog.Printf("Failed to get versions: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
targetVersion, err = SelectVersion(ctx, versionInfo.Versions, modulePath)
|
||||
if err != nil {
|
||||
mlog.Printf("Version selection failed: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Default: use latest version
|
||||
mlog.Print("Fetching latest version...")
|
||||
latest, err := GetLatestVersion(ctx, modulePath)
|
||||
if err != nil {
|
||||
mlog.Printf("Failed to get latest version, will try @latest tag: %v", err)
|
||||
targetVersion = "latest"
|
||||
} else {
|
||||
targetVersion = latest
|
||||
mlog.Printf("Latest version: %s", targetVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Download Template with determined version
|
||||
repoWithVersion := modulePath + "@" + targetVersion
|
||||
srcDir, err := downloadTemplate(ctx, repoWithVersion)
|
||||
if err != nil {
|
||||
// If specified version download failed, offer to select from available versions
|
||||
if specifiedVersion != "" {
|
||||
mlog.Printf("Failed to download specified version '%s': %v", specifiedVersion, err)
|
||||
mlog.Print("Fetching available versions...")
|
||||
|
||||
versionInfo, verErr := GetModuleVersions(ctx, modulePath)
|
||||
if verErr != nil {
|
||||
mlog.Printf("Failed to get available versions: %v", verErr)
|
||||
return err // Return original download error
|
||||
}
|
||||
|
||||
if len(versionInfo.Versions) == 0 {
|
||||
mlog.Print("No versions available for this module")
|
||||
return err
|
||||
}
|
||||
|
||||
// Let user select from available versions
|
||||
selectedVersion, selErr := SelectVersion(ctx, versionInfo.Versions, modulePath)
|
||||
if selErr != nil {
|
||||
mlog.Printf("Version selection failed: %v", selErr)
|
||||
return selErr
|
||||
}
|
||||
|
||||
// Retry download with selected version
|
||||
targetVersion = selectedVersion
|
||||
repoWithVersion = modulePath + "@" + targetVersion
|
||||
srcDir, err = downloadTemplate(ctx, repoWithVersion)
|
||||
if err != nil {
|
||||
mlog.Printf("Download failed: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
mlog.Printf("Download failed: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Debugf("Template located at: %s", srcDir)
|
||||
|
||||
// 3. Generate Project
|
||||
if err := generateProject(ctx, srcDir, name, modulePath, targetModulePath); err != nil {
|
||||
mlog.Printf("Generation failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Handle dependencies
|
||||
var projectDir string
|
||||
if name == "." {
|
||||
projectDir = gfile.Pwd()
|
||||
} else {
|
||||
projectDir = filepath.Join(gfile.Pwd(), name)
|
||||
}
|
||||
if opts.UpgradeDeps {
|
||||
// Upgrade all dependencies to latest
|
||||
if err := upgradeDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to upgrade dependencies: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Default: just tidy dependencies
|
||||
if err := tidyDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to tidy dependencies: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processGitSubdir handles git subdirectory download via sparse checkout
|
||||
func processGitSubdir(ctx context.Context, repo, name string, opts *ProcessOptions) error {
|
||||
mlog.Print("Detected subdirectory URL, using git sparse checkout...")
|
||||
|
||||
// Check if git is available
|
||||
gitVersion, err := CheckGitEnv(ctx)
|
||||
if err != nil {
|
||||
mlog.Printf("Git is required for subdirectory templates: %v", err)
|
||||
return err
|
||||
}
|
||||
mlog.Printf("Git available (%s)", gitVersion)
|
||||
|
||||
// Download via git sparse checkout
|
||||
srcDir, gitInfo, err := downloadGitSubdir(ctx, repo)
|
||||
if err != nil {
|
||||
mlog.Printf("Git download failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean up temp directory after generation
|
||||
// The temp dir is parent of parent of srcDir (tempDir/repo/subpath)
|
||||
tempDir := filepath.Dir(filepath.Dir(srcDir))
|
||||
if tempDir != "" && gfile.Exists(tempDir) && gstr.Contains(tempDir, "gf-init-git") {
|
||||
defer func() {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory %s: %v", tempDir, err)
|
||||
} else {
|
||||
mlog.Debugf("Cleaned up temp directory: %s", tempDir)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Default name to subpath basename if empty
|
||||
if name == "" {
|
||||
name = filepath.Base(gitInfo.SubPath)
|
||||
}
|
||||
|
||||
// Get original module name from go.mod (might be "main" or something else)
|
||||
oldModule := GetModuleNameFromGoMod(srcDir)
|
||||
if oldModule == "" {
|
||||
// Fallback: construct from git info
|
||||
oldModule = gitInfo.Host + "/" + gitInfo.Owner + "/" + gitInfo.Repo + "/" + gitInfo.SubPath
|
||||
}
|
||||
|
||||
// Determine the target module path for go.mod
|
||||
targetModulePath := name
|
||||
if opts.ModulePath != "" {
|
||||
targetModulePath = opts.ModulePath
|
||||
}
|
||||
|
||||
mlog.Debugf("Template located at: %s", srcDir)
|
||||
mlog.Debugf("Original module: %s", oldModule)
|
||||
|
||||
// Generate Project
|
||||
if err := generateProject(ctx, srcDir, name, oldModule, targetModulePath); err != nil {
|
||||
mlog.Printf("Generation failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle dependencies
|
||||
var projectDir string
|
||||
if name == "." {
|
||||
projectDir = gfile.Pwd()
|
||||
} else {
|
||||
projectDir = filepath.Join(gfile.Pwd(), name)
|
||||
}
|
||||
if opts.UpgradeDeps {
|
||||
// Upgrade all dependencies to latest
|
||||
if err := upgradeDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to upgrade dependencies: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Default: just tidy dependencies
|
||||
if err := tidyDependencies(ctx, projectDir); err != nil {
|
||||
mlog.Printf("Failed to tidy dependencies: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
125
cmd/gf/internal/cmd/geninit/geninit_ast.go
Normal file
125
cmd/gf/internal/cmd/geninit/geninit_ast.go
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// ASTReplacer handles import path replacement using Go AST
|
||||
type ASTReplacer struct {
|
||||
oldModule string
|
||||
newModule string
|
||||
fset *token.FileSet
|
||||
}
|
||||
|
||||
// NewASTReplacer creates a new AST-based import replacer
|
||||
func NewASTReplacer(oldModule, newModule string) *ASTReplacer {
|
||||
return &ASTReplacer{
|
||||
oldModule: oldModule,
|
||||
newModule: newModule,
|
||||
fset: token.NewFileSet(),
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceInFile replaces import paths in a single Go file
|
||||
func (r *ASTReplacer) ReplaceInFile(ctx context.Context, filePath string) error {
|
||||
// Read file content
|
||||
content := gfile.GetContents(filePath)
|
||||
if content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse the file
|
||||
file, err := parser.ParseFile(r.fset, filePath, content, parser.ParseComments)
|
||||
if err != nil {
|
||||
mlog.Debugf("Failed to parse %s: %v", filePath, err)
|
||||
return nil // Skip files that can't be parsed
|
||||
}
|
||||
|
||||
// Track if any changes were made
|
||||
changed := false
|
||||
|
||||
// Traverse and modify imports
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
switch x := n.(type) {
|
||||
case *ast.ImportSpec:
|
||||
if x.Path != nil {
|
||||
importPath := strings.Trim(x.Path.Value, `"`)
|
||||
if strings.HasPrefix(importPath, r.oldModule) {
|
||||
// Replace only the leading module prefix for clarity and correctness.
|
||||
newPath := r.newModule + strings.TrimPrefix(importPath, r.oldModule)
|
||||
x.Path.Value = `"` + newPath + `"`
|
||||
changed = true
|
||||
mlog.Debugf("Replaced import: %s -> %s in %s", importPath, newPath, filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write back to file without formatting.
|
||||
// Formatting will be handled by formatGoFiles after all replacements are done.
|
||||
var buf bytes.Buffer
|
||||
if err := printer.Fprint(&buf, r.fset, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gfile.PutContents(filePath, buf.String())
|
||||
}
|
||||
|
||||
// ReplaceInDir replaces import paths in all Go files in a directory (recursively)
|
||||
func (r *ASTReplacer) ReplaceInDir(ctx context.Context, dir string) error {
|
||||
mlog.Printf("Replacing imports: %s -> %s", r.oldModule, r.newModule)
|
||||
|
||||
// Find all .go files
|
||||
files, err := findGoFiles(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if err := r.ReplaceInFile(ctx, file); err != nil {
|
||||
mlog.Printf("Failed to process %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findGoFiles recursively finds all .go files in a directory
|
||||
func findGoFiles(dir string) ([]string, error) {
|
||||
var files []string
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(path, ".go") {
|
||||
files = append(files, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return files, err
|
||||
}
|
||||
111
cmd/gf/internal/cmd/geninit/geninit_downloader.go
Normal file
111
cmd/gf/internal/cmd/geninit/geninit_downloader.go
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// downloadTemplate fetches the remote repository using go get
|
||||
func downloadTemplate(ctx context.Context, repo string) (string, error) {
|
||||
// 1. Create a temporary directory workspace
|
||||
tempDir := gfile.Temp("gf-init-cli")
|
||||
if tempDir == "" {
|
||||
return "", fmt.Errorf("failed to create temporary directory")
|
||||
}
|
||||
if err := gfile.Mkdir(tempDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory %s: %v", tempDir, err)
|
||||
}
|
||||
}() // Clean up the temp workspace
|
||||
|
||||
mlog.Debugf("Using temp workspace: %s", tempDir)
|
||||
|
||||
// 2. Initialize a temp go module to perform go get
|
||||
// We run commands inside the temp directory
|
||||
if err := runCmd(ctx, tempDir, "go", "mod", "init", "temp"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 3. Run go get <repo>
|
||||
// Try different version strategies: original -> @latest -> @master
|
||||
moduleName := repo
|
||||
if gstr.Contains(repo, "@") {
|
||||
moduleName = gstr.Split(repo, "@")[0]
|
||||
}
|
||||
|
||||
var downloadErrs []string
|
||||
versionsToTry := []string{repo}
|
||||
if !gstr.Contains(repo, "@") {
|
||||
versionsToTry = append(versionsToTry, repo+"@latest", repo+"@master")
|
||||
}
|
||||
|
||||
var successRepo string
|
||||
for _, tryRepo := range versionsToTry {
|
||||
mlog.Printf("Downloading template %s...", tryRepo)
|
||||
if err := runCmd(ctx, tempDir, "go", "get", tryRepo); err == nil {
|
||||
successRepo = tryRepo
|
||||
break
|
||||
} else {
|
||||
downloadErrs = append(downloadErrs, fmt.Sprintf("%s: %v", tryRepo, err))
|
||||
mlog.Debugf("Failed to download %s, trying next...", tryRepo)
|
||||
}
|
||||
}
|
||||
|
||||
if successRepo == "" {
|
||||
errMsg := "all download attempts failed"
|
||||
if len(downloadErrs) > 0 {
|
||||
errMsg = strings.Join(downloadErrs, "; ")
|
||||
}
|
||||
return "", fmt.Errorf("failed to download repo %s: %s", repo, errMsg)
|
||||
}
|
||||
|
||||
// 4. Find the local path using go list -m -json <repo>
|
||||
listCmd := exec.CommandContext(ctx, "go", "list", "-m", "-json", moduleName)
|
||||
listCmd.Dir = tempDir
|
||||
output, err := listCmd.Output()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
return "", fmt.Errorf("go list failed: %s", string(exitErr.Stderr))
|
||||
}
|
||||
return "", fmt.Errorf("failed to locate module path: %w", err)
|
||||
}
|
||||
|
||||
var modInfo struct {
|
||||
Dir string `json:"Dir"`
|
||||
}
|
||||
if err := json.Unmarshal(output, &modInfo); err != nil {
|
||||
return "", fmt.Errorf("failed to parse go list output: %w", err)
|
||||
}
|
||||
|
||||
if modInfo.Dir == "" {
|
||||
return "", fmt.Errorf("module directory not found for %s", repo)
|
||||
}
|
||||
|
||||
return modInfo.Dir, nil
|
||||
}
|
||||
|
||||
func runCmd(ctx context.Context, dir string, name string, args ...string) error {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
90
cmd/gf/internal/cmd/geninit/geninit_env.go
Normal file
90
cmd/gf/internal/cmd/geninit/geninit_env.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// GoEnv represents Go environment variables
|
||||
type GoEnv struct {
|
||||
GOVERSION string `json:"GOVERSION"`
|
||||
GOROOT string `json:"GOROOT"`
|
||||
GOPATH string `json:"GOPATH"`
|
||||
GOMODCACHE string `json:"GOMODCACHE"`
|
||||
GOPROXY string `json:"GOPROXY"`
|
||||
GO111MODULE string `json:"GO111MODULE"`
|
||||
}
|
||||
|
||||
// CheckGoEnv verifies Go is installed and properly configured
|
||||
func CheckGoEnv(ctx context.Context) (*GoEnv, error) {
|
||||
// 1. Check if go binary exists
|
||||
goPath, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("go is not installed or not in PATH: %w", err)
|
||||
}
|
||||
mlog.Debugf("Found go binary at: %s", goPath)
|
||||
|
||||
// 2. Get go env as JSON
|
||||
cmd := exec.CommandContext(ctx, "go", "env", "-json")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
return nil, fmt.Errorf("go env failed: %s", string(exitErr.Stderr))
|
||||
}
|
||||
return nil, fmt.Errorf("failed to run go env: %w", err)
|
||||
}
|
||||
|
||||
// 3. Parse JSON output
|
||||
var env GoEnv
|
||||
if err := json.Unmarshal(output, &env); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse go env output: %w", err)
|
||||
}
|
||||
|
||||
// 4. Validate critical environment variables
|
||||
if env.GOROOT == "" {
|
||||
return nil, fmt.Errorf("GOROOT is not set")
|
||||
}
|
||||
if env.GOMODCACHE == "" && env.GOPATH == "" {
|
||||
return nil, fmt.Errorf("neither GOMODCACHE nor GOPATH is set")
|
||||
}
|
||||
|
||||
mlog.Debugf("Go Version: %s", env.GOVERSION)
|
||||
mlog.Debugf("GOROOT: %s", env.GOROOT)
|
||||
mlog.Debugf("GOMODCACHE: %s", env.GOMODCACHE)
|
||||
mlog.Debugf("GOPROXY: %s", env.GOPROXY)
|
||||
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
// CheckGitEnv verifies Git is installed and returns its version
|
||||
func CheckGitEnv(ctx context.Context) (string, error) {
|
||||
// 1. Check if git binary exists
|
||||
gitPath, err := exec.LookPath("git")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("git is not installed or not in PATH: %w", err)
|
||||
}
|
||||
mlog.Debugf("Found git binary at: %s", gitPath)
|
||||
|
||||
// 2. Get git version
|
||||
cmd := exec.CommandContext(ctx, "git", "--version")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get git version: %w", err)
|
||||
}
|
||||
|
||||
version := strings.TrimSpace(string(output))
|
||||
mlog.Debugf("Git version: %s", version)
|
||||
|
||||
return version, nil
|
||||
}
|
||||
146
cmd/gf/internal/cmd/geninit/geninit_generator.go
Normal file
146
cmd/gf/internal/cmd/geninit/geninit_generator.go
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// generateProject copies the template to the destination and performs cleanup
|
||||
// oldModule: original module path from template
|
||||
// newModule: target module path for go.mod (can be different from project name)
|
||||
func generateProject(ctx context.Context, srcPath, name, oldModule, newModule string) error {
|
||||
pwd := gfile.Pwd()
|
||||
|
||||
dstPath := filepath.Join(pwd, name)
|
||||
if name == "." {
|
||||
dstPath = pwd
|
||||
}
|
||||
|
||||
if gfile.Exists(dstPath) && !gfile.IsEmpty(dstPath) {
|
||||
return fmt.Errorf("target directory %s is not empty", dstPath)
|
||||
}
|
||||
|
||||
mlog.Printf("Generating project in %s...", dstPath)
|
||||
|
||||
// 1. Copy files
|
||||
if err := gfile.Copy(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. Clean up .git directory
|
||||
gitDir := filepath.Join(dstPath, ".git")
|
||||
if gfile.Exists(gitDir) {
|
||||
if err := gfile.Remove(gitDir); err != nil {
|
||||
mlog.Debugf("Failed to remove .git directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Clean up go.work and go.work.sum (workspace files should not be in generated project)
|
||||
for _, workFile := range []string{"go.work", "go.work.sum"} {
|
||||
workPath := filepath.Join(dstPath, workFile)
|
||||
if gfile.Exists(workPath) {
|
||||
if err := gfile.Remove(workPath); err != nil {
|
||||
mlog.Printf("Failed to remove %s: %v", workFile, err)
|
||||
} else {
|
||||
mlog.Debugf("Removed %s", workFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Update go.mod module name
|
||||
goModPath := filepath.Join(dstPath, "go.mod")
|
||||
if gfile.Exists(goModPath) {
|
||||
content := gfile.GetContents(goModPath)
|
||||
lines := gstr.Split(content, "\n")
|
||||
if len(lines) > 0 && gstr.HasPrefix(lines[0], "module ") {
|
||||
lines[0] = "module " + newModule
|
||||
newContent := gstr.Join(lines, "\n")
|
||||
if err := gfile.PutContents(goModPath, newContent); err != nil {
|
||||
mlog.Printf("Failed to update go.mod: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Use AST to replace import paths in all Go files
|
||||
if oldModule != "" && oldModule != newModule {
|
||||
replacer := NewASTReplacer(oldModule, newModule)
|
||||
if err := replacer.ReplaceInDir(ctx, dstPath); err != nil {
|
||||
return fmt.Errorf("failed to replace imports: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Format the generated Go files using go/format (not imports.Process)
|
||||
// Note: We use formatGoFiles instead of utils.GoFmt because imports.Process
|
||||
// may incorrectly "fix" local import paths by replacing them with cached module paths.
|
||||
formatGoFiles(dstPath)
|
||||
|
||||
mlog.Print("Project generated successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// tidyDependencies runs go mod tidy in the project directory
|
||||
func tidyDependencies(ctx context.Context, projectDir string) error {
|
||||
mlog.Print("Tidying dependencies (go mod tidy)...")
|
||||
if err := runCmd(ctx, projectDir, "go", "mod", "tidy"); err != nil {
|
||||
return fmt.Errorf("go mod tidy failed: %w", err)
|
||||
}
|
||||
mlog.Print("Dependencies tidied successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeDependencies runs go get -u ./... to upgrade all dependencies to latest
|
||||
func upgradeDependencies(ctx context.Context, projectDir string) error {
|
||||
mlog.Print("Upgrading dependencies to latest (go get -u ./...)...")
|
||||
if err := runCmd(ctx, projectDir, "go", "get", "-u", "./..."); err != nil {
|
||||
return fmt.Errorf("go get -u failed: %w", err)
|
||||
}
|
||||
// Run tidy again after upgrade
|
||||
if err := runCmd(ctx, projectDir, "go", "mod", "tidy"); err != nil {
|
||||
return fmt.Errorf("go mod tidy after upgrade failed: %w", err)
|
||||
}
|
||||
mlog.Print("Dependencies upgraded successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// formatGoFiles formats all Go files in the directory using go/format.
|
||||
// Unlike imports.Process, this only formats code without modifying imports,
|
||||
// which prevents incorrect "fixing" of local import paths.
|
||||
func formatGoFiles(dir string) {
|
||||
files, err := findGoFiles(dir)
|
||||
if err != nil {
|
||||
mlog.Printf("Failed to find Go files for formatting: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
content := gfile.GetContents(file)
|
||||
if content == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
formatted, err := format.Source([]byte(content))
|
||||
if err != nil {
|
||||
mlog.Debugf("Failed to format %s: %v", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(formatted) != content {
|
||||
if err := gfile.PutContents(file, string(formatted)); err != nil {
|
||||
mlog.Debugf("Failed to write formatted file %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
241
cmd/gf/internal/cmd/geninit/geninit_git_downloader.go
Normal file
241
cmd/gf/internal/cmd/geninit/geninit_git_downloader.go
Normal file
@ -0,0 +1,241 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// GitRepoInfo holds parsed git repository information
|
||||
type GitRepoInfo struct {
|
||||
Host string // e.g., github.com
|
||||
Owner string // e.g., gogf
|
||||
Repo string // e.g., examples
|
||||
Branch string // e.g., main (default: main)
|
||||
SubPath string // e.g., httpserver/jwt
|
||||
CloneURL string // e.g., https://github.com/gogf/examples.git
|
||||
}
|
||||
|
||||
// ParseGitURL parses a git URL and extracts repository info
|
||||
// Supports formats:
|
||||
// - github.com/owner/repo
|
||||
// - github.com/owner/repo/subdir/path
|
||||
// - github.com/owner/repo/tree/branch/subdir/path (from GitHub web URL)
|
||||
func ParseGitURL(url string) (*GitRepoInfo, error) {
|
||||
// Remove protocol prefix if present
|
||||
url = strings.TrimPrefix(url, "https://")
|
||||
url = strings.TrimPrefix(url, "http://")
|
||||
url = strings.TrimSuffix(url, ".git")
|
||||
|
||||
// Remove version suffix like @v1.0.0
|
||||
if idx := strings.Index(url, "@"); idx != -1 {
|
||||
url = url[:idx]
|
||||
}
|
||||
|
||||
parts := strings.Split(url, "/")
|
||||
if len(parts) < 3 {
|
||||
return nil, fmt.Errorf("invalid git URL: %s", url)
|
||||
}
|
||||
|
||||
info := &GitRepoInfo{
|
||||
Host: parts[0],
|
||||
Owner: parts[1],
|
||||
Repo: parts[2],
|
||||
Branch: "main", // default branch
|
||||
}
|
||||
|
||||
// Check for /tree/branch/ pattern (GitHub web URL)
|
||||
if len(parts) > 4 && parts[3] == "tree" {
|
||||
info.Branch = parts[4]
|
||||
if len(parts) > 5 {
|
||||
info.SubPath = strings.Join(parts[5:], "/")
|
||||
}
|
||||
} else if len(parts) > 3 {
|
||||
// Direct subpath: github.com/owner/repo/subdir/path
|
||||
info.SubPath = strings.Join(parts[3:], "/")
|
||||
}
|
||||
|
||||
info.CloneURL = fmt.Sprintf("https://%s/%s/%s.git", info.Host, info.Owner, info.Repo)
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// IsSubdirRepo checks if the URL points to a subdirectory of a repository
|
||||
// Returns false for Go module paths (which may have /vN suffix or nested module paths)
|
||||
// Note: This uses heuristics that may have false positives/negatives in edge cases
|
||||
func IsSubdirRepo(url string) bool {
|
||||
info, err := ParseGitURL(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if info.SubPath == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if this looks like a Go module path rather than a git subdirectory
|
||||
// Go modules can have nested paths like github.com/owner/repo/cmd/tool/v2
|
||||
// We should try to resolve it as a Go module first
|
||||
|
||||
// If the URL can be resolved as a Go module, it's not a subdir repo
|
||||
// We use a heuristic: check if the full path looks like a valid Go module
|
||||
// by checking if it ends with /vN (major version) or contains common module patterns
|
||||
|
||||
// Remove version suffix for checking
|
||||
cleanURL := url
|
||||
if before, _, ok := strings.Cut(url, "@"); ok {
|
||||
cleanURL = before
|
||||
}
|
||||
|
||||
// Check if the path ends with /vN (Go module major version)
|
||||
parts := strings.Split(cleanURL, "/")
|
||||
if len(parts) > 0 {
|
||||
lastPart := parts[len(parts)-1]
|
||||
if len(lastPart) >= 2 && lastPart[0] == 'v' {
|
||||
// Check if it's v2, v3, etc.
|
||||
if _, err := fmt.Sscanf(lastPart, "v%d", new(int)); err == nil {
|
||||
// This looks like a Go module with major version suffix
|
||||
// It could be either a versioned module or a subdir ending in vN
|
||||
// We'll treat it as a Go module and let go get handle it
|
||||
mlog.Debugf("URL %s detected as Go module (ends with /vN)", url)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For GitHub URLs, check if the subpath could be a nested Go module
|
||||
// Common patterns: cmd/*, internal/*, pkg/*, contrib/*
|
||||
subPathParts := strings.Split(info.SubPath, "/")
|
||||
if len(subPathParts) > 0 {
|
||||
firstPart := subPathParts[0]
|
||||
// These are common Go module nesting patterns
|
||||
if firstPart == "cmd" || firstPart == "contrib" || firstPart == "tools" {
|
||||
// This might be a nested Go module, not a simple subdirectory
|
||||
// Let go get try first
|
||||
mlog.Debugf("URL %s detected as Go module (starts with common pattern)", url)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
mlog.Debugf("URL %s detected as git subdirectory", url)
|
||||
return true
|
||||
}
|
||||
|
||||
// downloadGitSubdir downloads a subdirectory from a git repository using sparse checkout
|
||||
func downloadGitSubdir(ctx context.Context, repoURL string) (string, *GitRepoInfo, error) {
|
||||
info, err := ParseGitURL(repoURL)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if info.SubPath == "" {
|
||||
return "", nil, fmt.Errorf("not a subdirectory URL: %s", repoURL)
|
||||
}
|
||||
|
||||
// Create temp directory for clone
|
||||
tempDir := gfile.Temp("gf-init-git")
|
||||
if tempDir == "" {
|
||||
return "", nil, fmt.Errorf("failed to create temporary directory")
|
||||
}
|
||||
if err := gfile.Mkdir(tempDir); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
cloneDir := filepath.Join(tempDir, info.Repo)
|
||||
mlog.Debugf("Using git temp workspace: %s", tempDir)
|
||||
mlog.Printf("Cloning %s (sparse checkout: %s)...", info.CloneURL, info.SubPath)
|
||||
|
||||
// 1. Clone with no checkout, filter, and sparse
|
||||
if err := runCmd(ctx, tempDir, "git", "clone", "--filter=blob:none", "--no-checkout", "--sparse", info.CloneURL); err != nil {
|
||||
// Fallback: try without filter for older git versions
|
||||
mlog.Debugf("Sparse clone failed, trying full clone...")
|
||||
if err := gfile.Remove(cloneDir); err != nil {
|
||||
mlog.Debugf("Failed to remove clone directory: %v", err)
|
||||
}
|
||||
if err := runCmd(ctx, tempDir, "git", "clone", "--no-checkout", info.CloneURL); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git clone failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Set sparse-checkout to the subpath
|
||||
if err := runCmd(ctx, cloneDir, "git", "sparse-checkout", "set", info.SubPath); err != nil {
|
||||
// Fallback for older git: use sparse-checkout init + set
|
||||
mlog.Debugf("sparse-checkout set failed, trying legacy method...")
|
||||
if err := runCmd(ctx, cloneDir, "git", "sparse-checkout", "init", "--cone"); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git sparse-checkout init (legacy) failed: %w", err)
|
||||
}
|
||||
if err := runCmd(ctx, cloneDir, "git", "sparse-checkout", "set", info.SubPath); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git sparse-checkout set (legacy) failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Checkout the branch
|
||||
if err := runCmd(ctx, cloneDir, "git", "checkout", info.Branch); err != nil {
|
||||
// Try master if main fails
|
||||
if info.Branch == "main" {
|
||||
mlog.Debugf("Branch 'main' not found, trying 'master'...")
|
||||
info.Branch = "master"
|
||||
if err := runCmd(ctx, cloneDir, "git", "checkout", "master"); err != nil {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git checkout failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("git checkout failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the path to the subdirectory
|
||||
subDirPath := filepath.Join(cloneDir, info.SubPath)
|
||||
if !gfile.Exists(subDirPath) {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
return "", nil, fmt.Errorf("subdirectory not found: %s", info.SubPath)
|
||||
}
|
||||
|
||||
mlog.Debugf("Subdirectory located at: %s", subDirPath)
|
||||
return subDirPath, info, nil
|
||||
}
|
||||
|
||||
// GetModuleNameFromGoMod reads module name from go.mod file
|
||||
func GetModuleNameFromGoMod(dir string) string {
|
||||
goModPath := filepath.Join(dir, "go.mod")
|
||||
if !gfile.Exists(goModPath) {
|
||||
return ""
|
||||
}
|
||||
|
||||
content := gfile.GetContents(goModPath)
|
||||
lines := gstr.Split(content, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if after, ok := strings.CutPrefix(line, "module "); ok {
|
||||
return strings.TrimSpace(after)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
99
cmd/gf/internal/cmd/geninit/geninit_selector.go
Normal file
99
cmd/gf/internal/cmd/geninit/geninit_selector.go
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// SelectVersion prompts user to select a version interactively
|
||||
func SelectVersion(ctx context.Context, versions []string, modulePath string) (string, error) {
|
||||
if len(versions) == 0 {
|
||||
return "", fmt.Errorf("no versions available for selection")
|
||||
}
|
||||
|
||||
if len(versions) == 1 {
|
||||
mlog.Printf("Only one version available: %s", versions[0])
|
||||
return versions[0], nil
|
||||
}
|
||||
|
||||
// Display available versions
|
||||
fmt.Printf("\nAvailable versions for %s:\n", modulePath)
|
||||
fmt.Println(strings.Repeat("-", 40))
|
||||
|
||||
// Show versions with index (newest first)
|
||||
maxDisplay := 20 // Limit display to avoid overwhelming output
|
||||
displayCount := len(versions)
|
||||
if displayCount > maxDisplay {
|
||||
displayCount = maxDisplay
|
||||
}
|
||||
|
||||
for i := 0; i < displayCount; i++ {
|
||||
marker := ""
|
||||
if i == 0 {
|
||||
marker = " (latest)"
|
||||
}
|
||||
fmt.Printf(" [%2d] %s%s\n", i+1, versions[i], marker)
|
||||
}
|
||||
|
||||
if len(versions) > maxDisplay {
|
||||
fmt.Printf(" ... and %d more versions\n", len(versions)-maxDisplay)
|
||||
}
|
||||
|
||||
fmt.Println(strings.Repeat("-", 40))
|
||||
|
||||
// Prompt for selection
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Printf("Select version [1-%d] or enter version string (default: 1 for latest): ", displayCount)
|
||||
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read input: %w", err)
|
||||
}
|
||||
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// Default to latest
|
||||
if input == "" {
|
||||
fmt.Printf("Selected: %s (latest)\n", versions[0])
|
||||
return versions[0], nil
|
||||
}
|
||||
|
||||
// Try parsing as number first
|
||||
idx, err := strconv.Atoi(input)
|
||||
if err == nil {
|
||||
// Valid number - check if in range
|
||||
if idx >= 1 && idx <= len(versions) {
|
||||
// Allow selection from all versions, not just displayed ones
|
||||
selected := versions[idx-1]
|
||||
fmt.Printf("Selected: %s\n", selected)
|
||||
return selected, nil
|
||||
} else if idx < 1 || idx > displayCount {
|
||||
fmt.Printf("Invalid selection. Please enter a number between 1 and %d, or type a version string.\n", displayCount)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Try matching the input as a version string (e.g., "v1.2.3")
|
||||
for _, v := range versions {
|
||||
if v == input || strings.Contains(v, input) {
|
||||
fmt.Printf("Selected: %s\n", v)
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
fmt.Printf("Version '%s' not found. Please select by number or type a valid version string.\n", input)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
138
cmd/gf/internal/cmd/geninit/geninit_version.go
Normal file
138
cmd/gf/internal/cmd/geninit/geninit_version.go
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package geninit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||
)
|
||||
|
||||
// VersionInfo contains module version information
|
||||
type VersionInfo struct {
|
||||
Module string `json:"module"`
|
||||
Versions []string `json:"versions"`
|
||||
Latest string `json:"latest"`
|
||||
}
|
||||
|
||||
// GetModuleVersions fetches available versions for a Go module
|
||||
func GetModuleVersions(ctx context.Context, modulePath string) (*VersionInfo, error) {
|
||||
// Create a temporary directory for go list
|
||||
tempDir := gfile.Temp("gf-init-version")
|
||||
if tempDir == "" {
|
||||
return nil, fmt.Errorf("failed to create temporary directory for go list")
|
||||
}
|
||||
if err := gfile.Mkdir(tempDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := gfile.Remove(tempDir); err != nil {
|
||||
mlog.Debugf("Failed to remove temp directory: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize a temp go module
|
||||
if err := runCmd(ctx, tempDir, "go", "mod", "init", "temp"); err != nil {
|
||||
return nil, fmt.Errorf("failed to init temp module: %w", err)
|
||||
}
|
||||
|
||||
// Get versions using go list -m -versions
|
||||
cmd := exec.CommandContext(ctx, "go", "list", "-m", "-versions", modulePath)
|
||||
cmd.Dir = tempDir
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Try with @latest to see if module exists
|
||||
mlog.Debugf("go list -versions failed, trying @latest: %v", err)
|
||||
return getLatestOnly(ctx, tempDir, modulePath)
|
||||
}
|
||||
|
||||
// Parse output: "module/path v1.0.0 v1.1.0 v2.0.0"
|
||||
parts := strings.Fields(strings.TrimSpace(string(output)))
|
||||
if len(parts) < 1 {
|
||||
return nil, fmt.Errorf("no version information found for %s", modulePath)
|
||||
}
|
||||
|
||||
info := &VersionInfo{
|
||||
Module: parts[0],
|
||||
Versions: []string{},
|
||||
}
|
||||
|
||||
if len(parts) > 1 {
|
||||
info.Versions = parts[1:]
|
||||
// Sort versions in descending order (newest first)
|
||||
sort.Slice(info.Versions, func(i, j int) bool {
|
||||
return semver.Compare(info.Versions[i], info.Versions[j]) > 0
|
||||
})
|
||||
info.Latest = info.Versions[0]
|
||||
}
|
||||
|
||||
// If no tagged versions, try to get latest
|
||||
if len(info.Versions) == 0 {
|
||||
latestInfo, err := getLatestOnly(ctx, tempDir, modulePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.Latest = latestInfo.Latest
|
||||
if latestInfo.Latest != "" {
|
||||
info.Versions = []string{latestInfo.Latest}
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// getLatestOnly gets only the latest version when go list -versions fails
|
||||
func getLatestOnly(ctx context.Context, tempDir, modulePath string) (*VersionInfo, error) {
|
||||
// Try go list -m modulePath@latest
|
||||
cmd := exec.CommandContext(ctx, "go", "list", "-m", "-json", modulePath+"@latest")
|
||||
cmd.Dir = tempDir
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Try without @latest
|
||||
cmd = exec.CommandContext(ctx, "go", "list", "-m", "-json", modulePath)
|
||||
cmd.Dir = tempDir
|
||||
output, err = cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get module info for %s: %w", modulePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
var modInfo struct {
|
||||
Path string `json:"Path"`
|
||||
Version string `json:"Version"`
|
||||
}
|
||||
if err := json.Unmarshal(output, &modInfo); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse module info: %w", err)
|
||||
}
|
||||
|
||||
return &VersionInfo{
|
||||
Module: modInfo.Path,
|
||||
Versions: []string{modInfo.Version},
|
||||
Latest: modInfo.Version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLatestVersion returns the latest version of a module
|
||||
func GetLatestVersion(ctx context.Context, modulePath string) (string, error) {
|
||||
info, err := GetModuleVersions(ctx, modulePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.Latest == "" {
|
||||
return "", fmt.Errorf("no version found for %s", modulePath)
|
||||
}
|
||||
return info.Latest, nil
|
||||
}
|
||||
@ -4,7 +4,7 @@ go 1.23.0
|
||||
|
||||
toolchain go1.24.6
|
||||
|
||||
require github.com/gogf/gf/v2 v2.9.5
|
||||
require github.com/gogf/gf/v2 v2.9.7
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
|
||||
@ -4,8 +4,8 @@ github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyM
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@ package article
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v2"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v2"
|
||||
)
|
||||
|
||||
type IArticleV1 interface {
|
||||
@ -5,7 +5,7 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v1"
|
||||
)
|
||||
|
||||
// Create add title.
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) {
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) {
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v2"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) Create(ctx context.Context, req *v2.CreateReq) (res *v2.CreateRes, err error) {
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/api/article/v2"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/default/api/article/v2"
|
||||
)
|
||||
|
||||
func (c *ControllerV2) Update(ctx context.Context, req *v2.UpdateReq) (res *v2.UpdateRes, err error) {
|
||||
@ -7,7 +7,7 @@ package dict
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_ctrl/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_ctrl/api/dict/v1"
|
||||
)
|
||||
|
||||
type IDictV1 interface {
|
||||
@ -7,7 +7,7 @@ package dict
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_ctrl/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_ctrl/api/dict/v1"
|
||||
)
|
||||
|
||||
type IDictV1 interface {
|
||||
@ -5,7 +5,7 @@
|
||||
package dict
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_ctrl/api/dict"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_ctrl/api/dict"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_ctrl/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_ctrl/api/dict/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DictTypeAddPage(ctx context.Context, req *v1.DictTypeAddPageReq) (res *v1.DictTypeAddPageRes, err error) {
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_ctrl/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_ctrl/api/dict/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DictTypeAddPage(ctx context.Context, req *v1.DictTypeAddPageReq) (res *v1.DictTypeAddPageRes, err error) {
|
||||
@ -7,7 +7,7 @@ package dict
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_ctrl/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_ctrl/api/dict/v1"
|
||||
)
|
||||
|
||||
type IDictV1 interface {
|
||||
@ -7,7 +7,7 @@ package dict
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_file/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_file/api/dict/v1"
|
||||
)
|
||||
|
||||
type IDictV1 interface {
|
||||
@ -5,7 +5,7 @@
|
||||
package dict
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_file/api/dict"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_file/api/dict"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_file/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_file/api/dict/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DictTypeAddPage(ctx context.Context, req *v1.DictTypeAddPageReq) (res *v1.DictTypeAddPageRes, err error) {
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl-merge/add_new_file/api/dict/v1"
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/merge/add_new_file/api/dict/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) DictTypeAdd(ctx context.Context, req *v1.DictTypeAddReq) (res *v1.DictTypeAddRes, err error) {
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/article/article_expect.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/article/article_expect.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package article
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/admin/article/v1"
|
||||
)
|
||||
|
||||
type IArticleV1 interface {
|
||||
Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error)
|
||||
}
|
||||
19
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/article/v1/edit.go
vendored
Normal file
19
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/article/v1/edit.go
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
type (
|
||||
// CreateReq add title.
|
||||
CreateReq struct {
|
||||
g.Meta `path:"/article/create" method:"post" tags:"ArticleService"`
|
||||
Title string `v:"required"`
|
||||
}
|
||||
|
||||
CreateRes struct{}
|
||||
)
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/user/user_expect.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/user/user_expect.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/admin/user/v1"
|
||||
)
|
||||
|
||||
type IUserV1 interface {
|
||||
Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error)
|
||||
}
|
||||
19
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/user/v1/edit.go
vendored
Normal file
19
cmd/gf/internal/cmd/testdata/genctrl/multi/api/admin/user/v1/edit.go
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
type (
|
||||
// CreateReq add title.
|
||||
CreateReq struct {
|
||||
g.Meta `path:"/article/create" method:"post" tags:"ArticleService"`
|
||||
Title string `v:"required"`
|
||||
}
|
||||
|
||||
CreateRes struct{}
|
||||
)
|
||||
16
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/user_expect.go
vendored
Normal file
16
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/user_expect.go
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user/v1"
|
||||
)
|
||||
|
||||
type IUserV1 interface {
|
||||
Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error)
|
||||
Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error)
|
||||
}
|
||||
16
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext/user_ext_expect.go
vendored
Normal file
16
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext/user_ext_expect.go
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package user_ext
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext/v1"
|
||||
)
|
||||
|
||||
type IUserExtV1 interface {
|
||||
Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error)
|
||||
Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error)
|
||||
}
|
||||
28
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext/v1/edit.go
vendored
Normal file
28
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext/v1/edit.go
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
type (
|
||||
// CreateReq add title.
|
||||
CreateReq struct {
|
||||
g.Meta `path:"/article/create" method:"post" tags:"ArticleService"`
|
||||
Title string `v:"required"`
|
||||
}
|
||||
|
||||
CreateRes struct{}
|
||||
)
|
||||
|
||||
type (
|
||||
UpdateReq struct {
|
||||
g.Meta `path:"/article/update" method:"post" tags:"ArticleService"`
|
||||
Title string `v:"required"`
|
||||
}
|
||||
|
||||
UpdateRes struct{}
|
||||
)
|
||||
28
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/v1/edit.go
vendored
Normal file
28
cmd/gf/internal/cmd/testdata/genctrl/multi/api/app/user/v1/edit.go
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package v1
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
type (
|
||||
// CreateReq add title.
|
||||
CreateReq struct {
|
||||
g.Meta `path:"/article/create" method:"post" tags:"ArticleService"`
|
||||
Title string `v:"required"`
|
||||
}
|
||||
|
||||
CreateRes struct{}
|
||||
)
|
||||
|
||||
type (
|
||||
UpdateReq struct {
|
||||
g.Meta `path:"/article/update" method:"post" tags:"ArticleService"`
|
||||
Title string `v:"required"`
|
||||
}
|
||||
|
||||
UpdateRes struct{}
|
||||
)
|
||||
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/article/article.go
vendored
Normal file
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/article/article.go
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package article
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/article/article_new.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/article/article_new.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package article
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/admin/article"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() article.IArticleV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/article/article_v1_create.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/article/article_v1_create.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/admin/article/v1"
|
||||
)
|
||||
|
||||
// Create add title.
|
||||
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
|
||||
return nil, gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/user/user.go
vendored
Normal file
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/user/user.go
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package user
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/user/user_new.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/user/user_new.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/admin/user"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() user.IUserV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/user/user_v1_create.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/admin/user/user_v1_create.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/admin/user/v1"
|
||||
)
|
||||
|
||||
// Create add title.
|
||||
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
|
||||
return nil, gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user.go
vendored
Normal file
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user.go
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package user
|
||||
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext.go
vendored
Normal file
5
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext.go
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package user_ext
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext_new.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext_new.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package user_ext
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() user_ext.IUserExtV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext_v1_create.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext_v1_create.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package user_ext
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext/v1"
|
||||
)
|
||||
|
||||
// Create add title.
|
||||
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
|
||||
return nil, gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
14
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext_v1_update.go
vendored
Normal file
14
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_ext/user_ext_v1_update.go
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package user_ext
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user/user_ext/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
|
||||
return nil, gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_new.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_new.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// =================================================================================
|
||||
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
|
||||
// =================================================================================
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user"
|
||||
)
|
||||
|
||||
type ControllerV1 struct{}
|
||||
|
||||
func NewV1() user.IUserV1 {
|
||||
return &ControllerV1{}
|
||||
}
|
||||
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_v1_create.go
vendored
Normal file
15
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_v1_create.go
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user/v1"
|
||||
)
|
||||
|
||||
// Create add title.
|
||||
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
|
||||
return nil, gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
14
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_v1_update.go
vendored
Normal file
14
cmd/gf/internal/cmd/testdata/genctrl/multi/controller/app/user/user_v1_update.go
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
|
||||
"github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/genctrl/multi/api/app/user/v1"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) {
|
||||
return nil, gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
@ -7,245 +7,143 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// AnyAnyMap wraps map type `map[any]any` and provides more map features.
|
||||
type AnyAnyMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[any]any
|
||||
*KVMap[any, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewAnyAnyMap creates and returns an empty hash map.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewAnyAnyMap(safe ...bool) *AnyAnyMap {
|
||||
return &AnyAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[any]any),
|
||||
m := &AnyAnyMap{
|
||||
KVMap: NewKVMap[any, any](safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewAnyAnyMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewAnyAnyMapFrom(data map[any]any, safe ...bool) *AnyAnyMap {
|
||||
return &AnyAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
m := &AnyAnyMap{
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *AnyAnyMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[any, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *AnyAnyMap) Iterator(f func(k any, v any) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *AnyAnyMap) Clone(safe ...bool) *AnyAnyMap {
|
||||
return NewFrom(m.MapCopy(), safe...)
|
||||
m.lazyInit()
|
||||
return NewAnyAnyMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *AnyAnyMap) Map() map[any]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[any]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapCopy returns a shallow copy of the underlying data of the hash map.
|
||||
func (m *AnyAnyMap) MapCopy() map[any]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[any]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *AnyAnyMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *AnyAnyMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *AnyAnyMap) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterNil()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *AnyAnyMap) Set(key any, value any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *AnyAnyMap) Sets(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *AnyAnyMap) Search(key any) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *AnyAnyMap) Get(key any) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *AnyAnyMap) Pop() (key, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *AnyAnyMap) Pops(size int) map[any]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[any]any, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *AnyAnyMap) doSetWithLockCheck(key any, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *AnyAnyMap) GetOrSet(key any, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *AnyAnyMap) GetOrSetFunc(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -255,55 +153,50 @@ func (m *AnyAnyMap) GetOrSetFunc(key any, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *AnyAnyMap) GetOrSetFuncLock(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVar(key any) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *AnyAnyMap) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *AnyAnyMap) SetIfNotExist(key any, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *AnyAnyMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -312,119 +205,76 @@ func (m *AnyAnyMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *AnyAnyMap) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *AnyAnyMap) Remove(key any) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *AnyAnyMap) Removes(keys []any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *AnyAnyMap) Keys() []any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
keys = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *AnyAnyMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
values = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *AnyAnyMap) Contains(key any) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *AnyAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *AnyAnyMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *AnyAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[any]any)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *AnyAnyMap) Replace(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *AnyAnyMap) LockFunc(f func(m map[any]any)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *AnyAnyMap) RLockFunc(f func(m map[any]any)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -441,19 +291,8 @@ func (m *AnyAnyMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -461,79 +300,40 @@ func (m *AnyAnyMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m AnyAnyMap) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *AnyAnyMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *AnyAnyMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]any)
|
||||
}
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[k] = v
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *AnyAnyMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &AnyAnyMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[any, any]),
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[any]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v)
|
||||
}
|
||||
return NewFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -541,22 +341,6 @@ func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *AnyAnyMap) Diff(other *AnyAnyMap) (addedKeys, removedKeys, updatedKeys []any) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -8,244 +8,143 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// IntAnyMap implements map[int]any with RWMutex that has switch.
|
||||
type IntAnyMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[int]any
|
||||
*KVMap[int, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewIntAnyMap returns an empty IntAnyMap object.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntAnyMap(safe ...bool) *IntAnyMap {
|
||||
return &IntAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[int]any),
|
||||
m := &IntAnyMap{
|
||||
KVMap: NewKVMap[int, any](safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewIntAnyMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntAnyMapFrom(data map[int]any, safe ...bool) *IntAnyMap {
|
||||
return &IntAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
m := &IntAnyMap{
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *IntAnyMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[int, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntAnyMap) Iterator(f func(k int, v any) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntAnyMap) Clone() *IntAnyMap {
|
||||
return NewIntAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *IntAnyMap) Clone(safe ...bool) *IntAnyMap {
|
||||
m.lazyInit()
|
||||
return NewIntAnyMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *IntAnyMap) Map() map[int]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[int]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *IntAnyMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *IntAnyMap) MapCopy() map[int]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *IntAnyMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *IntAnyMap) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterNil()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntAnyMap) Set(key int, val any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntAnyMap) Sets(data map[int]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *IntAnyMap) Search(key int) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *IntAnyMap) Get(key int) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *IntAnyMap) Pop() (key int, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *IntAnyMap) Pops(size int) map[int]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[int]any, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *IntAnyMap) doSetWithLockCheck(key int, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *IntAnyMap) GetOrSet(key int, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist and returns this value.
|
||||
func (m *IntAnyMap) GetOrSetFunc(key int, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -254,55 +153,50 @@ func (m *IntAnyMap) GetOrSetFunc(key int, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVar(key int) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSet(key int, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntAnyMap) SetIfNotExist(key int, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -311,119 +205,76 @@ func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntAnyMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *IntAnyMap) Remove(key int) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntAnyMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntAnyMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *IntAnyMap) Contains(key int) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntAnyMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]any)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *IntAnyMap) Replace(data map[int]any) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *IntAnyMap) LockFunc(f func(m map[int]any)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *IntAnyMap) RLockFunc(f func(m map[int]any)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -440,19 +291,8 @@ func (m *IntAnyMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *IntAnyMap) Merge(other *IntAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -460,81 +300,40 @@ func (m *IntAnyMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m IntAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *IntAnyMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *IntAnyMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]any)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[gconv.Int(k)] = v
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *IntAnyMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &IntAnyMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[int, any]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v)
|
||||
}
|
||||
return NewIntAnyMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -542,22 +341,6 @@ func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *IntAnyMap) Diff(other *IntAnyMap) (addedKeys, removedKeys, updatedKeys []int) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -6,17 +6,12 @@
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
import "sync"
|
||||
|
||||
// IntIntMap implements map[int]int with RWMutex that has switch.
|
||||
type IntIntMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[int]int
|
||||
*KVMap[int, int]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewIntIntMap returns an empty IntIntMap object.
|
||||
@ -24,8 +19,7 @@ type IntIntMap struct {
|
||||
// which is false in default.
|
||||
func NewIntIntMap(safe ...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[int]int),
|
||||
KVMap: NewKVMap[int, int](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,193 +28,109 @@ func NewIntIntMap(safe ...bool) *IntIntMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntIntMapFrom(data map[int]int, safe ...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *IntIntMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[int, int](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntIntMap) Iterator(f func(k int, v int) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntIntMap) Clone() *IntIntMap {
|
||||
return NewIntIntMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *IntIntMap) Clone(safe ...bool) *IntIntMap {
|
||||
m.lazyInit()
|
||||
return &IntIntMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *IntIntMap) Map() map[int]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *IntIntMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *IntIntMap) MapCopy() map[int]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *IntIntMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntIntMap) Set(key int, val int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntIntMap) Sets(data map[int]int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *IntIntMap) Search(key int) (value int, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *IntIntMap) Get(key int) (value int) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *IntIntMap) Pop() (key, value int) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *IntIntMap) Pops(size int) map[int]int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[int]int, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *IntIntMap) doSetWithLockCheck(key int, value int) int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *IntIntMap) GetOrSet(key int, value int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist and returns this value.
|
||||
func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -229,41 +139,22 @@ func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntIntMap) SetIfNotExist(key int, value int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -272,126 +163,76 @@ func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntIntMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *IntIntMap) Remove(key int) (value int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntIntMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntIntMap) Values() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *IntIntMap) Contains(key int) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntIntMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntIntMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntIntMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]int)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *IntIntMap) Replace(data map[int]int) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *IntIntMap) LockFunc(f func(m map[int]int)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *IntIntMap) RLockFunc(f func(m map[int]int)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -408,19 +249,8 @@ func (m *IntIntMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *IntIntMap) Merge(other *IntIntMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -428,81 +258,40 @@ func (m *IntIntMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m IntIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *IntIntMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *IntIntMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]int)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[gconv.Int(k)] = gconv.Int(v)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *IntIntMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &IntIntMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[int, int]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewIntIntMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *IntIntMap) IsSubOf(other *IntIntMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -510,22 +299,6 @@ func (m *IntIntMap) IsSubOf(other *IntIntMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *IntIntMap) Diff(other *IntIntMap) (addedKeys, removedKeys, updatedKeys []int) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -7,16 +7,15 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// IntStrMap implements map[int]string with RWMutex that has switch.
|
||||
type IntStrMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[int]string
|
||||
*KVMap[int, string]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewIntStrMap returns an empty IntStrMap object.
|
||||
@ -24,8 +23,7 @@ type IntStrMap struct {
|
||||
// which is false in default.
|
||||
func NewIntStrMap(safe ...bool) *IntStrMap {
|
||||
return &IntStrMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[int]string),
|
||||
KVMap: NewKVMap[int, string](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,193 +32,109 @@ func NewIntStrMap(safe ...bool) *IntStrMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewIntStrMapFrom(data map[int]string, safe ...bool) *IntStrMap {
|
||||
return &IntStrMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *IntStrMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[int, string](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *IntStrMap) Clone() *IntStrMap {
|
||||
return NewIntStrMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *IntStrMap) Clone(safe ...bool) *IntStrMap {
|
||||
m.lazyInit()
|
||||
return &IntStrMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *IntStrMap) Map() map[int]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *IntStrMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *IntStrMap) MapCopy() map[int]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *IntStrMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *IntStrMap) Set(key int, val string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *IntStrMap) Sets(data map[int]string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *IntStrMap) Search(key int) (value string, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *IntStrMap) Get(key int) (value string) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *IntStrMap) Pop() (key int, value string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *IntStrMap) Pops(size int) map[int]string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[int]string, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *IntStrMap) GetOrSet(key int, value string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist and returns this value.
|
||||
func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -229,41 +143,22 @@ func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntStrMap) SetIfNotExist(key int, value string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -272,126 +167,76 @@ func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *IntStrMap) Removes(keys []int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *IntStrMap) Remove(key int) (value string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *IntStrMap) Keys() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *IntStrMap) Values() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *IntStrMap) Contains(key int) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *IntStrMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *IntStrMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *IntStrMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[int]string)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *IntStrMap) Replace(data map[int]string) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *IntStrMap) LockFunc(f func(m map[int]string)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *IntStrMap) RLockFunc(f func(m map[int]string)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -408,19 +253,8 @@ func (m *IntStrMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *IntStrMap) Merge(other *IntStrMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -428,81 +262,40 @@ func (m *IntStrMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m IntStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *IntStrMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *IntStrMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[int]string)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[gconv.Int(k)] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *IntStrMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &IntStrMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[int, string]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[int]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewIntStrMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *IntStrMap) IsSubOf(other *IntStrMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -510,22 +303,6 @@ func (m *IntStrMap) IsSubOf(other *IntStrMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *IntStrMap) Diff(other *IntStrMap) (addedKeys, removedKeys, updatedKeys []int) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
622
container/gmap/gmap_hash_k_v_map.go
Normal file
622
container/gmap/gmap_hash_k_v_map.go
Normal file
@ -0,0 +1,622 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// NilChecker is a function that checks whether the given value is nil.
|
||||
type NilChecker[V any] func(V) bool
|
||||
|
||||
// KVMap wraps map type `map[K]V` and provides more map features.
|
||||
type KVMap[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[K]V
|
||||
nilChecker NilChecker[V]
|
||||
}
|
||||
|
||||
// NewKVMap creates and returns an empty hash map.
|
||||
// The parameter `safe` is used to specify whether to use the map in concurrent-safety mode, which is false by default.
|
||||
func NewKVMap[K comparable, V any](safe ...bool) *KVMap[K, V] {
|
||||
return NewKVMapFrom(make(map[K]V), safe...)
|
||||
}
|
||||
|
||||
// NewKVMapWithChecker creates and returns an empty hash map with a custom nil checker.
|
||||
// The parameter `checker` is a function used to determine if a value is nil.
|
||||
// The parameter `safe` is used to specify whether to use the map in concurrent-safety mode, which is false by default.
|
||||
func NewKVMapWithChecker[K comparable, V any](checker NilChecker[V], safe ...bool) *KVMap[K, V] {
|
||||
return NewKVMapWithCheckerFrom(make(map[K]V), checker, safe...)
|
||||
}
|
||||
|
||||
// NewKVMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map (no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *KVMap[K, V] {
|
||||
m := &KVMap[K, V]{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewKVMapWithCheckerFrom creates and returns a hash map from given map `data` with a custom nil checker.
|
||||
// Note that, the param `data` map will be set as the underlying data map (no deep copy),
|
||||
// and there might be some concurrent-safe issues when changing the map outside.
|
||||
// The parameter `checker` is a function used to determine if a value is nil.
|
||||
// The parameter `safe` is used to specify whether to use the map in concurrent-safety mode, which is false by default.
|
||||
func NewKVMapWithCheckerFrom[K comparable, V any](data map[K]V, checker NilChecker[V], safe ...bool) *KVMap[K, V] {
|
||||
m := NewKVMapFrom[K, V](data, safe...)
|
||||
m.RegisterNilChecker(checker)
|
||||
return m
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the map values.
|
||||
// This function is used to determine if a value should be considered as nil.
|
||||
// The nil checker function takes a value of type V and returns a boolean indicating
|
||||
// whether the value should be treated as nil.
|
||||
func (m *KVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.nilChecker = nilChecker
|
||||
}
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
func (m *KVMap[K, V]) isNil(v V) bool {
|
||||
if m.nilChecker != nil {
|
||||
return m.nilChecker(v)
|
||||
}
|
||||
return any(v) == nil
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *KVMap[K, V]) Iterator(f func(k K, v V) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *KVMap[K, V]) Clone(safe ...bool) *KVMap[K, V] {
|
||||
if len(safe) == 0 {
|
||||
return NewKVMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
}
|
||||
return NewKVMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *KVMap[K, V]) Map() map[K]V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// MapCopy returns a shallow copy of the underlying data of the hash map.
|
||||
func (m *KVMap[K, V]) MapCopy() map[K]V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *KVMap[K, V]) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[gconv.String(k)] = v
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *KVMap[K, V]) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *KVMap[K, V]) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *KVMap[K, V]) Set(key K, value V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *KVMap[K, V]) Sets(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *KVMap[K, V]) Search(key K) (value V, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *KVMap[K, V]) Get(key K) (value V) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *KVMap[K, V]) Pop() (key K, value V) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *KVMap[K, V]) Pops(size int) map[K]V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[K]V, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *KVMap[K, V]) doSetWithLockCheck(key K, value V) (val V, ok bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v, true
|
||||
}
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value, false
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *KVMap[K, V]) GetOrSet(key K, value V) V {
|
||||
v, _ := m.doSetWithLockCheck(key, value)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *KVMap[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
v, _ := m.doSetWithLockCheck(key, f())
|
||||
return v
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *KVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
value := f()
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVar(key K) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVarOrSet(key K, value V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *KVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *KVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = value
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *KVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
if !m.Contains(key) {
|
||||
return m.SetIfNotExist(key, f())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *KVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *KVMap[K, V]) Remove(key K) (value V) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *KVMap[K, V]) Removes(keys []K) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *KVMap[K, V]) Keys() []K {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
keys = make([]K, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *KVMap[K, V]) Values() []V {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var (
|
||||
values = make([]V, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *KVMap[K, V]) Contains(key K) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *KVMap[K, V]) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *KVMap[K, V]) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *KVMap[K, V]) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[K]V)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *KVMap[K, V]) Replace(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *KVMap[K, V]) LockFunc(f func(m map[K]V)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *KVMap[K, V]) RLockFunc(f func(m map[K]V)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *KVMap[K, V]) Flip() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
var (
|
||||
k0 K
|
||||
v0 V
|
||||
)
|
||||
if err := gconv.Scan(v, &k0); err != nil {
|
||||
continue
|
||||
}
|
||||
if err := gconv.Scan(k, &v0); err != nil {
|
||||
continue
|
||||
}
|
||||
n[k0] = v0
|
||||
}
|
||||
m.data = n
|
||||
}
|
||||
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *KVMap[K, V]) Merge(other *KVMap[K, V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *KVMap[K, V]) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m KVMap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(gconv.Map(m.Map()))
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *KVMap[K, V]) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
var data map[string]V
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gconv.Scan(data, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *KVMap[K, V]) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]V)
|
||||
}
|
||||
data := gconv.Map(value)
|
||||
if err := gconv.Scan(data, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *KVMap[K, V]) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[K]V, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v).(V)
|
||||
}
|
||||
return NewKVMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *KVMap[K, V]) IsSubOf(other *KVMap[K, V]) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(otherValue, value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
// The returned `addedKeys` are the keys that are in map `m` but not in map `other`.
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *KVMap[K, V]) Diff(other *KVMap[K, V]) (addedKeys, removedKeys, updatedKeys []K) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -8,71 +8,66 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// StrAnyMap implements map[string]any with RWMutex that has switch.
|
||||
type StrAnyMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[string]any
|
||||
*KVMap[string, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewStrAnyMap returns an empty StrAnyMap object.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStrAnyMap(safe ...bool) *StrAnyMap {
|
||||
return &StrAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[string]any),
|
||||
m := &StrAnyMap{
|
||||
KVMap: NewKVMap[string, any](safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewStrAnyMapFrom creates and returns a hash map from given map `data`.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrAnyMapFrom(data map[string]any, safe ...bool) *StrAnyMap {
|
||||
return &StrAnyMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
m := &StrAnyMap{
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *StrAnyMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[string, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrAnyMap) Iterator(f func(k string, v any) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrAnyMap) Clone() *StrAnyMap {
|
||||
return NewStrAnyMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *StrAnyMap) Clone(safe ...bool) *StrAnyMap {
|
||||
m.lazyInit()
|
||||
return NewStrAnyMapFrom(m.MapCopy(), safe...)
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *StrAnyMap) Map() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
@ -82,165 +77,74 @@ func (m *StrAnyMap) MapStrAny() map[string]any {
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *StrAnyMap) MapCopy() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *StrAnyMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// FilterNil deletes all key-value pair of which the value is nil.
|
||||
func (m *StrAnyMap) FilterNil() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsNil(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterNil()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrAnyMap) Set(key string, val any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]any)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrAnyMap) Sets(data map[string]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *StrAnyMap) Search(key string) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *StrAnyMap) Get(key string) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *StrAnyMap) Pop() (key string, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *StrAnyMap) Pops(size int) map[string]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[string]any, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the hash map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *StrAnyMap) doSetWithLockCheck(key string, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]any)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *StrAnyMap) GetOrSet(key string, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *StrAnyMap) GetOrSetFunc(key string, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -250,55 +154,50 @@ func (m *StrAnyMap) GetOrSetFunc(key string, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVar(key string) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSet(key string, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrAnyMap) SetIfNotExist(key string, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -307,119 +206,76 @@ func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrAnyMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *StrAnyMap) Remove(key string) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrAnyMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrAnyMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]any, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *StrAnyMap) Contains(key string) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrAnyMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrAnyMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrAnyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]any)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *StrAnyMap) Replace(data map[string]any) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *StrAnyMap) LockFunc(f func(m map[string]any)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *StrAnyMap) RLockFunc(f func(m map[string]any)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -436,19 +292,8 @@ func (m *StrAnyMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *StrAnyMap) Merge(other *StrAnyMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -456,71 +301,40 @@ func (m *StrAnyMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m StrAnyMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *StrAnyMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]any)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *StrAnyMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.data = gconv.Map(value)
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *StrAnyMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &StrAnyMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[string, any]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = deepcopy.Copy(v)
|
||||
}
|
||||
return NewStrAnyMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -528,22 +342,6 @@ func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *StrAnyMap) Diff(other *StrAnyMap) (addedKeys, removedKeys, updatedKeys []string) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if !reflect.DeepEqual(m.data[key], other.data[key]) {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -8,16 +8,15 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// StrIntMap implements map[string]int with RWMutex that has switch.
|
||||
type StrIntMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[string]int
|
||||
*KVMap[string, int]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewStrIntMap returns an empty StrIntMap object.
|
||||
@ -25,8 +24,7 @@ type StrIntMap struct {
|
||||
// which is false in default.
|
||||
func NewStrIntMap(safe ...bool) *StrIntMap {
|
||||
return &StrIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[string]int),
|
||||
KVMap: NewKVMap[string, int](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,195 +33,110 @@ func NewStrIntMap(safe ...bool) *StrIntMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrIntMapFrom(data map[string]int, safe ...bool) *StrIntMap {
|
||||
return &StrIntMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *StrIntMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[string, int](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrIntMap) Clone() *StrIntMap {
|
||||
return NewStrIntMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *StrIntMap) Clone(safe ...bool) *StrIntMap {
|
||||
m.lazyInit()
|
||||
return &StrIntMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *StrIntMap) Map() map[string]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *StrIntMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *StrIntMap) MapCopy() map[string]int {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *StrIntMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrIntMap) Set(key string, val int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrIntMap) Sets(data map[string]int) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *StrIntMap) Search(key string) (value int, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *StrIntMap) Get(key string) (value int) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *StrIntMap) Pop() (key string, value int) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *StrIntMap) Pops(size int) map[string]int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[string]int, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *StrIntMap) doSetWithLockCheck(key string, value int) int {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *StrIntMap) GetOrSet(key string, value int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -233,41 +146,22 @@ func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrIntMap) GetOrSetFuncLock(key string, f func() int) int {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrIntMap) SetIfNotExist(key string, value int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -276,126 +170,76 @@ func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrIntMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *StrIntMap) Remove(key string) (value int) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrIntMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrIntMap) Values() []int {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]int, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *StrIntMap) Contains(key string) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrIntMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrIntMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrIntMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]int)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *StrIntMap) Replace(data map[string]int) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *StrIntMap) LockFunc(f func(m map[string]int)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *StrIntMap) RLockFunc(f func(m map[string]int)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -412,19 +256,8 @@ func (m *StrIntMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *StrIntMap) Merge(other *StrIntMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -432,81 +265,40 @@ func (m *StrIntMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m StrIntMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *StrIntMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *StrIntMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]int)
|
||||
}
|
||||
switch value.(type) {
|
||||
case string, []byte:
|
||||
return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data)
|
||||
default:
|
||||
for k, v := range gconv.Map(value) {
|
||||
m.data[k] = gconv.Int(v)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *StrIntMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &StrIntMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[string, int]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]int, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewStrIntMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *StrIntMap) IsSubOf(other *StrIntMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -514,22 +306,6 @@ func (m *StrIntMap) IsSubOf(other *StrIntMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *StrIntMap) Diff(other *StrIntMap) (addedKeys, removedKeys, updatedKeys []string) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
@ -7,17 +7,12 @@
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
import "sync"
|
||||
|
||||
// StrStrMap implements map[string]string with RWMutex that has switch.
|
||||
type StrStrMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[string]string
|
||||
*KVMap[string, string]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewStrStrMap returns an empty StrStrMap object.
|
||||
@ -25,8 +20,7 @@ type StrStrMap struct {
|
||||
// which is false in default.
|
||||
func NewStrStrMap(safe ...bool) *StrStrMap {
|
||||
return &StrStrMap{
|
||||
data: make(map[string]string),
|
||||
mu: rwmutex.Create(safe...),
|
||||
KVMap: NewKVMap[string, string](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,194 +29,110 @@ func NewStrStrMap(safe ...bool) *StrStrMap {
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
func NewStrStrMapFrom(data map[string]string, safe ...bool) *StrStrMap {
|
||||
return &StrStrMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: data,
|
||||
KVMap: NewKVMapFrom(data, safe...),
|
||||
}
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the map.
|
||||
func (m *StrStrMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.KVMap == nil {
|
||||
m.KVMap = NewKVMap[string, string](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator iterates the hash map readonly with custom callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *StrStrMap) Iterator(f func(k string, v string) bool) {
|
||||
for k, v := range m.Map() {
|
||||
if !f(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Iterator(f)
|
||||
}
|
||||
|
||||
// Clone returns a new hash map with copy of current map data.
|
||||
func (m *StrStrMap) Clone() *StrStrMap {
|
||||
return NewStrStrMapFrom(m.MapCopy(), m.mu.IsSafe())
|
||||
func (m *StrStrMap) Clone(safe ...bool) *StrStrMap {
|
||||
m.lazyInit()
|
||||
return &StrStrMap{KVMap: m.KVMap.Clone(safe...)}
|
||||
}
|
||||
|
||||
// Map returns the underlying data map.
|
||||
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
|
||||
// or else a pointer to the underlying data.
|
||||
func (m *StrStrMap) Map() map[string]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if !m.mu.IsSafe() {
|
||||
return m.data
|
||||
}
|
||||
data := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *StrStrMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
data := make(map[string]any, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// MapCopy returns a copy of the underlying data of the hash map.
|
||||
func (m *StrStrMap) MapCopy() map[string]string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.KVMap.MapCopy()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
|
||||
func (m *StrStrMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
for k, v := range m.data {
|
||||
if empty.IsEmpty(v) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the hash map.
|
||||
func (m *StrStrMap) Set(key string, val string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
m.data[key] = val
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Set(key, val)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the hash map.
|
||||
func (m *StrStrMap) Sets(data map[string]string) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = data
|
||||
} else {
|
||||
for k, v := range data {
|
||||
m.data[k] = v
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *StrStrMap) Search(key string) (value string, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value, found = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *StrStrMap) Get(key string) (value string) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
value = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *StrStrMap) Pop() (key, value string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key, value = range m.data {
|
||||
delete(m.data, key)
|
||||
return
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *StrStrMap) Pops(size int) map[string]string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
index = 0
|
||||
newMap = make(map[string]string, size)
|
||||
)
|
||||
for k, v := range m.data {
|
||||
delete(m.data, k)
|
||||
newMap[k] = v
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if v, ok := m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.KVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *StrStrMap) GetOrSet(key string, value string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -232,41 +142,22 @@ func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the hash map.
|
||||
func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if v, ok = m.data[key]; ok {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.KVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrStrMap) SetIfNotExist(key string, value string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -275,126 +166,76 @@ func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the hash map.
|
||||
func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
|
||||
if !m.Contains(key) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if _, ok := m.data[key]; !ok {
|
||||
m.data[key] = f()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.KVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *StrStrMap) Removes(keys []string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *StrStrMap) Remove(key string) (value string) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
var ok bool
|
||||
if value, ok = m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice.
|
||||
func (m *StrStrMap) Keys() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for key := range m.data {
|
||||
keys[index] = key
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.KVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *StrStrMap) Values() []string {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]string, len(m.data))
|
||||
index = 0
|
||||
)
|
||||
for _, value := range m.data {
|
||||
values[index] = value
|
||||
index++
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.KVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *StrStrMap) Contains(key string) bool {
|
||||
var ok bool
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return ok
|
||||
m.lazyInit()
|
||||
return m.KVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *StrStrMap) Size() int {
|
||||
m.mu.RLock()
|
||||
length := len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return length
|
||||
m.lazyInit()
|
||||
return m.KVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *StrStrMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *StrStrMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[string]string)
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *StrStrMap) Replace(data map[string]string) {
|
||||
m.mu.Lock()
|
||||
m.data = data
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.KVMap.Replace(data)
|
||||
}
|
||||
|
||||
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
|
||||
func (m *StrStrMap) LockFunc(f func(m map[string]string)) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.LockFunc(f)
|
||||
}
|
||||
|
||||
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
|
||||
func (m *StrStrMap) RLockFunc(f func(m map[string]string)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
f(m.data)
|
||||
m.lazyInit()
|
||||
m.KVMap.RLockFunc(f)
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -411,19 +252,8 @@ func (m *StrStrMap) Flip() {
|
||||
// Merge merges two hash maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *StrStrMap) Merge(other *StrStrMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = other.MapCopy()
|
||||
return
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
for k, v := range other.data {
|
||||
m.data[k] = v
|
||||
}
|
||||
m.lazyInit()
|
||||
m.KVMap.Merge(other.KVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
@ -431,71 +261,40 @@ func (m *StrStrMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.KVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m StrStrMap) MarshalJSON() ([]byte, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return json.Marshal(m.data)
|
||||
m.lazyInit()
|
||||
return m.KVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *StrStrMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]string)
|
||||
}
|
||||
if err := json.UnmarshalUseNumber(b, &m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *StrStrMap) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.data = gconv.MapStrStr(value)
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.UnmarshalValue(value)
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *StrStrMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return &StrStrMap{
|
||||
KVMap: m.KVMap.DeepCopy().(*KVMap[string, string]),
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[string]string, len(m.data))
|
||||
for k, v := range m.data {
|
||||
data[k] = v
|
||||
}
|
||||
return NewStrStrMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
// IsSubOf checks whether the current map is a sub-map of `other`.
|
||||
func (m *StrStrMap) IsSubOf(other *StrStrMap) bool {
|
||||
if m == other {
|
||||
return true
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
for key, value := range m.data {
|
||||
otherValue, ok := other.data[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if otherValue != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
m.lazyInit()
|
||||
return m.KVMap.IsSubOf(other.KVMap)
|
||||
}
|
||||
|
||||
// Diff compares current map `m` with map `other` and returns their different keys.
|
||||
@ -503,22 +302,6 @@ func (m *StrStrMap) IsSubOf(other *StrStrMap) bool {
|
||||
// The returned `removedKeys` are the keys that are in map `other` but not in map `m`.
|
||||
// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`).
|
||||
func (m *StrStrMap) Diff(other *StrStrMap) (addedKeys, removedKeys, updatedKeys []string) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
|
||||
for key := range m.data {
|
||||
if _, ok := other.data[key]; !ok {
|
||||
removedKeys = append(removedKeys, key)
|
||||
} else if m.data[key] != other.data[key] {
|
||||
updatedKeys = append(updatedKeys, key)
|
||||
}
|
||||
}
|
||||
for key := range other.data {
|
||||
if _, ok := m.data[key]; !ok {
|
||||
addedKeys = append(addedKeys, key)
|
||||
}
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.KVMap.Diff(other.KVMap)
|
||||
}
|
||||
|
||||
697
container/gmap/gmap_list_k_v_map.go
Normal file
697
container/gmap/gmap_list_k_v_map.go
Normal file
@ -0,0 +1,697 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// ListKVMap is a map that preserves insertion-order.
|
||||
//
|
||||
// It is backed by a hash table to store values and doubly-linked list to store ordering.
|
||||
//
|
||||
// Thread-safety is optional and controlled by the `safe` parameter during initialization.
|
||||
//
|
||||
// Reference: http://en.wikipedia.org/wiki/Associative_array
|
||||
type ListKVMap[K comparable, V any] struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[K]*glist.TElement[*gListKVMapNode[K, V]]
|
||||
list *glist.TList[*gListKVMapNode[K, V]]
|
||||
nilChecker NilChecker[V]
|
||||
}
|
||||
|
||||
type gListKVMapNode[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
// NewListKVMap returns an empty link map.
|
||||
// ListKVMap is backed by a hash table to store values and doubly-linked list to store ordering.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewListKVMap[K comparable, V any](safe ...bool) *ListKVMap[K, V] {
|
||||
return &ListKVMap[K, V]{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[K]*glist.TElement[*gListKVMapNode[K, V]]),
|
||||
list: glist.NewT[*gListKVMapNode[K, V]](),
|
||||
}
|
||||
}
|
||||
|
||||
// NewListKVMapWithChecker creates and returns a new ListKVMap instance with a custom nil checker.
|
||||
// The parameter `checker` is a function used to determine if a value is nil.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false by default.
|
||||
func NewListKVMapWithChecker[K comparable, V any](checker NilChecker[V], safe ...bool) *ListKVMap[K, V] {
|
||||
m := NewListKVMap[K, V](safe...)
|
||||
m.RegisterNilChecker(checker)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewListKVMapFrom returns a link map from given map `data`.
|
||||
// Note that, the param `data` map will be copied to the underlying data structure,
|
||||
// so changes to the original map will not affect the link map.
|
||||
func NewListKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *ListKVMap[K, V] {
|
||||
m := NewListKVMap[K, V](safe...)
|
||||
m.Sets(data)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewListKVMapWithCheckerFrom returns a link map from given map `data` with a custom nil checker.
|
||||
// Note that, the param `data` map will be copied to the underlying data structure,
|
||||
// so changes to the original map will not affect the link map.
|
||||
// The parameter `checker` is a function used to determine if a value is nil.
|
||||
// The parameter `safe` is used to specify whether using map in concurrent-safety,
|
||||
// which is false by default.
|
||||
func NewListKVMapWithCheckerFrom[K comparable, V any](data map[K]V, nilChecker NilChecker[V], safe ...bool) *ListKVMap[K, V] {
|
||||
m := NewListKVMapWithChecker[K, V](nilChecker, safe...)
|
||||
m.Sets(data)
|
||||
return m
|
||||
}
|
||||
|
||||
// RegisterNilChecker registers a custom nil checker function for the map values.
|
||||
// This function is used to determine if a value should be considered as nil.
|
||||
// The nil checker function takes a value of type V and returns a boolean indicating
|
||||
// whether the value should be treated as nil.
|
||||
func (m *ListKVMap[K, V]) RegisterNilChecker(nilChecker NilChecker[V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.nilChecker = nilChecker
|
||||
}
|
||||
|
||||
// isNil checks whether the given value is nil.
|
||||
// It first checks if a custom nil checker function is registered and uses it if available,
|
||||
// otherwise it performs a standard nil check using any(v) == nil.
|
||||
func (m *ListKVMap[K, V]) isNil(v V) bool {
|
||||
if m.nilChecker != nil {
|
||||
return m.nilChecker(v)
|
||||
}
|
||||
return any(v) == nil
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (m *ListKVMap[K, V]) Iterator(f func(key K, value V) bool) {
|
||||
m.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorAsc iterates the map readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListKVMap[K, V]) IteratorAsc(f func(key K, value V) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
return f(e.Value.key, e.Value.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the map readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListKVMap[K, V]) IteratorDesc(f func(key K, value V) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
m.list.IteratorDesc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
return f(e.Value.key, e.Value.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a new link map with copy of current map data.
|
||||
func (m *ListKVMap[K, V]) Clone(safe ...bool) *ListKVMap[K, V] {
|
||||
return NewListKVMapFrom(m.Map(), safe...)
|
||||
}
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *ListKVMap[K, V]) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *ListKVMap[K, V]) Replace(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
for key, value := range data {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Map returns a copy of the underlying data of the map.
|
||||
func (m *ListKVMap[K, V]) Map() map[K]V {
|
||||
m.mu.RLock()
|
||||
var data map[K]V
|
||||
if m.list != nil {
|
||||
data = make(map[K]V, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
data[e.Value.key] = e.Value.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *ListKVMap[K, V]) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
var data map[string]any
|
||||
if m.list != nil {
|
||||
data = make(map[string]any, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
data[gconv.String(e.Value.key)] = e.Value.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
func (m *ListKVMap[K, V]) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
if m.list != nil {
|
||||
var keys = make([]K, 0, m.list.Size())
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
if empty.IsEmpty(e.Value.value) {
|
||||
keys = append(keys, e.Value.key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if len(keys) > 0 {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Set sets key-value to the map.
|
||||
func (m *ListKVMap[K, V]) Set(key K, value V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{key, value}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the map.
|
||||
func (m *ListKVMap[K, V]) Sets(data map[K]V) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{key, value}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *ListKVMap[K, V]) Search(key K) (value V, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.value
|
||||
found = ok
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *ListKVMap[K, V]) Get(key K) (value V) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.value
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *ListKVMap[K, V]) Pop() (key K, value V) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, e := range m.data {
|
||||
value = e.Value.value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
return k, value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *ListKVMap[K, V]) Pops(size int) map[K]V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
newMap := make(map[K]V, size)
|
||||
for k, e := range m.data {
|
||||
value := e.Value.value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
newMap[k] = value
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *ListKVMap[K, V]) doSetWithLockCheck(key K, value V) V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
return m.doSetWithLockCheckWithoutLock(key, value)
|
||||
}
|
||||
|
||||
func (m *ListKVMap[K, V]) doSetWithLockCheckWithoutLock(key K, value V) V {
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.value
|
||||
}
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *ListKVMap[K, V]) GetOrSet(key K, value V) V {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *ListKVMap[K, V]) GetOrSetFunc(key K, f func() V) V {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
//
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the map.
|
||||
func (m *ListKVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.value
|
||||
}
|
||||
value := f()
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVar(key K) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVarOrSet(key K, value V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false
|
||||
}
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false
|
||||
}
|
||||
value := f()
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
//
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the map.
|
||||
func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if _, ok := m.data[key]; ok {
|
||||
return false
|
||||
}
|
||||
value := f()
|
||||
if !m.isNil(value) {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *ListKVMap[K, V]) Remove(key K) (value V) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.value
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *ListKVMap[K, V]) Removes(keys []K) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice in ascending order.
|
||||
func (m *ListKVMap[K, V]) Keys() []K {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]K, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
keys[index] = e.Value.key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *ListKVMap[K, V]) Values() []V {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]V, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
values[index] = e.Value.value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *ListKVMap[K, V]) Contains(key K) (ok bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *ListKVMap[K, V]) Size() (size int) {
|
||||
m.mu.RLock()
|
||||
size = len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *ListKVMap[K, V]) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
func (m *ListKVMap[K, V]) Flip() error {
|
||||
data := m.Map()
|
||||
m.Clear()
|
||||
for key, value := range data {
|
||||
var (
|
||||
newKey K
|
||||
newValue V
|
||||
)
|
||||
|
||||
if err := gconv.Scan(value, &newKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gconv.Scan(key, &newValue); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Set(newKey, newValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges two link maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *ListKVMap[K, V]) Merge(other *ListKVMap[K, V]) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
var node *gListKVMapNode[K, V]
|
||||
other.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
node = e.Value
|
||||
if e, ok := m.data[node.key]; !ok {
|
||||
m.data[node.key] = m.list.PushBack(&gListKVMapNode[K, V]{node.key, node.value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{node.key, node.value}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *ListKVMap[K, V]) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m ListKVMap[K, V]) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
if m.data == nil {
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteByte('{')
|
||||
m.Iterator(func(key K, value V) bool {
|
||||
valueBytes, valueJSONErr := json.Marshal(value)
|
||||
if valueJSONErr != nil {
|
||||
err = valueJSONErr
|
||||
return false
|
||||
}
|
||||
if buffer.Len() > 1 {
|
||||
buffer.WriteByte(',')
|
||||
}
|
||||
fmt.Fprintf(buffer, `"%v":%s`, key, valueBytes)
|
||||
return true
|
||||
})
|
||||
buffer.WriteByte('}')
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *ListKVMap[K, V]) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
var data map[string]V
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
var kvData map[K]V
|
||||
if err := gconv.Scan(data, &kvData); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range kvData {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{key, value}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *ListKVMap[K, V]) UnmarshalValue(value any) (err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]])
|
||||
m.list = glist.NewT[*gListKVMapNode[K, V]]()
|
||||
}
|
||||
var dataMap map[K]V
|
||||
if err = gconv.Scan(value, &dataMap); err != nil {
|
||||
return
|
||||
}
|
||||
for k, v := range dataMap {
|
||||
if e, ok := m.data[k]; !ok {
|
||||
m.data[k] = m.list.PushBack(&gListKVMapNode[K, V]{k, v})
|
||||
} else {
|
||||
e.Value = &gListKVMapNode[K, V]{k, v}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy implements interface for deep copy of current type.
|
||||
func (m *ListKVMap[K, V]) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[K]V, len(m.data))
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool {
|
||||
data[e.Value.key] = deepcopy.Copy(e.Value.value).(V)
|
||||
return true
|
||||
})
|
||||
}
|
||||
return NewListKVMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
@ -7,15 +7,9 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/internal/deepcopy"
|
||||
"github.com/gogf/gf/v2/internal/empty"
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/internal/rwmutex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@ -27,15 +21,11 @@ import (
|
||||
//
|
||||
// Reference: http://en.wikipedia.org/wiki/Associative_array
|
||||
type ListMap struct {
|
||||
mu rwmutex.RWMutex
|
||||
data map[any]*glist.Element
|
||||
list *glist.List
|
||||
*ListKVMap[any, any]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
type gListMapNode struct {
|
||||
key any
|
||||
value any
|
||||
}
|
||||
type gListMapNode = gListKVMapNode[any, any]
|
||||
|
||||
// NewListMap returns an empty link map.
|
||||
// ListMap is backed by a hash table to store values and doubly-linked list to store ordering.
|
||||
@ -43,9 +33,7 @@ type gListMapNode struct {
|
||||
// which is false in default.
|
||||
func NewListMap(safe ...bool) *ListMap {
|
||||
return &ListMap{
|
||||
mu: rwmutex.Create(safe...),
|
||||
data: make(map[any]*glist.Element),
|
||||
list: glist.New(),
|
||||
ListKVMap: NewListKVMap[any, any](safe...),
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +46,15 @@ func NewListMapFrom(data map[any]any, safe ...bool) *ListMap {
|
||||
return m
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes the list map.
|
||||
func (m *ListMap) lazyInit() {
|
||||
m.once.Do(func() {
|
||||
if m.ListKVMap == nil {
|
||||
m.ListKVMap = NewListKVMap[any, any](false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterator is alias of IteratorAsc.
|
||||
func (m *ListMap) Iterator(f func(key, value any) bool) {
|
||||
m.IteratorAsc(f)
|
||||
@ -66,29 +63,15 @@ func (m *ListMap) Iterator(f func(key, value any) bool) {
|
||||
// IteratorAsc iterates the map readonly in ascending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListMap) IteratorAsc(f func(key any, value any) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
var node *gListMapNode
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
return f(node.key, node.value)
|
||||
})
|
||||
}
|
||||
m.lazyInit()
|
||||
m.ListKVMap.IteratorAsc(f)
|
||||
}
|
||||
|
||||
// IteratorDesc iterates the map readonly in descending order with given callback function `f`.
|
||||
// If `f` returns true, then it continues iterating; or false to stop.
|
||||
func (m *ListMap) IteratorDesc(f func(key any, value any) bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if m.list != nil {
|
||||
var node *gListMapNode
|
||||
m.list.IteratorDesc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
return f(node.key, node.value)
|
||||
})
|
||||
}
|
||||
m.lazyInit()
|
||||
m.ListKVMap.IteratorDesc(f)
|
||||
}
|
||||
|
||||
// Clone returns a new link map with copy of current map data.
|
||||
@ -98,232 +81,85 @@ func (m *ListMap) Clone(safe ...bool) *ListMap {
|
||||
|
||||
// Clear deletes all data of the map, it will remake a new underlying data map.
|
||||
func (m *ListMap) Clear() {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Clear()
|
||||
}
|
||||
|
||||
// Replace the data of the map with given `data`.
|
||||
func (m *ListMap) Replace(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Replace(data)
|
||||
}
|
||||
|
||||
// Map returns a copy of the underlying data of the map.
|
||||
func (m *ListMap) Map() map[any]any {
|
||||
m.mu.RLock()
|
||||
var node *gListMapNode
|
||||
var data map[any]any
|
||||
if m.list != nil {
|
||||
data = make(map[any]any, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
data[node.key] = node.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Map()
|
||||
}
|
||||
|
||||
// MapStrAny returns a copy of the underlying data of the map as map[string]any.
|
||||
func (m *ListMap) MapStrAny() map[string]any {
|
||||
m.mu.RLock()
|
||||
var node *gListMapNode
|
||||
var data map[string]any
|
||||
if m.list != nil {
|
||||
data = make(map[string]any, len(m.data))
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
data[gconv.String(node.key)] = node.value
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return data
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.MapStrAny()
|
||||
}
|
||||
|
||||
// FilterEmpty deletes all key-value pair of which the value is empty.
|
||||
func (m *ListMap) FilterEmpty() {
|
||||
m.mu.Lock()
|
||||
if m.list != nil {
|
||||
var (
|
||||
keys = make([]any, 0)
|
||||
node *gListMapNode
|
||||
)
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
if empty.IsEmpty(node.value) {
|
||||
keys = append(keys, node.key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(keys) > 0 {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.FilterEmpty()
|
||||
}
|
||||
|
||||
// Set sets key-value to the map.
|
||||
func (m *ListMap) Set(key any, value any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Set(key, value)
|
||||
}
|
||||
|
||||
// Sets batch sets key-values to the map.
|
||||
func (m *ListMap) Sets(data map[any]any) {
|
||||
m.mu.Lock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Sets(data)
|
||||
}
|
||||
|
||||
// Search searches the map with given `key`.
|
||||
// Second return parameter `found` is true if key was found, otherwise false.
|
||||
func (m *ListMap) Search(key any) (value any, found bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
found = ok
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Search(key)
|
||||
}
|
||||
|
||||
// Get returns the value by given `key`.
|
||||
func (m *ListMap) Get(key any) (value any) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
}
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Get(key)
|
||||
}
|
||||
|
||||
// Pop retrieves and deletes an item from the map.
|
||||
func (m *ListMap) Pop() (key, value any) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for k, e := range m.data {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
return k, value
|
||||
}
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Pop()
|
||||
}
|
||||
|
||||
// Pops retrieves and deletes `size` items from the map.
|
||||
// It returns all items if size == -1.
|
||||
func (m *ListMap) Pops(size int) map[any]any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if size > len(m.data) || size == -1 {
|
||||
size = len(m.data)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
index := 0
|
||||
newMap := make(map[any]any, size)
|
||||
for k, e := range m.data {
|
||||
value := e.Value.(*gListMapNode).value
|
||||
delete(m.data, k)
|
||||
m.list.Remove(e)
|
||||
newMap[k] = value
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// doSetWithLockCheck checks whether value of the key exists with mutex.Lock,
|
||||
// if not exists, set value to the map with given `key`,
|
||||
// or else just return the existing value.
|
||||
//
|
||||
// When setting value, if `value` is type of `func() interface {}`,
|
||||
// it will be executed with mutex.Lock of the map,
|
||||
// and its return value will be set to the map with `key`.
|
||||
//
|
||||
// It returns value with given `key`.
|
||||
func (m *ListMap) doSetWithLockCheck(key any, value any) any {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
if e, ok := m.data[key]; ok {
|
||||
return e.Value.(*gListMapNode).value
|
||||
}
|
||||
if f, ok := value.(func() any); ok {
|
||||
value = f()
|
||||
}
|
||||
if value != nil {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
}
|
||||
return value
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Pops(size)
|
||||
}
|
||||
|
||||
// GetOrSet returns the value by key,
|
||||
// or sets value with given `value` if it does not exist and then returns this value.
|
||||
func (m *ListMap) GetOrSet(key any, value any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, value)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value by key,
|
||||
// or sets value with returned value of callback function `f` if it does not exist
|
||||
// and then returns this value.
|
||||
func (m *ListMap) GetOrSetFunc(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f())
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value by key,
|
||||
@ -333,55 +169,50 @@ func (m *ListMap) GetOrSetFunc(key any, f func() any) any {
|
||||
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f`
|
||||
// with mutex.Lock of the map.
|
||||
func (m *ListMap) GetOrSetFuncLock(key any, f func() any) any {
|
||||
if v, ok := m.Search(key); !ok {
|
||||
return m.doSetWithLockCheck(key, f)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// GetVar returns a Var with the value by given `key`.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVar(key any) *gvar.Var {
|
||||
return gvar.New(m.Get(key))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVar(key)
|
||||
}
|
||||
|
||||
// GetVarOrSet returns a Var with result from GetVarOrSet.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSet(key any, value any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSet(key, value))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVarOrSet(key, value)
|
||||
}
|
||||
|
||||
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSetFunc(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFunc(key, f))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVarOrSetFunc(key, f)
|
||||
}
|
||||
|
||||
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
|
||||
// The returned Var is un-concurrent safe.
|
||||
func (m *ListMap) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var {
|
||||
return gvar.New(m.GetOrSetFuncLock(key, f))
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.GetVarOrSetFuncLock(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListMap) SetIfNotExist(key any, value any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.SetIfNotExist(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true.
|
||||
// It returns false if `key` exists, and `value` would be ignored.
|
||||
func (m *ListMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.SetIfNotExistFunc(key, f)
|
||||
}
|
||||
|
||||
// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true.
|
||||
@ -390,100 +221,52 @@ func (m *ListMap) SetIfNotExistFunc(key any, f func() any) bool {
|
||||
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
|
||||
// it executes function `f` with mutex.Lock of the map.
|
||||
func (m *ListMap) SetIfNotExistFuncLock(key any, f func() any) bool {
|
||||
if !m.Contains(key) {
|
||||
m.doSetWithLockCheck(key, f)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.SetIfNotExistFuncLock(key, f)
|
||||
}
|
||||
|
||||
// Remove deletes value from map by given `key`, and return this deleted value.
|
||||
func (m *ListMap) Remove(key any) (value any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
if e, ok := m.data[key]; ok {
|
||||
value = e.Value.(*gListMapNode).value
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Remove(key)
|
||||
}
|
||||
|
||||
// Removes batch deletes values of the map by keys.
|
||||
func (m *ListMap) Removes(keys []any) {
|
||||
m.mu.Lock()
|
||||
if m.data != nil {
|
||||
for _, key := range keys {
|
||||
if e, ok := m.data[key]; ok {
|
||||
delete(m.data, key)
|
||||
m.list.Remove(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.lazyInit()
|
||||
m.ListKVMap.Removes(keys)
|
||||
}
|
||||
|
||||
// Keys returns all keys of the map as a slice in ascending order.
|
||||
func (m *ListMap) Keys() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
keys = make([]any, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
keys[index] = e.Value.(*gListMapNode).key
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return keys
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Keys()
|
||||
}
|
||||
|
||||
// Values returns all values of the map as a slice.
|
||||
func (m *ListMap) Values() []any {
|
||||
m.mu.RLock()
|
||||
var (
|
||||
values = make([]any, m.list.Len())
|
||||
index = 0
|
||||
)
|
||||
if m.list != nil {
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
values[index] = e.Value.(*gListMapNode).value
|
||||
index++
|
||||
return true
|
||||
})
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return values
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Values()
|
||||
}
|
||||
|
||||
// Contains checks whether a key exists.
|
||||
// It returns true if the `key` exists, or else false.
|
||||
func (m *ListMap) Contains(key any) (ok bool) {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
_, ok = m.data[key]
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Contains(key)
|
||||
}
|
||||
|
||||
// Size returns the size of the map.
|
||||
func (m *ListMap) Size() (size int) {
|
||||
m.mu.RLock()
|
||||
size = len(m.data)
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.Size()
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the map is empty.
|
||||
// It returns true if map is empty, or else false.
|
||||
func (m *ListMap) IsEmpty() bool {
|
||||
return m.Size() == 0
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.IsEmpty()
|
||||
}
|
||||
|
||||
// Flip exchanges key-value of the map to value-key.
|
||||
@ -498,90 +281,35 @@ func (m *ListMap) Flip() {
|
||||
// Merge merges two link maps.
|
||||
// The `other` map will be merged into the map `m`.
|
||||
func (m *ListMap) Merge(other *ListMap) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
if other != m {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
var node *gListMapNode
|
||||
other.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
if e, ok := m.data[node.key]; !ok {
|
||||
m.data[node.key] = m.list.PushBack(&gListMapNode{node.key, node.value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{node.key, node.value}
|
||||
}
|
||||
return true
|
||||
})
|
||||
m.lazyInit()
|
||||
other.lazyInit()
|
||||
m.ListKVMap.Merge(other.ListKVMap)
|
||||
}
|
||||
|
||||
// String returns the map as a string.
|
||||
func (m *ListMap) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
b, _ := m.MarshalJSON()
|
||||
return string(b)
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (m ListMap) MarshalJSON() (jsonBytes []byte, err error) {
|
||||
if m.data == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteByte('{')
|
||||
m.Iterator(func(key, value any) bool {
|
||||
valueBytes, valueJSONErr := json.Marshal(value)
|
||||
if valueJSONErr != nil {
|
||||
err = valueJSONErr
|
||||
return false
|
||||
}
|
||||
if buffer.Len() > 1 {
|
||||
buffer.WriteByte(',')
|
||||
}
|
||||
fmt.Fprintf(buffer, `"%v":%s`, key, valueBytes)
|
||||
return true
|
||||
})
|
||||
buffer.WriteByte('}')
|
||||
return buffer.Bytes(), nil
|
||||
return m.ListKVMap.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
|
||||
func (m *ListMap) UnmarshalJSON(b []byte) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
var data map[string]any
|
||||
if err := json.UnmarshalUseNumber(b, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range data {
|
||||
if e, ok := m.data[key]; !ok {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
} else {
|
||||
e.Value = &gListMapNode{key, value}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
m.lazyInit()
|
||||
return m.ListKVMap.UnmarshalJSON(b)
|
||||
}
|
||||
|
||||
// UnmarshalValue is an interface implement which sets any type of value for map.
|
||||
func (m *ListMap) UnmarshalValue(value any) (err error) {
|
||||
m.lazyInit()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.data == nil {
|
||||
m.data = make(map[any]*glist.Element)
|
||||
m.list = glist.New()
|
||||
}
|
||||
|
||||
for k, v := range gconv.Map(value) {
|
||||
if e, ok := m.data[k]; !ok {
|
||||
m.data[k] = m.list.PushBack(&gListMapNode{k, v})
|
||||
@ -597,16 +325,8 @@ func (m *ListMap) DeepCopy() any {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data := make(map[any]any, len(m.data))
|
||||
if m.list != nil {
|
||||
var node *gListMapNode
|
||||
m.list.IteratorAsc(func(e *glist.Element) bool {
|
||||
node = e.Value.(*gListMapNode)
|
||||
data[node.key] = deepcopy.Copy(node.value)
|
||||
return true
|
||||
})
|
||||
m.lazyInit()
|
||||
return &ListMap{
|
||||
ListKVMap: m.ListKVMap.DeepCopy().(*ListKVMap[any, any]),
|
||||
}
|
||||
return NewListMapFrom(data, m.mu.IsSafe())
|
||||
}
|
||||
|
||||
32
container/gmap/gmap_tree_k_v_map.go
Normal file
32
container/gmap/gmap_tree_k_v_map.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
//go:build go1.24
|
||||
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/container/gtree"
|
||||
)
|
||||
|
||||
// TreeKVMap based on red-black tree, alias of RedBlackKVTree.
|
||||
type TreeKVMap[K comparable, V any] = gtree.RedBlackKVTree[K, V]
|
||||
|
||||
// NewTreeKVMap instantiates a tree map with the custom comparator.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewTreeKVMap[K comparable, V any](comparator func(v1, v2 K) int, safe ...bool) *TreeKVMap[K, V] {
|
||||
return gtree.NewRedBlackKVTree[K, V](comparator, safe...)
|
||||
}
|
||||
|
||||
// NewTreeKVMapFrom instantiates a tree map with the custom comparator and `data` map.
|
||||
// Note that, the param `data` map will be set as the underlying data map(no deep copy),
|
||||
// there might be some concurrent-safe issues when changing the map outside.
|
||||
// The parameter `safe` is used to specify whether using tree in concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewTreeKVMapFrom[K comparable, V any](comparator func(v1, v2 K) int, data map[K]V, safe ...bool) *TreeKVMap[K, V] {
|
||||
return gtree.NewRedBlackKVTreeFrom(comparator, data, safe...)
|
||||
}
|
||||
@ -443,3 +443,49 @@ func Test_AnyAnyMap_Diff(t *testing.T) {
|
||||
t.Assert(updatedKeys, []any{3})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_AnyAnyMap_DoSetWithLockCheck_FuncValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewAnyAnyMap(true)
|
||||
|
||||
// Test GetOrSetFuncLock with function value
|
||||
// Function should be executed and its return value should be set
|
||||
callCount := 0
|
||||
result := m.GetOrSetFuncLock(1, func() any {
|
||||
callCount++
|
||||
return "value1"
|
||||
})
|
||||
t.Assert(result, "value1")
|
||||
t.Assert(callCount, 1)
|
||||
t.Assert(m.Get(1), "value1")
|
||||
|
||||
// Test GetOrSetFuncLock again with same key
|
||||
// Function should NOT be called since key exists
|
||||
result = m.GetOrSetFuncLock(1, func() any {
|
||||
callCount++
|
||||
return "value2"
|
||||
})
|
||||
t.Assert(result, "value1")
|
||||
t.Assert(callCount, 1) // Should still be 1, function not called
|
||||
|
||||
// Test SetIfNotExistFuncLock with function value
|
||||
callCount = 0
|
||||
ok := m.SetIfNotExistFuncLock(2, func() any {
|
||||
callCount++
|
||||
return "value2"
|
||||
})
|
||||
t.Assert(ok, true)
|
||||
t.Assert(callCount, 1)
|
||||
t.Assert(m.Get(2), "value2")
|
||||
|
||||
// Test SetIfNotExistFuncLock again with same key
|
||||
// Function should NOT be called since key exists
|
||||
ok = m.SetIfNotExistFuncLock(2, func() any {
|
||||
callCount++
|
||||
return "value3"
|
||||
})
|
||||
t.Assert(ok, false)
|
||||
t.Assert(callCount, 1) // Should still be 1, function not called
|
||||
t.Assert(m.Get(2), "value2") // Value should not change
|
||||
})
|
||||
}
|
||||
|
||||
@ -96,6 +96,42 @@ func Test_StrAnyMap_Set_Fun(t *testing.T) {
|
||||
|
||||
t.Assert(m.SetIfNotExistFuncLock("b", getAny), false)
|
||||
t.Assert(m.SetIfNotExistFuncLock("d", getAny), true)
|
||||
|
||||
type T struct {
|
||||
A int
|
||||
}
|
||||
|
||||
av := m.GetOrSetFunc("s1", func() any {
|
||||
return &T{
|
||||
A: 1,
|
||||
}
|
||||
})
|
||||
ta, ok := av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 1)
|
||||
|
||||
av = m.GetOrSetFunc("s1", func() any {
|
||||
return &T{
|
||||
A: 2,
|
||||
}
|
||||
})
|
||||
ta, ok = av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 1)
|
||||
|
||||
av = m.GetOrSet("s1", &T{
|
||||
A: 3,
|
||||
})
|
||||
ta, ok = av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 1)
|
||||
|
||||
av = m.GetOrSet("s2", &T{
|
||||
A: 4,
|
||||
})
|
||||
ta, ok = av.(*T)
|
||||
t.Assert(ok, true)
|
||||
t.Assert(ta.A, 4)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
1696
container/gmap/gmap_z_unit_k_v_map_test.go
Normal file
1696
container/gmap/gmap_z_unit_k_v_map_test.go
Normal file
File diff suppressed because it is too large
Load Diff
326
container/gmap/gmap_z_unit_list_k_v_map_race_test.go
Normal file
326
container/gmap/gmap_z_unit_list_k_v_map_race_test.go
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gmap_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
)
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_Race tests the atomicity of GetOrSetFuncLock.
|
||||
// This test ensures that the callback function is only executed once even under
|
||||
// high concurrency, which verifies that the function holds the lock during the
|
||||
// entire check-and-set operation.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "counter"
|
||||
callCount := int32(0)
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
// Start multiple goroutines trying to set the same key
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m.GetOrSetFuncLock(key, func() int {
|
||||
// Increment call count atomically
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
// Simulate some work
|
||||
time.Sleep(time.Microsecond)
|
||||
return 100
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// The callback should only be called once because of proper locking
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
t.Assert(m.Get(key), 100)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_Race tests the atomicity of SetIfNotExistFuncLock.
|
||||
// This test ensures that only one goroutine can successfully set the value and
|
||||
// execute the callback function, even under high concurrency.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_Race(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "counter"
|
||||
callCount := int32(0)
|
||||
successCount := int32(0)
|
||||
goroutines := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
// Start multiple goroutines trying to set the same key
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(key, func() int {
|
||||
// Increment call count atomically
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
// Simulate some work
|
||||
time.Sleep(time.Microsecond)
|
||||
return 200
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// The callback should only be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Only one goroutine should succeed
|
||||
t.Assert(atomic.LoadInt32(&successCount), 1)
|
||||
t.Assert(m.Get(key), 200)
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_MultipleKeys tests GetOrSetFuncLock with different keys.
|
||||
// This ensures that operations on different keys don't interfere with each other.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_MultipleKeys(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
keys := []string{"key1", "key2", "key3", "key4", "key5"}
|
||||
callCounts := make([]int32, len(keys))
|
||||
goroutines := 20
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// For each key, start multiple goroutines
|
||||
for i, key := range keys {
|
||||
keyIndex := i
|
||||
for j := 0; j < goroutines; j++ {
|
||||
wg.Add(1)
|
||||
go func(idx int, k string) {
|
||||
defer wg.Done()
|
||||
m.GetOrSetFuncLock(k, func() int {
|
||||
atomic.AddInt32(&callCounts[idx], 1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return (idx + 1) * 100
|
||||
})
|
||||
}(keyIndex, key)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Each key's callback should only be called once
|
||||
for _, count := range callCounts {
|
||||
t.Assert(atomic.LoadInt32(&count), 1)
|
||||
}
|
||||
|
||||
// Verify all keys are set correctly
|
||||
for i, key := range keys {
|
||||
t.Assert(m.Get(key), (i+1)*100)
|
||||
}
|
||||
t.Assert(m.Size(), len(keys))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys tests SetIfNotExistFuncLock with different keys.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[int, string](true)
|
||||
keys := []int{1, 2, 3, 4, 5}
|
||||
callCounts := make([]int32, len(keys))
|
||||
successCounts := make([]int32, len(keys))
|
||||
goroutines := 20
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// For each key, start multiple goroutines
|
||||
for i, key := range keys {
|
||||
keyIndex := i
|
||||
for j := 0; j < goroutines; j++ {
|
||||
wg.Add(1)
|
||||
go func(idx int, k int) {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(k, func() string {
|
||||
atomic.AddInt32(&callCounts[idx], 1)
|
||||
time.Sleep(time.Microsecond)
|
||||
return gtest.DataContent()
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCounts[idx], 1)
|
||||
}
|
||||
}(keyIndex, key)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Each key's callback should only be called once
|
||||
for _, count := range callCounts {
|
||||
t.Assert(atomic.LoadInt32(&count), 1)
|
||||
}
|
||||
|
||||
// Each key should have exactly one successful set
|
||||
for _, count := range successCounts {
|
||||
t.Assert(atomic.LoadInt32(&count), 1)
|
||||
}
|
||||
|
||||
t.Assert(m.Size(), len(keys))
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_NilValue tests that nil values are handled correctly.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_NilValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *int](true)
|
||||
key := "nilKey"
|
||||
callCount := int32(0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
goroutines := 50
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m.GetOrSetFuncLock(key, func() *int {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Typed nil pointer (*int)(nil) is stored because any(value) != nil for typed nil
|
||||
// This is a Go language feature: typed nil is not the same as interface nil
|
||||
t.Assert(m.Contains(key), true)
|
||||
t.Assert(m.Get(key), (*int)(nil))
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_NilValue tests that nil values are handled correctly.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_NilValue(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, *string](true)
|
||||
key := "nilKey"
|
||||
callCount := int32(0)
|
||||
successCount := int32(0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
goroutines := 50
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(key, func() *string {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return nil
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should be called once
|
||||
t.Assert(atomic.LoadInt32(&callCount), 1)
|
||||
// Should report success once
|
||||
t.Assert(atomic.LoadInt32(&successCount), 1)
|
||||
// Typed nil pointer (*string)(nil) is stored because any(value) != nil for typed nil
|
||||
t.Assert(m.Contains(key), true)
|
||||
t.Assert(m.Get(key), (*string)(nil))
|
||||
t.Assert(m.Size(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_GetOrSetFuncLock_ExistingKey tests behavior when key already exists.
|
||||
func Test_ListKVMap_GetOrSetFuncLock_ExistingKey(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "existing"
|
||||
m.Set(key, 999)
|
||||
|
||||
callCount := int32(0)
|
||||
goroutines := 50
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
val := m.GetOrSetFuncLock(key, func() int {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return 123
|
||||
})
|
||||
// Should always get the existing value
|
||||
t.Assert(val, 999)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should never be called since key exists
|
||||
t.Assert(atomic.LoadInt32(&callCount), 0)
|
||||
t.Assert(m.Get(key), 999)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_ListKVMap_SetIfNotExistFuncLock_ExistingKey tests behavior when key already exists.
|
||||
func Test_ListKVMap_SetIfNotExistFuncLock_ExistingKey(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gmap.NewListKVMap[string, int](true)
|
||||
key := "existing"
|
||||
m.Set(key, 888)
|
||||
|
||||
callCount := int32(0)
|
||||
successCount := int32(0)
|
||||
goroutines := 50
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(goroutines)
|
||||
|
||||
for i := 0; i < goroutines; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
success := m.SetIfNotExistFuncLock(key, func() int {
|
||||
atomic.AddInt32(&callCount, 1)
|
||||
return 456
|
||||
})
|
||||
if success {
|
||||
atomic.AddInt32(&successCount, 1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Callback should never be called since key exists
|
||||
t.Assert(atomic.LoadInt32(&callCount), 0)
|
||||
// No goroutine should succeed
|
||||
t.Assert(atomic.LoadInt32(&successCount), 0)
|
||||
// Original value should remain
|
||||
t.Assert(m.Get(key), 888)
|
||||
})
|
||||
}
|
||||
1407
container/gmap/gmap_z_unit_list_k_v_map_test.go
Normal file
1407
container/gmap/gmap_z_unit_list_k_v_map_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,41 +8,19 @@
|
||||
package gpool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/glist"
|
||||
"github.com/gogf/gf/v2/container/gtype"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/os/gtimer"
|
||||
)
|
||||
|
||||
// Pool is an Object-Reusable Pool.
|
||||
type Pool struct {
|
||||
list *glist.List // Available/idle items list.
|
||||
closed *gtype.Bool // Whether the pool is closed.
|
||||
TTL time.Duration // Time To Live for pool items.
|
||||
NewFunc func() (any, error) // Callback function to create pool item.
|
||||
// ExpireFunc is the function for expired items destruction.
|
||||
// This function needs to be defined when the pool items
|
||||
// need to perform additional destruction operations.
|
||||
// Eg: net.Conn, os.File, etc.
|
||||
ExpireFunc func(any)
|
||||
}
|
||||
|
||||
// Pool item.
|
||||
type poolItem struct {
|
||||
value any // Item value.
|
||||
expireAt int64 // Expire timestamp in milliseconds.
|
||||
*TPool[any]
|
||||
}
|
||||
|
||||
// NewFunc Creation function for object.
|
||||
type NewFunc func() (any, error)
|
||||
type NewFunc = TPoolNewFunc[any]
|
||||
|
||||
// ExpireFunc Destruction function for object.
|
||||
type ExpireFunc func(any)
|
||||
type ExpireFunc = TPoolExpireFunc[any]
|
||||
|
||||
// New creates and returns a new object pool.
|
||||
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
|
||||
@ -52,134 +30,40 @@ type ExpireFunc func(any)
|
||||
// ttl < 0 : immediate expired after use;
|
||||
// ttl > 0 : timeout expired;
|
||||
func New(ttl time.Duration, newFunc NewFunc, expireFunc ...ExpireFunc) *Pool {
|
||||
r := &Pool{
|
||||
list: glist.New(true),
|
||||
closed: gtype.NewBool(),
|
||||
TTL: ttl,
|
||||
NewFunc: newFunc,
|
||||
return &Pool{
|
||||
TPool: NewTPool(ttl, newFunc, expireFunc...),
|
||||
}
|
||||
if len(expireFunc) > 0 {
|
||||
r.ExpireFunc = expireFunc[0]
|
||||
}
|
||||
gtimer.AddSingleton(context.Background(), time.Second, r.checkExpireItems)
|
||||
return r
|
||||
}
|
||||
|
||||
// Put puts an item to pool.
|
||||
func (p *Pool) Put(value any) error {
|
||||
if p.closed.Val() {
|
||||
return gerror.NewCode(gcode.CodeInvalidOperation, "pool is closed")
|
||||
}
|
||||
item := &poolItem{
|
||||
value: value,
|
||||
}
|
||||
if p.TTL == 0 {
|
||||
item.expireAt = 0
|
||||
} else {
|
||||
// As for Golang version < 1.13, there's no method Milliseconds for time.Duration.
|
||||
// So we need calculate the milliseconds using its nanoseconds value.
|
||||
item.expireAt = gtime.TimestampMilli() + p.TTL.Nanoseconds()/1000000
|
||||
}
|
||||
p.list.PushBack(item)
|
||||
return nil
|
||||
return p.TPool.Put(value)
|
||||
}
|
||||
|
||||
// MustPut puts an item to pool, it panics if any error occurs.
|
||||
func (p *Pool) MustPut(value any) {
|
||||
if err := p.Put(value); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.TPool.MustPut(value)
|
||||
}
|
||||
|
||||
// Clear clears pool, which means it will remove all items from pool.
|
||||
func (p *Pool) Clear() {
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.(*poolItem).value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.list.RemoveAll()
|
||||
}
|
||||
p.TPool.Clear()
|
||||
}
|
||||
|
||||
// Get picks and returns an item from pool. If the pool is empty and NewFunc is defined,
|
||||
// it creates and returns one from NewFunc.
|
||||
func (p *Pool) Get() (any, error) {
|
||||
for !p.closed.Val() {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
f := r.(*poolItem)
|
||||
if f.expireAt == 0 || f.expireAt > gtime.TimestampMilli() {
|
||||
return f.value, nil
|
||||
} else if p.ExpireFunc != nil {
|
||||
// TODO: move expire function calling asynchronously out from `Get` operation.
|
||||
p.ExpireFunc(f.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.NewFunc != nil {
|
||||
return p.NewFunc()
|
||||
}
|
||||
return nil, gerror.NewCode(gcode.CodeInvalidOperation, "pool is empty")
|
||||
return p.TPool.Get()
|
||||
}
|
||||
|
||||
// Size returns the count of available items of pool.
|
||||
func (p *Pool) Size() int {
|
||||
return p.list.Len()
|
||||
return p.TPool.Size()
|
||||
}
|
||||
|
||||
// Close closes the pool. If `p` has ExpireFunc,
|
||||
// then it automatically closes all items using this function before it's closed.
|
||||
// Commonly you do not need to call this function manually.
|
||||
func (p *Pool) Close() {
|
||||
p.closed.Set(true)
|
||||
}
|
||||
|
||||
// checkExpire removes expired items from pool in every second.
|
||||
func (p *Pool) checkExpireItems(ctx context.Context) {
|
||||
if p.closed.Val() {
|
||||
// If p has ExpireFunc,
|
||||
// then it must close all items using this function.
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.(*poolItem).value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
gtimer.Exit()
|
||||
}
|
||||
// All items do not expire.
|
||||
if p.TTL == 0 {
|
||||
return
|
||||
}
|
||||
// The latest item expire timestamp in milliseconds.
|
||||
var latestExpire int64 = -1
|
||||
// Retrieve the current timestamp in milliseconds, it expires the items
|
||||
// by comparing with this timestamp. It is not accurate comparison for
|
||||
// every item expired, but high performance.
|
||||
var timestampMilli = gtime.TimestampMilli()
|
||||
for latestExpire <= timestampMilli {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
latestExpire = item.expireAt
|
||||
// TODO improve the auto-expiration mechanism of the pool.
|
||||
if item.expireAt > timestampMilli {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
if p.ExpireFunc != nil {
|
||||
p.ExpireFunc(item.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.TPool.Close()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user