Skip to content

Commit

Permalink
add setkeypath! (#83)
Browse files Browse the repository at this point in the history
* add setkeypath!

* docs

* cleanup

* separate workflow for documentation

* fix doctest
  • Loading branch information
CarloLucibello authored Jun 4, 2024
1 parent e3aa5ef commit 54e238d
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 43 deletions.
25 changes: 0 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
24 changes: 24 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
1 change: 1 addition & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ Functors.IterateWalk
Functors.KeyPath
Functors.haskeypath
Functors.getkeypath
Functors.setkeypath!
```
2 changes: 1 addition & 1 deletion src/Functors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
38 changes: 31 additions & 7 deletions src/keypath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -88,19 +90,25 @@ _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
_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
Expand All @@ -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
Expand All @@ -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
15 changes: 15 additions & 0 deletions test/keypath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
9 changes: 0 additions & 9 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 54e238d

Please sign in to comment.