Skip to content

Commit

Permalink
Create
Browse files Browse the repository at this point in the history
  • Loading branch information
Heptazhou committed Mar 4, 2023
1 parent e78cc07 commit 3328b36
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 0 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI
on:
pull_request:
branches:
- "master"
push:
branches:
- "master"
tags:
- "*"
defaults:
run:
shell: bash
jobs:
test:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os:
- windows-latest
- ubuntu-latest
- macos-latest
julia-version:
- "1.6"
- "1"
- "nightly"
exclude:
- os: macos-latest
julia-version: nightly
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false
- uses: julia-actions/setup-julia@v1
with:
show-versioninfo: true
version: ${{ matrix.julia-version }}
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: heptazhou/julia-codecov@v1
- uses: codecov/codecov-action@v3
with:
file: lcov.info
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

lcov.info
Manifest.toml
14 changes: 14 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name = "UUID4"
uuid = "379725f3-1ad5-416d-b88a-50ced391fe04"
authors = ["Heptazhou <zhou at 0h7z dot com>"]
version = "1.8.0"

[deps]
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# UUID4.jl
[![CI status](https://github.com/0h7z/uuid4.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/0h7z/uuid4.jl/actions/workflows/CI.yml)
[![codecov.io](https://codecov.io/gh/0h7z/uuid4.jl/branch/master/graph/badge.svg)](https://app.codecov.io/gh/0h7z/uuid4.jl)

*****
## Usage
```julia
pkg> registry add https://github.com/0h7z/0hjl.git
pkg> add UUID4

julia> using UUID4
help?> UUID4
help?> uuid
```

204 changes: 204 additions & 0 deletions src/UUID4.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# Copyright (C) 2022-2023 Heptazhou <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""
UUID4
The `UUID4` module provides universally unique identifier (UUID), version 4,
along with related functions.
"""
module UUID4

export uuid, uuid4
export uuid_formats
export uuid_parse
export uuid_string
export uuid_version

export AbstractRNG, MersenneTwister, RandomDevice
export LittleDict, OrderedDict
export UUID
const UUID = Base.UUID
using OrderedCollections: LittleDict, OrderedDict
using Random: AbstractRNG, MersenneTwister, RandomDevice

"""
uuid(rng::AbstractRNG = RandomDevice()) -> UUID
Generate a version 4 (random or pseudo-random) universally unique identifier
(UUID), as specified by [RFC 4122](https://www.ietf.org/rfc/rfc4122).
# Examples
```jldoctest
julia> rng = MersenneTwister(42);
julia> uuid(rng)
UUID("bc8f8f98-a497-45c4-817b-b034d1a24a0e")
```
"""
function uuid(rng::AbstractRNG = RandomDevice())::UUID
id = rand(rng, UInt128)
id &= 0xffffffffffff0fff3fffffffffffffff
id |= 0x00000000000040008000000000000000
id |> UUID
end
const uuid4 = uuid

"""
uuid_formats() -> Vector{Int}
Return all supported UUID string formats.
"""
function uuid_formats()::Vector{Int}
[
# case-sensitive
22 # (base62) 22
24 # (base62) 7-7-8
# case-insensitive
25 # (base36) 25
29 # (base36) 5-5-5-5-5
32 # (base16) 32
36 # (base16) 8-4-4-4-12
39 # (base16) 4-4-4-4-4-4-4-4
]
end

"""
uuid_parse(str::String; fmt::Int = length(str)) -> Tuple{Int, UUID}
"""
function uuid_parse end
function uuid_parse(str::UUID; fmt::Any = 0x0)::Tuple{Int, UUID}
uuid_parse(string(str); fmt = Int(fmt))
end
function uuid_parse(str::Any; fmt::Number = 0)::Tuple{Int, UUID}
uuid_parse(String(str); fmt = Int(fmt))
end
function uuid_parse(str::String; fmt::Int = 0)::Tuple{Int, UUID}
len = length(str)
ret = if 0 > fmt
argumenterror("Invalid format `$fmt` (should be positive)")
elseif len fmt > 0
argumenterror("Invalid id `$str` with length = $len (should be $fmt)")
elseif len 24
uuid_parse(replace((str), "-" => ""), fmt = 22)[end]
elseif len 29
uuid_parse(replace((str), "-" => ""), fmt = 25)[end]
elseif len 39
uuid_parse(replace((str), "-" => ""), fmt = 32)[end]
elseif len 22
UUID(parse(UInt128, str, base = 62))
elseif len 25
UUID(parse(UInt128, str, base = 36))
elseif len 32
UUID(parse(UInt128, str, base = 16))
elseif len 36
UUID(str)
else
argumenterror("Invalid id `$str` with length = $len")
end
len, ret
end

"""
uuid_string(id::UUID = uuid()) -> Dict{Int, String}
uuid_string(id::UUID = uuid(), T) -> T{Int, String} where T <: AbstractDict
uuid_string(id::UUID = uuid(), fmt::Int) -> String
# Examples
```jldoctest
julia> uuid_string(OrderedDict, "123e4567-e89b-12d3-a456-426614174000")
OrderedDict{Int64, String} with 7 entries:
22 => "0YQJpYwUwvbaLOwTUr4thA"
24 => "0YQJpYw-UwvbaLO-wTUr4thA"
25 => "12vqjrnxk8whv3i8qi6qgrlz4"
29 => "12vqj-rnxk8-whv3i-8qi6q-grlz4"
32 => "123e4567e89b12d3a456426614174000"
36 => "123e4567-e89b-12d3-a456-426614174000"
39 => "123e-4567-e89b-12d3-a456-4266-1417-4000"
```
"""
function uuid_string end
function uuid_string(::Type{T}, id::Any) where T <: AbstractDict
uuid_string(uuid_parse(id)[end], T)
end
function uuid_string(id::Any, ::Type{T} = Dict) where T <: AbstractDict
uuid_string(uuid_parse(id)[end], T)
end
function uuid_string(::Type{T}, id::UUID = uuid()) where T <: AbstractDict
uuid_string(id, T)
end
function uuid_string(id::UUID = uuid(), ::Type{T} = Dict) where T <: AbstractDict
id36 = string(id)
id22 = string(id.value, base = 62, pad = 22)
id25 = string(id.value, base = 36, pad = 25)
id32 = string(id.value, base = 16, pad = 32)
id24 = replace(id22, r"(.{7})" => s"\1-", count = 2)
id29 = replace(id25, r"(.{5})" => s"\1-", count = 4)
id39 = replace(id32, r"(.{4})" => s"\1-", count = 7)
T(22 => id22, 24 => id24, 25 => id25, 29 => id29, 32 => id32, 36 => id36, 39 => id39)
end
function uuid_string(fmt::Number, id::Any)::String
uuid_string(uuid_parse(id)[end], Int(fmt))
end
function uuid_string(id::Any, fmt::Number)::String
uuid_string(uuid_parse(id)[end], Int(fmt))
end
function uuid_string(fmt::Int, id::UUID = uuid())::String
uuid_string(id, fmt)
end
function uuid_string(id::UUID, fmt::Int)::String
if 0 fmt
argumenterror("Invalid format `$fmt` (should be positive)")
elseif fmt 36
string(id)
elseif fmt 22
string(id.value, base = 62, pad = fmt)
elseif fmt 25
string(id.value, base = 36, pad = fmt)
elseif fmt 32
string(id.value, base = 16, pad = fmt)
elseif fmt 24
replace(uuid_string(id, 22), r"(.{7})" => s"\1-", count = fmt - 22)
elseif fmt 29
replace(uuid_string(id, 25), r"(.{5})" => s"\1-", count = fmt - 25)
elseif fmt 39
replace(uuid_string(id, 32), r"(.{4})" => s"\1-", count = fmt - 32)
else
argumenterror("Invalid format `$fmt` (undefined)")
end
end

"""
uuid_version(id::String) -> Int
uuid_version(id::UUID) -> Int
Inspect the given UUID or UUID string and return its version (see [RFC
4122](https://www.ietf.org/rfc/rfc4122)).
# Examples
```jldoctest
julia> uuid_version(uuid())
4
```
"""
function uuid_version end
uuid_version(id::Any)::Int = uuid_version(String(id))
uuid_version(id::String)::Int = uuid_version(uuid_parse(id)[end])
uuid_version(id::UUID)::Int = Int(id.value >> 76 & 0xf)

@noinline argumenterror(msg::AbstractString) = throw(ArgumentError(msg))

end # module

79 changes: 79 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright (C) 2022-2023 Heptazhou <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

using Random: Random
using Test, UUID4

u4 = uuid4()
@test 4 == uuid_version(u4)
@test 4 == uuid_version(u4 |> string)
@test 4 == uuid_version(u4 |> string |> GenericString)
@test 4 == uuid_version(u4 |> string |> s -> replace(s, "-" => ""))
@test u4 == UUID(string(u4)) == UUID(GenericString(string(u4)))
@test u4 == UUID(UInt128(u4))
@test uuid4(MersenneTwister(0)) == uuid4(MersenneTwister(0))
@test_throws ArgumentError UUID("550e8400e29b-41d4-a716-446655440000")
@test_throws ArgumentError UUID("550e8400e29b-41d4-a716-44665544000098")
@test_throws ArgumentError UUID("z50e8400-e29b-41d4-a716-446655440000")

# https://github.com/JuliaLang/julia/issues/35860
Random.seed!(Random.GLOBAL_RNG, 10)
@sync u4 = uuid4()
Random.seed!(Random.GLOBAL_RNG, 10)
@test u4 uuid4()

str = "22b4a8a1-e548-4eeb-9270-60426d66a48e"
@test_throws ArgumentError UUID("22b4a8a1ae548-4eeb-9270-60426d66a48e")
@test_throws ArgumentError UUID("22b4a8a1-e548a4eeb-9270-60426d66a48e")
@test_throws ArgumentError UUID("22b4a8a1-e548-4eeba9270-60426d66a48e")
@test_throws ArgumentError UUID("22b4a8a1-e548-4eeb-9270a60426d66a48e")
@test UUID(uppercase(str)) == UUID(str)
for r in (rand(UInt128, 10^3))
@test UUID(r) == UUID(string(UUID(r)))
end

fmt = [22, 24, 25, 29, 32, 36, 39]
vec = [
fmt[1] => "50XjbNooVpOszESTWcsJDk"
fmt[2] => "50XjbNo-oVpOszE-STWcsJDk"
fmt[3] => "9qr1zsf8wf3fn8st1t5r8hh1s"
fmt[4] => "9qr1z-sf8wf-3fn8s-t1t5r-8hh1s"
fmt[5] => "a4929835c612495983c50ac8e9265490"
fmt[6] => "a4929835-c612-4959-83c5-0ac8e9265490"
fmt[7] => "a492-9835-c612-4959-83c5-0ac8-e926-5490"
]
d_o, d_u = OrderedDict(vec), Dict(vec)
u = UUID(d_u[36])
s = GenericString(string(u))

@test uuid_parse(s) == uuid_parse(u) == (36, u)
@test uuid_formats() == d_o.keys == fmt
for n in fmt
@test (n, u) == uuid_parse(d_u[n]) == uuid_parse(d_u[n] |> GenericString)
@test d_u[n] == uuid_string(n, u) == uuid_string(u, n |> UInt32) == d_o[n]
@test d_o[n] == uuid_string(n, u |> string) == uuid_string(u |> string, n)
@test n == uuid_parse(uuid_string(n))[1]
end

@test_throws ArgumentError uuid_parse(d_u[32], fmt = -1)
@test_throws ArgumentError uuid_parse(d_u[32], fmt = 42)
@test_throws ArgumentError uuid_parse(d_u[32]^2)

@test uuid_string(u, OrderedDict) == uuid_string(OrderedDict, u)
@test uuid_string(u) == uuid_string(Dict, u) == d_u == Dict(d_o)
@test uuid_string(u) == uuid_string(Dict, s) == uuid_string(s, Dict)
@test_throws ArgumentError uuid_string(u, -1)
@test_throws ArgumentError uuid_string(u, 42)

0 comments on commit 3328b36

Please sign in to comment.