Compare commits

..

No commits in common. "21e0f11c41d36f265817597d1951d278addaadfe" and "f8f514e6dd2149931b0f39a5b68fbfe9617ad288" have entirely different histories.

11 changed files with 6 additions and 461 deletions

View file

@ -1,17 +1,7 @@
name = "CualerID"
uuid = "a4892dd5-cee5-4429-912e-1922c40e7f9b"
version = "0.0.1"
authors = ["Thomas A. Christensen II <25492070+MillironX@users.noreply.github.com> and contributors"]
[deps]
ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
FilePathsBase = "48062228-2e41-5def-b9a4-89aafe57970f"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
version = "0.0.1"
[compat]
ArgParse = "1.2.0"
FilePathsBase = "0.9.24"
UUIDs = "1.11.0"
julia = "1.12"
[apps.cualer-id]

View file

@ -1,4 +1,3 @@
# CualerID
[![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)

View file

@ -1,106 +0,0 @@
"""
BASE30_ALPHABET::Vector{UInt8}
An alphabet designed for unambiguous representation of values in either upper-
or lower-case. The alphabet was formed by taking the numbers 0-9 and the Latin
alphabet and removing characters that are considered easy to confuse. Excluded
characters are
- ``I`` => easily confused for lowercase ``L`` or the digit ``1``
- ``L`` => easily confused for uppercase ``I`` or the digit ``1``
- ``O`` => easily confused for the digit ``0``
- ``Q`` => easily confused with uppercase ``O`` or lowercase ``g``
- ``V`` => easily confused with ``U``
This array contains the ASCII index of the characters included in the base30
alphabet starting with 0 at position `BASE30_ALPHABET[1]`. Use `String()` to
convert an array of these indices to a base30 string.
"""
const BASE30_ALPHABET = UInt8.([
48:57..., #0-9
65:72..., #A-H
74:75..., #J-K
77:78..., #M-N
80, #P
83:85..., #S-U
87:90..., #W-Z
])
"""
rebase(v::Integer, base::Integer)
Rebase `v` into the base system of `base`. `v` can be any integer type, and can
be represented in base 10 (decimal) or base 16 (hexadecimal) based on Julia's
treatment of that integer type. Returns a `Vector{UInt8}` containing the
**indices** of each digit in the rebased number. Since Julia is 1-indexed, this
means that `rebase(0, base)[1]` actually returns a value of `UInt8[1]`, since
the first index of a numeral system's alphabet is assumed to be its ``0`` value.
# Examples
```jldoctest
julia> rebase(0, 10)
1-element Vector{UInt8}:
0x01
julia> # Two plus two is ten... IN BASE FOUR! I'M FINE!
julia> rebase(2 + 2, 4)
2-element Vector{UInt8}:
0x02
0x01
julia> base_4_alphabet = ['0', '1', '2', '3'];
julia> String([base_4_alphabet[i] for i in rebase(2 + 2, 4)])
"10"
```
!!! warning
This method is not tested on negative integers and may produce incorrect
output when passed negative numbers
"""
function rebase(v::Integer, base::Integer)
remainder = UInt8((v % base) + 1)
quotient = div(v, base)
if quotient == 0
return [remainder]
else
return [rebase(quotient, base)..., remainder]
end #if
end #function
"""
base30encode(v::Integer)
base30encode(v::UUID)
Creates a base30 representation of `v`. If `v` is a `UUID`, then the `UInt128`
value of the `UUID` is passed directly into the method.
# Examples
```jldoctest; filter = r"[0-9A-Z]{26}"
julia> base30encode(30)
"10"
julia> using UUIDs
julia> base30encode(UUIDs.uuid1())
"7ZK00JECBDF2H7GNBXN59C6S9S"
```
!!! warning
Just like [`rebase`](@ref), this method is not tested on negative integers
and may produce incorrect output when passed negative numbers
"""
function base30encode(v::Integer)
return String([BASE30_ALPHABET[i] for i in rebase(v, 30)])
end #function
function base30encode(v::UUID)
return base30encode(v.value)
end #function

View file

@ -1,174 +1,5 @@
module CualerID
using ArgParse: ArgParse, ArgParseSettings, parse_args, parse_item, @add_arg_table!
using FilePathsBase: AbstractPath, Path, exists, isfile
using UUIDs: UUID
# Write your package code here.
include("Base30.jl")
include("ResultCodeOption.jl")
export ResultCode
export ResultCodeOption
export base30encode
export has_duplicate
export has_fixed
export has_not_fixable
export has_valid
export rebase
function _create_arg_table()
s = ArgParseSettings(autofix_names = true)
#! format: off
@add_arg_table! s begin
"create"
help = "Create CualerID sample ids or barcode labels"
action = :command
"fix"
help = "Compare a set of possibly invalid IDs against a correct set of IDs to identify errors in the IDs"
action = :command
"convert"
help = "Convert CualID sample ids to CualerID sample ids or vice-versa"
action = :command
end #add_arg_table
@add_arg_table! s["create"] begin
"ids"
help = "Generate sample ids"
action = :command
"labels"
help = "Generate a pdf of barcodes (NOT YET IMPLEMENTED)"
action = :command
end #add_arg_table
@add_arg_table! s["create"]["ids"] begin
"--length", "-l"
help = "The length of the printed sample ids"
arg_type = UInt64
default = UInt64(5)
"--fail-threshold", "-f"
help = "NOT IMPLEMENTED: The rate at which similar ids will fail the process"
arg_type = Float64
range_tester = x -> x >= 0 && x <= 1.0
default = 0.99
"number_of_ids"
help = "The number of sample ids to print"
arg_type = UInt64
range_tester = x -> x >= 1
required = true
end #add_arg_table
@add_arg_table! s["fix"] begin
"--correct-ids"
help = "Path to the list of correct ids"
arg_type = AbstractPath
range_tester = x -> exists(x) && isfile(x)
required = true
"--show", "-s"
help = """
Which types of fixes should be printed to output. Can be a
combination of D for duplicate, F for fixed, N for not fixable,
and V for valid.
"""
arg_type = ResultCodeOption
default = ResultCodeOption("DFN")
"--all", "-a"
help = "Show all ids regardless of fixed status"
action = :store_true
"ids_to_be_corrected"
help = "File containing CualerIDs that need correction"
arg_type = AbstractPath
range_tester = x -> exists(x) && isfile(x)
required = true
end #add_arg_table
@add_arg_table! s["convert"] begin
"--length", "-l"
help = "The length of the printed sample ids"
arg_type = UInt64
default = UInt64(5)
"--fail-threshold", "-f"
help = "NOT IMPLEMENTED: The rate at which similar ids will fail the process"
arg_type = Float64
range_tester = x -> x >= 0 && x <= 1.0
default = 0.99
"--to-cual-id"
help = "Convert from CualerID to CualID"
action = :store_true
"ids_to_be_converted"
help = "TSV file containing CualIDs or CualerIDs to be converted"
arg_type = AbstractPath
range_tester = x -> exists(x) && isfile(x)
required = true
end #@add_arg_table!
#! format: on
return s
end #function
const ARG_PARSE_TABLE = _create_arg_table()
function _create(parsed_args::Dict{Symbol,Any})
if parsed_args[:_COMMAND_] == :ids
_create_ids(parsed_args[:ids])
elseif parsed_args[:_COMMAND_] == :labels
_create_labels(parsed_args[:labels])
end #if
return 0
end #function
function _create_ids(parsed_args::Dict{Symbol,Any})
println("COMMAND:\tcreate ids")
for (key, val) in parsed_args
println("\t$key:\t$val")
end #function
return 0
end #function
function _create_labels(parsed_args::Dict{Symbol,Any})
println("COMMAND:\tcreate labels")
for (key, val) in parsed_args
println("\t$key:\t$val")
end #function
return 0
end #function
function _fix(parsed_args::Dict{Symbol,Any})
println("COMMAND:\tfix")
for (key, val) in parsed_args
println("\t$key:\t$val")
end #function
return 0
end #function
function _convert(parsed_args::Dict{Symbol,Any})
println("COMMAND:\tconvert")
for (key, val) in parsed_args
println("\t$key:\t$val")
end #function
return 0
end #function
function (@main)()
parsed_args = parse_args(ARG_PARSE_TABLE; as_symbols = true)
if parsed_args[:_COMMAND_] == :create
_create(parsed_args[:create])
elseif parsed_args[:_COMMAND_] == :fix
_fix(parsed_args[:fix])
elseif parsed_args[:_COMMAND_] == :convert
_convert(parsed_args[:convert])
end #if
return 0
end #function
end #module
end

View file

@ -1,120 +0,0 @@
"""
ResultCode::UInt8
Encodes a result of attempting to correct a CualerID
The valid codes are
- **D**: duplicate
- **F**: fixed
- **N**: not fixable
- **V**: valid (no need for correction)
"""
@enum ResultCode::UInt8 begin
D = UInt8(1)
F = UInt8(2)
N = UInt8(4)
V = UInt8(8)
end #enum
"""
ResultCodeOption
Encodes potential corrected id codes.
The valid codes are
- **D**: duplicate
- **F**: fixed
- **N**: not fixable
- **V**: valid (no need for correction)
The codes are implemented exactly as from cual-id and in that order
# Constructors
ResultCodeOption(str::AbstractString)
Create a `ResultCodeOption` based on a the character codes contained within
`str`. `str` is case insensitive, may contain an arbitrary number of valid codes
(as specified above), and may contain duplicates. A `ResultCodeOption` cannot be
constructed from a combination of strings and bitmasks/integers.
# Extended help
The struct stores values as a bitmask
| Bit (Int) | Bit (UInt) | Description |
| ---------:| ----------:| --------------- |
| 1 | 0x01 | D - duplicate |
| 2 | 0x02 | F - fixed |
| 4 | 0x04 | N - not fixable |
| 8 | 0x08 | V - valid |
"""
struct ResultCodeOption
val::UInt8
end #struct
ResultCodeOption(str::AbstractString) = Base.parse(ResultCodeOption, str)
function Base.parse(::Type{ResultCodeOption}, str::AbstractString)
upper_str = uppercase(str)
# Throw if there are any invalid codes
occursin(r"[^DFNV]", upper_str) &&
throw(DomainError("Valid `ResultCodeOption` must contain D, F, N, or V only"))
# Throw if there are no valid codes
occursin(r"[DFNV]", upper_str) || throw(
DomainError("Valid `ResultCodeOption` must contain at least one of D, F, N, or V"),
)
result_code = sum(
c -> occursin(string(Symbol(c)), upper_str) ? UInt8(c) : UInt8(0),
instances(ResultCode),
)
return ResultCodeOption(result_code)
end #function
function Base.show(io::IO, r::ResultCodeOption)
print_code = ""
print(io, summary(r), " (")
foreach(
c -> r.val & UInt8(c) != 0 && print(io, string(Symbol(c))),
instances(ResultCode),
)
print(io, ")")
end #function
"""
has_duplicate(r::ResultCodeOption)
Determines if `r` contains a duplicate result ([`ResultCode`](@ref) D)
"""
has_duplicate(r::ResultCodeOption) = r.val & UInt8(D) != 0
"""
has_fixed(r::ResultCodeOption)
Determines if `r` contains a fixed result ([`ResultCode`](@ref) F)
"""
has_fixed(r::ResultCodeOption) = r.val & UInt8(F) != 0
"""
has_not_fixable(r::ResultCodeOption)
Determines if `r` contains a not fixable result ([`ResultCode`](@ref) N)
"""
has_not_fixable(r::ResultCodeOption) = r.val & UInt8(N) != 0
"""
has_valid(r::ResultCodeOption)
Determines if `r` contains a valid (not corrected) result
([`ResultCode`](@ref) V)
"""
has_valid(r::ResultCodeOption) = r.val & UInt8(V) != 0

View file

@ -1,5 +0,0 @@
using Aqua
@testset "Aqua" begin
Aqua.test_all(CualerID)
end #@testset

View file

@ -1,21 +0,0 @@
using CualerID: base30encode, rebase
@testset "rebase" begin
@test rebase(0, 10) == UInt8[1]
@test rebase(0, 2) == UInt8[1]
@test rebase(0, 32) == UInt8[1]
@test rebase(10, 10) == UInt8[2, 1]
@test rebase(2, 2) == UInt8[2, 1]
@test rebase(32, 32) == UInt8[2, 1]
@test rebase(11, 10) == UInt8[2, 2]
@test rebase(3, 2) == UInt8[2, 2]
@test rebase(33, 32) == UInt8[2, 2]
end #@testset
@testset "base30encode" begin
@test base30encode(0) == "0"
@test base30encode(1) == "1"
@test base30encode(30) == "10"
end

View file

@ -1,6 +0,0 @@
using Documenter
@testset "doctests" begin
DocMeta.setdocmeta!(CualerID, :DocTestSetup, :(using CualerID); recursive = true)
doctest(CualerID)
end

View file

@ -1,10 +1,2 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[compat]
Aqua = "0.8"
Documenter = "1.16"
UUIDs = "1.11.0"

View file

@ -1,8 +0,0 @@
@testset "ResultCode" begin
@test_throws DomainError ResultCodeOption("A")
@test_throws DomainError ResultCodeOption("")
@test has_duplicate(ResultCodeOption("D"))
@test has_fixed(ResultCodeOption("F"))
@test has_not_fixable(ResultCodeOption("N"))
@test has_valid(ResultCodeOption("V"))
end #@testset

View file

@ -1,7 +1,6 @@
using CualerID
using Test
include("Doctests.jl")
include("Aqua.jl")
include("Base30.jl")
include("ResultCodeOption.jl")
@testset "CualerID.jl" begin
# Write your tests here.
end