From 3211943b8bab7d22e2a864e6d09c7e87284d0c95 Mon Sep 17 00:00:00 2001 From: Filippos Christou Date: Fri, 23 Jun 2023 11:46:47 +0200 Subject: [PATCH] Formats outside @require + Package extensions (#51) * Formats outside @require * Use package extensions * Add Aqua and JuliaFormatter * Fix Requires * Typos in README and Aqua test --------- Co-authored-by: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> --- .JuliaFormatter.toml | 1 + .github/workflows/ci.yml | 4 +- Project.toml | 20 ++- README.md | 39 ++++-- ext/GraphIODOTExt.jl | 95 +++++++++++++++ ext/GraphIOGEXFExt.jl | 58 +++++++++ ext/GraphIOGMLExt.jl | 101 ++++++++++++++++ ext/GraphIOGraphMLExt.jl | 151 +++++++++++++++++++++++ ext/GraphIOLGCompressedExt.jl | 60 +++++++++ src/CDF/Cdf.jl | 1 - src/DOT/Dot.jl | 81 +------------ src/Edgelist/Edgelist.jl | 5 +- src/Edgelist/IntEdgeList.jl | 34 +++--- src/GEXF/Gexf.jl | 49 +------- src/GML/Gml.jl | 93 +------------- src/Graph6/Graph6.jl | 201 ++++++++++++++++--------------- src/GraphIO.jl | 46 +++---- src/GraphML/GraphML.jl | 129 +------------------- src/LGCompressed/LGCompressed.jl | 44 +------ src/NET/Net.jl | 11 +- src/deprecations.jl | 77 ------------ test/DOT/runtests.jl | 68 +++++------ test/Edgelist/runtests.jl | 1 - test/GML/runtests.jl | 2 +- test/Graph6/runtests.jl | 1 - test/GraphML/runtests.jl | 14 ++- test/NET/runtests.jl | 2 - test/graphio.jl | 63 +++++++--- test/runtests.jl | 27 +++-- 29 files changed, 781 insertions(+), 697 deletions(-) create mode 100644 .JuliaFormatter.toml create mode 100644 ext/GraphIODOTExt.jl create mode 100644 ext/GraphIOGEXFExt.jl create mode 100644 ext/GraphIOGMLExt.jl create mode 100644 ext/GraphIOGraphMLExt.jl create mode 100644 ext/GraphIOLGCompressedExt.jl delete mode 100644 src/deprecations.jl diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..c743950 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1 @@ +style = "blue" \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92ca689..3de1b6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,9 @@ jobs: fail-fast: false matrix: version: - - '1' + - '1.6' + - '1.9' + - 'nightly' os: - ubuntu-latest arch: diff --git a/Project.toml b/Project.toml index a6fa44f..918fc3d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "GraphIO" uuid = "aa1b3936-2fda-51b9-ab35-c553d3a640a2" -version = "0.6.0" +version = "0.7.0" [deps] DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" @@ -8,7 +8,21 @@ Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" Requires = "ae029012-a4dd-5104-9daa-d747884805df" SimpleTraits = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +[weakdeps] +CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" +EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" +ParserCombinator = "fae87a5f-d1ad-5cf0-8f61-c941e1580b46" + +[extensions] +GraphIODOTExt = "ParserCombinator" +GraphIOGEXFExt = "EzXML" +GraphIOGMLExt = "ParserCombinator" +GraphIOGraphMLExt = "EzXML" +GraphIOLGCompressedExt = "CodecZlib" + [compat] +CodecZlib = "0.7" +DelimitedFiles = "1" EzXML = "1" Graphs = "1.4" ParserCombinator = "2.1" @@ -17,11 +31,13 @@ SimpleTraits = "0.9" julia = "1" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" ParserCombinator = "fae87a5f-d1ad-5cf0-8f61-c941e1580b46" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["CodecZlib", "Graphs", "EzXML", "ParserCombinator", "Test"] +test = ["Aqua", "CodecZlib", "Graphs", "JuliaFormatter", "EzXML", "ParserCombinator", "Test"] diff --git a/README.md b/README.md index bcdf30f..2c745e2 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,23 @@ [![Build Status](https://github.com/JuliaGraphs/GraphIO.jl/workflows/CI/badge.svg)](https://github.com/JuliaGraphs/GraphIO.jl/actions?query=workflow%3ACI+branch%3Amaster) [![codecov.io](http://codecov.io/github/JuliaGraphs/GraphIO.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaGraphs/GraphIO.jl?branch=master) +[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) +[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) GraphIO provides support to [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl) for reading/writing graphs in various formats. Currently, the following functionality is provided: -Format | Read | Write | Multiple Graphs| Format Name | Comment | ---------------|------|-------|----------------|--------------|----------| -EdgeList | ✓ | ✓ | |EdgeListFormat| a simple list of sources and dests separated by whitespace and/or comma, one pair per line. | -[GML] | ✓ | ✓ | ✓ |GMLFormat | -[Graph6] | ✓ | ✓ | ✓ |Graph6Format | -[GraphML] | ✓ | ✓ | ✓ |GraphMLFormat | -[Pajek NET] | ✓ | ✓ | |NETFormat | -[GEXF] | | ✓ | |GEXFFormat | -[DOT] | ✓ | | ✓ |DOTFormat | -[CDF] | ✓ | | |CDFFormat | +| Format | Read | Write | Multiple Graphs | Format Name | Comment | +| ----------- | ---- | ----- | --------------- | -------------- | ------------------------------------------------------------------------------------------- | +| EdgeList | ✓ | ✓ | | EdgeListFormat | a simple list of sources and dests separated by whitespace and/or comma, one pair per line. | +| [GML] | ✓ | ✓ | ✓ | GMLFormat | | +| [Graph6] | ✓ | ✓ | ✓ | Graph6Format | | +| [GraphML] | ✓ | ✓ | ✓ | GraphMLFormat | | +| [Pajek NET] | ✓ | ✓ | | NETFormat | | +| [GEXF] | | ✓ | | GEXFFormat | | +| [DOT] | ✓ | | ✓ | DOTFormat | | +| [CDF] | ✓ | | | CDFFormat | | Graphs are read using either the `loadgraph` function or, for formats that support multiple graphs in a single file, @@ -28,6 +30,23 @@ For example, an edgelist file could be loaded as: graph = loadgraph("path_to_graph/my_edgelist.txt", "graph_key", EdgeListFormat()) ``` +## Reading different graph types + +All `*Format` types are readily accessible. +However, in order to use some of them with `loadgraph`, additional packages are required. +You may thus need to install and load the following dependencies before using parts of GraphIO.jl: +- Reading [DOT] or [GML] files: do `using ParserCombinator` +- Reading [GEXF] or [GraphML] files: do `using EzXML` +- Reading [GML] files: do `using CodecZlib` + +The current design avoids populating your environment with unnecessary dependencies. + +> **_IMPLEMENTATION NOTE:_** +> The current design uses package extensions, introduced in Julia v1.9. +> At the moment, package extensions cannot conditionally load types, that is one of the main reasons why all `*Format` types are readily accessible. +> However, the functionality of `loadgraph` is extended for the various types only when the appropriate dependencies are available. +> We are searching for more intuitive ways to design this interface. + [CDF]: http://www2.ee.washington.edu/research/pstca/formats/cdf.txt [GML]: https://en.wikipedia.org/wiki/Graph_Modelling_Language [Graph6]: https://users.cecs.anu.edu.au/~bdm/data/formats.html diff --git a/ext/GraphIODOTExt.jl b/ext/GraphIODOTExt.jl new file mode 100644 index 0000000..f424e9d --- /dev/null +++ b/ext/GraphIODOTExt.jl @@ -0,0 +1,95 @@ +module GraphIODOTExt + +using Graphs +import Graphs: loadgraph, loadgraphs, savegraph + +@static if isdefined(Base, :get_extension) + using GraphIO + using ParserCombinator + import GraphIO.DOT.DOTFormat +else # not required for julia >= v1.9 + using ..GraphIO + using ..ParserCombinator + import ..GraphIO.DOT.DOTFormat +end + +function savedot(io::IO, g::AbstractGraph, gname::String="") + isdir = is_directed(g) + println(io, (isdir ? "digraph " : "graph ") * gname * " {") + for i in vertices(g) + println(io, "\t" * string(i)) + end + if isdir + for u in vertices(g) + out_nbrs = outneighbors(g, u) + length(out_nbrs) == 0 && continue + println(io, "\t" * string(u) * " -> {" * join(out_nbrs, ',') * "}") + end + else + for e in edges(g) + source = string(src(e)) + dest = string(dst(e)) + println(io, "\t" * source * " -- " * dest) + end + end + println(io, "}") + return 1 +end + +function savedot_mult(io::IO, graphs::Dict) + ng = 0 + for (gname, g) in graphs + ng += savedot(io, g, gname) + end + return ng +end + +function _dot_read_one_graph(pg::Parsers.DOT.Graph) + isdir = pg.directed + nvg = length(Parsers.DOT.nodes(pg)) + nodedict = Dict(zip(collect(Parsers.DOT.nodes(pg)), 1:nvg)) + if isdir + g = DiGraph(nvg) + else + g = Graph(nvg) + end + for es in Parsers.DOT.edges(pg) + s = nodedict[es[1]] + d = nodedict[es[2]] + add_edge!(g, s, d) + end + return g +end + +function _name(pg::Parsers.DOT.Graph) + return if pg.id !== nothing + pg.id.id + else + Parsers.DOT.StringID(pg.directed ? "digraph" : "graph") + end +end + +function loaddot(io::IO, gname::String) + p = Parsers.DOT.parse_dot(read(io, String)) + for pg in p + _name(pg) == gname && return _dot_read_one_graph(pg) + end + return error("Graph $gname not found") +end + +function loaddot_mult(io::IO) + p = Parsers.DOT.parse_dot(read(io, String)) + graphs = Dict{String,AbstractGraph}() + + for pg in p + graphs[_name(pg)] = _dot_read_one_graph(pg) + end + return graphs +end + +loadgraph(io::IO, gname::String, ::DOTFormat) = loaddot(io, gname) +loadgraphs(io::IO, ::DOTFormat) = loaddot_mult(io) +savegraph(io::IO, g::AbstractGraph, gname::String, ::DOTFormat) = savedot(io, g, gname) +savegraph(io::IO, d::Dict, ::DOTFormat) = savedot_mult(io, d) + +end diff --git a/ext/GraphIOGEXFExt.jl b/ext/GraphIOGEXFExt.jl new file mode 100644 index 0000000..fe2cbf6 --- /dev/null +++ b/ext/GraphIOGEXFExt.jl @@ -0,0 +1,58 @@ +module GraphIOGEXFExt + +using Graphs +import Graphs: loadgraph, loadgraphs, savegraph, AbstractGraph + +@static if isdefined(Base, :get_extension) + using GraphIO + using EzXML + import GraphIO.GEXF.GEXFFormat +else # not required for julia >= v1.9 + using ..GraphIO + using ..EzXML + import ..GraphIO.GEXF.GEXFFormat +end + +""" + savegexf(f, g, gname) + +Write a graph `g` with name `gname` to an IO stream `io` in the +[Gexf](http://gexf.net/format/) format. Return 1 (number of graphs written). +""" +function savegexf(io::IO, g::AbstractGraph, gname::String) + xdoc = XMLDocument() + xroot = setroot!(xdoc, ElementNode("gexf")) + xroot["xmlns"] = "http://www.gexf.net/1.2draft" + xroot["version"] = "1.2" + xroot["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance" + xroot["xsi:schemaLocation"] = "http://www.gexf.net/1.2draft/gexf.xsd" + + xmeta = addelement!(xroot, "meta") + addelement!(xmeta, "description", gname) + xg = addelement!(xroot, "graph") + strdir = is_directed(g) ? "directed" : "undirected" + xg["defaultedgetype"] = strdir + + xnodes = addelement!(xg, "nodes") + for i in 1:nv(g) + xv = addelement!(xnodes, "node") + xv["id"] = "$(i-1)" + end + + xedges = addelement!(xg, "edges") + m = 0 + for e in edges(g) + xe = addelement!(xedges, "edge") + xe["id"] = "$m" + xe["source"] = "$(src(e)-1)" + xe["target"] = "$(dst(e)-1)" + m += 1 + end + + prettyprint(io, xdoc) + return 1 +end + +savegraph(io::IO, g::AbstractGraph, gname::String, ::GEXFFormat) = savegexf(io, g, gname) + +end diff --git a/ext/GraphIOGMLExt.jl b/ext/GraphIOGMLExt.jl new file mode 100644 index 0000000..951d498 --- /dev/null +++ b/ext/GraphIOGMLExt.jl @@ -0,0 +1,101 @@ +module GraphIOGMLExt + +using Graphs +import Graphs: loadgraph, loadgraphs, savegraph + +@static if isdefined(Base, :get_extension) + using GraphIO + using ParserCombinator + import GraphIO.GML.GMLFormat +else # not required for julia >= v1.9 + using ..GraphIO + using ..ParserCombinator + import ..GraphIO.GML.GMLFormat +end + +function _gml_read_one_graph(gs, dir) + nodes = [x[:id] for x in gs[:node]] + if dir + g = DiGraph(length(nodes)) + else + g = Graph(length(nodes)) + end + mapping = Dict{Int,Int}() + for (i, n) in enumerate(nodes) + mapping[n] = i + end + sds = [(Int(x[:source]), Int(x[:target])) for x in gs[:edge]] + for (s, d) in (sds) + add_edge!(g, mapping[s], mapping[d]) + end + return g +end + +function loadgml(io::IO, gname::String) + p = Parsers.GML.parse_dict(read(io, String)) + for gs in p[:graph] + dir = Bool(get(gs, :directed, 0)) + graphname = get(gs, :label, dir ? "digraph" : "graph") + + (gname == graphname) && return _gml_read_one_graph(gs, dir) + end + return error("Graph $gname not found") +end + +function loadgml_mult(io::IO) + p = Parsers.GML.parse_dict(read(io, String)) + graphs = Dict{String,AbstractGraph}() + for gs in p[:graph] + dir = Bool(get(gs, :directed, 0)) + graphname = get(gs, :label, dir ? "digraph" : "graph") + graphs[graphname] = _gml_read_one_graph(gs, dir) + end + return graphs +end + +""" + savegml(f, g, gname="graph") + +Write a graph `g` with name `gname` to an IO stream `io` in the +[GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language) format. Return 1. +""" +function savegml(io::IO, g::AbstractGraph, gname::String="") + println(io, "graph") + println(io, "[") + length(gname) > 0 && println(io, "label \"$gname\"") + is_directed(g) && println(io, "directed 1") + for i in 1:nv(g) + println(io, "\tnode") + println(io, "\t[") + println(io, "\t\tid $i") + println(io, "\t]") + end + for e in edges(g) + s, t = Tuple(e) + println(io, "\tedge") + println(io, "\t[") + println(io, "\t\tsource $s") + println(io, "\t\ttarget $t") + println(io, "\t]") + end + println(io, "]") + return 1 +end + +""" + savegml_mult(io, graphs) +Write a dictionary of (name=>graph) to an IO stream `io` Return number of graphs written. +""" +function savegml_mult(io::IO, graphs::Dict) + ng = 0 + for (gname, g) in graphs + ng += savegml(io, g, gname) + end + return ng +end +loadgraph(io::IO, gname::String, ::GMLFormat) = loadgml(io, gname) +loadgraphs(io::IO, ::GMLFormat) = loadgml_mult(io) +savegraph(io::IO, g::AbstractGraph, gname::String, ::GMLFormat) = savegml(io, g, gname) +savegraph(io::IO, d::Dict, ::GMLFormat) = savegml_mult(io, d) + +end diff --git a/ext/GraphIOGraphMLExt.jl b/ext/GraphIOGraphMLExt.jl new file mode 100644 index 0000000..99e28b1 --- /dev/null +++ b/ext/GraphIOGraphMLExt.jl @@ -0,0 +1,151 @@ +module GraphIOGraphMLExt + +using Graphs +import Graphs: loadgraph, loadgraphs, savegraph + +@static if isdefined(Base, :get_extension) + using GraphIO + using EzXML + import GraphIO.GraphML.GraphMLFormat +else # not required for julia >= v1.9 + using ..GraphIO + using ..EzXML + import ..GraphIO.GraphML.GraphMLFormat +end + +function _graphml_read_one_graph(reader::EzXML.StreamReader, isdirected::Bool) + nodes = Dict{String,Int}() + xedges = Vector{Edge}() + nodeid = 1 + for typ in reader + if typ == EzXML.READER_ELEMENT + elname = EzXML.nodename(reader) + if elname == "node" + nodes[reader["id"]] = nodeid + nodeid += 1 + elseif elname == "edge" + src = reader["source"] + tar = reader["target"] + push!(xedges, Edge(nodes[src], nodes[tar])) + else + @warn "Skipping unknown node '$(elname)' - further warnings will be suppressed" maxlog = + 1 _id = :unknode + end + end + end + g = (isdirected ? DiGraph : Graph)(length(nodes)) + for edge in xedges + add_edge!(g, edge) + end + return g +end + +function loadgraphml(io::IO, gname::String) + reader = EzXML.StreamReader(io) + for typ in reader + if typ == EzXML.READER_ELEMENT + elname = EzXML.nodename(reader) + if elname == "graphml" + # ok + elseif elname == "graph" + edgedefault = reader["edgedefault"] + directed = if edgedefault == "directed" + true + elseif edgedefault == "undirected" + false + else + error("Unknown value of edgedefault: $edgedefault") + end + if haskey(reader, "id") + graphname = reader["id"] + else + graphname = directed ? "digraph" : "graph" + end + if gname == graphname + return _graphml_read_one_graph(reader, directed) + end + elseif elname == "node" || elname == "edge" + # ok + else + @warn "Skipping unknown XML element '$(elname)' - further warnings will be suppressed" maxlog = + 1 _id = :unkel + end + end + end + return error("Graph $gname not found") +end + +function loadgraphml_mult(io::IO) + reader = EzXML.StreamReader(io) + graphs = Dict{String,AbstractGraph}() + for typ in reader + if typ == EzXML.READER_ELEMENT + elname = EzXML.nodename(reader) + if elname == "graphml" + # ok + elseif elname == "graph" + edgedefault = reader["edgedefault"] + directed = if edgedefault == "directed" + true + elseif edgedefault == "undirected" + false + else + error("Unknown value of edgedefault: $edgedefault") + end + if haskey(reader, "id") + graphname = reader["id"] + else + graphname = directed ? "digraph" : "graph" + end + graphs[graphname] = _graphml_read_one_graph(reader, directed) + else + @warn "Skipping unknown XML element '$(elname)' - further warnings will be suppressed" maxlog = + 1 _id = :unkelmult + end + end + end + return graphs +end + +function savegraphml_mult(io::IO, graphs::Dict) + xdoc = XMLDocument() + xroot = setroot!(xdoc, ElementNode("graphml")) + xroot["xmlns"] = "http://graphml.graphdrawing.org/xmlns" + xroot["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance" + xroot["xsi:schemaLocation"] = "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd" + + for (gname, g) in graphs + xg = addelement!(xroot, "graph") + xg["id"] = gname + xg["edgedefault"] = is_directed(g) ? "directed" : "undirected" + + for i in 1:nv(g) + xv = addelement!(xg, "node") + xv["id"] = "n$(i-1)" + end + + m = 0 + for e in edges(g) + xe = addelement!(xg, "edge") + xe["id"] = "e$m" + xe["source"] = "n$(src(e)-1)" + xe["target"] = "n$(dst(e)-1)" + m += 1 + end + end + prettyprint(io, xdoc) + return length(graphs) +end + +function savegraphml(io::IO, g::AbstractGraph, gname::String) + return savegraphml_mult(io, Dict(gname => g)) +end + +loadgraph(io::IO, gname::String, ::GraphMLFormat) = loadgraphml(io, gname) +loadgraphs(io::IO, ::GraphMLFormat) = loadgraphml_mult(io) +function savegraph(io::IO, g::AbstractGraph, gname::String, ::GraphMLFormat) + return savegraphml(io, g, gname) +end +savegraph(io::IO, d::Dict, ::GraphMLFormat) = savegraphml_mult(io, d) + +end diff --git a/ext/GraphIOLGCompressedExt.jl b/ext/GraphIOLGCompressedExt.jl new file mode 100644 index 0000000..96a2c72 --- /dev/null +++ b/ext/GraphIOLGCompressedExt.jl @@ -0,0 +1,60 @@ +module GraphIOLGCompressedExt + +using Graphs +import Graphs: loadgraph, loadgraphs, savegraph, LGFormat + +@static if isdefined(Base, :get_extension) + using GraphIO + using CodecZlib + import GraphIO.LGCompressed.LGCompressedFormat +else # not required for julia >= v1.9 + using ..GraphIO + using ..CodecZlib + import ..GraphIO.LGCompressed.LGCompressedFormat +end + +function savegraph( + fn::AbstractString, g::AbstractGraph, gname::AbstractString, format::LGCompressedFormat +) + io = open(fn, "w") + try + io = GzipCompressorStream(io) + return savegraph(io, g, gname, LGFormat()) + catch + rethrow() + finally + close(io) + end +end + +function savegraph(fn::AbstractString, g::AbstractGraph, format::LGCompressedFormat) + return savegraph(fn, g, "graph", format) +end + +function savegraph( + fn::AbstractString, d::Dict{T,U}, format::LGCompressedFormat +) where {T<:AbstractString} where {U<:AbstractGraph} + io = open(fn, "w") + try + io = GzipCompressorStream(io) + return savegraph(io, d, LGFormat()) + catch + rethrow() + finally + close(io) + end +end + +# savegraph(fn::AbstractString, d::Dict; compress) = savegraph(fn, d, LGCompressedFormat()) + +function loadgraph(fn::AbstractString, gname::AbstractString, format::LGCompressedFormat) + return loadgraph(fn, gname, LGFormat()) +end + +function loadgraph(fn::AbstractString, format::LGCompressedFormat) + return loadgraph(fn, "graph", LGFormat()) +end + +loadgraphs(fn::AbstractString, format::LGCompressedFormat) = loadgraphs(fn, LGFormat()) + +end diff --git a/src/CDF/Cdf.jl b/src/CDF/Cdf.jl index bc297ef..404d398 100644 --- a/src/CDF/Cdf.jl +++ b/src/CDF/Cdf.jl @@ -11,7 +11,6 @@ import Graphs: loadgraph, loadgraphs, savegraph export CDFFormat - struct CDFFormat <: AbstractGraphFormat end function _loadcdf(io::IO) diff --git a/src/DOT/Dot.jl b/src/DOT/Dot.jl index 2e050bb..b6360d5 100644 --- a/src/DOT/Dot.jl +++ b/src/DOT/Dot.jl @@ -1,88 +1,9 @@ module DOT -using GraphIO.ParserCombinator.Parsers -using Graphs -using Graphs: AbstractGraphFormat - -import Graphs: loadgraph, loadgraphs, savegraph +import Graphs: AbstractGraphFormat export DOTFormat struct DOTFormat <: AbstractGraphFormat end -function savedot(io::IO, g::Graphs.AbstractGraph, gname::String = "") - isdir = Graphs.is_directed(g) - println(io,(isdir ? "digraph " : "graph ") * gname * " {") - for i in Graphs.vertices(g) - println(io,"\t" * string(i)) - end - if isdir - for u in Graphs.vertices(g) - out_nbrs = Graphs.outneighbors(g, u) - length(out_nbrs) == 0 && continue - println(io, "\t" * string(u) * " -> {" * join(out_nbrs,',') * "}") - end - else - for e in Graphs.edges(g) - source = string(Graphs.src(e)) - dest = string(Graphs.dst(e)) - println(io, "\t" * source * " -- " * dest) - end - end - println(io,"}") - return 1 -end - -function savedot_mult(io::IO, graphs::Dict) - ng = 0 - for (gname, g) in graphs - ng += savedot(io, g, gname) - end - return ng -end - -function _dot_read_one_graph(pg::Parsers.DOT.Graph) - isdir = pg.directed - nvg = length(Parsers.DOT.nodes(pg)) - nodedict = Dict(zip(collect(Parsers.DOT.nodes(pg)), 1:nvg)) - if isdir - g = Graphs.DiGraph(nvg) - else - g = Graphs.Graph(nvg) - end - for es in Parsers.DOT.edges(pg) - s = nodedict[es[1]] - d = nodedict[es[2]] - add_edge!(g, s, d) - end - return g -end - -_name(pg::Parsers.DOT.Graph) = - pg.id !== nothing ? pg.id.id : - Parsers.DOT.StringID(pg.directed ? "digraph" : "graph") - -function loaddot(io::IO, gname::String) - p = Parsers.DOT.parse_dot(read(io, String)) - for pg in p - _name(pg) == gname && return _dot_read_one_graph(pg) - end - error("Graph $gname not found") -end - -function loaddot_mult(io::IO) - p = Parsers.DOT.parse_dot(read(io, String)) - graphs = Dict{String,AbstractGraph}() - - for pg in p - graphs[_name(pg)] = _dot_read_one_graph(pg) - end - return graphs -end - -loadgraph(io::IO, gname::String, ::DOTFormat) = loaddot(io, gname) -loadgraphs(io::IO, ::DOTFormat) = loaddot_mult(io) -savegraph(io::IO, g::AbstractGraph, gname::String, ::DOTFormat) = savedot(io, g, gname) -savegraph(io::IO, d::Dict, ::DOTFormat) = savedot_mult(io, d) - end #module diff --git a/src/Edgelist/Edgelist.jl b/src/Edgelist/Edgelist.jl index 8b05f93..73e01de 100644 --- a/src/Edgelist/Edgelist.jl +++ b/src/Edgelist/Edgelist.jl @@ -12,7 +12,6 @@ import Graphs: loadgraph, loadgraphs, savegraph export EdgeListFormat - struct EdgeListFormat <: AbstractGraphFormat end function loadedgelist(io::IO, gname::String) @@ -50,7 +49,9 @@ end loadgraph(io::IO, gname::String, ::EdgeListFormat) = loadedgelist(io, gname) loadgraphs(io::IO, ::EdgeListFormat) = Dict("graph" => loadedgelist(io, "graph")) -savegraph(io::IO, g::AbstractGraph, gname::String, ::EdgeListFormat) = saveedgelist(io, g, gname) +function savegraph(io::IO, g::AbstractGraph, gname::String, ::EdgeListFormat) + return saveedgelist(io, g, gname) +end include("IntEdgeList.jl") diff --git a/src/Edgelist/IntEdgeList.jl b/src/Edgelist/IntEdgeList.jl index 6dc27f6..4de6ea4 100644 --- a/src/Edgelist/IntEdgeList.jl +++ b/src/Edgelist/IntEdgeList.jl @@ -3,10 +3,10 @@ export IntEdgeListFormat struct IntEdgeListFormat <: AbstractGraphFormat offset::Int64 end -IntEdgeListFormat(;offset = 0) = IntEdgeListFormat(offset) +IntEdgeListFormat(; offset=0) = IntEdgeListFormat(offset) function loadintedgelist(io::IO, gname::String, offset::Int64) - elist = Vector{Tuple{Int64, Int64}}() + elist = Vector{Tuple{Int64,Int64}}() nvg = 0 neg = 0 fadjlist = Vector{Vector{Int64}}() @@ -15,7 +15,7 @@ function loadintedgelist(io::IO, gname::String, offset::Int64) while x[i] != ' ' && x[i] != ',' i += 1 end - s = parse(Int64, x[1:i-1]) + s = parse(Int64, x[1:(i - 1)]) while x[i] == ' ' || x[i] == ',' i += 1 end @@ -23,26 +23,32 @@ function loadintedgelist(io::IO, gname::String, offset::Int64) while i <= length(x) && x[i] != ' ' i += 1 end - d = parse(Int64, x[ii:i-1]) - s = s-offset - d = d-offset + d = parse(Int64, x[ii:(i - 1)]) + s = s - offset + d = d - offset if nvg < max(s, d) nvg = max(s, d) - append!(fadjlist, [Vector{Int64}() for _ in 1:nvg-length(fadjlist)]) + append!(fadjlist, [Vector{Int64}() for _ in 1:(nvg - length(fadjlist))]) end push!(fadjlist[s], d) neg += 1 end sort!.(fadjlist) badjlist = [Vector{Int64}() for _ in 1:nvg] - for u = 1:nvg - for v in fadjlist[u] - push!(badjlist[v], u) - end + for u in 1:nvg + for v in fadjlist[u] + push!(badjlist[v], u) + end end return Graphs.DiGraph(neg, fadjlist, badjlist) end -loadgraph(io::IO, gname::String, fmt::IntEdgeListFormat) = loadintedgelist(io, gname, fmt.offset) -loadgraphs(io::IO, fmt::IntEdgeListFormat) = Dict("graph" => loadintedgelist(io, "graph", fmt.offset)) -savegraph(io::IO, g::AbstractGraph, gname::String, ::IntEdgeListFormat) = saveedgelist(io, g, gname) +function loadgraph(io::IO, gname::String, fmt::IntEdgeListFormat) + return loadintedgelist(io, gname, fmt.offset) +end +function loadgraphs(io::IO, fmt::IntEdgeListFormat) + return Dict("graph" => loadintedgelist(io, "graph", fmt.offset)) +end +function savegraph(io::IO, g::AbstractGraph, gname::String, ::IntEdgeListFormat) + return saveedgelist(io, g, gname) +end diff --git a/src/GEXF/Gexf.jl b/src/GEXF/Gexf.jl index 363b0c9..ebe625b 100644 --- a/src/GEXF/Gexf.jl +++ b/src/GEXF/Gexf.jl @@ -1,55 +1,10 @@ module GEXF -using GraphIO.EzXML -using Graphs -using Graphs: AbstractGraph, AbstractGraphFormat +import Graphs: AbstractGraphFormat -import Graphs: savegraph - -export GEXFFormat +export GEXFFormat # TODO: implement readgexf struct GEXFFormat <: AbstractGraphFormat end -""" - savegexf(f, g, gname) - -Write a graph `g` with name `gname` to an IO stream `io` in the -[Gexf](http://gexf.net/format/) format. Return 1 (number of graphs written). -""" -function savegexf(io::IO, g::Graphs.AbstractGraph, gname::String) - xdoc = XMLDocument() - xroot = setroot!(xdoc, ElementNode("gexf")) - xroot["xmlns"] = "http://www.gexf.net/1.2draft" - xroot["version"] = "1.2" - xroot["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance" - xroot["xsi:schemaLocation"] = "http://www.gexf.net/1.2draft/gexf.xsd" - - xmeta = addelement!(xroot, "meta") - addelement!(xmeta, "description", gname) - xg = addelement!(xroot, "graph") - strdir = is_directed(g) ? "directed" : "undirected" - xg["defaultedgetype"] = strdir - - xnodes = addelement!(xg, "nodes") - for i in 1:nv(g) - xv = addelement!(xnodes, "node") - xv["id"] = "$(i-1)" - end - - xedges = addelement!(xg, "edges") - m = 0 - for e in Graphs.edges(g) - xe = addelement!(xedges, "edge") - xe["id"] = "$m" - xe["source"] = "$(src(e)-1)" - xe["target"] = "$(dst(e)-1)" - m += 1 - end - - prettyprint(io, xdoc) - return 1 -end - -savegraph(io::IO, g::AbstractGraph, gname::String, ::GEXFFormat) = savegexf(io, g, gname) end #module diff --git a/src/GML/Gml.jl b/src/GML/Gml.jl index cdf374c..2869015 100644 --- a/src/GML/Gml.jl +++ b/src/GML/Gml.jl @@ -1,100 +1,9 @@ module GML -using GraphIO.ParserCombinator.Parsers -using Graphs -using Graphs: AbstractGraphFormat - -import Graphs: loadgraph, loadgraphs, savegraph +import Graphs: AbstractGraphFormat export GMLFormat - struct GMLFormat <: AbstractGraphFormat end -function _gml_read_one_graph(gs, dir) - nodes = [x[:id] for x in gs[:node]] - if dir - g = Graphs.DiGraph(length(nodes)) - else - g = Graphs.Graph(length(nodes)) - end - mapping = Dict{Int,Int}() - for (i, n) in enumerate(nodes) - mapping[n] = i - end - sds = [(Int(x[:source]), Int(x[:target])) for x in gs[:edge]] - for (s, d) in (sds) - add_edge!(g, mapping[s], mapping[d]) - end - return g -end - -function loadgml(io::IO, gname::String) - p = Parsers.GML.parse_dict(read(io, String)) - for gs in p[:graph] - dir = Bool(get(gs, :directed, 0)) - graphname = get(gs, :label, dir ? "digraph" : "graph") - - (gname == graphname) && return _gml_read_one_graph(gs, dir) - end - error("Graph $gname not found") -end - -function loadgml_mult(io::IO) - p = Parsers.GML.parse_dict(read(io, String)) - graphs = Dict{String,Graphs.AbstractGraph}() - for gs in p[:graph] - dir = Bool(get(gs, :directed, 0)) - graphname = get(gs, :label, dir ? "digraph" : "graph") - graphs[graphname] = _gml_read_one_graph(gs, dir) - end - return graphs -end - -""" - savegml(f, g, gname="graph") - -Write a graph `g` with name `gname` to an IO stream `io` in the -[GML](https://en.wikipedia.org/wiki/Graph_Modelling_Language) format. Return 1. -""" -function savegml(io::IO, g::Graphs.AbstractGraph, gname::String = "") - println(io, "graph") - println(io, "[") - length(gname) > 0 && println(io, "label \"$gname\"") - is_directed(g) && println(io, "directed 1") - for i = 1:nv(g) - println(io, "\tnode") - println(io, "\t[") - println(io, "\t\tid $i") - println(io, "\t]") - end - for e in Graphs.edges(g) - s, t = Tuple(e) - println(io, "\tedge") - println(io, "\t[") - println(io, "\t\tsource $s") - println(io, "\t\ttarget $t") - println(io, "\t]") - end - println(io, "]") - return 1 -end - - -""" - savegml_mult(io, graphs) -Write a dictionary of (name=>graph) to an IO stream `io` Return number of graphs written. -""" -function savegml_mult(io::IO, graphs::Dict) - ng = 0 - for (gname, g) in graphs - ng += savegml(io, g, gname) - end - return ng -end -loadgraph(io::IO, gname::String, ::GMLFormat) = loadgml(io, gname) -loadgraphs(io::IO, ::GMLFormat) = loadgml_mult(io) -savegraph(io::IO, g::AbstractGraph, gname::String, ::GMLFormat) = savegml(io, g, gname) -savegraph(io::IO, d::Dict, ::GMLFormat) = savegml_mult(io, d) - end # module diff --git a/src/Graph6/Graph6.jl b/src/Graph6/Graph6.jl index 5ecc3e5..4a38181 100644 --- a/src/Graph6/Graph6.jl +++ b/src/Graph6/Graph6.jl @@ -11,126 +11,130 @@ export Graph6Format struct Graph6Format <: AbstractGraphFormat end function _bv2int(x::BitVector) - @assert(length(x) <= 8 * sizeof(Int)) - acc = 0 - for i = 1:length(x) - acc = acc << 1 + x[i] - end - return acc + @assert(length(x) <= 8 * sizeof(Int)) + acc = 0 + for i in 1:length(x) + acc = acc << 1 + x[i] + end + return acc end function _int2bv(n::Int, k::Int) - bitstr = lstrip(bitstring(n), '0') - l = length(bitstr) - padding = k - l - bv = falses(k) - for i = 1:l - bv[padding + i] = (bitstr[i] == '1') - end - return bv + bitstr = lstrip(bitstring(n), '0') + l = length(bitstr) + padding = k - l + bv = falses(k) + for i in 1:l + bv[padding + i] = (bitstr[i] == '1') + end + return bv end function _g6_R(_x::BitVector)::Vector{UInt8} - k = length(_x) - padding = cld(k, 6) * 6 - k - x = vcat(_x, falses(padding)) - nbytes = div(length(x), 6) - bytevec = Vector{UInt8}(undef, nbytes) # uninitialized data! - for i = 1:nbytes - xslice = x[((i - 1) * 6 + 1):(i * 6)] - - intslice = 0 - for bit in xslice - intslice = (intslice << 1) + bit + k = length(_x) + padding = cld(k, 6) * 6 - k + x = vcat(_x, falses(padding)) + nbytes = div(length(x), 6) + bytevec = Vector{UInt8}(undef, nbytes) # uninitialized data! + for i in 1:nbytes + xslice = x[((i - 1) * 6 + 1):(i * 6)] + + intslice = 0 + for bit in xslice + intslice = (intslice << 1) + bit + end + intslice += 63 + bytevec[i] = intslice end - intslice += 63 - bytevec[i] = intslice - end - return UInt8.(bytevec) + return UInt8.(bytevec) end _g6_R(n::Int, k::Int) = _g6_R(_int2bv(n, k)) function _g6_Rp(bytevec::Vector{UInt8}) - nbytes = length(bytevec) - x = BitVector() - for byte in bytevec - bits = _int2bv(byte - 63, 6) - x = vcat(x, bits) - end - return x + nbytes = length(bytevec) + x = BitVector() + for byte in bytevec + bits = _int2bv(byte - 63, 6) + x = vcat(x, bits) + end + return x end function _g6_N(x::Integer)::Vector{UInt8} - if (x < 0) || (x > 68719476735) error("x must satisfy 0 <= x <= 68719476735") - elseif (x <= 62) nvec = [x + 63] - elseif (x <= 258047) - nvec = vcat([0x7e], _g6_R(x, 18)) - else - nvec = vcat([0x7e; 0x7e], _g6_R(x, 36)) - end - return UInt8.(nvec) + if (x < 0) || (x > 68719476735) + error("x must satisfy 0 <= x <= 68719476735") + elseif (x <= 62) + nvec = [x + 63] + elseif (x <= 258047) + nvec = vcat([0x7e], _g6_R(x, 18)) + else + nvec = vcat([0x7e; 0x7e], _g6_R(x, 36)) + end + return UInt8.(nvec) end function _g6_Np(N::Vector{UInt8}) - if N[1] < 0x7e return (Int(N[1] - 63), N[2:end]) - elseif N[2] < 0x7e return (_bv2int(_g6_Rp(N[2:4])), N[5:end]) - else return(_bv2int(_g6_Rp(N[3:8])), N[9:end]) - end + if N[1] < 0x7e + return (Int(N[1] - 63), N[2:end]) + elseif N[2] < 0x7e + return (_bv2int(_g6_Rp(N[2:4])), N[5:end]) + else + return (_bv2int(_g6_Rp(N[3:8])), N[9:end]) + end end - """ _graphToG6String(g) Given a graph `g`, create the corresponding Graph6 string. """ function _graphToG6String(g::Graphs.Graph) - A = adjacency_matrix(g, Bool) - n = nv(g) - nbits = div(n * (n - 1), 2) - x = BitVector(undef, nbits) - - ind = 0 - for col = 2:n, row = 1:(col - 1) - ind += 1 - x[ind] = A[row, col] - end - return join([">>graph6<<", String(_g6_N(n)), String(_g6_R(x))]) + A = adjacency_matrix(g, Bool) + n = nv(g) + nbits = div(n * (n - 1), 2) + x = BitVector(undef, nbits) + + ind = 0 + for col in 2:n, row in 1:(col - 1) + ind += 1 + x[ind] = A[row, col] + end + return join([">>graph6<<", String(_g6_N(n)), String(_g6_R(x))]) end function _g6StringToGraph(s::AbstractString) - if startswith(s, ">>graph6<<") - s = s[11:end] - end - V = Vector{UInt8}(s) - (nv, rest) = _g6_Np(V) - bitvec = _g6_Rp(rest) - - g = Graphs.Graph(nv) - n = 0 - for i in 2:nv, j in 1:(i - 1) - n += 1 - if bitvec[n] - add_edge!(g, j, i) + if startswith(s, ">>graph6<<") + s = s[11:end] + end + V = Vector{UInt8}(s) + (nv, rest) = _g6_Np(V) + bitvec = _g6_Rp(rest) + + g = Graphs.Graph(nv) + n = 0 + for i in 2:nv, j in 1:(i - 1) + n += 1 + if bitvec[n] + add_edge!(g, j, i) + end end - end - return g + return g end function loadgraph6_mult(io::IO) - n = 0 - graphdict = Dict{String,Graphs.Graph}() - while !eof(io) - n += 1 - line = strip(chomp(readline(io))) - gname = "graph$n" - if length(line) > 0 - g = _g6StringToGraph(line) - graphdict[gname] = g + n = 0 + graphdict = Dict{String,Graphs.Graph}() + while !eof(io) + n += 1 + line = strip(chomp(readline(io))) + gname = "graph$n" + if length(line) > 0 + g = _g6StringToGraph(line) + graphdict[gname] = g + end end - end - return graphdict + return graphdict end """ @@ -141,7 +145,6 @@ format. Return the graph. """ loadgraph6(io::IO, gname::String="graph") = loadgraph6_mult(io)[gname] - """ savegraph6(io, g, gname="graph") @@ -149,24 +152,26 @@ Write a graph `g` to IO stream `io` in the [Graph6](http://users.cecs.anu.edu.au format. Return 1 (number of graphs written). """ function savegraph6 end -@traitfn function savegraph6(io::IO, g::::(!Graphs.IsDirected), gname::String = "graph") - str = _graphToG6String(g) - println(io, str) - return 1 +@traitfn function savegraph6(io::IO, g::::(!Graphs.IsDirected), gname::String="graph") + str = _graphToG6String(g) + println(io, str) + return 1 end function savegraph6_mult(io::IO, graphs::Dict) - ng = 0 - sortkeys = sort(collect(keys(graphs))) - for gname in sortkeys - ng += savegraph6(io, graphs[gname], gname) - end - return ng + ng = 0 + sortkeys = sort(collect(keys(graphs))) + for gname in sortkeys + ng += savegraph6(io, graphs[gname], gname) + end + return ng end loadgraph(io::IO, gname::String, ::Graph6Format) = loadgraph6(io, gname) loadgraphs(io::IO, ::Graph6Format) = loadgraph6_mult(io) -savegraph(io::IO, g::AbstractGraph, gname::String, ::Graph6Format) = savegraph6(io, g, gname) +function savegraph(io::IO, g::AbstractGraph, gname::String, ::Graph6Format) + return savegraph6(io, g, gname) +end savegraph(io::IO, d::Dict, ::Graph6Format) = savegraph6_mult(io, d) end # module diff --git a/src/GraphIO.jl b/src/GraphIO.jl index 6f67734..b429bb6 100644 --- a/src/GraphIO.jl +++ b/src/GraphIO.jl @@ -1,31 +1,33 @@ module GraphIO -using Requires +@static if !isdefined(Base, :get_extension) + using Requires +end -#= - NOTE: This is a temporary fix until we can have multiple sub-packages with their own - requirements in a single repository. -=# -function __init__() - @require CodecZlib="944b1d66-785c-5afd-91f1-9de20f533193" begin - include("LGCompressed/LGCompressed.jl") - end - @require EzXML="8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" begin - include("GEXF/Gexf.jl") - include("GraphML/GraphML.jl") - end - @require ParserCombinator="fae87a5f-d1ad-5cf0-8f61-c941e1580b46" begin - include("DOT/Dot.jl") - include("GML/Gml.jl") +@static if !isdefined(Base, :get_extension) + function __init__() + @require CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193" begin + include("../ext/GraphIOLGCompressedExt.jl") + end + @require EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" begin + include("../ext/GraphIOGEXFExt.jl") + include("../ext/GraphIOGraphMLExt.jl") + end + @require ParserCombinator = "fae87a5f-d1ad-5cf0-8f61-c941e1580b46" begin + include("../ext/GraphIODOTExt.jl") + include("../ext/GraphIOGMLExt.jl") + end end end - +include("CDF/Cdf.jl") +include("DOT/Dot.jl") +include("Edgelist/Edgelist.jl") +include("GEXF/Gexf.jl") +include("GML/Gml.jl") +include("GraphML/GraphML.jl") include("Graph6/Graph6.jl") +include("LGCompressed/LGCompressed.jl") include("NET/Net.jl") -include("Edgelist/Edgelist.jl") -include("CDF/Cdf.jl") -include("deprecations.jl") - -end # module +end diff --git a/src/GraphML/GraphML.jl b/src/GraphML/GraphML.jl index a7ab5fb..466247c 100644 --- a/src/GraphML/GraphML.jl +++ b/src/GraphML/GraphML.jl @@ -1,136 +1,9 @@ module GraphML -using GraphIO.EzXML -using Graphs -using Graphs: AbstractGraphFormat - -import Graphs: loadgraph, loadgraphs, savegraph +import Graphs: AbstractGraphFormat export GraphMLFormat - struct GraphMLFormat <: AbstractGraphFormat end -function _graphml_read_one_graph(reader::EzXML.StreamReader, isdirected::Bool) - nodes = Dict{String,Int}() - xedges = Vector{Graphs.Edge}() - nodeid = 1 - for typ in reader - if typ == EzXML.READER_ELEMENT - elname = EzXML.nodename(reader) - if elname == "node" - nodes[reader["id"]] = nodeid - nodeid += 1 - elseif elname == "edge" - src = reader["source"] - tar = reader["target"] - push!(xedges, Graphs.Edge(nodes[src], nodes[tar])) - else - @warn "Skipping unknown node '$(elname)' - further warnings will be suppressed" maxlog=1 _id=:unknode - end - end - end - g = (isdirected ? Graphs.DiGraph : Graphs.Graph)(length(nodes)) - for edge in xedges - add_edge!(g, edge) - end - return g -end - -function loadgraphml(io::IO, gname::String) - reader = EzXML.StreamReader(io) - for typ in reader - if typ == EzXML.READER_ELEMENT - elname = EzXML.nodename(reader) - if elname == "graphml" - # ok - elseif elname == "graph" - edgedefault = reader["edgedefault"] - directed = edgedefault == "directed" ? true : - edgedefault == "undirected" ? false : - error("Unknown value of edgedefault: $edgedefault") - if haskey(reader, "id") - graphname = reader["id"] - else - graphname = directed ? "digraph" : "graph" - end - if gname == graphname - return _graphml_read_one_graph(reader, directed) - end - elseif elname == "node" || elname == "edge" - # ok - else - @warn "Skipping unknown XML element '$(elname)' - further warnings will be suppressed" maxlog=1 _id=:unkel - end - end - end - error("Graph $gname not found") -end - -function loadgraphml_mult(io::IO) - reader = EzXML.StreamReader(io) - graphs = Dict{String,Graphs.AbstractGraph}() - for typ in reader - if typ == EzXML.READER_ELEMENT - elname = EzXML.nodename(reader) - if elname == "graphml" - # ok - elseif elname == "graph" - edgedefault = reader["edgedefault"] - directed = edgedefault == "directed" ? true : - edgedefault == "undirected" ? false : - error("Unknown value of edgedefault: $edgedefault") - if haskey(reader, "id") - graphname = reader["id"] - else - graphname = directed ? "digraph" : "graph" - end - graphs[graphname] = _graphml_read_one_graph(reader, directed) - else - @warn "Skipping unknown XML element '$(elname)' - further warnings will be suppressed" maxlog=1 _id=:unkelmult - end - end - end - return graphs -end - -function savegraphml_mult(io::IO, graphs::Dict) - xdoc = XMLDocument() - xroot = setroot!(xdoc, ElementNode("graphml")) - xroot["xmlns"] = "http://graphml.graphdrawing.org/xmlns" - xroot["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance" - xroot["xsi:schemaLocation"] = "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd" - - for (gname, g) in graphs - xg = addelement!(xroot, "graph") - xg["id"] = gname - xg["edgedefault"] = is_directed(g) ? "directed" : "undirected" - - for i in 1:nv(g) - xv = addelement!(xg, "node") - xv["id"] = "n$(i-1)" - end - - m = 0 - for e in Graphs.edges(g) - xe = addelement!(xg, "edge") - xe["id"] = "e$m" - xe["source"] = "n$(src(e)-1)" - xe["target"] = "n$(dst(e)-1)" - m += 1 - end - end - prettyprint(io, xdoc) - return length(graphs) -end - -savegraphml(io::IO, g::Graphs.AbstractGraph, gname::String) = - savegraphml_mult(io, Dict(gname => g)) - - -loadgraph(io::IO, gname::String, ::GraphMLFormat) = loadgraphml(io, gname) -loadgraphs(io::IO, ::GraphMLFormat) = loadgraphml_mult(io) -savegraph(io::IO, g::AbstractGraph, gname::String, ::GraphMLFormat) = savegraphml(io, g, gname) -savegraph(io::IO, d::Dict, ::GraphMLFormat) = savegraphml_mult(io, d) - end # module diff --git a/src/LGCompressed/LGCompressed.jl b/src/LGCompressed/LGCompressed.jl index 15a6231..2673cee 100644 --- a/src/LGCompressed/LGCompressed.jl +++ b/src/LGCompressed/LGCompressed.jl @@ -1,51 +1,9 @@ module LGCompressed -using GraphIO.CodecZlib -using Graphs -using Graphs: AbstractGraphFormat - -import Graphs: loadgraph, loadgraphs, savegraph +import Graphs: AbstractGraphFormat export LGCompressedFormat struct LGCompressedFormat <: AbstractGraphFormat end -function savegraph(fn::AbstractString, g::AbstractGraph, gname::AbstractString, - format::LGCompressedFormat) - io = open(fn, "w") - try - io = GzipCompressorStream(io) - return savegraph(io, g, gname, Graphs.LGFormat()) - catch - rethrow() - finally - close(io) - end -end - -savegraph(fn::AbstractString, g::AbstractGraph, format::LGCompressedFormat) = - savegraph(fn, g, "graph", format) - -function savegraph(fn::AbstractString, d::Dict{T,U}, - format::LGCompressedFormat) where T <: AbstractString where U <: AbstractGraph - io = open(fn, "w") - try - io = GzipCompressorStream(io) - return savegraph(io, d, Graphs.LGFormat()) - catch - rethrow() - finally - close(io) - end -end - -# savegraph(fn::AbstractString, d::Dict; compress) = savegraph(fn, d, LGCompressedFormat()) - -loadgraph(fn::AbstractString, gname::AbstractString, format::LGCompressedFormat) = - loadgraph(fn, gname, Graphs.LGFormat()) - -loadgraph(fn::AbstractString, format::LGCompressedFormat) = loadgraph(fn, "graph", Graphs.LGFormat()) - -loadgraphs(fn::AbstractString, format::LGCompressedFormat) = loadgraphs(fn, LGFormat()) - end # module diff --git a/src/NET/Net.jl b/src/NET/Net.jl index b5e86f3..ffdc617 100644 --- a/src/NET/Net.jl +++ b/src/NET/Net.jl @@ -1,6 +1,6 @@ module NET -import Graphs +using Graphs: Graphs using Graphs using Graphs: AbstractGraphFormat @@ -8,7 +8,6 @@ import Graphs: loadgraph, loadgraphs, savegraph export NETFormat - struct NETFormat <: AbstractGraphFormat end """ savenet(io, g, gname="g") @@ -16,7 +15,7 @@ struct NETFormat <: AbstractGraphFormat end Write a graph `g` to an IO stream `io` in the [Pajek NET](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) format. Return 1 (number of graphs written). """ -function savenet(io::IO, g::Graphs.AbstractGraph, gname::String = "g") +function savenet(io::IO, g::Graphs.AbstractGraph, gname::String="g") println(io, "*Vertices $(nv(g))") # write edges if is_directed(g) @@ -36,7 +35,7 @@ end Read a graph from IO stream `io` in the [Pajek NET](http://gephi.github.io/users/supported-graph-formats/pajek-net-format/) format. Return the graph. """ -function loadnet(io::IO, gname::String = "graph") +function loadnet(io::IO, gname::String="graph") line = readline(io) # skip comments while startswith(line, "%") @@ -55,7 +54,7 @@ function loadnet(io::IO, gname::String = "graph") while occursin(r"^\*Arcs", line) for ioline in eachline(io) line = ioline - ms = collect(m.match for m in eachmatch(r"\d+", line, overlap=false)) + ms = collect(m.match for m in eachmatch(r"\d+", line; overlap=false)) length(ms) < 2 && break add_edge!(g, parse(Int, ms[1]), parse(Int, ms[2])) end @@ -63,7 +62,7 @@ function loadnet(io::IO, gname::String = "graph") while occursin(r"^\*Edges", line) # add edges in both directions for ioline in eachline(io) line = ioline - ms = collect(m.match for m in eachmatch(r"\d+", line, overlap=false)) + ms = collect(m.match for m in eachmatch(r"\d+", line; overlap=false)) length(ms) < 2 && break i1, i2 = parse(Int, ms[1]), parse(Int, ms[2]) add_edge!(g, i1, i2) diff --git a/src/deprecations.jl b/src/deprecations.jl deleted file mode 100644 index 1523b44..0000000 --- a/src/deprecations.jl +++ /dev/null @@ -1,77 +0,0 @@ -using Base: depwarn - -function GEXFFormat() - depwarn(""" - `GraphIO.GEXFFormat` has been moved to submodule `GraphIO.GEXF` and needs `EzXML.jl` to be imported first. I.e use. - using EzXML - GraphIO.GEXF.GEXFFormat() - """, :GEXFFormat) - return GraphIO.GEXF.GEXFFormat() -end -export GEXFFormat - -function GraphMLFormat() - depwarn(""" - `GraphIO.GraphMLFormat` has been moved to submodule `GraphIO.GraphML` and needs `EzXML.jl` to be imported first. I.e. use - using EzXML - GraphIO.GraphML.GraphMLFormat() - """, :GraphMLFormat) - return GraphIO.GraphML.GraphMLFormat() -end -export GraphMLFormat - -function DOTFormat() - depwarn(""" - `GraphIO.DOTFormat` has been moved to submodule `GraphIO.DOT` and needs `ParserCombinator.jl` to be imported first. I.e. use - using ParserCombinator - GraphIO.DOT.DOTFormat() - """, :DOTFormat) - return GraphIO.DOT.DOTFormat() -end -export DOTFormat - -function GMLFormat() - depwarn(""" - `GraphIO.GMLFormat` has been moved to submodule `GraphIO.GML` and needs `ParserCombinator.jl` to be imported first. I.e. use - using ParserCombinator - GraphIO.GML.GMLFormat() - """, :GMLFormat) - return GraphIO.GML.GMLFormat() -end -export GMLFormat - -function Graph6Format() - depwarn(""" - `GraphIO.Graph6Format` has been moved to submodule `GraphIO.Graph6`. I.e. use - GraphIO.Graph6.Graph6Format() - """, :Graph6Format) - return GraphIO.Graph6.Graph6Format() -end -export Graph6Format - -function NETFormat() - depwarn(""" - `GraphIO.NETFormat` has been moved to submodule `GraphIO.NET`. I.e. use - GraphIO.NET.NETFormat() - """, :NETFormat) - return GraphIO.NET.NETFormat() -end -export NETFormat - -function EdgeListFormat() - depwarn(""" - `GraphIO.EdgeListFormat` has been moved to submodule `GraphIO.EdgeList`. I.e. use - GraphIO.EdgeList.EdgeListFormat() - """, :EdgeListFormat) - return GraphIO.EdgeList.EdgeListFormat() -end -export EdgeListFormat - -function CDFFormat() - depwarn(""" - `GraphIO.CDFFormat` has been moved to submodule `GraphIO.CDF`. I.e. use - GraphIO.CDF.CDFFormat() - """, :CDFFormat) - return GraphIO.CDF.CDFFormat() -end -export CDFFormat diff --git a/test/DOT/runtests.jl b/test/DOT/runtests.jl index 83a95d6..ce6a9ee 100644 --- a/test/DOT/runtests.jl +++ b/test/DOT/runtests.jl @@ -6,42 +6,42 @@ using Graphs.Experimental @testset "DOT" begin g = complete_graph(6) dg = DiGraph(4) - for e in [Edge(1,2), Edge(1,3), Edge(2,2), Edge(2,3), Edge(4,1), Edge(4,3)] + for e in [Edge(1, 2), Edge(1, 3), Edge(2, 2), Edge(2, 3), Edge(4, 1), Edge(4, 3)] add_edge!(dg, e) end fname = joinpath(testdir, "testdata", "twographs.dot") - read_test(DOTFormat(), g, "g1", fname, testfail=true) + read_test(DOTFormat(), g, "g1", fname; testfail=true) read_test(DOTFormat(), dg, "g2", fname) - read_test_mult(DOTFormat(), Dict{String,AbstractGraph}("g1"=>g, "g2"=>dg), fname) - - #tests for multiple graphs - - fname = joinpath(testdir, "testdata", "saved3graphs.dot") - #connected graph - g1 = SimpleGraph(5,10) - #disconnected graph - g2 = SimpleGraph(5,2) - #directed graph - dg = SimpleDiGraph(5,8) - GraphDict = Dict("g1" => g1, "g2" => g2, "dg" => dg) - write_test(DOTFormat(), GraphDict, fname, remove = false, silent = true) - - #adding this test because currently the Parser returns unordered vertices - @test has_isomorph(loadgraph(fname, "g1", DOTFormat()), g1) - @test has_isomorph(loadgraph(fname, "g2", DOTFormat()), g2) - @test has_isomorph(loadgraph(fname, "dg", DOTFormat()), dg) - - rm(fname) - - #tests for single graph - - fname1 = joinpath(testdir, "testdata", "saved1graph.dot") - write_test(DOTFormat(), g1, "g1", fname1, remove = false, silent = true) - @test has_isomorph(loadgraph(fname1, "g1", DOTFormat()), g1) - fname2 = joinpath(testdir, "testdata", "saved1digraph.dot") - write_test(DOTFormat(), dg, "dg", fname2, remove = false, silent = true) - @test has_isomorph(loadgraph(fname2, "dg", DOTFormat()), dg) - - rm(fname1) - rm(fname2) + read_test_mult(DOTFormat(), Dict{String,AbstractGraph}("g1" => g, "g2" => dg), fname) + + #tests for multiple graphs + + fname = joinpath(testdir, "testdata", "saved3graphs.dot") + #connected graph + g1 = SimpleGraph(5, 10) + #disconnected graph + g2 = SimpleGraph(5, 2) + #directed graph + dg = SimpleDiGraph(5, 8) + GraphDict = Dict("g1" => g1, "g2" => g2, "dg" => dg) + write_test(DOTFormat(), GraphDict, fname; remove=false, silent=true) + + #adding this test because currently the Parser returns unordered vertices + @test has_isomorph(loadgraph(fname, "g1", DOTFormat()), g1) + @test has_isomorph(loadgraph(fname, "g2", DOTFormat()), g2) + @test has_isomorph(loadgraph(fname, "dg", DOTFormat()), dg) + + rm(fname) + + #tests for single graph + + fname1 = joinpath(testdir, "testdata", "saved1graph.dot") + write_test(DOTFormat(), g1, "g1", fname1; remove=false, silent=true) + @test has_isomorph(loadgraph(fname1, "g1", DOTFormat()), g1) + fname2 = joinpath(testdir, "testdata", "saved1digraph.dot") + write_test(DOTFormat(), dg, "dg", fname2; remove=false, silent=true) + @test has_isomorph(loadgraph(fname2, "dg", DOTFormat()), dg) + + rm(fname1) + rm(fname2) end diff --git a/test/Edgelist/runtests.jl b/test/Edgelist/runtests.jl index d4fb0b8..a3e5bad 100644 --- a/test/Edgelist/runtests.jl +++ b/test/Edgelist/runtests.jl @@ -8,4 +8,3 @@ using GraphIO.EdgeList: IntEdgeListFormat readback_test(IntEdgeListFormat(), g) end end - diff --git a/test/GML/runtests.jl b/test/GML/runtests.jl index 0fdf37a..2056892 100644 --- a/test/GML/runtests.jl +++ b/test/GML/runtests.jl @@ -4,7 +4,7 @@ using GraphIO.GML @testset "GML" begin for g in values(allgraphs) - readback_test(GMLFormat(), g, testfail=true) + readback_test(GMLFormat(), g; testfail=true) end write_test(GMLFormat(), allgraphs) end diff --git a/test/Graph6/runtests.jl b/test/Graph6/runtests.jl index 3c20595..1d88603 100644 --- a/test/Graph6/runtests.jl +++ b/test/Graph6/runtests.jl @@ -20,4 +20,3 @@ using GraphIO.Graph6 read_test_mult(Graph6Format(), graphs, f) rm(f) end - diff --git a/test/GraphML/runtests.jl b/test/GraphML/runtests.jl index 2fec88a..ce5d672 100644 --- a/test/GraphML/runtests.jl +++ b/test/GraphML/runtests.jl @@ -4,13 +4,17 @@ using GraphIO.GraphML @testset "GraphML" begin for g in values(allgraphs) - readback_test(GraphMLFormat(), g, testfail=true) + readback_test(GraphMLFormat(), g; testfail=true) end fname = joinpath(testdir, "testdata", "warngraph.graphml") - - @test_logs (:warn, "Skipping unknown node 'warnnode' - further warnings will be suppressed") match_mode=:any loadgraphs(fname, GraphMLFormat()) - @test_logs (:warn, "Skipping unknown XML element 'warnelement' - further warnings will be suppressed") match_mode=:any loadgraph(fname, "graph", GraphMLFormat()) + + @test_logs ( + :warn, "Skipping unknown node 'warnnode' - further warnings will be suppressed" + ) match_mode = :any loadgraphs(fname, GraphMLFormat()) + @test_logs ( + :warn, + "Skipping unknown XML element 'warnelement' - further warnings will be suppressed", + ) match_mode = :any loadgraph(fname, "graph", GraphMLFormat()) d = loadgraphs(fname, GraphMLFormat()) write_test(GraphMLFormat(), d) end - diff --git a/test/NET/runtests.jl b/test/NET/runtests.jl index e990b0b..3a90b47 100644 --- a/test/NET/runtests.jl +++ b/test/NET/runtests.jl @@ -8,5 +8,3 @@ using GraphIO.NET fname = joinpath(testdir, "testdata", "kinship.net") @test length(loadgraphs(fname, NETFormat())) == 1 end - - diff --git a/test/graphio.jl b/test/graphio.jl index f443a5b..782403c 100644 --- a/test/graphio.jl +++ b/test/graphio.jl @@ -4,14 +4,12 @@ using Graphs graphs = Dict{String,Graph}( - "graph1" => complete_graph(5), - "graph2" => path_graph(6), - "graph3" => wheel_graph(4) - ) + "graph1" => complete_graph(5), "graph2" => path_graph(6), "graph3" => wheel_graph(4) +) digraphs = Dict{String,DiGraph}( - "digraph1" => complete_digraph(5), - "digraph2" => path_digraph(6), - "digraph3" => wheel_digraph(4) + "digraph1" => complete_digraph(5), + "digraph2" => path_digraph(6), + "digraph3" => wheel_digraph(4), ) allgraphs = merge(graphs, digraphs) @@ -21,23 +19,37 @@ function gettempname() return f end -function read_test(format::Graphs.AbstractGraphFormat, g::Graphs.AbstractGraph, gname::String="g", - fname::AbstractString=""; testfail=false) +function read_test( + format::Graphs.AbstractGraphFormat, + g::Graphs.AbstractGraph, + gname::String="g", + fname::AbstractString=""; + testfail=false, +) @test loadgraph(fname, gname, format) == g if testfail - @test_throws Union{ArgumentError, ErrorException} loadgraph(fname, "badgraphXXX", format) + @test_throws Union{ArgumentError,ErrorException} loadgraph( + fname, "badgraphXXX", format + ) end @test loadgraphs(fname, format)[gname] == g end -function read_test_mult(format::Graphs.AbstractGraphFormat, d::Dict{String,G}, fname::AbstractString="") where G<: AbstractGraph +function read_test_mult( + format::Graphs.AbstractGraphFormat, d::Dict{String,G}, fname::AbstractString="" +) where {G<:AbstractGraph} rd = loadgraphs(fname, format) @test rd == d - end -function write_test(format::Graphs.AbstractGraphFormat, g::Graphs.AbstractGraph, gname::String="g", - fname::AbstractString=gettempname(); remove=true, silent=false) +function write_test( + format::Graphs.AbstractGraphFormat, + g::Graphs.AbstractGraph, + gname::String="g", + fname::AbstractString=gettempname(); + remove=true, + silent=false, +) @test savegraph(fname, g, gname, format) == 1 if remove rm(fname) @@ -46,8 +58,13 @@ function write_test(format::Graphs.AbstractGraphFormat, g::Graphs.AbstractGraph, end end -function write_test(format::Graphs.AbstractGraphFormat, d::Dict{String,G}, - fname::AbstractString=gettempname(); remove=true, silent=false) where G <: Graphs.AbstractGraph +function write_test( + format::Graphs.AbstractGraphFormat, + d::Dict{String,G}, + fname::AbstractString=gettempname(); + remove=true, + silent=false, +) where {G<:Graphs.AbstractGraph} @test savegraph(fname, d, format) == length(d) if remove rm(fname) @@ -56,13 +73,21 @@ function write_test(format::Graphs.AbstractGraphFormat, d::Dict{String,G}, end end -function readback_test(format::Graphs.AbstractGraphFormat, g::Graphs.AbstractGraph, gname="graph", - fname=gettempname(); remove=true, testfail=false) +function readback_test( + format::Graphs.AbstractGraphFormat, + g::Graphs.AbstractGraph, + gname="graph", + fname=gettempname(); + remove=true, + testfail=false, +) @test savegraph(fname, g, format) == 1 @test loadgraphs(fname, format)[gname] == g @test loadgraph(fname, gname, format) == g if testfail - @test_throws Union{ArgumentError, ErrorException} loadgraph(fname, "badgraphXXX", format) + @test_throws Union{ArgumentError,ErrorException} loadgraph( + fname, "badgraphXXX", format + ) end if remove rm(fname) diff --git a/test/runtests.jl b/test/runtests.jl index b32e045..cc08fe4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,24 +1,29 @@ +using Aqua +using GraphIO using Graphs +using JuliaFormatter using Test testdir = dirname(@__FILE__) modules = [ - "CDF", - "Edgelist", - "GML", - "NET", - "DOT", - "GEXF", - "Graph6", - "GraphML", - "LGCompressed" - ] + "CDF", "Edgelist", "GML", "NET", "DOT", "GEXF", "Graph6", "GraphML", "LGCompressed" +] include("graphio.jl") # write your own tests here -@testset "GraphIO" begin +@testset verbose = true "GraphIO" begin + @testset "Code quality" begin + Aqua.test_all(GraphIO; stale_deps=false, project_toml_formatting=false) + Aqua.test_stale_deps(GraphIO; ignore=[:Requires]) + if VERSION >= v"1.9" + Aqua.test_project_toml_formatting(GraphIO) + end + end + @testset "Code formatting" begin + @test JuliaFormatter.format(GraphIO; verbose=false, overwrite=false) + end for name in modules path = joinpath(testdir, name, "runtests.jl") include(path)