From 85e545618764bf23221cef62e9dde7f0b00b18c7 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 1 Aug 2017 12:03:48 -0400 Subject: [PATCH 1/4] define Bib and BibItem types for better IO, rather than Dict --- src/BibTeX.jl | 173 ++-------------------------------------------- src/bib.jl | 39 +++++++++++ src/bibitem.jl | 41 +++++++++++ src/parser.jl | 168 ++++++++++++++++++++++++++++++++++++++++++++ test/benchmark.jl | 2 +- test/runtests.jl | 9 ++- 6 files changed, 259 insertions(+), 173 deletions(-) create mode 100644 src/bib.jl create mode 100644 src/bibitem.jl create mode 100644 src/parser.jl diff --git a/src/BibTeX.jl b/src/BibTeX.jl index 3509e06..d7f036c 100644 --- a/src/BibTeX.jl +++ b/src/BibTeX.jl @@ -1,173 +1,8 @@ module BibTeX +export Bib, BibItem -struct Parser{T} - tokens::T - substitutions::Dict{String, String} - records::Dict{String, Dict{String, String}} - line::Ref{Int} -end - -Base.eltype(p::Parser) = eltype(p.tokens) -Base.one(p::Parser) = eltype(p)("") - -Parser(tokens::T, substitutions, records, line) where T = - Parser{T}(tokens, substitutions, records, line) - -parse_text(text) = begin - tokens = matchall(r"[^\s\n\"#{}@,=]+|\n|\"|#|{|}|@|,|=", text) - Parser(tokens, Dict{String, String}(), Dict{String, String}(), Ref(1)) -end - -location(parser) = "on line $(parser.line.x)" - -next_token_default!(parser) = - if isempty(parser.tokens) - one(parser) - else - result = shift!(parser.tokens) - if result == "\n" - parser.line.x = parser.line.x + 1 - next_token_default!(parser) - else - result - end - end - -next_token!(parser, eol = "additional tokens") = begin - result = next_token_default!(parser) - if result == "" - error("Expected $eol $(location(parser))") - else - result - end -end - -expect(parser, result, expectation) = - if result != expectation - error("Expected $expectation $(location(parser))") - end - -expect!(parser, expectation) = expect(parser, next_token!(parser, expectation), expectation) - -token_and_counter!(parser, bracket_counter = 1) = begin - token = next_token!(parser, "}") - if token == "{" - bracket_counter += 1 - elseif token == "}" - bracket_counter -= 1 - end - token, bracket_counter -end - -value!(parser, values = eltype(parser)[]) = begin - token = next_token!(parser) - if token == "\"" - token = next_token!(parser, "\"") - while token != "\"" - push!(values, token) - token = next_token!(parser, "\"") - end - elseif token == "{" - token, counter = token_and_counter!(parser) - while counter > 0 - push!(values, token) - token, counter = token_and_counter!(parser, counter) - end - else - push!(values, getkey(parser.substitutions, token, String(token) ) ) - end - token = next_token!(parser, ", or }") - if token == "#" - value!(parser, values) - else - token, join(values, " ") - end -end - -field!(parser, dict) = begin - token = "," - while token == "," - token = next_token!(parser, "a new entry or }") - if token != "}" - key = token - expect!(parser, "=") - token, dict[key] = value!(parser) - end - end - expect(parser, token, "}") -end - -export parse_bibtex -""" - parse_bibtex(text) - -This is a simple input parser for BibTex. I had trouble finding a standard -specification, but I've included several features of real BibTex. Returns -a preamble (or an empty string) and a dict of dicts. - -```jldoctest -julia> using BibTeX - -julia> preamble, result = parse_bibtex(""\" - @preamble{some instructions} - @comment blah blah - @string{short = long} - @a{b, - c = { {c} c}, - d = "d d", - e = f # short - } - ""\"); - -julia> preamble -"some instructions" - -julia> result["b"]["type"] -"a" - -julia> result["b"]["c"] -"{ c } c" - -julia> result["b"]["d"] -"d d" - -julia> result["b"]["e"] -"f short" - -julia> parse_bibtex("@book") -ERROR: Expected { on line 1 -[...] - -julia> parse_bibtex("@book@") -ERROR: Expected { on line 1 -[...] -``` -""" -parse_bibtex(text) = begin - parser = parse_text(text) - token = next_token_default!(parser) - preamble = "" - while token != "" - if token == "@" - record_type = lowercase(next_token!(parser)) - if record_type == "preamble" - trash, preamble = value!(parser) - elseif record_type != "comment" - expect!(parser, "{") - if record_type == "string" - field!(parser, parser.substitutions) - else - id = next_token!(parser) - dict = Dict("type" => record_type) - expect!(parser, ",") - field!(parser, dict) - parser.records[id] = dict - end - end - end - token = next_token_default!(parser) - end - preamble, parser.records -end +include("parser.jl") +include("bibitem.jl") +include("bib.jl") end diff --git a/src/bib.jl b/src/bib.jl new file mode 100644 index 0000000..3eb5ab8 --- /dev/null +++ b/src/bib.jl @@ -0,0 +1,39 @@ +struct Bib <: Associative{String,BibItem} + preamble::String + data::Dict{String,BibItem} +end + +""" + Bib(bibtex::String) + Bib(io::IO) + +Given a string (or IO stream) of bibtex-format bibliography data, +parses the data and returns a `Dict`-like object `b::Bib` that +behaves as a dictionary mapping strings to bibliography items +[`BibItem`](@ref). +""" +function Bib(bibtex::String) + preamble, data = parse_bibtex(bibtex) + return Bib(preamble, Dict(k=>BibItem!(v) for (k,v) in data)) +end +Bib(io::IO) = Bib(readstring(io)) +Base.open(::Type{Bib}, args...) = open(io -> Bib(io), args...) + +Base.similar(b::Bib) = Bib("", Dict{String,BibItem}()) +Base.rehash!(b::Bib, n=length(b.data)) = begin rehash!(b.data, n); b; end +Base.sizehint!(b::Bib, n) = begin sizehint!(b.data, n); b; end +Base.empty!(b::Bib) = begin empty!(b.data); b; end +Base.copy(b::Bib) = Bib(b.preamble, copy(b.data)) + +function Base.setindex!(b::Bib, v::BibItem, k::AbstractString) + setindex!(b.data[String(k)], v) + return b +end +Base.get(b::Bib, k::AbstractString, default) = get(b.data, String(k), default) + +Base.start(b::Bib) = start(b.data) +Base.done(b::Bib, i) = done(b.data, i) +Base.next(b::Bib, i) = next(b.data, i) +Base.length(b::Bib) = length(b.data) + +# todo: add specialized Base.show methods for MIME"text/bibtex" etc. diff --git a/src/bibitem.jl b/src/bibitem.jl new file mode 100644 index 0000000..2f1f755 --- /dev/null +++ b/src/bibitem.jl @@ -0,0 +1,41 @@ +""" + BibItem{S}(data::Dict{String,String}) + +A bibliography item in a bibTeX database, based on a dictionary of +strings to values. It is parameterized by a symbol `S` giving the +type of the item (`:article` etcetera). A `b::BibItem` supports +`b[key]` access to retrieve the data and in general acts like +a dictionary from `String` to `String`. +""" +struct BibItem{S} <: Associative{String,String} + data::Dict{String,String} +end + +function BibItem!(data::Dict{String,String}) + S = Symbol(pop!(data, "__type__")) + return BibItem{S}(data) +end + +Base.similar(b::BibItem{S}) where {S} = BibItem{S}(Dict{String,String}()) +Base.rehash!(b::BibItem, n=length(b.data)) = begin rehash!(b.data, n); b; end +Base.sizehint!(b::BibItem, n) = begin sizehint!(b.data, n); b; end +Base.empty!(b::BibItem) = begin empty!(b.data); b; end +Base.copy(b::BibItem{S}) where {S} = BibItem{S}(copy(b.data)) + +Base.get(b::BibItem, k::AbstractString, default) = get(b.data, String(k), default) +Base.getindex(b::BibItem, k::AbstractString) = getindex(b.data, String(k)) +function Base.setindex!(b::BibItem, v::AbstractString, k::AbstractString) + b.data[String(k)] = String(v) + return b +end + +Base.start(b::BibItem) = start(b.data) +Base.done(b::BibItem, i) = done(b.data, i) +Base.next(b::BibItem, i) = next(b.data, i) +Base.length(b::BibItem) = length(b.data) + +function Base.show{S}(io::IO, b::BibItem{S}) + print(io, "BibItem{:$S}(", length(b), " entries)") +end + +# TODO: add Base.show text/plain and text/markdown for formatted citation diff --git a/src/parser.jl b/src/parser.jl new file mode 100644 index 0000000..df6d296 --- /dev/null +++ b/src/parser.jl @@ -0,0 +1,168 @@ +struct Parser{T} + tokens::T + substitutions::Dict{String, String} + records::Dict{String, Dict{String, String}} + line::Ref{Int} +end + +Base.eltype(p::Parser) = eltype(p.tokens) +Base.one(p::Parser) = eltype(p)("") + +Parser(tokens::T, substitutions, records, line) where T = + Parser{T}(tokens, substitutions, records, line) + +parse_text(text) = begin + tokens = matchall(r"[^\s\n\"#{}@,=]+|\n|\"|#|{|}|@|,|=", text) + Parser(tokens, Dict{String, String}(), Dict{String, String}(), Ref(1)) +end + +location(parser) = "on line $(parser.line.x)" + +next_token_default!(parser) = + if isempty(parser.tokens) + one(parser) + else + result = shift!(parser.tokens) + if result == "\n" + parser.line.x = parser.line.x + 1 + next_token_default!(parser) + else + result + end + end + +next_token!(parser, eol = "additional tokens") = begin + result = next_token_default!(parser) + if result == "" + error("Expected $eol $(location(parser))") + else + result + end +end + +expect(parser, result, expectation) = + if result != expectation + error("Expected $expectation $(location(parser))") + end + +expect!(parser, expectation) = expect(parser, next_token!(parser, expectation), expectation) + +token_and_counter!(parser, bracket_counter = 1) = begin + token = next_token!(parser, "}") + if token == "{" + bracket_counter += 1 + elseif token == "}" + bracket_counter -= 1 + end + token, bracket_counter +end + +value!(parser, values = eltype(parser)[]) = begin + token = next_token!(parser) + if token == "\"" + token = next_token!(parser, "\"") + while token != "\"" + push!(values, token) + token = next_token!(parser, "\"") + end + elseif token == "{" + token, counter = token_and_counter!(parser) + while counter > 0 + push!(values, token) + token, counter = token_and_counter!(parser, counter) + end + else + push!(values, getkey(parser.substitutions, token, String(token) ) ) + end + token = next_token!(parser, ", or }") + if token == "#" + value!(parser, values) + else + token, join(values, " ") + end +end + +field!(parser, dict) = begin + token = "," + while token == "," + token = next_token!(parser, "a new entry or }") + if token != "}" + key = token + expect!(parser, "=") + token, dict[key] = value!(parser) + end + end + expect(parser, token, "}") +end + +""" + parse_bibtex(text) + +This is a simple input parser for BibTex. I had trouble finding a standard +specification, but I've included several features of real BibTex. Returns +a preamble (or an empty string) and a dict of dicts. + +```jldoctest +julia> using BibTeX: parse_bibtex + +julia> preamble, result = parse_bibtex(""\" + @preamble{some instructions} + @comment blah blah + @string{short = long} + @a{b, + c = { {c} c}, + d = "d d", + e = f # short + } + ""\"); + +julia> preamble +"some instructions" + +julia> result["b"]["__type__"] +"a" + +julia> result["b"]["c"] +"{ c } c" + +julia> result["b"]["d"] +"d d" + +julia> result["b"]["e"] +"f short" + +julia> parse_bibtex("@book") +ERROR: Expected { on line 1 +[...] + +julia> parse_bibtex("@book@") +ERROR: Expected { on line 1 +[...] +``` +""" +parse_bibtex(text) = begin + parser = parse_text(text) + token = next_token_default!(parser) + preamble = "" + while token != "" + if token == "@" + record_type = lowercase(next_token!(parser)) + if record_type == "preamble" + trash, preamble = value!(parser) + elseif record_type != "comment" + expect!(parser, "{") + if record_type == "string" + field!(parser, parser.substitutions) + else + id = next_token!(parser) + dict = Dict("__type__" => record_type) + expect!(parser, ",") + field!(parser, dict) + parser.records[id] = dict + end + end + end + token = next_token_default!(parser) + end + preamble, parser.records +end diff --git a/test/benchmark.jl b/test/benchmark.jl index 737dad3..cb868d1 100644 --- a/test/benchmark.jl +++ b/test/benchmark.jl @@ -3,4 +3,4 @@ const file = joinpath((@__FILE__) |> dirname |> dirname, "example", "examples.bi using BenchmarkTools using BibTeX -@benchmark parse_bibtex(file) +@benchmark BibTeX.parse_bibtex(file) diff --git a/test/runtests.jl b/test/runtests.jl index 1990dd9..ba1ee3e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using BibTeX +using BibTeX, Base.Test import Documenter Documenter.makedocs( @@ -13,5 +13,8 @@ Documenter.makedocs( authors = "Brandon Taylor" ) -# just test if it parses (for now) -joinpath((@__FILE__) |> dirname |> dirname, "example", "examples.bib") |> readstring |> parse_bibtex +@testset "examples.bib" begin + b = open(Bib, joinpath("..", "example", "examples.bib"), "r") + @test length(b) == 92 + @test (b["angenendt"]::BibItem{:article})["date"] == "2002" +end From 75809edb6b6f426fb947e79b9668777001299013 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 1 Aug 2017 12:07:50 -0400 Subject: [PATCH 2/4] test case-insensitivity of entry types and field keys --- example/examples.bib | 4 ++-- src/parser.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/examples.bib b/example/examples.bib index 36ac849..b784a7d 100644 --- a/example/examples.bib +++ b/example/examples.bib @@ -55,12 +55,12 @@ indextitle = {Effect of immobilization on catalytic characteristics}, } -@article{angenendt, +@Article{angenendt, author = {Angenendt, Arnold}, title = {In Honore Salvatoris~-- Vom Sinn und Unsinn der Patrozinienkunde}, journaltitle = {Revue d'Histoire Eccl{\'e}siastique}, - date = 2002, + Date = 2002, volume = 97, pages = {431--456, 791--823}, langid = {german}, diff --git a/src/parser.jl b/src/parser.jl index df6d296..f1382b3 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -89,7 +89,7 @@ field!(parser, dict) = begin if token != "}" key = token expect!(parser, "=") - token, dict[key] = value!(parser) + token, dict[lowercase(key)] = value!(parser) end end expect(parser, token, "}") From 457f4104d4d8af1d615b3ab03ddfb0474c18ea6b Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 1 Aug 2017 16:51:03 -0400 Subject: [PATCH 3/4] more tests and fixes, renamed to Bibliography and Citation --- src/BibTeX.jl | 2 +- src/bib.jl | 44 ++++++++++++++++++++++---------------------- src/bibitem.jl | 39 ++++++++++++++++++++------------------- test/runtests.jl | 26 ++++++++++++++++++++++++-- 4 files changed, 67 insertions(+), 44 deletions(-) diff --git a/src/BibTeX.jl b/src/BibTeX.jl index d7f036c..3aee2b7 100644 --- a/src/BibTeX.jl +++ b/src/BibTeX.jl @@ -1,5 +1,5 @@ module BibTeX -export Bib, BibItem +export Bibliography, Citation include("parser.jl") include("bibitem.jl") diff --git a/src/bib.jl b/src/bib.jl index 3eb5ab8..8d727dc 100644 --- a/src/bib.jl +++ b/src/bib.jl @@ -1,39 +1,39 @@ -struct Bib <: Associative{String,BibItem} +struct Bibliography <: Associative{String,Citation} preamble::String - data::Dict{String,BibItem} + data::Dict{String,Citation} end """ - Bib(bibtex::String) - Bib(io::IO) + Bibliography(bibtex::String) + Bibliography(io::IO) Given a string (or IO stream) of bibtex-format bibliography data, -parses the data and returns a `Dict`-like object `b::Bib` that +parses the data and returns a `Dict`-like object `b::Bibliography` that behaves as a dictionary mapping strings to bibliography items -[`BibItem`](@ref). +[`Citation`](@ref). """ -function Bib(bibtex::String) +function Bibliography(bibtex::String) preamble, data = parse_bibtex(bibtex) - return Bib(preamble, Dict(k=>BibItem!(v) for (k,v) in data)) + return Bibliography(preamble, Dict(k=>Citation!(v) for (k,v) in data)) end -Bib(io::IO) = Bib(readstring(io)) -Base.open(::Type{Bib}, args...) = open(io -> Bib(io), args...) +Bibliography(io::IO) = Bibliography(readstring(io)) +Base.open(::Type{Bibliography}, args...) = open(io -> Bibliography(io), args...) -Base.similar(b::Bib) = Bib("", Dict{String,BibItem}()) -Base.rehash!(b::Bib, n=length(b.data)) = begin rehash!(b.data, n); b; end -Base.sizehint!(b::Bib, n) = begin sizehint!(b.data, n); b; end -Base.empty!(b::Bib) = begin empty!(b.data); b; end -Base.copy(b::Bib) = Bib(b.preamble, copy(b.data)) +Base.similar(b::Bibliography) = Bibliography("", Dict{String,Citation}()) +Base.rehash!(b::Bibliography, n=length(b.data)) = begin Base.rehash!(b.data, n); b; end +Base.sizehint!(b::Bibliography, n) = begin sizehint!(b.data, n); b; end +Base.empty!(b::Bibliography) = begin empty!(b.data); b; end +Base.copy(b::Bibliography) = Bibliography(b.preamble, copy(b.data)) -function Base.setindex!(b::Bib, v::BibItem, k::AbstractString) - setindex!(b.data[String(k)], v) +function Base.setindex!(b::Bibliography, v::Citation, k::AbstractString) + b.data[String(k)] = v return b end -Base.get(b::Bib, k::AbstractString, default) = get(b.data, String(k), default) +Base.get(b::Bibliography, k::AbstractString, default) = get(b.data, String(k), default) -Base.start(b::Bib) = start(b.data) -Base.done(b::Bib, i) = done(b.data, i) -Base.next(b::Bib, i) = next(b.data, i) -Base.length(b::Bib) = length(b.data) +Base.start(b::Bibliography) = start(b.data) +Base.done(b::Bibliography, i) = done(b.data, i) +Base.next(b::Bibliography, i) = next(b.data, i) +Base.length(b::Bibliography) = length(b.data) # todo: add specialized Base.show methods for MIME"text/bibtex" etc. diff --git a/src/bibitem.jl b/src/bibitem.jl index 2f1f755..b5b230a 100644 --- a/src/bibitem.jl +++ b/src/bibitem.jl @@ -1,41 +1,42 @@ """ - BibItem{S}(data::Dict{String,String}) + Citation{S}(data::Dict{String,String}) A bibliography item in a bibTeX database, based on a dictionary of strings to values. It is parameterized by a symbol `S` giving the -type of the item (`:article` etcetera). A `b::BibItem` supports +type of the item (`:article` etcetera). A `b::Citation` supports `b[key]` access to retrieve the data and in general acts like a dictionary from `String` to `String`. """ -struct BibItem{S} <: Associative{String,String} +struct Citation{S} <: Associative{String,String} data::Dict{String,String} end +Citation{S}() where {S} = Citation{S}(Dict{String,String}()) -function BibItem!(data::Dict{String,String}) +function Citation!(data::Dict{String,String}) S = Symbol(pop!(data, "__type__")) - return BibItem{S}(data) + return Citation{S}(data) end -Base.similar(b::BibItem{S}) where {S} = BibItem{S}(Dict{String,String}()) -Base.rehash!(b::BibItem, n=length(b.data)) = begin rehash!(b.data, n); b; end -Base.sizehint!(b::BibItem, n) = begin sizehint!(b.data, n); b; end -Base.empty!(b::BibItem) = begin empty!(b.data); b; end -Base.copy(b::BibItem{S}) where {S} = BibItem{S}(copy(b.data)) +Base.similar(b::Citation{S}) where {S} = Citation{S}(Dict{String,String}()) +Base.rehash!(b::Citation, n=length(b.data)) = begin rehash!(b.data, n); b; end +Base.sizehint!(b::Citation, n) = begin sizehint!(b.data, n); b; end +Base.empty!(b::Citation) = begin empty!(b.data); b; end +Base.copy(b::Citation{S}) where {S} = Citation{S}(copy(b.data)) -Base.get(b::BibItem, k::AbstractString, default) = get(b.data, String(k), default) -Base.getindex(b::BibItem, k::AbstractString) = getindex(b.data, String(k)) -function Base.setindex!(b::BibItem, v::AbstractString, k::AbstractString) +Base.get(b::Citation, k::AbstractString, default) = get(b.data, String(k), default) +Base.getindex(b::Citation, k::AbstractString) = getindex(b.data, String(k)) +function Base.setindex!(b::Citation, v::AbstractString, k::AbstractString) b.data[String(k)] = String(v) return b end -Base.start(b::BibItem) = start(b.data) -Base.done(b::BibItem, i) = done(b.data, i) -Base.next(b::BibItem, i) = next(b.data, i) -Base.length(b::BibItem) = length(b.data) +Base.start(b::Citation) = start(b.data) +Base.done(b::Citation, i) = done(b.data, i) +Base.next(b::Citation, i) = next(b.data, i) +Base.length(b::Citation) = length(b.data) -function Base.show{S}(io::IO, b::BibItem{S}) - print(io, "BibItem{:$S}(", length(b), " entries)") +function Base.show{S}(io::IO, b::Citation{S}) + print(io, "Citation{:$S}(", length(b), " entries)") end # TODO: add Base.show text/plain and text/markdown for formatted citation diff --git a/test/runtests.jl b/test/runtests.jl index ba1ee3e..a04dac2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,7 +14,29 @@ Documenter.makedocs( ) @testset "examples.bib" begin - b = open(Bib, joinpath("..", "example", "examples.bib"), "r") + b = open(Bibliography, joinpath("..", "example", "examples.bib"), "r") @test length(b) == 92 - @test (b["angenendt"]::BibItem{:article})["date"] == "2002" + @test (b["angenendt"]::Citation{:article})["date"] == "2002" +end + +@testset "small bib" begin + b = Bibliography(""" + @article{foo, bar=baz} + @book{bar, foobar=1} + """) + @test get(b, "foobar", nothing) === nothing + @test get(b["foo"], "blah", nothing) === nothing + + @test string(b["foo"]) == "Citation{:article}(1 entries)" + + Base.rehash!(b) + b2 = copy(b) + @test isempty(sizehint!(empty!(b2),10)) + @test isempty(similar(b)) + b2["x"] = Citation{:foo}() + b2["x"]["bar"] = "blah" + @test length(b2) == length(b2["x"]) == 1 + @test b2["x"]["bar"] == "blah" + @test collect(b2)[1][2] == b2["x"] + @test collect(b2["x"])[1] == ("bar"=>"blah") end From e6c07028119209bf63682d875257a13fdf068f5d Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Tue, 1 Aug 2017 17:04:18 -0400 Subject: [PATCH 4/4] more tests and fixes --- src/BibTeX.jl | 4 ++-- src/{bib.jl => bibliography.jl} | 0 src/{bibitem.jl => citation.jl} | 2 +- test/runtests.jl | 7 +++++++ 4 files changed, 10 insertions(+), 3 deletions(-) rename src/{bib.jl => bibliography.jl} (100%) rename src/{bibitem.jl => citation.jl} (94%) diff --git a/src/BibTeX.jl b/src/BibTeX.jl index 3aee2b7..b17118d 100644 --- a/src/BibTeX.jl +++ b/src/BibTeX.jl @@ -2,7 +2,7 @@ module BibTeX export Bibliography, Citation include("parser.jl") -include("bibitem.jl") -include("bib.jl") +include("citation.jl") +include("bibliography.jl") end diff --git a/src/bib.jl b/src/bibliography.jl similarity index 100% rename from src/bib.jl rename to src/bibliography.jl diff --git a/src/bibitem.jl b/src/citation.jl similarity index 94% rename from src/bibitem.jl rename to src/citation.jl index b5b230a..9204eeb 100644 --- a/src/bibitem.jl +++ b/src/citation.jl @@ -18,7 +18,7 @@ function Citation!(data::Dict{String,String}) end Base.similar(b::Citation{S}) where {S} = Citation{S}(Dict{String,String}()) -Base.rehash!(b::Citation, n=length(b.data)) = begin rehash!(b.data, n); b; end +Base.rehash!(b::Citation, n=length(b.data)) = begin Base.rehash!(b.data, n); b; end Base.sizehint!(b::Citation, n) = begin sizehint!(b.data, n); b; end Base.empty!(b::Citation) = begin empty!(b.data); b; end Base.copy(b::Citation{S}) where {S} = Citation{S}(copy(b.data)) diff --git a/test/runtests.jl b/test/runtests.jl index a04dac2..0831462 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,12 +31,19 @@ end Base.rehash!(b) b2 = copy(b) + @test length(b2) == length(b) @test isempty(sizehint!(empty!(b2),10)) @test isempty(similar(b)) b2["x"] = Citation{:foo}() b2["x"]["bar"] = "blah" @test length(b2) == length(b2["x"]) == 1 @test b2["x"]["bar"] == "blah" + @test get(b2["x"], "foo", nothing) === nothing @test collect(b2)[1][2] == b2["x"] @test collect(b2["x"])[1] == ("bar"=>"blah") + Base.rehash!(b2["x"]) + x2 = copy(b2["x"])::Citation{:foo} + @test length(x2) == 1 + @test isempty(similar(x2)) + @test isempty(sizehint!(empty!(x2),10)) end