diff --git a/README.md b/README.md index 5869ab4..ddd989c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://m3g.github.io/PDBTools.jl/stable) [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://m3g.github.io/PDBTools.jl/dev) [![Tests](https://img.shields.io/badge/build-passing-green)](https://github.com/m3g/PDBTools.jl/actions) +[![codecov](https://codecov.io/gh/m3g/PDBTools.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/m3g/PDBTools.jl) [![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) # PDBTools diff --git a/src/Atom.jl b/src/Atom.jl index 99feb89..9d82292 100644 --- a/src/Atom.jl +++ b/src/Atom.jl @@ -292,12 +292,17 @@ atom_line(atom::Atom) = @sprintf( atoms = read_pdb(PDBTools.SMALLPDB, "protein and index 1") @test PDBTools.atom_line(atoms[1]) == " 1 N ALA A 1 1 -9.229 -14.861 -5.481 0.00 0.00 1 PROT 1" + buff = IOBuffer() + printatom(buff, atoms[1]) + @test length(split(String(take!(buff)))) == 28 end """ printatom(atom::Atom) + printatom(io::IO, atom::Atom) -Prints an `Atom` structure in a human-readable format, with a title line. +Prints an `Atom` structure in a human-readable format, with a title line. By default the output is printed to `stdout`, +and the `io` argument can be used to specify a different output stream. ### Example @@ -315,10 +320,11 @@ julia> atoms[1] # default show method ``` """ -function printatom(atom::Atom) - println(atom_title) - println(atom_line(atom)) +function printatom(io::IO, atom::Atom) + println(io, atom_title) + println(io, atom_line(atom)) end +printatom(atom::Atom) = printatom(stdout, atom) # # Print a formatted list of atoms @@ -452,6 +458,8 @@ end @test element(Atom(name = "N", pdb_element="A")) == "A" @test element(Atom(name = "A")) === nothing @test element(Atom(name = " ")) === nothing + atom = Atom(name="", pdb_element="") + @test element(atom) === nothing end # @@ -624,3 +632,19 @@ end @test atomic_mass(at) ≈ 14.0067 @test position(at) ≈ StaticArrays.SVector(0.0, 0.0, 0.0) end + +@testitem "atom - show" begin + using PDBTools: print_short_atom_list + at = Atom(;segname="X") + buff = IOBuffer() + show(buff, at) + @test length(split(String(take!(buff)))) == 14 + print_short_atom_list(buff, [at, at]) + @test length(split(String(take!(buff)))) == 14*3 + print_short_atom_list(buff, [at, at]) + @test length(split(String(take!(buff)))) == 14*3 + print_short_atom_list(buff, [copy(at) for _ in 1:20]) + @test length(split(String(take!(buff)))) == 14*7 + 1 + show(buff, [at]) + @test String(take!(buff)) == "PDBTools.Atom{Nothing}[ 0 X XXX X 0 0 0.000 0.000 0.000 0.00 0.00 0 X 0]" +end \ No newline at end of file diff --git a/src/distance.jl b/src/distance.jl index 6d08687..d1b4bbb 100644 --- a/src/distance.jl +++ b/src/distance.jl @@ -155,6 +155,8 @@ end @test all(isapprox.(closest(r1[1], coor(r2[2])),(1, 1, 5.121218702613667); atol=1e-3)) @test all(isapprox.(closest(coor(r1[1]), r2[2]),(1, 1, 5.121218702613667); atol=1e-3)) @test all(isapprox.(closest(r1[1], r2[2]),(1, 1, 5.121218702613667); atol=1e-3)) + @test all(isapprox.(closest(r1[1], r2), (1, 2, 5.121218702613667); atol=1e-3)) + @test all(isapprox.(closest(r2, r1[1]), (2, 1, 5.121218702613667); atol=1e-3)) @test distance(r1, r2) ≈ 3.6750402718881863 atol=1e-3 @test distance(coor(r1), r2) ≈ 3.6750402718881863 atol=1e-3 @@ -173,6 +175,10 @@ end r = collect(eachresidue(atoms)) @test all(isapprox.(closest(r[1], [0.0, 0.0, 0.0]), (12, 1, 16.545482827648158); atol=1e-3)) @test all(isapprox.(closest([0.0, 0.0, 0.0], r[1]), (1, 12, 16.545482827648158); atol=1e-3)) + @test all(isapprox.(closest(Atom(), r[1]), (1, 12, 16.545482827648158); atol=1e-3)) + @test all(isapprox.(closest(r[1], Atom()), (12, 1, 16.545482827648158); atol=1e-3)) + @test all(isapprox.(closest([coor(Atom())], r[1]), (1, 12, 16.545482827648158); atol=1e-3)) + @test all(isapprox.(closest(r[1], [coor(Atom())]), (12, 1, 16.545482827648158); atol=1e-3)) end diff --git a/src/formula.jl b/src/formula.jl index 834651d..5bd821b 100644 --- a/src/formula.jl +++ b/src/formula.jl @@ -4,7 +4,7 @@ struct Formula formula::Vector{Tuple{Atom,Int}} end -Base.getindex(f::Formula, i) = f.formula[i] +Base.getindex(f::Formula, i) = (String(pdb_element(first(f.formula[i]))), last(f.formula[i])) const sub_int = ( "0" => "₀", @@ -22,7 +22,8 @@ const sub_int = ( """ formula(atoms::AbstractVector{<:Atom}) -Returns the molecular formula of the current selection. +Returns the molecular formula of the current selection. The output is an indexable +"Formula" structure, where each element is a tuple with the element name and the number of atoms. ## Example @@ -34,13 +35,17 @@ julia> pdb = read_pdb(PDBTools.TESTPDB, "residue 1"); # testing PDB file julia> resname(pdb[1]) "ALA" -julia> formula(pdb) +julia> f = formula(pdb) H₇C₃N₁O₁ + +julia> f[1] +("H", 7) + ``` """ -function formula(atoms::AbstractVector{<:Atom}) - f = Formula(Tuple{Atom,Int}[]) +function formula(atoms::AbstractVector{<:AtomType}) where {AtomType<:Atom} + f = Formula(Tuple{AtomType,Int}[]) for at in atoms i = findfirst(el -> pdb_element(first(el)) == element(at), f.formula) if isnothing(i) @@ -71,7 +76,7 @@ Returns the stoichiometry of atom selection in a `Formula` structure. ### Example -```julia-repl +```jldoctest julia> using PDBTools julia> pdb = read_pdb(PDBTools.TESTPDB, "water"); # testing PDB file @@ -89,3 +94,20 @@ function stoichiometry(atoms::AbstractVector{<:Atom}) end f end + +@testitem "Formula" begin + using PDBTools + pdb = read_pdb(PDBTools.TESTPDB, "resname GLY") + f = formula(pdb) + buff = IOBuffer() + show(buff, f) + @test String(take!(buff)) == "H₃₆C₂₄N₁₂O₁₂" + @test f[1] == ("H", 36) + @test f[4] == ("O", 12) + s = stoichiometry(pdb) + buff = IOBuffer() + show(buff, s) + @test String(take!(buff)) == "H₃C₂N₁O₁" + @test s[1] == ("H", 3) + @test s[4] == ("O", 1) +end diff --git a/src/getseq.jl b/src/getseq.jl index 6f79927..ef5cff7 100644 --- a/src/getseq.jl +++ b/src/getseq.jl @@ -5,23 +5,25 @@ Returns the sequence of aminoacids from the vector of atoms or file name. Select ### Example -```julia-repl -julia> protein = wget("1LBD"); +```jldoctest +julia> using PDBTools + +julia> protein = read_pdb(PDBTools.TESTPDB); julia> getseq(protein,"residue < 3") 2-element Vector{String}: - "S" "A" + "C" julia> getseq(protein,"residue < 3", code=2) 2-element Vector{String}: - "SER" "ALA" + "CYS" julia> getseq(protein,"residue < 3",code=3) 2-element Vector{String}: - "Serine" "Alanine" + "Cysteine" ``` @@ -64,3 +66,26 @@ function getseq(file::String, selection::String; code::Int=1) end getseq(file::String; only=all, code::Int=1) = getseq(read_pdb(file), only=only, code=code) + +@testitem "getseq" begin + using PDBTools + ats = read_pdb(PDBTools.TESTPDB) + s = getseq(ats, "residue < 3") + @test s == ["A", "C"] + s = getseq(ats, "residue < 3"; code=2) + @test s == ["ALA", "CYS"] + s = getseq(ats, "residue < 3"; code=3) + @test s == ["Alanine", "Cysteine"] + wat = select(ats, "water") + s = getseq(wat, "resnum < 3") + @test s == ["T", "T", "T", "T"] + s = getseq(wat, "resnum < 3"; code=2) + @test s == ["TIP3", "TIP3", "TIP3", "TIP3"] + s = getseq(wat, "resnum < 3"; code=3) + @test s == ["TIP3", "TIP3", "TIP3", "TIP3"] + s = getseq(PDBTools.TESTPDB, "residue < 3") + s = getseq(ats, "residue < 3") + @test s == ["A", "C"] + s = getseq(PDBTools.TESTPDB; only = at -> resnum(at) == 1) + @test s == ["A", "C", "S", "T", "T", "T"] +end diff --git a/src/parsers.jl b/src/parsers.jl index 7ed99c1..c06675f 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -19,7 +19,7 @@ function _parse( x = _tryparse(T, s) !isnothing(x) && return x if isnothing(alt) - error("Could not read $T from string: \"$s\"") + throw(ArgumentError("Could not read $T from string: \"$s\"")) else return alt end @@ -105,16 +105,31 @@ function setfield_recursive!(atom::AtomType, field_values::FIELDS, inds_and_name setfield_recursive!(atom, field_values, Base.tail(inds_and_names)) end -# Alternative implementation using generated functions (same peformance as far as tested) -# https://discourse.julialang.org/t/unroll-setfield/122545/22?u=lmiq -@generated function setfield_generated!(atom, field_values::FIELDS, inds_and_names::TUPTUP, ::Val{N}) where {FIELDS,TUPTUP,N} - quote - @inline - Base.@nexprs $N i -> begin - ifield, valfield = inds_and_names[i] - field = unwrap(valfield) - T = typeof(getfield(atom, field)) - setfield!(atom, field, _parse(T, field_values[ifield]; alt=_alt(T))) - end - end +## Alternative implementation using generated functions (same peformance as far as tested) +## https://discourse.julialang.org/t/unroll-setfield/122545/22?u=lmiq +#@generated function setfield_generated!(atom, field_values::FIELDS, inds_and_names::TUPTUP, ::Val{N}) where {FIELDS,TUPTUP,N} +# quote +# @inline +# Base.@nexprs $N i -> begin +# ifield, valfield = inds_and_names[i] +# field = unwrap(valfield) +# T = typeof(getfield(atom, field)) +# setfield!(atom, field, _parse(T, field_values[ifield]; alt=_alt(T))) +# end +# end +#end + +@testitem "_parse" begin + using PDBTools: _parse, _parse_charge + @test _parse(Int, " 1 ") == 1 + @test _parse(Int, " A ") == 10 + @test _parse(Float32, " 1.0 ") == 1.0f0 + @test_throws ArgumentError _parse(Int, " ??? ") + @test_throws ArgumentError _parse(Float32, " A ") + @test _parse(String, " A ") == "A" + @test _parse(String, "") === nothing + @test _parse_charge("1+") == "1" + @test _parse_charge("+1") == "1" + @test _parse_charge("1-") == "-1" + @test _parse_charge("-1") == "-1" end