diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index c56f089..f5b7a2f 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - version: '1.5' + version: '1.7' - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy diff --git a/.gitignore b/.gitignore index 03f824e..9955766 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ Manifest.toml *.~ docs/build/ -scratch/ \ No newline at end of file +scratch/ +scratch* +.vscode/ \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 656eabb..08b6ab1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -17,12 +17,9 @@ makedocs( sitename = "CitableBase.jl", pages = [ "Home" => "index.md", - "URNs" => "urns.md", - "Citable resources" => "citable.md", - "Citable trait" => "citabletrait.md", - "URN comparison" => "urncomparison.md", - "CEX serialization" => "cex.md", - "Implementations" => "implementations.md", + "Identifiers" => "urns.md", + "Citable objects" => "citable.md", + "Citable collections" => "collections.md", "API documentation" => "apis.md" ] ) diff --git a/docs/src/apis.md b/docs/src/apis.md index afc16ab..b4baaa5 100644 --- a/docs/src/apis.md +++ b/docs/src/apis.md @@ -8,17 +8,8 @@ CurrentModule = CitableBase ```@docs Urn -``` - -Concrete functions for Urns - -```@docs components parts -``` -URN abstractions to implement for specific types - -```@docs addversion dropversion ``` @@ -28,12 +19,10 @@ dropversion ```@docs Citable -``` - -Functions to implement for citable resources: - - -```@docs +CitableTrait +NotCitable +citabletrait +citable urn label ``` @@ -43,6 +32,10 @@ label ```@docs UrnComparisonTrait +NotUrnComparable +urncomparisontrait +urncomparable +urnequals urncontains urnsimilar ``` @@ -53,6 +46,21 @@ urnsimilar ```@docs CexTrait +NotCexSerializable +cextrait +cexserializable cex fromcex +``` + + + + +## Citable collections + +```@docs +CitableCollectionTrait +NotCitableCollection +citablecollectiontrait +citablecollection ``` \ No newline at end of file diff --git a/docs/src/citable.md b/docs/src/citable.md index f8a1885..6288b7b 100644 --- a/docs/src/citable.md +++ b/docs/src/citable.md @@ -1,75 +1,330 @@ -# Citable entities: an example implementation +```@setup book +using CitableBase +struct Isbn10Urn <: Urn + isbn::AbstractString +end -`CitableBase` defines three traits that all citable entities must implement: +import Base: show +function show(io::IO, u::Isbn10Urn) + print(io, u.isbn) +end -- the `CitableTrait`. Citable objects are identified by URN, and have a human readable label. -- the `UrnComparisonTrait`. Citable objects can be compared with other citable objects of the same type using URN logic. -- the `CexTrait`. Citable objects can be round tripped to/from serialization to text strings in CEX format. +struct IsbnComparable <: UrnComparisonTrait end +import CitableBase: urncomparisontrait +function urncomparisontrait(::Type{Isbn10Urn}) + IsbnComparable() +end +import CitableBase: urnequals +function urnequals(u1::Isbn10Urn, u2::Isbn10Urn) + u1 == u2 +end -The next three pages walk through implementing these three traits for a custom type of citable object. +import CitableBase: urncontains +function urncontains(u1::Isbn10Urn, u2::Isbn10Urn) + initial1 = components(u1.isbn)[3][1] + initial2 = components(u2.isbn)[3][1] + initial1 == initial2 +end +function english(urn::Isbn10Urn) + langarea = components(urn.isbn)[3][1] + langarea == '0' || langarea == '1' +end -## Defining the citable type +import CitableBase: urnsimilar +function urnsimilar(u1::Isbn10Urn, u2::Isbn10Urn) + initial1 = components(u1.isbn)[3][1] + initial2 = components(u2.isbn)[3][1] -We'll begin by defining a custom type of citable object, citable by its own custom type of URN. Note that we make the types `MyOwnUrn` and `MyOwnCite` subtypes of the abstract `Urn` and `Citable` types, respectively. + (english(u1) && english(u2)) || initial1 == initial2 +end -```jldoctest citable -using CitableBase -struct MyOwnUrn <: Urn - urn::AbstractString + +distanthorizons = Isbn10Urn("urn:isbn10:022661283X") +enumerations = Isbn10Urn("urn:isbn10:022656875X") +qi = Isbn10Urn("urn:isbn10:3030234133") +wrong = Isbn10Urn("urn:isbn10:1108922036") +``` + + +# Citable entities + + +> ## Summary +> +> **The task**: We will define a type representing a book identified by ISBN-10 number. Our type will follow the CITE architecture's model of a citable object, so that we can identify it by URN and label, apply URN logic to compare objects of our new type, and serialize citable books to plain-text format. +> +> **The implementation**: +> +> - define a new type of citable object, the `CitableBook` +> - implement citation functions for it (the `CitableTrait`) +> - implement comparison using URN logic (the `UrnComparisonTrait`) +> - implement round-trip serialization (the `CexTrait`) + + + + +## Defining the `CitableBook` + +We'll take advantage of Julia's type hierarchy to create an abstract `CitablePublication` type, and make `CitableBook` a subtype of it. We won't create any further subtypes in this guide, but if we wanted to implement a type for some other form of citable publication, we could then share code applicable to any type of publication (that is, any subtype of `CitablePublication`). + +We'll identify the book using the `Isbn10Urn` type we previously defined. Again, we'll keep the example simple, and just include strings for authors and a title. You could elaborate this type however you choose. + +```@example book +abstract type CitablePublication end +struct CitableBook <: CitablePublication + urn::Isbn10Urn + title::AbstractString + authors::AbstractString +end +``` + +As we did with the `Isbn10Urn`, we'll override the `Base` package's `show` function for our new type. + +```@example book +function show(io::IO, book::CitableBook) + print(io, book.authors, ", *", book.title, "* (", book.urn, ")") +end +``` + +We'll also override the `==` function so we can easily compare books for equality. + +```@example book +import Base.== +function ==(book1::CitableBook, book2::CitableBook) + book1.urn == book2.urn && book1.title == book2.title && book1.authors == book2.authors +end +``` + + +We can test these by creating a couple of examples of our new type. + +```@example book +distantbook = CitableBook(distanthorizons, "Distant Horizons: Digital Evidence and Literary Change", "Ted Underwood") +enumerationsbook = CitableBook(enumerations, "Enumerations: Data and Literary Study", "Andrew Piper") +``` + +```@example book +distantbook == enumerationsbook +``` + + + +## Implementing the `CitableTrait` + +The first trait we will implement is the `CitableTrait`, which specifies that citable objects have an identifying URN and a human-readable label. We'll follow the same general pattern we saw when we implemented the `UrnComparisonTrait` for the `Isbn10Urn` type, namely: + +1. define a singleton type to use for the trait value +2. override the function identifying the trait value for our new type. This time the function is named `citabletrait`, and we'll define it to return the concrete value `CitableByIsnb10()` for the type `CitableBook`. + +```@example book +struct CitableByIsbn10 <: CitableTrait end + +import CitableBase: citabletrait +function citabletrait(::Type{CitableBook}) + CitableByIsbn10() +end +``` + +```@example book +citabletrait(typeof(distantbook)) +``` + +`CitableBase` includes the `citable` function to test whether individual objects belong to a type implementing the function. (This is parallel to the `urncomparable` function we saw before.) + +```@example book +citable(distantbook) +``` + +### Implementing the required functions `urn` and `label` + +Implementing `urn` and `label` is now trivial. The `urn` function just returns the `urn` field of the book. In Julia, `Base.show` underlies the `string` function, so since we have already implemented `show` for our book type, we can just return `string(book)` for the `label` function. + + +```@example book +import CitableBase: urn +function urn(book::CitableBook) + book.urn +end + +import CitableBase: label +function label(book::CitableBook) + string(book) +end +``` + +```@example book +urn(distantbook) +``` + + +```@example book +label(distantbook) +``` + +## Implementing the `UrnComparisonTrait` + +We've already seen the `UrnComparisonTrait`. We'll now define it for our book type in exactly the same way we did for our URN type. (We don't even need to re-import its functions.) + +```@example book +struct BookComparable <: UrnComparisonTrait end + +function urncomparisontrait(::Type{CitableBook}) + BookComparable() +end +``` + +```@example book +urncomparisontrait(typeof(distantbook)) +``` + +```@example book +urncomparable(distantbook) +``` + +### Defining the required functions `urnequals`, `urncontains` and `urnsimilar` + +Implementing the URN comparison functions for a pair of `CitableBook`s is nothing more than applying the same URN comparison to the books' URNs. + + +```@example book +function urnequals(bk1::CitableBook, bk2::CitableBook) + bk1.urn == bk2.urn +end +function urncontains(bk1::CitableBook, bk2::CitableBook) + urncontains(bk1.urn, bk2.urn) end -struct MyOwnCite <: Citable - urn::MyOwnUrn - label::AbstractString +function urnsimilar(bk1::CitableBook, bk2::CitableBook) + urnsimilar(bk1.urn, bk2.urn) end -u = MyOwnUrn("urn:fake:id.subid") -citablething = MyOwnCite(u, "Some citable resource") +``` + -# output +Let's test these functions on `CitableBook`s the same way we tested them for URNs. + +```@example book +dupebook = distantbook +urnequals(distantbook, dupebook) +``` -MyOwnCite(MyOwnUrn("urn:fake:id.subid"), "Some citable resource") +```@example book +wrongbook = CitableBook(wrong, "Andrew Piper", "Can We Be Wrong? The Problem of Textual Evidence in a Time of Data") +urnequals(distantbook, wrongbook) ``` +As before, our URNs define "similarity" as belonging to the same language area, so *Distant Horizons* and *Can We Be Wrong?* are similar. + +```@example book +urnsimilar(distantbook, wrongbook) +``` + +But "containment" was defined as code for the same ISBN areas, so *Distant Horizons* does not "contain" *Can We Be Wrong?*. + +```@example book +urncontains(distantbook, wrongbook) +``` + + -## Recognizing the three core traits of the CITE architecture -Because `MyOwnUrn` is a subtype of `Urn`, `MyOwnUrn` objects are assumed to implement the `UrnComparison` trait. +## Implementing the `CexTrait` -```jldoctest citable -urncomparable(u) +Finally, we will implement the `CexTrait`. It requires that we be able to round trip citable content to a plain-text representation in CEX format, and instantiate an equivalent object from the generated CEX. Once again we will: -# output +1. define a singleton type to use for the trait value +2. override the function identifying the trait value for our new type. This time the function is named `cextrait`, and we'll define it to return the concrete value `CitableBook()` for the type `CitableBook`. -true + +```@example book +struct BookCex <: CexTrait end +import CitableBase: cextrait +function cextrait(::Type{CitableBook}) + BookCex() +end +``` + +```@example book +cextrait(typeof(distantbook)) ``` -Because `MyOwnCite` is a subtype of `Citable`, objects of that type are now recognizable as citable objects implementing all three core traits +`CitableBase` includes the `cexserializable` function to test individual objects. + +```@example book +cexserializable(distantbook) +``` + + -```jldoctest citable -citable(citablething) +### Defining the required functions `cex` and `fromcex` -# output +The `cex` function composes a delimited-text representation of an object on a single line, with fields separated by an optionally specified delimiting string. -true +```@example book +import CitableBase: cex +function cex(book::CitableBook; delimiter = "|") + join([string(book.urn), book.title, book.authors], delimiter) +end +``` + +```@example book +cexoutput = cex(distantbook) ``` +The inverse of `cex` is `fromcex`. We need two essential pieces of information to convert a CEX string to an object: the CEX source data, and the *type* of object to instantiate. However, `CitableBase` dispatches this function on the *trait value* of the type want to instantiate. Although we can find that value with the `cextrait` function, it needs to appear in the function signature for dispatch to work. We will therefore implement a function with three mandatory parameters: one for the trait value, and two more for the CEX data and Julia type to create. (Two optional parameters allow you to define the delimiting string value, or create a dictionary with other configuration settings, but we won't need that for our implementation.) + + +```@example book +import CitableBase: fromcex +function fromcex(traitvalue::BookCex, cexsrc::AbstractString, T; + delimiter = "|", configuration = nothing) + fields = split(cexsrc, delimiter) + urn = Isbn10Urn(fields[1]) + CitableBook(urn, fields[2], fields[3]) +end +``` +!!! tip "Example of configuring `fromcex`" -```jldoctest citable -urncomparable(citablething) + The `CitableLibrary` package implements `fromcex` for its `CiteLibrary` class. It uses the `configuration` parameter to map different kinds of content to Julia classes, and create a library that many include many different kinds of citable collections. See its [documentation](https://cite-architecture.github.io/CitableLibrary.jl/stable/). -# output +Note that because `CitableBase` can find our type's trait value on its own, it can delegate to the function we just wrote even when you invoke it only two parameters: all a user needs to specify is the CEX data and Julia type. -true +```@example book +restored = fromcex(cexoutput, CitableBook) ``` +The acid test: did we wind up with an equivalent book? + +```@example book +distantbook == restored +``` + +## Recap: citable objects + +This page first defined the `CitableBook`. Here's what an example looks like: + +```@example book +dump(distantbook) +``` + +We implemented three traits which you can test for with boolean functions. + +```@example book +citable(distantbook) +``` + +```@example book +urncomparable(distantbook) +``` + + +```@example book +cexserializable(distantbook) +``` -```jldoctest citable -cexserializable(citablething) +Those three traits allowed us to identify books by URN, compare books by URN, and round-trip books to and from plain-text representation. -# output +Our initial goal was to manage a reading list of books citable by ISBN number. We could do that directly with, say, a Vector of `CitableBook`s, but the next page shows how we could go a step futher by wrapping a Vector of `CitableBook`s in a type supporting the CITE architecture's definition of a collection with citable content. -true -``` \ No newline at end of file diff --git a/docs/src/collections.md b/docs/src/collections.md new file mode 100644 index 0000000..d9e4077 --- /dev/null +++ b/docs/src/collections.md @@ -0,0 +1,418 @@ +```@setup collections +#page 1 +using CitableBase +struct Isbn10Urn <: Urn + isbn::AbstractString +end + +import Base: show +function show(io::IO, u::Isbn10Urn) + print(io, u.isbn) +end + +struct IsbnComparable <: UrnComparisonTrait end +import CitableBase: urncomparisontrait +function urncomparisontrait(::Type{Isbn10Urn}) + IsbnComparable() +end + +import CitableBase: urnequals +function urnequals(u1::Isbn10Urn, u2::Isbn10Urn) + u1 == u2 +end + +import CitableBase: urncontains +function urncontains(u1::Isbn10Urn, u2::Isbn10Urn) + initial1 = components(u1.isbn)[3][1] + initial2 = components(u2.isbn)[3][1] + initial1 == initial2 +end + +function english(urn::Isbn10Urn) + langarea = components(urn.isbn)[3][1] + langarea == '0' || langarea == '1' +end + +import CitableBase: urnsimilar +function urnsimilar(u1::Isbn10Urn, u2::Isbn10Urn) + initial1 = components(u1.isbn)[3][1] + initial2 = components(u2.isbn)[3][1] + + (english(u1) && english(u2)) || initial1 == initial2 +end + + + +distanthorizons = Isbn10Urn("urn:isbn10:022661283X") +enumerations = Isbn10Urn("urn:isbn10:022656875X") +wrong = Isbn10Urn("urn:isbn10:1108922036") + +#page2 +abstract type CitablePublication end +struct CitableBook <: CitablePublication + urn::Isbn10Urn + title::AbstractString + authors::AbstractString +end + +function show(io::IO, book::CitableBook) + print(io, book.authors, ", *", book.title, "* (", book.urn, ")") +end + +import Base.== +function ==(book1::CitableBook, book2::CitableBook) + book1.urn == book2.urn && book1.title == book2.title && book1.authors == book2.authors +end + +struct CitableByIsbn10 <: CitableTrait end +import CitableBase: citabletrait +function citabletrait(::Type{CitableBook}) + CitableByIsbn10() +end + +import CitableBase: urn +function urn(book::CitableBook) + book.urn +end + +import CitableBase: label +function label(book::CitableBook) + string(book) +end + + +struct BookComparable <: UrnComparisonTrait end +function urncomparisontrait(::Type{CitableBook}) + BookComparable() +end + +function urnequals(bk1::CitableBook, bk2::CitableBook) + bk1.urn == bk2.urn +end +function urncontains(bk1::CitableBook, bk2::CitableBook) + urncontains(bk1.urn, bk2.urn) +end +function urnsimilar(bk1::CitableBook, bk2::CitableBook) + urnsimilar(bk1.urn, bk2.urn) +end + +struct BookCex <: CexTrait end +import CitableBase: cextrait +function cextrait(::Type{CitableBook}) + BookCex() +end + +import CitableBase: cex +"Implement for CitableBook" +function cex(book::CitableBook; delimiter = "|") + join([string(book.urn), book.title, book.authors], delimiter) +end + +import CitableBase: fromcex +function fromcex(traitvalue::BookCex, cexsrc::AbstractString, T; + delimiter = "|", configuration = nothing) + fields = split(cexsrc, delimiter) + urn = Isbn10Urn(fields[1]) + CitableBook(urn, fields[2], fields[3]) +end + + +qi = Isbn10Urn("urn:isbn10:3030234133") + +distantbook = CitableBook(distanthorizons, "Distant Horizons: Digital Evidence and Literary Change", "Ted Underwood") +enumerationsbook = CitableBook(enumerations, "Enumerations: Data and Literary Study", "Andrew Piper") +wrongbook = CitableBook(wrong, "Can We Be Wrong? The Problem of Textual Evidence in a Time of Data", "Andrew Piper") +qibook = CitableBook(qi, "Quantitative Intertextuality: Analyzing the Markers of Information Reuse","Christopher W. Forstall and Walter J. Scheirer") + +``` + + + +# Citable collections + +> ## Summary +> +> **The task**: We want to create a type for working with a *collection* of the citable books we developed on the previous page. +> We should be able to filter the collection by appying URN logic to the identifiers for our books. We should be able to write our collection to plain-text format and re-instantiate it from the plain-text representation. And we should be able to apply any Julia functions for working with iterable content to our book list. +> +> **The implementation**: +> - define a new type for a collection of citable books, the `ReadingList` type +> - identify it as a citable collection (the `CitableCollectionTrait`) +> - implement filtering the collection using URN logic (the `UrnComparisonTrait`) +> - implement round-trip serialization (the `CexTrait`) +> - make the collection available to all Julia functions working with iterable content (`Iterators`) +> + + + +## Defining the `ReadingList` + +Our model for a reading list is simple: it's just a Vector of citable publications. We'll annotate our vector as containing subtypes of the abstract `CitablePublication` we previously defined, even though in this example we'll only use our one concrete implementation, the `CitableBook`. As with our other custom types, we'll override `Base.show`. + + +```@example collections +struct ReadingList + publications::Vector{<: CitablePublication} +end +``` + +```@example collections +function show(io::IO, readingList::ReadingList) + print(io, "ReadingList with ", length(readingList.publications), " items") +end +``` + +Let's see an example. + +```@example collections +books = [distantbook, enumerationsbook, wrongbook, qibook] +rl = ReadingList(books) +``` + +The `publications` field is just a normal Julia Vector. + + +```@example collections +rl.publications[4] +``` + +What will make it different from other Vectors is that it will support a series of CITE traits + + +## Implementing the `CitableCollectionTrait` + +We first want to identify our new type as fufilling the requirements of a citable collection with the `CitableCollectionTrait`. We'll repeat the pattern: + +1. define a singleton type for the trait value. +2. override the function identifying the trait value for our new type. Here the function is named `citablecollectiontrait`, and we'll define it to return the concrete value `CitableReadingList` for the tyupe `ReadingList`. + + +```@example collections +struct CitableReadingList <: CitableCollectionTrait end + +import CitableBase: citablecollectiontrait +function citablecollectiontrait(::Type{ReadingList}) + CitableReadingList() +end +``` + +```@example collections +citablecollectiontrait(typeof(rl)) +``` +Use the `citablecollection` function to test if a specific object is a citable collection. + +```@example collections +citablecollection(rl) +``` + +The promise we now need to fulfill is that our collection will implement three further traits for URN comparison, serialization and iteration. + +## Implementing the `UrnComparisonTrait` + +We have previously implemented the `UrnComparisonTrait` for an identifer type (the `Isbn10Urn`) and for a citable object type (the `CitableBook`). In both of those cases, we compared two objects of the same type, and returned a boolean result of comparing them on URN logic. + +For our citable collection, we will implement the same suite of functions, but with a different signature and result type. This time, our first parameter will be a URN which we will use to *filter* the collection given in the second parameter. The result will be a (possibly empty) list of content in our citable collection -- in this example, a list of `CitableBook`s. + +We mark our `ReadingList` type as urn-comparable exactly as we did for `Isbn10Urn`s and `CitableBook`s. + +```@example collections +struct ReadingListComparable <: UrnComparisonTrait end +function urncomparisontrait(::Type{ReadingList}) + ReadingListComparable() +end +``` + +```@example collections +urncomparable(rl) +``` + + +### Implementing the required functions `urnequals`, `urncontains` and `urnsimilar` + +To implement the required functions, we'll just lean on the work we've already done: we'll use the boolean version of those functions to filter our collections. + +```@example collections +function urnequals(urn::Isbn10Urn, reading::ReadingList, ) + filter(item -> urnequals(item.urn, urn), reading.publications) +end + +function urncontains(urn::Isbn10Urn, reading::ReadingList) + filter(item -> urncontains(item.urn, urn), reading.publications) +end + +function urnsimilar(urn::Isbn10Urn, reading::ReadingList) + filter(item -> urnsimilar(item.urn, urn), reading.publications) +end +``` + +If your collection does not allow duplicate identifiers, `urnequals` should return a list of 0 or 1 item. + + +```@example collections +urnequals(distanthorizons, rl) +``` + +Three of the books in our list are published in the English-language zone, and therefore will satisfy `urnsimilar` when compared to *Distant Horizons*. + +```@example collections +urnsimilar(distanthorizons, rl) +``` + + +But only two are published in the same ISBN area code as *Distant Horizons*: + +```@example collections +urncontains(distanthorizons, rl) +``` + + +## Implementing the `CexTrait` + +As we did with citable objects, we want to ensure that we can round-trip an entire collection to and from delimited-text format. We'll make our new `ReadingList` type implement `CexTrait` in the same way as `CitableBook`. + + +```@example collections +struct ReadingListCex <: CexTrait end +function cextrait(::Type{ReadingList}) + ReadingListCex() +end +``` + +```@example collections +cexserializable(rl) +``` + + +### Implementing the required functions `cex` and `fromcex` + +We will serialize our collection with a header line identifying it as `citecollection` block, followed by one line for each book in our list. We can format the books' data by mapping each book to an invocation the `cex` that we previously wrote for `CitableBook`s. + +```@example collections +function cex(reading::ReadingList; delimiter = "|") + header = "#!citecollection\n" + strings = map(ref -> cex(ref, delimiter=delimiter), reading.publications) + header * join(strings, "\n") +end +``` + + +```@example collections +cexoutput = cex(rl) +println(cexoutput) +``` + +Recall from our experience implementing CEX serialization for `CitableBook`s that we will need to expose three mandatory parameters for `fromcex`: the trait value, the CEX data and the Julia type we want to instantiate. + + +```@example collections +function fromcex(trait::ReadingListCex, cexsrc::AbstractString, T; + delimiter = "|", configuration = nothing) + + lines = split(cexsrc, "\n") + isbns = CitableBook[] + inblock = false + for ln in lines + if ln == "#!citecollection" + inblock = true + elseif inblock + bk = fromcex(ln, CitableBook) + push!(isbns, bk) + end + end + ReadingList(isbns) +end +``` + +Once again, we can now invoke `fromcex` with just the parameters for the CEX data and desired Julia type to create, and `CitableBase` will find our implementation. + +```@example collections +fromcex(cexoutput, ReadingList) +``` + +## Implementing required and optional frnctions from `Base.Iterators` + +The `Iterators` module in Julia `Base` was one of the first traits or interfaces in Julia. It allows you to apply the same functions to many types of iterable collections. We need to import the `Base.iterate` function, and implement two versions of it for our new type: one with a single parameter for the collection, and one with a second parameter maintaining some kind of state information. Both of them have the same return type: either `nothing`, or a Tuple pairing one item in the collection with state information. + +Since our reading list is keeping books in a Vector internally, we can use the state parameter to pass along an index into the Vector. In the version of `iterate` with no parameters, we'll return the first item in the list, and set the "state" value to 2. In the two-parameter version, we'll return the item indexed by the state count, and bump the count up one. + + +```@example collections +import Base: iterate + +function iterate(rlist::ReadingList) + isempty(rlist.publications) ? nothing : (rlist.publications[1], 2) +end + +function iterate(rlist::ReadingList, state) + state > length(rlist.publications) ? nothing : (rlist.publications[state], state + 1) +end +``` + + +It is also useful (and trivial) to implement the optional methods for the length and base type of the collection. + +```@example collections +import Base: length +function length(readingList::ReadingList) + length(readingList.publications) +end + + +import Base: eltype +function eltype(readingList::ReadingList) + CitablePublication +end +``` + +```@example collections +length(rl) +``` + +```@example collections +eltype(rl) +``` + +Now our `ReadingList` type is usable with all the richness of [the Julia interface for iterators](https://docs.julialang.org/en/v1/base/collections/#lib-collections-iteration). Just a few examples: + +- `for` loops + +```@example collections +for item in rl + println(item) +end +``` + +- checking for presence of an item + +```@example collections +distantbook in rl +``` + + +- collect contents without having to know anything about the internal structure of the type + +```@example collections +collect(rl) +``` + +## Recap: citable collections + +On this page, we wrapped a citable collection type, te `ReadingList` around a Vector of `CitableBook`s. We made the type identifiable as a citable collection. We implemented filter of the collection on URN logic with the `UrnComparisonTrait`, and serialization with the `CexSerializableTrait`. You can test these for these traits with boolean functions. + + +```@example collections +citablecollection(rl) +``` + + + +```@example collections +urncomparable(rl) +``` + + +```@example collections +cexserializable(rl) +``` + +In addition, we made the `ReadingList` implement Julia's `Iterators` behavior. \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 056d8cb..8087c3a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,20 +1,41 @@ # CitableBase -The CitableBase module defines two core abstractions of the CITE architecture: +This package defines traits and abstract types for the essential concepts of the CITE architecture. -1. *identifiers* expressible using the syntax of the IETF URN specification (the `Urn` abstract type) -2. *citable entities*, identified by URN (the `Citable` abstract type) +## Essential concepts -Three Julia traits define the essential semantics of these types. +### Behaviors of citable resources -1. the `CitableTrait` requires that citable entities (subtypes of `Citable`) be identified by a URN and have a human-readable label -2. the `UrnComparisonTrait` requires that identifiers (subtypes of `Urn`) *and* citable entities be comparable based on URN *equality*, *containment* and *similarity* -3. the `CexSerializable` treat requires that citable entities be losslessly serialized to plain-text representation in CEX format and instantiated from the same plain-text representation +The CITE architecture can be described by four kinds of behavior, corresponding in Julia to four traits or interfaces. -The following pages: -1. illustrate how to implement the `Urn` type and its `UrnComparisonTrait`, the three traits of the `Citable` type, namely, the `CitableTrait`, the `UrnComparisonTrait` and the `CexTrait` -2. list examples of implementations -3. document public functions and types of the `CitableBase` module +1. *identification*. Scholarly resources are identified using the syntax of the [IETF URN specification](https://www.ietf.org/rfc/rfc2141.txt), and have a human-readable label. This is expressed by implementing the `CitableTrait`. +2. *comparison*. Citable resources can be compared using the URN logic of *equality*, *containment* and *similarity*. This is expressed by implementing the `UrnComparisonTrait`. +3. *serialization*. Citable resources can be losslessly serialized to plain-text representation in CEX format and instantiated from the same plain-text representation. This is expressed by implementing the `CexTrait`. +4. *iteration*. Collections of citable content can be processed sequentially. This is expressed by implementing the iterators interface from Julia's `Iterators` module. + + + +### Abstractions of essential types + +Using these building blocks, the `CitableBase` further defines three core abstractions: + +1. an *identifier* uniquely identifies scholarly resources using the syntax of the IETF URN specification. This is represented by the `Urn` abstract type, and requires implementing the `UrnComparisonTrait`. +2. a *citable entity* is a discrete object identified by a URN. This is represented by the `Citable` abstract type, and requires implementing the `CitableTrait`, `UrnComparisonTrait`, and `CexTrait`. +3. a *citable collection* is a collection of content identifiable by URN. Unlike identifiers and citable entities, they do not fall within a single type hierarchy, and are not represented by subtyping an abstract type. Instead, they are identified by the `CitableCollectionTrait`, and implement the `UrnComparisonTrait`, `CexTrait` and `Iterators`. + +!!! tip "An illustration: the CitableCorpus package" + + Some citable collections might additionally implement the `CitableTrait`, in effect making them simultaneously a discrete citable obect (the collection as a whole), and a collection with citable content. The `CitableCorpus` package illustrates these distinctions handily. Its `CitablePassage` is a citable object representing a single passage of text. The `CitableDocument` is both a citable object with its own URN and label, and a collection of citable passages. The `CitableCorpus` is a pure citable collection of citable documents and citable passages, but does not have its own distinct identifier and label: it is purely a container type. + + + +## Contents of this user's guide + +It is perfectly possible to use packages implementing the abstractions of `CitableBase` without understanding how `CitableBase` is designed. This user's guide is for anyone who needs to build their own custom implementations or simply wishes to understand how these abstractions can be implemented. + +The guide works through a hypothetical example to design a reading list of books citable by URN values. The guide first illustrates how to implement a custom URN type for ISBN-10 numbers. It then creates a custom citable object for books cited by ISBN-10 numbers, and finally defines a custom citable collection representing a reading list. + +Following the user's guide, the documentation includes the formal API documentation for the exported functions and types of the `CitableBase` package. diff --git a/docs/src/urns.md b/docs/src/urns.md index 0539d0e..d90f86c 100644 --- a/docs/src/urns.md +++ b/docs/src/urns.md @@ -1,146 +1,221 @@ -# URNs: an example implementation +# Identification with URNs -The `Urn` abstract type models a Uniform Resource Name (URN). URNs have a string value with a specified syntax. Here's a minimal example subtyping the `Urn` abstraction. +> ## Summary +> +> **The task**: ISBN numbers uniquely identify published editions of a book. We want to create a type representing a 10-digit ISBN number, and be able to compare ISBN numbers using URN logic. +> +> **The implementation**: +> +> - define a new URN type representing an ISBN-10 number +> - implement the `UrnComparisonTrait` for the new type +> -```jldoctest urns + + + + +## Defining the `Isbn10Urn` type + +The `Urn` abstract type models a Uniform Resource Name (URN). We'll follow the requirements of the URN standard to create a URN type for ISBN-10 numbers. Its URN strings will have three colon-delimited components, beginning with the required prefix `urn`, then a URN type we'll call `isbn10`, followed by a 10-digit ISBN number. For example, the URN for *Distant Horizons* by Ted Underwood will be `urn:isbn10:022661283X`. (Yes, the last "digit" of an ISBN number can be `X`.) + +We will make the new type a subtype of `Urn`, so that we can use it freely with other packages that recognize URNs. + + +```@example urns using CitableBase -struct FakeUrn <: Urn - urn::AbstractString +struct Isbn10Urn <: Urn + isbn::AbstractString end -fake = FakeUrn("urn:fake:objectclass.objectid") -typeof(fake) |> supertype +``` -# output -Urn -``` -Because it is a subtype of `Urn`, our new type is recognized as comparable on URN logic. +!!! warning "Note on the ISBN format and our `Isbn10Urn` type" -```jldoctest urns -urncomparable(fake) + There is in fact a URN namespace for ISBN numbers identifeid by the `isbn` namespace identifier. (See this [blogpost about citing publications with URNs](https://www.benmeadowcroft.com/webdev/articles/urns-and-citations/).) This guide invents an `isbn10` URN type solely to illustrate how you could create your own URN type using the `CitableBase` package. -# output + Parsing the full [ISBN-10 format](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is extremely complicated: ISBN-10 numbers have four components, each of which is variable in length! In this user's guide example, we'll restrict ourselves to ISBNs for books published in English-, French- or German-speaking countries, indicated by an initial digit of `0` or `1` (English), `2` (French) or `3` (German). In a real program, we would enforce this in the constructor, but to keep our example brief and focused on the `CitableBase` class, we blindly accept any string value for the `isbn` field of our type. -true + +Our new type is a subtype of `Urn`. +```@example urns +supertype(Isbn10Urn) ``` +As often in Julia, we'll override the default `show` method for our type. (Note that in Julia this requires *importing* the specific method, not just using the package.) + + +```@example urns +import Base: show +function show(io::IO, u::Isbn10Urn) + print(io, u.isbn) +end +``` +Now when we create objects of our new type, the display in our REPL (or other contexts) will be easily recognizable as an `Isbn10Urn`. +```@example urns +distanthorizons = Isbn10Urn("urn:isbn10:022661283X") +``` -## URN comparison -We need to implement three functions for our new URN type: `urnequals`, `urncontains` and `urnsimilar`. +## Defining the `UrnComparisonTrait` -!!! note +Subtypes of `Urn` are required to implement the `UrnComparisonTrait`, and its three functions. `CitableBase` uses the "Holy trait trick" to dispatch functions implementing URN comparison. - In addition to URN values, these three functions can be implemented for other types of objects. See the following pages for an example of how they are applied to citable objects (subtypes of `Citable`); for their use with collections of citable content, see the documentation for the [CitableLibary package](https://cite-architecture.github.io/CitableLibrary.jl/stable/). +!!! tip "The Tim Holy Trait Trick" - + See [this post on julia bloggers](https://www.juliabloggers.com/the-emergent-features-of-julialang-part-ii-traits/) for an introduction to the "Tim Holy Trait Trick" (THTT). . -The `==` function of Julia Base is overridden in `CitableBase` for all subtypes of `Urn`. This in turn serves as an implementation of `urnequals` for subtypes of `Urn`. +We first define a subtype of the abstract `UrnComparisonTrait`. It's a singleton type with no fields which we'll use as the trait value for our ISBN type. `CitableBase` provides the `urncomparisontrait` function to determine if a class implements the `UrnComparisonTrait` so we'll import `urncomparisontrait`, and define a function returning a concrete value of `IsbnComparable()` for the type `Isbn10Urn`. +```@example urns +struct IsbnComparable <: UrnComparisonTrait end +import CitableBase: urncomparisontrait +function urncomparisontrait(::Type{Isbn10Urn}) + IsbnComparable() +end +``` -!!! warning +Let's test it. - Note that in order to compare two URNs for equality, you'll need to import or use `CitableBase` (as in the block above). +```@example urns +urncomparisontrait(typeof(distanthorizons)) +``` -```jldoctest urns -FakeUrn("urn:fake:demo1") == FakeUrn("urn:fake:demo1") +This lets us use `CitableBase`s boolean function `urncomparable` to test specific objects. -# output -true +```@example urns +urncomparable(distanthorizons) ``` -To implement `urncontains` and `urnsimilar`, first import the method from `CitableBase`; then, implement the function with parameters for your new type. -For this artificial example, we'll define one URN as "containing" another if they both belong to the URN type "urn:fake:". We'll use a good generic definition for URN similarity: two URNs are similar if one contains the other or if both are equal. +## Implementing the logic of URN comparison -```jldoctest urns -import CitableBase: urncontains -function urncontains(u1::FakeUrn, u2::FakeUrn) - startswith(u1.urn, "urn:fake:") && startswith(u2.urn, "urn:fake:") -end +To fulfill the contract of the `UrnComparisonTrait`, we must implement three boolean functions for three kinds of URN comparison: `urnequals` (for *equality*), `urncontains` (for *containment*) and and `urnsimilar` (for *similarity*). Because we have defined our type as implementing the `UrnComparisonTrait`, `CitableBase` can dispatch to functions including an `Isbn10Urn` as the first parameter. -import CitableBase: urnsimilar -function urnsimilar(u1::FakeUrn, u2::FakeUrn) - urncontains(u1, u2) || urnequals(u1, u2) +### Equality + + + +The `==` function of Julia Base is overridden in `CitableBase` for all subtypes of `Urn`. This makes it trivial to implement `urnequals` once we use `CitableBase` and import `urnequals`. + + +```@example urns +import CitableBase: urnequals +function urnequals(u1::Isbn10Urn, u2::Isbn10Urn) + u1 == u2 end +``` + +```@example urns +dupe = distanthorizons +urnequals(distanthorizons, dupe) +``` + +```@example urns +enumerations = Isbn10Urn("urn:isbn10:022656875X") +urnequals(distanthorizons, enumerations) +``` + + + +!!! tip "Why do we need 'urnequals' when we already have '==' ?" + + Our implementation of `urnequals` uses two parameters of the same type to compare two URNs and produce a boolean result. In the following section, we will implement the functions of `UrnComparisonTrait` with one URN parameter and one parameter giving a citable collection. In those implementations, we can filter the collection by comparing the URN parameter to the URNs of items in the collection. We will reserve `==` for comparing the contents of two collections, and use `urnequals` to filter a collection's content. + + + +### Containment -urnsimilar(FakeUrn("urn:fake:demo1"), FakeUrn("urn:fake:demo2")) +For our ISBN type, we'll define "containment" as true when two ISBNS belong to the same initial-digit group (`0` - `4`). We'll use the `components` functions from `CitableBase` to extract the third part of each URN string, and compare their first characters. -# output +```@example urns +import CitableBase: urncontains +function urncontains(u1::Isbn10Urn, u2::Isbn10Urn) + initial1 = components(u1.isbn)[3][1] + initial2 = components(u2.isbn)[3][1] -true + initial1 == initial2 +end ``` -```jldoctest urns +Both *Distant Horizons* and *Enumerations* are in ISBN group 0. -urncontains(FakeUrn("urn:fake:demo1"), FakeUrn("urn:fake:demo2")) +```@example urns +urncontains(distanthorizons, enumerations) +``` -# output +But *Can We Be Wrong?* is in ISBN group 1. -true +```@example urns +wrong = Isbn10Urn("urn:isbn10:1108922036") +urncontains(distanthorizons, wrong) ``` -## URN manipulation -Subtypes of `Urn` should also override the Base definition of `print`. This makes it possible to use the generic `components` and `parts` functions in `CitableBase`. +### Similarity + +We'll define "similarity" as belonging to the same language area. In this definition, both `0` and `1` indicate English-language countries. -```jldoctest urns -import Base: print -function print(io::IO, u::FakeUrn) - print(io, u.urn) + +```@example urns +# True if ISBN starts with `0` or `1` +function english(urn::Isbn10Urn) + langarea = components(urn.isbn)[3][1] + langarea == '0' || langarea == '1' end -print(fake) -# output +import CitableBase: urnsimilar +function urnsimilar(u1::Isbn10Urn, u2::Isbn10Urn) + initial1 = components(u1.isbn)[3][1] + initial2 = components(u2.isbn)[3][1] -urn:fake:objectclass.objectid + (english(u1) && english(u2)) || initial1 == initial2 +end ``` -Top-level syntactic units are separated by colons: `CitableBase` refers to these units as *components*. +Both *Distant Horizons* and *Can We Be Wrong?* are published in English-language areas. +```@example urns +urnsimilar(distanthorizons, wrong) +``` -```jldoctest urns -components(fake) - -# output +But they are coded for different ISBN areas. -3-element Vector{SubString{String}}: - "urn" - "fake" - "objectclass.objectid" +```@example urns +wrong = Isbn10Urn("urn:isbn10:1108922036") +urncontains(distanthorizons, wrong) ``` -At a second syntactic level, units are separated by periods. `CitableBase` refers to these as *parts* of a component. -```jldoctest urns -components(fake)[3] |> parts -# output -2-element Vector{SubString{String}}: - "objectclass" - "objectid" +## Recap: identifiers + + +On this page, we defined the `Isnb10Urn` type as a subtype of `Urn` and identified our type as implementing the `UrnComparisonTrait`. You can test this with `urncomparable`s. + +```@example urns +urncomparable(distanthorizons) ``` +We implemented the trait's required functions to compare pairs of URNs based on URN logic for equality, similarity and containment, and return boolean values. + +The next page will make use of our URN type to define a citable object identified by `Isbn10Urn`. -Implementations of the `URN` interface should dispatch the following two methods to type-specific functions: -- `dropversion(u::Urn)` -- `addversion(u::Urn, versionId)` diff --git a/docs/src/cex.md b/olddocs/cex.md similarity index 99% rename from docs/src/cex.md rename to olddocs/cex.md index f4cf794..293396e 100644 --- a/docs/src/cex.md +++ b/olddocs/cex.md @@ -59,5 +59,3 @@ s = "urn:fake:id.subid|Some citable resource" fromcex(s, MyOwnCite) ``` - -??? \ No newline at end of file diff --git a/docs/src/citabletrait.md b/olddocs/citabletrait.md similarity index 100% rename from docs/src/citabletrait.md rename to olddocs/citabletrait.md diff --git a/docs/src/implementations.md b/olddocs/implementations.md similarity index 100% rename from docs/src/implementations.md rename to olddocs/implementations.md diff --git a/docs/src/urncomparison.md b/olddocs/urncomparison.md similarity index 93% rename from docs/src/urncomparison.md rename to olddocs/urncomparison.md index f2163a1..5f8739e 100644 --- a/docs/src/urncomparison.md +++ b/olddocs/urncomparison.md @@ -8,7 +8,7 @@ In this example, we'll create a type that is *not* a subtype of `Citable`. We i ## Comparing individual objects -```jldoctest citable +``` using CitableBase import CitableBase: UrnComparisonTrait struct UrnThing @@ -23,7 +23,7 @@ UrnComparisonTrait The `UrnComparisonTrait` requires us to implement three functions, `urnequals`, `urncontains` and `urnsimilar`. For this example, we'll just say that any pair of `UrnThing`s starting with the `fake` class contain each other and are similar. -```jldoctest citable +``` import CitableBase: urnequals function urnequals(u1::UrnThing, u2::UrnThing) u1.urn == u2.urn @@ -48,7 +48,7 @@ urnsimilar (generic function with 2 methods) Let's try it out. -```jldoctest citable +``` thing1 = UrnThing("urn:fake:id.subid") thing2 = UrnThing("urn:fake:id2") thing3 = UrnThing("urn:notevenfake:id") @@ -59,7 +59,7 @@ urnsimilar(thing1, thing2) true ``` -```jldoctest citable +``` urnsimilar(thing1, thing3) # output @@ -67,7 +67,7 @@ urnsimilar(thing1, thing3) false ``` -```jldoctest citable +``` urncontains(thing1, thing2) # output @@ -75,7 +75,7 @@ urncontains(thing1, thing2) true ``` -```jldoctest citable +``` urnequals(thing1,thing2) # output @@ -90,7 +90,7 @@ We're not limited to implementing the `UrnComparable` trait for individual obje Because it is *not* a subtype of `Citable`, we again explicitly define its trait value as `UrnComparable()`. -```jldoctest citable +``` struct UrnThingList arr::Vector{UrnThing} end @@ -103,7 +103,7 @@ UrnComparisonTrait We can verify that objets of our new type are now recognized as `urncomparable`. -```jldoctest citable +``` ulist = UrnThingList([thing1, thing2, thing3]) urncomparable(ulist) @@ -121,7 +121,7 @@ using URN logic. The functions will return a (possibly empty) list of `UrnThing` This is the same semantics as in the [CitableLibary package](https://cite-architecture.github.io/CitableLibrary.jl/stable/) where the `UrnComparisonTrait` is used to filter citable collections. -```jldoctest citable +``` function urnequals(urnlist::UrnThingList, uthing::UrnThing) filter(u -> urnequals(uthing, u), urnlist.arr) end @@ -141,7 +141,7 @@ urnsimilar (generic function with 3 methods) ``` -```jldoctest citable +``` urnsimilar(ulist, thing1) @@ -152,7 +152,7 @@ urnsimilar(ulist, thing1) UrnThing("urn:fake:id2") ``` -```jldoctest citable +``` urnequals(ulist, thing1) # output diff --git a/src/CitableBase.jl b/src/CitableBase.jl index 85a2453..53820d4 100644 --- a/src/CitableBase.jl +++ b/src/CitableBase.jl @@ -3,30 +3,38 @@ module CitableBase using Documenter, DocStringExtensions import Base: == -# Urn and its required functions: +# Urn and its functions export Urn export dropversion, addversion export == # Concrete implementations: export components, parts +# Citable object abstract type +export Citable + # The three key traits of citable content, # together with their required functions: -export Citable, CitableTrait -export CitableByCtsUrn, CitableByCite2Urn, NotCitable, citable +export UrnComparisonTrait, NotUrnComparable +export urncomparisontrait, urncomparable +export urnsimilar, urncontains, urnequals + +export CitableTrait, NotCitable +export citabletrait, citable export urn, label -export CexTrait, CexSerializable, NotCexSerializable, cexserializable +export CexTrait, NotCexSerializable +export cextrait, cexserializable export cex, fromcex -export UrnComparisonTrait, UrnComparable, NotUrnComparable, urncomparable -export urnsimilar, urncontains, urnequals - +# The citable collection trait +export CitableCollectionTrait, NotCitableCollection +export citablecollectiontrait, citablecollection include("citable.jl") include("urns.jl") include("urncomparison.jl") include("cex.jl") - +include("collectiontrait.jl") end # module diff --git a/src/cex.jl b/src/cex.jl index f7d3c14..752eff2 100644 --- a/src/cex.jl +++ b/src/cex.jl @@ -7,43 +7,59 @@ struct CexSerializable <: CexTrait end """Value for the CexTrait for content not serializable to CEX format.""" struct NotCexSerializable <: CexTrait end -"""The default value of `CitableTrait` is `NotCexSerializable`.""" -CexTrait(::Type) = NotCexSerializable() +"""The default value of `CexTrait` is `NotCexSerializable`. -"""Subtypes of `Citable` are `CexSerializable`.""" -CexTrait(::Type{<:Citable}) = CexSerializable() +$(SIGNATURES) +""" +function cextrait(::Type) + NotCexSerializable() +end +"""Subtypes of `Citable` are `CexSerializable`. +$(SIGNATURES) +""" +function cextrait(::Type{<:Citable}) + CexSerializable() +end + +"""True if type `T` is serializable to CEX format +$(SIGNATURES) +""" function cexserializable(x::T) where {T} - CexTrait(T) != NotCexSerializable() + #@warn("x/T/trait", x, T, cextrait(T)) + cextrait(T) != NotCexSerializable() end #= Define delegation for the 2 functions of the CexTrait: - - cex - fromcex =# """Delegate `cex` to specific functions based on -type's citable trait value. +type's `cextrait` value. $(SIGNATURES) """ function cex(x::T; delimiter = "|") where {T} - cex(CexTrait(T), x; delimiter = delimiter) + cex(cextrait(T), x; delimiter = delimiter) end -"""Delegate `fromcex` to specific functions based on -type's citable trait value. + +"""Instantiate an object of type `T` from CEX-formatted data `cexsrc` by dispatching based on `cextrait` of `T`. $(SIGNATURES) -""" -function fromcex(s::AbstractString, T::Type{<: DataType}; delimiter = "|") - fromcex(CexTrait(T), s, T; delimiter = delimiter) +""" +function fromcex(cexsrc::AbstractString, T; + delimiter = "|", configuration = nothing) + fromcex(cextrait(T), cexsrc, T, + delimiter = delimiter, configuration = configuration) end + + # Catch attempts to use these functions on NotCexSerializable: """It is an error to invoke the `cex` function on material that is not CEX serializable. @@ -58,6 +74,16 @@ end $(SIGNATURES) """ -function fromcex(::NotCexSerializable, cex, T::Type{<: DataType}; delimiter) +function fromcex(::NotCexSerializable, cex, T::Type{<: Any}; delimiter , configuration) throw(DomainError(T, "$(T) is not a CexSerializable type.")) end + +"""It is an error invoke `fromcex` function with an +unimplemented trait value. + +$(SIGNATURES) +""" +function fromcex(traitvalue::TraitType, cexsrc::AbstractString, T; + delimiter, configuration) where {TraitType <: CexTrait} + throw(DomainError(traitvalue, "`fromcex` is not implemented for trait $(traitvalue) on type $(T).")) +end diff --git a/src/citable.jl b/src/citable.jl index 20d9d24..ce522d7 100644 --- a/src/citable.jl +++ b/src/citable.jl @@ -4,20 +4,17 @@ abstract type Citable end """Abstraction of values for a citable trait.""" abstract type CitableTrait end - """Value for the CitableTrait for everything not citable.""" struct NotCitable <: CitableTrait end -"""Define default value of CitableTrait as NotCitable.""" -CitableTrait(::Type) = NotCitable() -"""Value for the CitableTrait for all subtypes of `Citable`.""" -struct CitableObject <: CitableTrait end -"""Define value of CitableTrait for subtypes of `Citable`.""" -CitableTrait(::Type{<:Citable}) = CitableObject() +"""Define default value of CitableTrait as NotCitable.""" +function citabletrait(::Type) + NotCitable() +end """True if `x` is a citable object.""" function citable(x) - CitableTrait(typeof(x)) != NotCitable() + citabletrait(typeof(x)) != NotCitable() end #= @@ -26,14 +23,13 @@ Define delegation for the 2 functions of the CitableTrait: 2. label =# - """Delegate `urn` to specific functions based on type's citable trait value. $(SIGNATURES) """ function urn(x::T) where {T} - urn(CitableTrait(T), x) + urn(citabletrait(T), x) end """Delegate `label` to specific functions based on @@ -42,7 +38,7 @@ type's citable trait value. $(SIGNATURES) """ function label(x::T) where {T} - label(CitableTrait(T), x) + label(citabletrait(T), x) end # Catch attempts to use these functions on NotCitable: diff --git a/src/collectiontrait.jl b/src/collectiontrait.jl new file mode 100644 index 0000000..d260725 --- /dev/null +++ b/src/collectiontrait.jl @@ -0,0 +1,20 @@ +"""Abstraction of values for a citable library collection trait.""" +abstract type CitableCollectionTrait end + +"""Value for the CitableCollectionTrait for evertything that is not a citable library collection.""" +struct NotCitableCollection <: CitableCollectionTrait end + +"""Define default value of CitableCollectionTrait as NotCitableCollection.""" +function citablecollectiontrait(::Type) + NotCitableCollection() +end + + +"""True if `x` has the value `CitableCollection` for the `CitableCollectionTrait`. + +$(SIGNATURES) +""" +function citablecollection(x::T) where {T} + citablecollectiontrait(T) != NotCitableCollection() +end + diff --git a/src/urncomparison.jl b/src/urncomparison.jl index 975b59a..a1ea2c4 100644 --- a/src/urncomparison.jl +++ b/src/urncomparison.jl @@ -1,50 +1,40 @@ "Abstraction of values for URN manipulation." abstract type UrnComparisonTrait end -"""Value of the UrnComparisonTrait for evertything that can be compared using URN logic.""" -struct UrnComparable <: UrnComparisonTrait end - """Value of the UrnComparisonTrait for evertything that can NOT be compared using URN logic.""" struct NotUrnComparable <: UrnComparisonTrait end - """Default value of `UrnComparisonTrait` is `NotUrnComparable`.""" -UrnComparisonTrait(::Type) = NotUrnComparable() - -"""All subtypes of `Urn` are URN comparable.""" -UrnComparisonTrait(::Type{<:Urn}) = UrnComparable() - -"""All subtypes of `Citable` are URN comparable.""" -UrnComparisonTrait(::Type{<:Citable}) = UrnComparable() - +function urncomparisontrait(::Type) + NotUrnComparable() +end # Delegate functions based on trait value. """URN-comparable objects must implement `urncontains`. $(SIGNATURES) """ -function urncontains(x::T, y) where {T} - urncontains(UrnComparisonTrait(T), x, y) +function urncontains(x::T, y) where {T <: Urn} + urncontains(urncomparisontrait(T), x, y) end """URN-comparable objects must implement `urnsimilar`. $(SIGNATURES) """ -function urnsimilar(x::T, y) where {T} - urnsimilar(UrnComparisonTrait(T), x, y) +function urnsimilar(x::T, y) where {T <: Urn} + urnsimilar(urncomparisontrait(T), x, y) end - """URN-comparable objects must implement `urnequals`. $(SIGNATURES) """ -function urnequals(x::T, y) where {T} - urnequals(UrnComparisonTrait(T), x, y) +function urnequals(x::T, y) where {T <: Urn} + urnequals(urncomparisontrait(T), x, y) end -"""True if `T` implements the `UrnComparisonTrait`. +"""True if type `T` implements the `UrnComparisonTrait`. $SIGNATURES """ function urncomparable(u::T) where {T} - UrnComparisonTrait(T) != NotUrnComparable() + urncomparisontrait(T) != NotUrnComparable() end