Skip to content

Commit

Permalink
feat: dotmaps initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
mcmcgrath13 committed Jun 5, 2020
1 parent d351ff4 commit cf5a379
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name = "DotMap"
name = "DotMaps"
uuid = "98454f44-f182-4945-8d34-ddde53e72162"
authors = ["mcmcgrath13 <[email protected]> and contributors"]
version = "0.1.0"
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# DotMap
# DotMaps

[![Build Status](https://github.com/mcmcgrath13/DotMap.jl/workflows/CI/badge.svg)](https://github.com/mcmcgrath13/DotMap.jl/actions)
[![Build Status](https://github.com/mcmcgrath13/DotMaps.jl/workflows/CI/badge.svg)](https://github.com/mcmcgrath13/DotMaps.jl/actions)

A wrapper for dictionaries that allows dot notation indexing as well as traditional bracket indexing.

```julia
dict = Dict("a"=>1, "b"=>2, "c" => Dict("d"=>3))
dm = DotMap(dict)

dm.c.d # returns 3
dm.c.e = 5
dm["c"].e # returns 5
```

**NOTE** This is not as performative as using normal dictionaries, but is nice for accessing deeply nested dictionary structures, such as large config/yaml/json files.
5 changes: 0 additions & 5 deletions src/DotMap.jl

This file was deleted.

82 changes: 82 additions & 0 deletions src/DotMaps.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
module DotMaps

export DotMap

"""
DotMaps.DotMap(::Dict)
Constructs a DotMap from a Dict. This provides the same functionaliity as dictionaries, but allows indexing with `.` instead of (or in addition to) `[""]`.
"""
mutable struct DotMap
__dict__ ::Dict{Symbol,Any}
DotMap() = new(Dict{Symbol,Any}())
end

function DotMap(d::Dict)
dm = DotMap()
for (k, v) in d
dm.__dict__[Symbol(k)] = DotMap(v)
end
return dm
end

DotMap(d::Any) = d

# make dots work
function Base.getproperty(obj::DotMap, name::Symbol)
if name == :__dict__
return getfield(obj, name)
else
return obj.__dict__[name]
end
end

function Base.setproperty!(obj::DotMap, name::Symbol, x)
obj.__dict__[name] = x
end

# make dictionary indexing work
Base.getindex(obj::DotMap, name::Symbol) = Base.getindex(obj.__dict__, name)
Base.getindex(obj::DotMap, name::Any) = Base.getindex(obj, Symbol(name))

Base.setindex!(obj::DotMap, name::Symbol, x) = Base.setindex!(obj.__dict__, name, x)
Base.setindex!(obj::DotMap, name, x) = Base.setindex!(obj, Symbol(name), x)

# iteration
Base.iterate(obj::DotMap) = Base.iterate(obj.__dict__)
Base.iterate(obj::DotMap, s::Any) = Base.iterate(obj.__dict__, s)
Base.length(obj::DotMap) = Base.length(obj.__dict__)
Base.firstindex(obj::DotMap) = Base.firstindex(obj.__dict__)
Base.lastindex(obj::DotMap) = Base.lastindex(obj.__dict__)

# dictionary methods
Base.keys(obj::DotMap) = Base.keys(obj.__dict__)
Base.values(obj::DotMap) = Base.values(obj.__dict__)
Base.collect(obj::DotMap) = Base.collect(obj.__dict__)

# retrieval/modification
Base.get(obj::DotMap, key::Symbol, default) = Base.get(obj.__dict__, key, default)
Base.get(obj::DotMap, key::Any, default) = Base.get(obj.__dict__, Symbol(key), default)
Base.get!(obj::DotMap, key::Symbol, default) = Base.get!(obj.__dict__, key, default)
Base.get!(obj::DotMap, key::Any, default) = Base.get!(obj.__dict__, Symbol(key), default)
Base.getkey(obj::DotMap, key::Symbol, default) = Base.getkey(obj.__dict__, key, default)
Base.getkey(obj::DotMap, key::Any, default) = Base.getkey(obj.__dict__, Symbol(key), default)
Base.pop!(obj::DotMap, key::Symbol) = Base.pop!(obj.__dict__, key)
Base.pop!(obj::DotMap, key::Any) = Base.pop!(obj.__dict__, Symbol(key))
Base.delete!(obj::DotMap, key::Symbol) = Base.pop!(obj.__dict__, key)
Base.delete!(obj::DotMap, key::Any) = Base.pop!(obj.__dict__, Symbol(key))
Base.filter!(pred, obj::DotMap) = Base.filter!(pred, obj.__dict__)
Base.filter(pred, obj::DotMap) = DotMap(Base.filter(pred, obj.__dict__))

# about this dict
Base.propertynames(obj::DotMap) = Base.keys(obj.__dict__)
Base.isempty(obj::DotMap) = Base.isempty(obj.__dict__)
Base.show(obj::DotMap) = Base.show(obj.__dict__)

# containment
Base.in(key::Symbol, obj::DotMap) = Base.in(key, obj.__dict__)
Base.in(key::Any, obj::DotMap) = Base.in(Symbol(key), obj.__dict__)
Base.haskey(obj::DotMap, key::Symbol) = Base.haskey(obj.__dict__, key)
Base.haskey(obj::DotMap, key::Any) = Base.haskey(obj.__dict__, Symbol(key))

end
42 changes: 39 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
using DotMap
using DotMaps
using Test

@testset "DotMap.jl" begin
# Write your tests here.
@testset "DotMaps.jl" begin

dict = Dict("a"=>1, "b"=>2, "c" => Dict("d"=>3))
DM = DotMap(dict)

@test DM.a == 1
@test DM.c.d == 3

DM.c.e = 4
@test DM.c.e == 4

delete!(DM.c, "e")
@test !("e" in keys(DM.c))

@test get!(DM.c, "e", 5) == 5
@test DM.c.e == 5
@test DM["c"].e == 5

for (k, v) in DM
@test isa(k, Symbol)
end

@test length(DM) == 3
@test 3 in collect(values(DM.c))

@test pop!(DM.c, "e") == 5

filtered = filter(x -> isa(last(x), Int), DM)
@test !("c" in keys(filtered))
@test filtered.a == 1

filter!(x -> isa(last(x), Int), DM)
@test !("c" in keys(DM))
@test haskey(DM, "a")
@test DM.a == 1
@test :a in propertynames(DM)

@test isempty(DotMap())
end

0 comments on commit cf5a379

Please sign in to comment.