From 54e238d7a6c595f674cd771fe8bf26676b34de96 Mon Sep 17 00:00:00 2001 From: Carlo Lucibello Date: Tue, 4 Jun 2024 16:40:29 +0200 Subject: [PATCH] add setkeypath! (#83) * add setkeypath! * docs * cleanup * separate workflow for documentation * fix doctest --- .github/workflows/ci.yml | 25 ------------------------- .github/workflows/docs.yml | 24 ++++++++++++++++++++++++ docs/make.jl | 1 - docs/src/api.md | 1 + src/Functors.jl | 2 +- src/keypath.jl | 38 +++++++++++++++++++++++++++++++------- test/keypath.jl | 15 +++++++++++++++ test/runtests.jl | 9 --------- 8 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2a7e19..75035d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,28 +56,3 @@ jobs: continue-on-error: ${{ matrix.julia-version == 'nightly' }} - uses: julia-actions/julia-runtest@v1 continue-on-error: ${{ matrix.julia-version == 'nightly' }} - - docs: - name: Documentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: julia-actions/setup-julia@v2 - with: - version: '1.6' - - run: | - julia --project=docs -e ' - using Pkg - Pkg.develop(PackageSpec(path=pwd())) - Pkg.instantiate()' - - run: | - julia --project=docs/ -e ' - using Functors - using Documenter - using Documenter: doctest - DocMeta.setdocmeta!(Functors, :DocTestSetup, :(using Functors); recursive = true) - doctest(Functors)' - - run: julia --project=docs docs/make.jl - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..08b3911 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,24 @@ +name: Documentation + +on: + push: + branches: + - master + tags: '*' + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@latest + with: + version: '1.10' + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key + run: julia --project=docs/ docs/make.jl \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 2f8beeb..c1424a6 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -3,7 +3,6 @@ using Documenter, Functors DocMeta.setdocmeta!(Functors, :DocTestSetup, :(using Functors); recursive = true) makedocs(modules = [Functors], - doctest = false, sitename = "Functors.jl", pages = ["Home" => "index.md", "API" => "api.md"], diff --git a/docs/src/api.md b/docs/src/api.md index 70dd2ff..2acdd5c 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -50,4 +50,5 @@ Functors.IterateWalk Functors.KeyPath Functors.haskeypath Functors.getkeypath +Functors.setkeypath! ``` diff --git a/src/Functors.jl b/src/Functors.jl index 71e0c8b..ffcbf8f 100644 --- a/src/Functors.jl +++ b/src/Functors.jl @@ -2,7 +2,7 @@ module Functors export @functor, @flexiblefunctor, fmap, fmapstructure, fcollect, execute, fleaves, fmap_with_path, fmapstructure_with_path, - KeyPath, getkeypath, haskeypath + KeyPath, getkeypath, haskeypath, setkeypath! include("functor.jl") include("keypath.jl") diff --git a/src/keypath.jl b/src/keypath.jl index f64be1d..280ef94 100644 --- a/src/keypath.jl +++ b/src/keypath.jl @@ -65,12 +65,14 @@ end Base.isempty(kp::KeyPath) = false Base.isempty(kp::KeyPath{Tuple{}}) = true -Base.getindex(kp::KeyPath, i::Int) = kp.keys[i] +Base.getindex(kp::KeyPath, i::Integer) = kp.keys[i] +Base.getindex(kp::KeyPath, r::AbstractVector) = KeyPath(kp.keys[r]) +Base.last(kp::KeyPath) = last(kp.keys) +Base.lastindex(kp::KeyPath) = lastindex(kp.keys) Base.length(kp::KeyPath) = length(kp.keys) Base.iterate(kp::KeyPath, state=1) = iterate(kp.keys, state) -Base.:(==)(kp1::KeyPath, kp2::KeyPath) = kp1.keys == kp2.keys Base.tail(kp::KeyPath) = KeyPath(Base.tail(kp.keys)) -Base.last(kp::KeyPath) = last(kp.keys) +Base.:(==)(kp1::KeyPath, kp2::KeyPath) = kp1.keys == kp2.keys function Base.show(io::IO, kp::KeyPath) compat = get(io, :compact, false) @@ -88,6 +90,11 @@ _getkey(x, k::Symbol) = getproperty(x, k) _getkey(x::AbstractDict, k::Symbol) = x[k] _getkey(x, k::AbstractString) = x[k] +_setkey!(x, k::Integer, v) = (x[k] = v) +_setkey!(x, k::Symbol, v) = setproperty!(x, k, v) +_setkey!(x::AbstractDict, k::Symbol, v) = (x[k] = v) +_setkey!(x, k::AbstractString, v) = (x[k] = v) + _haskey(x, k::Integer) = haskey(x, k) _haskey(x::Tuple, k::Integer) = 1 <= k <= length(x) _haskey(x::AbstractArray, k::Integer) = 1 <= k <= length(x) # TODO: extend to generic indexing @@ -95,12 +102,13 @@ _haskey(x, k::Symbol) = k in propertynames(x) _haskey(x::AbstractDict, k::Symbol) = haskey(x, k) _haskey(x, k::AbstractString) = haskey(x, k) + """ getkeypath(x, kp::KeyPath) Return the value in `x` at the path `kp`. -See also [`KeyPath`](@ref) and [`haskeypath`](@ref). +See also [`KeyPath`](@ref), [`haskeypath`](@ref), and [`setkeypath!`](@ref). # Examples ```jldoctest @@ -126,14 +134,14 @@ end Return `true` if `x` has a value at the path `kp`. -See also [`KeyPath`](@ref) and [`getkeypath`](@ref). +See also [`KeyPath`](@ref), [`getkeypath`](@ref), and [`setkeypath!`](@ref). # Examples ```jldoctest julia> x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7])) -Dict{Any,Any} with 2 entries: +Dict{Symbol, Any} with 2 entries: :a => 3 - :b => Dict{Any,Any}(:c=>4,"d"=>[5, 6, 7]) + :b => Dict{Any, Any}(:c=>4, "d"=>[5, 6, 7]) julia> haskeypath(x, KeyPath(:a)) true @@ -153,3 +161,19 @@ function haskeypath(x, kp::KeyPath) return _haskey(x, k) && haskeypath(_getkey(x, k), tail(kp)) end end + +""" + setkeypath!(x, kp::KeyPath, v) + +Set the value in `x` at the path `kp` to `v`. + +See also [`KeyPath`](@ref), [`getkeypath`](@ref), and [`haskeypath`](@ref). +""" +function setkeypath!(x, kp::KeyPath, v) + if isempty(kp) + error("Empty keypath not allowed.") + end + y = getkeypath(x, kp[1:end-1]) + k = kp[end] + return _setkey!(y, k, v) +end diff --git a/test/keypath.jl b/test/keypath.jl index a206d53..a303a02 100644 --- a/test/keypath.jl +++ b/test/keypath.jl @@ -52,6 +52,21 @@ end end + @testset "setkeypath!" begin + x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7])) + setkeypath!(x, KeyPath(:a), 4) + @test x[:a] == 4 + setkeypath!(x, KeyPath(:b, "d", 1), 17) + @test x[:b]["d"][1] == 17 + setkeypath!(x, KeyPath(:b, "d"), [0]) + @test x[:b]["d"] == [0] + + x = Tkp(3, Tkp(4, 5, [6, 7]), 8) + kp = KeyPath(:b, :c, 2) + setkeypath!(x, kp, 17) + @test x.b.c[2] == 17 + end + @testset "haskeypath" begin x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7])) @test haskeypath(x, KeyPath(:a)) diff --git a/test/runtests.jl b/test/runtests.jl index 99b0c02..7db1e1b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,13 +9,4 @@ using StaticArrays include("base.jl") include("keypath.jl") - if VERSION < v"1.6" # || VERSION > v"1.7-" - @warn "skipping doctests, on Julia $VERSION" - else - using Documenter - @testset "doctests" begin - DocMeta.setdocmeta!(Functors, :DocTestSetup, :(using Functors); recursive=true) - doctest(Functors, manual=true) - end - end end