From aa6ea8c6e2e49c3de7136bbc0352a2991b67b778 Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Tue, 12 Jul 2022 16:16:18 -0400 Subject: [PATCH 01/11] Add basic introduction to the concept of sources --- docs/make.jl | 9 +- docs/src/concepts/sources.md | 250 +++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 docs/src/concepts/sources.md diff --git a/docs/make.jl b/docs/make.jl index 9bb1631..9efb86d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -20,10 +20,13 @@ makedocs(; ), pages=[ "Home" => "index.md", - "Examples" => [ - "Describing datasets" => "examples/datasets-examples.md", - "Working with sources" => "examples/sources.md" + "Concepts" => [ + "Sources" => "concepts/sources.md", ], + # "Examples" => [ + # "Describing datasets" => "examples/datasets-examples.md", + # "Working with sources" => "examples/sources.md" + # ], "Julia Reference" => "julia-reference.md", "MATLAB Reference" => "matlab-reference.md" ], diff --git a/docs/src/concepts/sources.md b/docs/src/concepts/sources.md new file mode 100644 index 0000000..6e4b2b2 --- /dev/null +++ b/docs/src/concepts/sources.md @@ -0,0 +1,250 @@ +# Sources + +A `Source` is a [type [Julia]](/julia-reference.html#DatasetManager.Source) or [class +[MATLAB]](/matlab-reference.html#Source) that refers to the location of a source of data +(typically a path to a file). DatasetManager normally assumes that sources contain +time-series data (e.g. in [`readsegment`](@ref), however this is not a requirement. + +Datasets often have more than one kind of source (e.g. if multiple systems were +used to collect different kinds of data, such as EMG and motion capture). These different +kinds of data require special code to load them for analysis. Furthermore, even within the +same file extension (e.g. .csv), files can have different data organization (e.g. the number +of lines in the header, type of data in columns, etc) which require special handling. These +differences make using the file extension too inaccurate for choosing which function is +appropriate for reading a particular file. + +By defining custom `readsource` functions +\[[Julia](/julia-reference.html#DatasetManager.readsource)/[MATLAB](/matlab-reference.html#Source.readsource)\], we can ensure that the data in any source can be correctly accessed using a single standard function. + +## Example source definitions + +[OpenSim](https://opensim.stanford.edu/) is an open-source software for neuromusculoskeletal modeling, simulation, and +analysis. Motion data is stored in tab-separated files with the extension +[`.mot`](https://simtk-confluence.stanford.edu:8443/display/OpenSim/Motion+%28.mot%29+Files). + +```@raw html +
+Julia +
+``` + +We'll define a singleton type to dispatch on: + +```julia +struct OSimMotion; end +``` + +and then define a `readsource` method for `Source{OSimMotion}`: + +```julia +using CSV, DataFrames +function DatasetManager.readsource(src::Source{OSimMotion}) + data = CSV.File(sourcepath(src); header=11, delim='\t') |> DataFrame + + return data +end +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + +We'll define a class inheriting from the [Source](/matlab-reference.html#Source) class, and +use the OpenSim MATLAB bindings to read the motion file: + +```matlab +classdef OSimMotion < Source + methods + function data = readsource(obj, varargin) + import org.opensim.modeling.* + % TimeSeriesTable from the OpenSim MATLAB bindings can read standard OpenSim + % file types + data = TimeSeriesTable(obj.path); + end + end +end +``` + +```@raw html +
+
+

+``` + +These are the minimal implementations needed to define a `Source` for reading OpenSim motion +files, and could be used like this: + +```julia +readsource(Source{OSimMotion}("/path/to/mot")) +``` + +```matlab +readsource(OSimMotion('/path/to/mot')) +``` + +!!! info + + Theoretically, Sources do not need to be locally stored or individual files (e.g. a + source could be stored in a database and could be defined to include a SQL query to retrieve the + particular referenced data from the server). However at the time of writing, all + datasets that have been used with DatasetManager have been locally stored in sources + referring to separate, individual files. + +### Adding a `readsegment` method + +A `Segment` \[[Julia](/julia-reference.html#DatasetManager.Segment)/[MATLAB](/matlab-reference.html#Segment)\] refers to a segment of time in a time-series source. A `readsegment` method should be functionally equivalent to +`readsource(segment.source)[starttime:finishtime]`. Here is an example for our `OSimMotion` source: + +```@raw html +
+Julia +
+``` + +```julia +function DatasetManager.readsegment(seg::Segment{Source{OSimMotion}}) + data = readsource(seg.source) + if !isnothing(seg.start) + starti = searchsortedfirst(data[!, "time"], seg.start) + else + starti = firstindex(data, 1) + end + + if !isnothing(seg.finish) + lasti = searchsortedlast(data[!, "time"], seg.finish) + else + lasti = lastindex(data, 1) + end + + return data[starti:lasti, :] +end +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + +```matlab +% define in methods of the classdef for OSimMotion +function data = readsegment(seg) + data = readsource(seg.source); + % the trimFrom function is a part of OpenSim's MATLAB bindings + data.trimFrom(seg.start, seg.finish); +end +``` + +```@raw html +
+
+

+``` + +## Required sources and automatically generating sources + +Sometimes a source can be generated from another source (or sources). This could be a simple +file conversion (e.g. `.c3d` to `.trc`), or something more involved, such as running inverse +kinematics in OpenSim. The [`requiresource!`](@ref)/[`requiresource` +[MATLAB]](/matlab-reference.html#Trial.requiresource) interface will check if a source +exists, and attempt to generate it if not, and throw an error if it is unable to generate +the source. + +To support generating a source, a `dependencies` method +\[[Julia](/julia-reference.html#DatasetManager.dependencies)/[MATLAB](/matlab-reference.html#Source.dependencies)\] +must be defined to declare what other sources the source of interest depends on to be +generated. + +```@raw html +
+Julia +
+``` + +Define the dependencies: + +```julia +DatasetManager.dependencies(::Source{OSimMotion}) = (Source{OSimIKSetup},) +``` + +Then we define a `generatesource` method: + +```julia +function DatasetManager.generatesource(trial, src::Source{OSimMotion}, deps) + iksetup = getsource(trial, only(filter(x-> Source{OSimIKSetup} === x, deps))) + run(`opensim-cmd run-tool $(sourcepath(iksetup))`) + + return src +end +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + +Define the dependencies: + +```matlab +% define in methods of the classdef for OSimMotion +function deps = dependencies(obj) + deps = {OSimIKSetup()}; +end +``` + +Then we define a `generatesource` method: + +```matlab +% define in methods of the classdef for OSimMotion +function src = generatesource(obj, trial, deps) + status = system(['opensim-cmd run-tool ' sourcepath(obj)]); + assert(status == 0) + + src = obj; +end +``` + +```@raw html +
+
+

+``` + +With those two methods now defined, the `requiresource!` function can be called, which will +generate the `OSimMotion` source if it is not already present: + +```julia +requiresource!(trial, Source{OSimMotion}) +``` +```matlab +requiresource(trial, OSimMotion) +``` + +The preceding Julia/MATLAB examples are simplified for clarity. Complete working examples in +Julia and MATLAB for an OpenSim motion file source and other sources can be found at +[LabDataSources.jl](https://github.com/NantelBiomechLab/LabDataSources.jl) and +[LabDataSources](https://github.com/NantelBiomechLab/LabDataSources), respectively. + + + From 6dfd7ec3a75df9b321e82043f18b3ac4b25c149a Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Wed, 13 Jul 2022 19:32:06 -0400 Subject: [PATCH 02/11] Update Documenter --- docs/Manifest.toml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/Manifest.toml b/docs/Manifest.toml index 1598908..ab17999 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -2,7 +2,7 @@ julia_version = "1.8.0-rc1" manifest_format = "2.0" -project_hash = "ba5ca65fdf24291970581f472112aa588f4315cd" +project_hash = "b8329e4236acd4eb6c5a3f40fc7286feb5ab0a68" [[deps.ANSIColoredPrinters]] git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" @@ -21,9 +21,9 @@ uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" [[deps.CSV]] deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"] -git-tree-sha1 = "49f14b6c56a2da47608fe30aed711b5882264d7a" +git-tree-sha1 = "873fb188a4b9d76549b81465b1f75c82aaf59238" uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -version = "0.9.11" +version = "0.10.4" [[deps.CategoricalArrays]] deps = ["DataAPI", "Future", "Missings", "Printf", "Requires", "Statistics", "Unicode"] @@ -33,9 +33,9 @@ version = "0.10.6" [[deps.ChainRulesCore]] deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "9489214b993cd42d17f44c36e359bf6a7c919abf" +git-tree-sha1 = "2dd813e5f2f7eec2d1268c57cf2373d3ee91fcea" uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.15.0" +version = "1.15.1" [[deps.ChangesOfVariables]] deps = ["ChainRulesCore", "LinearAlgebra", "Test"] @@ -113,9 +113,9 @@ version = "0.8.6" [[deps.Documenter]] deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "f7809f532671564e48cd81627ddcfb1ba670f87d" +git-tree-sha1 = "2498c3704e9cd26bf586a351473417881676bb2d" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.27.19" +version = "0.27.21" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] @@ -154,9 +154,9 @@ version = "0.2.2" [[deps.InlineStrings]] deps = ["Parsers"] -git-tree-sha1 = "61feba885fac3a407465726d0c330b3055df897f" +git-tree-sha1 = "d19f9edd8c34760dca2de2b503f969d8700ed288" uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.1.2" +version = "1.1.4" [[deps.InteractiveUtils]] deps = ["Markdown"] @@ -364,9 +364,9 @@ version = "1.4.0" [[deps.StatsBase]] deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "8977b17906b0a1cc74ab2e3a05faa16cf08a8291" +git-tree-sha1 = "48598584bacbebf7d30e20880438ed1d24b7c7d6" uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.33.16" +version = "0.33.18" [[deps.TOML]] deps = ["Dates"] From 4fb7131b0e4f0504f60527b5b84adc0a7c01d083 Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Sat, 10 Sep 2022 13:30:51 -0400 Subject: [PATCH 03/11] Add concepts intro for subsets --- docs/make.jl | 1 + docs/src/concepts/subsets.md | 160 +++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 docs/src/concepts/subsets.md diff --git a/docs/make.jl b/docs/make.jl index 9efb86d..0e34bf0 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -22,6 +22,7 @@ makedocs(; "Home" => "index.md", "Concepts" => [ "Sources" => "concepts/sources.md", + "Data Subsets" => "concepts/subsets.md", ], # "Examples" => [ # "Describing datasets" => "examples/datasets-examples.md", diff --git a/docs/src/concepts/subsets.md b/docs/src/concepts/subsets.md new file mode 100644 index 0000000..16277c1 --- /dev/null +++ b/docs/src/concepts/subsets.md @@ -0,0 +1,160 @@ +# Data Subsets + +A `DataSubset` is a [type [Julia]](../../julia-reference#DatasetManager.DataSubset) or +[class [MATLAB]](../../matlab-reference#DataSubset) that describes a group of sources of a +single kind of `Source` in a dataset. + +A dataset can have multiple different kinds of sources, which may be located and/or +organized in different manners (e.g. due to being created with different software or on +different systems). So we use the term `DataSubset` to refer to these subsets of a +dataset. + +In simple cases, the files in a `DataSubset` might be all of the files within a single +directory, e.g. `/data/events/`. However, in many cases, the files may be organized with a +more complex structure, involving multiple folders and subfolders. For these situations, +a [glob](https://en.wikipedia.org/wiki/Glob_(programming)) is used to specify the +locations and files/filetypes that are a part of the subset. For example, instead of +including all the files in a folder, `/data/events/`, a specific filetype can be specified: +`/data/events/*.csv` + +## Examples + +```@raw html +
+Julia +
+``` + +```@setup subsets +using DatasetManager +struct Events; end +struct C3DFile; end +``` + +This is an example of a `DataSubset` named `"events"` of `Source{Events}`. Note the +[wildcard](https://en.wikipedia.org/wiki/Glob_(programming)#Syntax) after `"Subject"`, which +will match any text (e.g. `"Subject 12/events/..."` or `"Subject 13_BAD/events/..."`). + +```@repl subsets +DataSubset("events", Source{Events}, "/path/to/subset", "Subject */events/*.tsv") +``` + +Alternately, the following is slightly more specific because the wildcard after `"Subject"` +is limited to the characters `0` through `9`. + +```@repl subsets +DataSubset("events", Source{Events}, "/path/to/subset", "Subject [0-9]*/events/*.tsv") +``` +In some cases, a more specific glob may be necessary or preferable to exclude certain +folders/files, due to the organization of sources, or for performance (if the more specific +glob matches significantly fewer files), however in many cases, a simpler, less specific +glob will be perfectly acceptable. + +The folder given as the third argument should be the deepest, non-glob containing (e.g. `*`, +etc) path. Globs in the third argument will cause errors, and a shallower path split (e.g. +`"/path/to/"`, `"subset/Subject */events/*.tsv"` vs `"/path/to/subset/"`, `"Subject +*/events/*.tsv"`) may be noticeably slower (due to searching more files and folders). + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + +The source type for `DataSubset`s in MATLAB is prefixed with an ampersand (`@`) to denote that +we are referring to the class itself, not an instance of the class. More info +[here](https://www.mathworks.com/help/matlab/ref/function_handle.html?s_tid=doc_ta) and +[here](https://www.mathworks.com/help/matlab/matlab_prog/pass-a-function-to-another-function.html). + +```matlab +DataSubset('events', @EventsSource, '/path/to/subset/Subject */events/*.tsv') +``` + +Globbing support is different in MATLAB, and the complete path, including globs, is given as +a single string; additionally, only asterisk globs are supported in MATLAB. More info +[here](https://www.mathworks.com/help/matlab/ref/dir.html#bup_1_c-2). + +```@raw html +
+
+

+``` + +## Dependent subsets + +(The following refers to concepts which haven't been introduced yet. Read [Trials](../trials) and +[TrialConditions](../trialconditions) for background information.) + +Some sources described by a `DataSubset` may not be relevant as standalone/independent +Trials (e.g. maximal voluntary contraction "trials", when collecting EMG data, are typically +only relevant to movement trials for that specific subject/session of a data collection, but +are may not be useful on their own). + +When `findtrials` is called with a dependent `DataSubset`, sources will only be added to pre-existing trials and no new trials will be created solely for a dependent source. Dependent sources are only added to trials when the required conditions for the dependent `DataSubset` match the corresponding conditions in the trials. + +```@raw html +
+Julia +
+``` + +```@repl subsets +labels = Dict( + :subject => r"(?<=Patient )\\d+", + :session => r"(?<=Session )\\d+", + :mvic => r"mvic_[rl](bic|tric)", # Defines possible source names for MVIC "trials" +); +conds = TrialConditions((:subject,:session,:mvic), labels; + required=(:subject,:session,)); +subsets = [ + DataSubset("mvic", Source{C3DFile}, "/path/to/c3d", "Subject */Session */*.c3d"; + dependent=true) +]; +``` + +The pattern for the `:mvic` "condition" would match any of `["mvic_rbic", "mvic_lbic", +"mvic_rtric", "mvic_ltric"]`. + +The dependent `"mvic"` subset has the `:subject` and `:session` conditions set as required, +so only sources (that match the `:mvic` pattern) with matching `:subject` and `:session` +will be added to a given trial. For example, a source with a filename of `"Subject 3/Session +2/mvic_rbic.c3d"` would be recognized as having conditions `(:subject => 3, :session => 2, +:mvic => "mvic_rbic")` and would be added as a source to any trial(s) +with conditions `(:subject => 3, :session => 2)`. + +!!! warning "Dependent source names" + + Note that the names of dependent sources are determined differently than regular + sources. While regular sources are given the name of the `DataSubset` they originate + from (e.g. `"mvic"`), this would cause `DuplicateSourceError`s if multiple (and unique) + dependent sources were needed for a `Trial`. Instead, dependent sources are named using + the (value of the) "condition" which matches the `DataSubset` name. Using the above + example, the `"mvic"` subset looks for a "condition" with the same name (e.g. `:mvic`), + and the source name is set to the value of the matched condition: `"mvic_rbic"`. + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + +\[Not implemented yet\] + +```@raw html +
+
+

+``` + From 947ad4dc99e006e4a5fcf6aa3d3db8babe86191d Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Sat, 10 Sep 2022 13:31:45 -0400 Subject: [PATCH 04/11] Add draft of trials concepts --- docs/make.jl | 1 + docs/src/concepts/trials.md | 122 ++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 docs/src/concepts/trials.md diff --git a/docs/make.jl b/docs/make.jl index 0e34bf0..e317d58 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,6 +23,7 @@ makedocs(; "Concepts" => [ "Sources" => "concepts/sources.md", "Data Subsets" => "concepts/subsets.md", + "Trials" => "concepts/trials.md", ], # "Examples" => [ # "Describing datasets" => "examples/datasets-examples.md", diff --git a/docs/src/concepts/trials.md b/docs/src/concepts/trials.md new file mode 100644 index 0000000..62f5cb0 --- /dev/null +++ b/docs/src/concepts/trials.md @@ -0,0 +1,122 @@ +# Trials + +A `Trial` is a [type [Julia]](../../julia-reference#DatasetManager.Trial) or [class +[MATLAB]](../../matlab-reference#Trial) that describes a single (temporal) instance of data +collected from a specific subject. `Trial`s are primarily descriptive, and do not contain +the data that was collected during the trial. `Trial`s record the (unique) subject +identifier, experimental conditions (or other relevant metadata, such as subject specific +characteristics), and they have have one or more `Source`s which refer to the files +containing the actual data collected during the trial. + +```@raw html +
+Julia +
+``` + +```@setup trials +using DatasetManager +``` + +```@repl trials +trial1 = Trial(1, "baseline", Dict(:group => "control", :session => 2)) +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + + +```matlab +Trial('1', 'baseline', struct('group', 'Control', 'session', 2)) +``` +``` +ans = + + Trial with properties: + + subject: '1' + name: 'baseline' + conditions: [1×1 struct] + sources: [1×1 struct] +``` + +```@raw html +
+
+

+``` + +The descriptive nature of `Trial`s makes it easy to include or exclude trials for an +analysis based on conditions, etc. Various convenience functions have been defined for this +purpose: `hassubject`, `hascondition`, and `hassource`. + +```@raw html +
+Julia +
+``` + +```@setup trials +using DatasetManager +``` + +```@repl trials +trials = [ Trial(id, "", Dict(:group => group, :week => week, :stimulus => stim)) + for id in 1:5 + for group in 'A':'B' + for week in 1:4 + for stim in ("sham", "low", "high") ]; +trials |> summary +trials[hascondition.(trials, :group => 'A')] |> summary +trials[hascondition.(trials, :group => 'A', :stimulus => ("low", "high"))] |> summary +filter(hassubject(3), trials) |> summary +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + + +```matlab +trials = Trial.empty(); +for id = 1:5 +for group = ['A', 'B'] +for week = 1:4 +for stim = {'sham', 'low', 'high'} +trials(end+1) = Trial(num2str(id), '', struct('group', group, 'week', week, 'stim', stim)); +end +end +end +end + +% TODO Add hascondition examples +``` + +```@raw html +
+
+

+``` + + + + + + From 9d20fb9aa349a700c64180169a3d0b379d9eeb50 Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Tue, 27 Sep 2022 18:27:43 -0400 Subject: [PATCH 05/11] Update manifest --- docs/Manifest.toml | 149 +++++++++++++-------------------------------- 1 file changed, 43 insertions(+), 106 deletions(-) diff --git a/docs/Manifest.toml b/docs/Manifest.toml index ab17999..651e475 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -1,6 +1,6 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.8.0-rc1" +julia_version = "1.8.0" manifest_format = "2.0" project_hash = "b8329e4236acd4eb6c5a3f40fc7286feb5ab0a68" @@ -9,10 +9,6 @@ git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" version = "0.0.1" -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" -version = "1.1.1" - [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" @@ -27,21 +23,21 @@ version = "0.10.4" [[deps.CategoricalArrays]] deps = ["DataAPI", "Future", "Missings", "Printf", "Requires", "Statistics", "Unicode"] -git-tree-sha1 = "5f5a975d996026a8dd877c35fe26a7b8179c02ba" +git-tree-sha1 = "5084cc1a28976dd1642c9f337b28a3cb03e0f7d2" uuid = "324d7699-5711-5eae-9e2f-1d82baa6b597" -version = "0.10.6" +version = "0.10.7" [[deps.ChainRulesCore]] deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "2dd813e5f2f7eec2d1268c57cf2373d3ee91fcea" +git-tree-sha1 = "e7ff6cadf743c098e08fca25c91103ee4303c9bb" uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.15.1" +version = "1.15.6" [[deps.ChangesOfVariables]] deps = ["ChainRulesCore", "LinearAlgebra", "Test"] -git-tree-sha1 = "1e315e3f4b0b7ce40feded39c73049692126cf53" +git-tree-sha1 = "38f7a08f19d8810338d4f5085211c7dfa5d5bdd8" uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" -version = "0.1.3" +version = "0.1.4" [[deps.CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] @@ -50,10 +46,10 @@ uuid = "944b1d66-785c-5afd-91f1-9de20f533193" version = "0.7.0" [[deps.Compat]] -deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "9be8be1d8a6f44b96482c8af52238ea7987da3e3" +deps = ["Dates", "LinearAlgebra", "UUIDs"] +git-tree-sha1 = "5856d3031cdb1f3b2b6340dfdc66b6d9a149a374" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.45.0" +version = "4.2.0" [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] @@ -66,15 +62,15 @@ uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" version = "4.1.1" [[deps.DataAPI]] -git-tree-sha1 = "fb5f5316dd3fd4c5e7c30a24d50643b73e37cd40" +git-tree-sha1 = "1106fa7e1256b402a86a8e7b15c00c85036fef49" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.10.0" +version = "1.11.0" [[deps.DataFrames]] deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "daa21eb85147f72e41f6352a57fccea377e310a9" +git-tree-sha1 = "db2a9cb664fcea7836da4b414c3278d71dd602d2" uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.3.4" +version = "1.3.6" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] @@ -91,45 +87,33 @@ version = "1.0.0" deps = ["CSV", "CategoricalArrays", "Crayons", "DataFrames", "Glob", "NaturalSort", "PrettyTables", "Printf", "ProgressMeter", "StatsBase", "ThreadPools"] path = ".." uuid = "2ca6b172-e30b-458d-964f-9c975788bc07" -version = "0.4.0" +version = "0.4.1" [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" -[[deps.DelimitedFiles]] -deps = ["Mmap"] -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - [[deps.Distributed]] deps = ["Random", "Serialization", "Sockets"] uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" [[deps.DocStringExtensions]] deps = ["LibGit2"] -git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" +git-tree-sha1 = "5158c2b41018c5f7eb1470d558127ac274eca0c9" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.6" +version = "0.9.1" [[deps.Documenter]] deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "2498c3704e9cd26bf586a351473417881676bb2d" +git-tree-sha1 = "6030186b00a38e9d0434518627426570aac2ef95" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.27.21" - -[[deps.Downloads]] -deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] -uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" -version = "1.6.0" +version = "0.27.23" [[deps.FilePathsBase]] deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"] -git-tree-sha1 = "129b104185df66e408edd6625d480b7f9e9823a0" +git-tree-sha1 = "e27c4ebe80e8699540f2d6c805cc12203b614f12" uuid = "48062228-2e41-5def-b9a4-89aafe57970f" -version = "0.9.18" - -[[deps.FileWatching]] -uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "0.9.20" [[deps.Formatting]] deps = ["Printf"] @@ -189,25 +173,10 @@ git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" version = "0.21.3" -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" -version = "0.6.3" - -[[deps.LibCURL_jll]] -deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] -uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "7.81.0+0" - [[deps.LibGit2]] deps = ["Base64", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.10.2+0" - [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -217,9 +186,9 @@ uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" [[deps.LogExpFunctions]] deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "09e4b894ce6a976c354a69041a04748180d43637" +git-tree-sha1 = "94d9c52ca447e23eac0c0f074effbcd38830deb5" uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.15" +version = "0.3.18" [[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" @@ -228,11 +197,6 @@ uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.0+0" - [[deps.Missings]] deps = ["DataAPI"] git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f" @@ -242,10 +206,6 @@ version = "1.0.2" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" -[[deps.MozillaCACerts_jll]] -uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2022.2.1" - [[deps.NaturalSort]] git-tree-sha1 = "eda490d06b9f7c00752ee81cfa451efe55521e21" uuid = "c020b1a1-e9b0-503a-9c33-f039bfc54a85" @@ -267,14 +227,9 @@ version = "1.4.1" [[deps.Parsers]] deps = ["Dates"] -git-tree-sha1 = "0044b23da09b5608b4ecacb4e5e6c6332f833a7e" +git-tree-sha1 = "3d5bf43e3e8b412656404ed9466f1dcbf7c50269" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.3.2" - -[[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] -uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.8.0" +version = "2.4.0" [[deps.PooledArrays]] deps = ["DataAPI", "Future"] @@ -307,9 +262,10 @@ deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[deps.RecipesBase]] -git-tree-sha1 = "6bf3f380ff52ce0832ddd3a2a7b9538ed1bcca7d" +deps = ["SnoopPrecompile"] +git-tree-sha1 = "612a4d76ad98e9722c8ba387614539155a59e30c" uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -version = "1.2.1" +version = "1.3.0" [[deps.Reexport]] git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" @@ -328,16 +284,17 @@ version = "0.7.0" [[deps.SentinelArrays]] deps = ["Dates", "Random"] -git-tree-sha1 = "db8481cf5d6278a121184809e9eb1628943c7704" +git-tree-sha1 = "c0f56940fc967f3d5efed58ba829747af5f8b586" uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.3.13" +version = "1.3.15" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" +[[deps.SnoopPrecompile]] +git-tree-sha1 = "f604441450a3c0569830946e5b33b78c928e1a85" +uuid = "66db9d55-30c0-4569-8b51-7e840670fc0c" +version = "1.0.1" [[deps.Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" @@ -358,20 +315,15 @@ uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [[deps.StatsAPI]] deps = ["LinearAlgebra"] -git-tree-sha1 = "2c11d7290036fe7aac9038ff312d3b3a2a5bf89e" +git-tree-sha1 = "f9af7f195fb13589dd2e2d57fdb401717d2eb1f6" uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.4.0" +version = "1.5.0" [[deps.StatsBase]] deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "48598584bacbebf7d30e20880438ed1d24b7c7d6" +git-tree-sha1 = "d1bf48bfcc554a3761a133fe3a9bb01488e06916" uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.33.18" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -version = "1.0.0" +version = "0.33.21" [[deps.TableTraits]] deps = ["IteratorInterfaceExtensions"] @@ -381,14 +333,9 @@ version = "1.0.1" [[deps.Tables]] deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"] -git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1" +git-tree-sha1 = "2d7164f7b8a066bcfa6224e67736ce0eb54aef5b" uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.7.0" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" -version = "1.10.0" +version = "1.9.0" [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] @@ -402,9 +349,9 @@ version = "2.1.1" [[deps.TranscodingStreams]] deps = ["Random", "Test"] -git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c" +git-tree-sha1 = "8a75929dcd3c38611db2f8d08546decb514fcadf" uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" -version = "0.9.6" +version = "0.9.9" [[deps.UUIDs]] deps = ["Random", "SHA"] @@ -427,14 +374,4 @@ version = "1.2.12+3" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.1.0+0" - -[[deps.nghttp2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.41.0+1" - -[[deps.p7zip_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+0" +version = "5.1.1+0" From cc2a89019565ce32d7380c35a800e8bea8d4baae Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Tue, 4 Oct 2022 18:41:24 -0400 Subject: [PATCH 06/11] Add incomplete introduction of TrialConditions --- docs/make.jl | 1 + docs/src/concepts/conditions.md | 199 ++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 docs/src/concepts/conditions.md diff --git a/docs/make.jl b/docs/make.jl index e317d58..b04b8da 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -24,6 +24,7 @@ makedocs(; "Sources" => "concepts/sources.md", "Data Subsets" => "concepts/subsets.md", "Trials" => "concepts/trials.md", + "Trial Conditions" => "concepts/conditions.md", ], # "Examples" => [ # "Describing datasets" => "examples/datasets-examples.md", diff --git a/docs/src/concepts/conditions.md b/docs/src/concepts/conditions.md new file mode 100644 index 0000000..088ba24 --- /dev/null +++ b/docs/src/concepts/conditions.md @@ -0,0 +1,199 @@ +# TrialConditions + +A `TrialConditions` is a [type +[Julia]](../../julia-reference#DatasetManager.TrialConditions) or [class +[MATLAB]](../../matlab-reference#TrialConditions) that describes the names and possible +values for the experimental conditions (aka factors) and other characteristics (e.g. subject +ID, etc) which are needed to describe and recognize multiple sources as being +associated with a single, unique `Trial`. + +A correctly specified `TrialConditions` allows the `findtrials` function to search +`DataSubset`s for suitable sources and match/group them into a `Trial`. Datasets may have +a complex design and a complex +organization (including typos, inconsistent naming of the same level in a condition, etc). +Therefore `TrialConditions` is designed to be capable of describing such complexity. + +!!! tip "Terms" + + **condition**: + + 1. The name of an experimental condition or factor + + **label**: + + 1. A value of a condition + + Example: `"control"` is a valid label for the condition "*group*" + +!!! compat "Current Assumptions/Limitations" + + - All conditions (needed to uniquely describe a trial) are present in the absolute path of a + source + - Conditions have a consistent order (e.g. condition *session* is always after *group*) + + +The simplest datasets can be described by listing all valid labels for each condition. This might look like: + +```@raw html +
+Julia +
+``` + +```@setup conditions +using DatasetManager +``` + +```@repl conditions +labels = Dict( + :subject => ["1", "2", "3", "4", "5"], + :arms => ["held", "norm", "active"] +); +conds = TrialConditions((:subject,:arms), labels); +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + + +```matlab +labels.subject(1).to = '1'; +labels.subject(2).to = '2'; +labels.subject(3).to = '3'; +labels.subject(4).to = '4'; +labels.subject(5).to = '5'; +labels.arms(1).to = 'held'; +labels.arms(2).to = 'norm'; +labels.arms(3).to = 'active'; + +conds = TrialConditions.generate({'subject','arms'}, labels); +``` + +```@raw html +
+
+

+``` + +But many datasets aren't that simple or organized so perfectly. Suppose some trials had capitalized first letters for the arms conditions. The original `labels` would not match, and those trials would be ignored. We can explicitly add capitalized options: + +```@raw html +
+Julia +
+``` + +```@setup conditions +using DatasetManager +``` + +```@repl conditions +labels = Dict( + :subject => ["1", "2", "3", "4", "5"], + :arms => ["Held" => "held", "Norm" => "norm", "Active" => "active"] +); +conds = TrialConditions((:subject,:arms), labels); +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + + +```matlab +labels.subject(1).to = '1'; +labels.subject(2).to = '2'; +labels.subject(3).to = '3'; +labels.subject(4).to = '4'; +labels.subject(5).to = '5'; +labels.arms(1).from = 'Held'; +labels.arms(1).to = 'held'; +labels.arms(2).from = 'Norm'; +labels.arms(2).to = 'norm'; +labels.arms(3).from = 'Active'; +labels.arms(3).to = 'active'; + +conds = TrialConditions.generate({'subject','arms'}, labels); +``` + +```@raw html +
+
+

+``` + +We now are matching all hypothetical trials. However, the capitalized conditions will not be recognized as the same actual levels as the corresponding lowercase levels. One solution is to define conversions for non-canonical (e.g. capitalized, known typos, etc) levels. + +```@raw html +
+Julia +
+``` + +```@setup conditions +using DatasetManager +``` + +```@repl conditions +labels = Dict( + :subject => ["1", "2", "3", "4", "5"], + :arms => ["Held" => "held", "Norm" => "norm", "Active" => "active"] +); +conds = TrialConditions((:subject,:arms), labels); +``` + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + + +```matlab +labels.subject(1).to = '1'; +labels.subject(2).to = '2'; +labels.subject(3).to = '3'; +labels.subject(4).to = '4'; +labels.subject(5).to = '5'; +labels.arms(1).from = 'Held'; +labels.arms(1).to = 'held'; +labels.arms(2).from = 'Norm'; +labels.arms(2).to = 'norm'; +labels.arms(3).from = 'Active'; +labels.arms(3).to = 'active'; + +conds = TrialConditions.generate({'subject','arms'}, labels); +``` + +```@raw html +
+
+

+``` + +Now all the capitalized conditions will be converted to lowercase. + + From 52319948319127aaeff00fa46a81b960d0b6ac76 Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Fri, 7 Oct 2022 14:04:05 -0400 Subject: [PATCH 07/11] Update trials concept intro --- docs/src/concepts/trials.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/src/concepts/trials.md b/docs/src/concepts/trials.md index 62f5cb0..ba87f5a 100644 --- a/docs/src/concepts/trials.md +++ b/docs/src/concepts/trials.md @@ -55,6 +55,9 @@ ans =

``` +`Trial`s can be manually constructed, but generally will be automatically found and created +by the `findtrials` function. + The descriptive nature of `Trial`s makes it easy to include or exclude trials for an analysis based on conditions, etc. Various convenience functions have been defined for this purpose: `hassubject`, `hascondition`, and `hassource`. @@ -106,7 +109,7 @@ end end end -% TODO Add hascondition examples +% TODO Add hascondition examples matching the Julia examples ``` ```@raw html From 04b4545717f75bf5e5952ef74435993555071605 Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Fri, 7 Oct 2022 14:04:42 -0400 Subject: [PATCH 08/11] Update TrialConditions concept intro --- docs/make.jl | 2 +- docs/src/concepts/conditions.md | 75 +++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index b04b8da..a618130 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,7 +13,7 @@ makedocs(; assets=String[ "assets/css/custom.css" ], - highlights=["matlab"], + highlights=["matlab", "diff"], highlightjs=joinpath(@__DIR__, "src/assets/js/highlight.min.js"), prerender=true, ansicolor=true, diff --git a/docs/src/concepts/conditions.md b/docs/src/concepts/conditions.md index 088ba24..6c1c374 100644 --- a/docs/src/concepts/conditions.md +++ b/docs/src/concepts/conditions.md @@ -40,10 +40,6 @@ The simplest datasets can be described by listing all valid labels for each cond
``` -```@setup conditions -using DatasetManager -``` - ```@repl conditions labels = Dict( :subject => ["1", "2", "3", "4", "5"], @@ -92,16 +88,11 @@ But many datasets aren't that simple or organized so perfectly. Suppose some tri
``` -```@setup conditions -using DatasetManager -``` - ```@repl conditions labels = Dict( :subject => ["1", "2", "3", "4", "5"], :arms => ["Held" => "held", "Norm" => "norm", "Active" => "active"] ); -conds = TrialConditions((:subject,:arms), labels); ``` ```@raw html @@ -129,8 +120,6 @@ labels.arms(2).from = 'Norm'; labels.arms(2).to = 'norm'; labels.arms(3).from = 'Active'; labels.arms(3).to = 'active'; - -conds = TrialConditions.generate({'subject','arms'}, labels); ``` ```@raw html @@ -147,16 +136,11 @@ We now are matching all hypothetical trials. However, the capitalized conditions
``` -```@setup conditions -using DatasetManager -``` - ```@repl conditions labels = Dict( :subject => ["1", "2", "3", "4", "5"], :arms => ["Held" => "held", "Norm" => "norm", "Active" => "active"] ); -conds = TrialConditions((:subject,:arms), labels); ``` ```@raw html @@ -184,8 +168,6 @@ labels.arms(2).from = 'Norm'; labels.arms(2).to = 'norm'; labels.arms(3).from = 'Active'; labels.arms(3).to = 'active'; - -conds = TrialConditions.generate({'subject','arms'}, labels); ``` ```@raw html @@ -196,4 +178,61 @@ conds = TrialConditions.generate({'subject','arms'}, labels); Now all the capitalized conditions will be converted to lowercase. +## Advanced definitions + +In some cases, explicitly listing all the possible labels is untenable. In these cases, the +`labels` in `TrialConditions` can use regexes, and functions which modify strings (Julia only), in addition to arrays of strings, to identify all conditions/labels and improve the consistency of label naming. + +Some examples (multiple equivalent alternatives presented in green): + +```@raw html +
+Julia +
+``` + +```diff +labels = Dict( +- :subject => ["1", "2", "3", "4", "5"], ++ :subject => r"\d", + +- :arms => ["Held" => "held", "Norm" => "norm", "Active" => "active"], ++ :arms => [r"held"i, r"norm"i, r"active"i] => lowercase, ++ :arms => r"(held|norm|active)"i => lowercase, +); +``` + +In the example for `:arms`, the `'i'` after the closing double quote signals that the regex +is case-insensitive. By itself, this would lead to the earlier discussed problem of matching +both the lowercase and capitalized labels, and would produce mismatched labels. The pair `=>` syntax should be read as "convert to/with", so the matches by the regex are given as arguments to the `lowercase` function with the function output being added as an `:arms` condition to any trials, instead of the direct match from the regex. See [`TrialConditions`](@ref) for more details. + +```@raw html +
+
+

+``` + +```@raw html +
+MATLAB +
+``` + + +```diff +- labels.subject(1).to = '1'; ++ labels.subject(1).to = '\d'; +``` + +Regexes could be used for the `arms` label, but the MATLAB version doesn't currently support +using functions to transform regex matches. If the use of regexes is needed or desired, it +would be possible to normalize mismatched labels after running `findtrials`. + +```@raw html +
+
+

+``` + + From 12e9a08fbd664d03101c5f06cc40c13c2c9fdc2a Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Mon, 24 Oct 2022 14:29:20 -0400 Subject: [PATCH 09/11] Update highlightjs --- docs/src/assets/js/highlight.min.js | 417 +++++++++++++++------------- 1 file changed, 217 insertions(+), 200 deletions(-) diff --git a/docs/src/assets/js/highlight.min.js b/docs/src/assets/js/highlight.min.js index 14abc90..6101618 100644 --- a/docs/src/assets/js/highlight.min.js +++ b/docs/src/assets/js/highlight.min.js @@ -1,6 +1,6 @@ /*! - Highlight.js v11.0.0 (git: fd3df920dc) - (c) 2006-2021 Ivan Sagalaev and other contributors + Highlight.js v11.6.0 (git: 414d31c654) + (c) 2006-2022 undefined and other contributors License: BSD-3-Clause */ var hljs=function(){"use strict";var e={exports:{}};function n(e){ @@ -14,19 +14,20 @@ void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} ignoreMatch(){this.isMatchIgnored=!0}}function r(e){ return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") }function a(e,...n){const t=Object.create(null);for(const n in e)t[n]=e[n] -;return n.forEach((e=>{for(const n in e)t[n]=e[n]})),t}const s=e=>!!e.kind -;class o{constructor(e,n){ +;return n.forEach((e=>{for(const n in e)t[n]=e[n]})),t} +const s=e=>!!e.scope||e.sublanguage&&e.language;class o{constructor(e,n){ this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){ -this.buffer+=r(e)}openNode(e){if(!s(e))return;let n=e.kind -;n=e.sublanguage?"language-"+n:((e,{prefix:n})=>{if(e.includes(".")){ +this.buffer+=r(e)}openNode(e){if(!s(e))return;let n="" +;n=e.sublanguage?"language-"+e.language:((e,{prefix:n})=>{if(e.includes(".")){ const t=e.split(".") ;return[`${n}${t.shift()}`,...t.map(((e,n)=>`${e}${"_".repeat(n+1)}`))].join(" ") -}return`${n}${e}`})(n,{prefix:this.classPrefix}),this.span(n)}closeNode(e){ -s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ -this.buffer+=``}}class l{constructor(){this.rootNode={ -children:[]},this.stack=[this.rootNode]}get top(){ +}return`${n}${e}`})(e.scope,{prefix:this.classPrefix}),this.span(n)} +closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const c=(e={})=>{const n={children:[]} +;return Object.assign(n,e),n};class l{constructor(){ +this.rootNode=c(),this.stack=[this.rootNode]}get top(){ return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ -this.top.children.push(e)}openNode(e){const n={kind:e,children:[]} +this.top.children.push(e)}openNode(e){const n=c({scope:e}) ;this.add(n),this.stack.push(n)}closeNode(){ if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} @@ -34,98 +35,100 @@ walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){ return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n), n.children.forEach((n=>this._walk(e,n))),e.closeNode(n)),e}static _collapse(e){ "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ -l._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e} +l._collapse(e)})))}}class g extends l{constructor(e){super(),this.options=e} addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())} addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root -;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){ -return new o(this,this.options).value()}finalize(){return!0}}function g(e){ -return e?"string"==typeof e?e:e.source:null}function d(...e){ -return e.map((e=>g(e))).join("")}function u(...e){return"("+((e=>{ -const n=e[e.length-1] +;t.sublanguage=!0,t.language=n,this.add(t)}toHTML(){ +return new o(this,this.options).value()}finalize(){return!0}}function d(e){ +return e?"string"==typeof e?e:e.source:null}function u(e){return b("(?=",e,")")} +function h(e){return b("(?:",e,")*")}function p(e){return b("(?:",e,")?")} +function b(...e){return e.map((e=>d(e))).join("")}function f(...e){ +return"("+((e=>{const n=e[e.length-1] ;return"object"==typeof n&&n.constructor===Object?(e.splice(e.length-1,1),n):{} -})(e).capture?"":"?:")+e.map((e=>g(e))).join("|")+")"}function h(e){ +})(e).capture?"":"?:")+e.map((e=>d(e))).join("|")+")"}function m(e){ return RegExp(e.toString()+"|").exec("").length-1} -const b=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ -;function p(e,{joinWith:n}){let t=0;return e.map((e=>{t+=1;const n=t -;let i=g(e),r="";for(;i.length>0;){const e=b.exec(i);if(!e){r+=i;break} +const E=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function x(e,{joinWith:n}){let t=0;return e.map((e=>{t+=1;const n=t +;let i=d(e),r="";for(;i.length>0;){const e=E.exec(i);if(!e){r+=i;break} r+=i.substring(0,e.index), i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+n):(r+=e[0], "("===e[0]&&t++)}return r})).map((e=>`(${e})`)).join(n)} -const f="[a-zA-Z]\\w*",m="[a-zA-Z_]\\w*",E="\\b\\d+(\\.\\d+)?",x="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",y="\\b(0b[01]+)",w={ -begin:"\\\\[\\s\\S]",relevance:0},_={scope:"string",begin:"'",end:"'", -illegal:"\\n",contains:[w]},N={scope:"string",begin:'"',end:'"',illegal:"\\n", -contains:[w]},A=(e,n,t={})=>{const i=a({scope:"comment",begin:e,end:n, +const y="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",_="\\b\\d+(\\.\\d+)?",N="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",A="\\b(0b[01]+)",v={ +begin:"\\\\[\\s\\S]",relevance:0},S={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[v]},M={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[v]},O=(e,n,t={})=>{const i=a({scope:"comment",begin:e,end:n, contains:[]},t);i.contains.push({scope:"doctag", begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) -;const r=u("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) -;return i.contains.push({begin:d(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i -},S=A("//","$"),v=A("/\\*","\\*/"),O=A("#","$");var k=Object.freeze({ -__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:f,UNDERSCORE_IDENT_RE:m, -NUMBER_RE:E,C_NUMBER_RE:x,BINARY_NUMBER_RE:y, +;const r=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:b(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},R=O("//","$"),k=O("/\\*","\\*/"),I=O("#","$");var C=Object.freeze({ +__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:y,UNDERSCORE_IDENT_RE:w, +NUMBER_RE:_,C_NUMBER_RE:N,BINARY_NUMBER_RE:A, RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", SHEBANG:(e={})=>{const n=/^#![ ]*\// -;return e.binary&&(e.begin=d(n,/.*\b/,e.binary,/\b.*/)),a({scope:"meta",begin:n, +;return e.binary&&(e.begin=b(n,/.*\b/,e.binary,/\b.*/)),a({scope:"meta",begin:n, end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)}, -BACKSLASH_ESCAPE:w,APOS_STRING_MODE:_,QUOTE_STRING_MODE:N,PHRASAL_WORDS_MODE:{ +BACKSLASH_ESCAPE:v,APOS_STRING_MODE:S,QUOTE_STRING_MODE:M,PHRASAL_WORDS_MODE:{ begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ -},COMMENT:A,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:v,HASH_COMMENT_MODE:O, -NUMBER_MODE:{scope:"number",begin:E,relevance:0},C_NUMBER_MODE:{scope:"number", -begin:x,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:y,relevance:0}, +},COMMENT:O,C_LINE_COMMENT_MODE:R,C_BLOCK_COMMENT_MODE:k,HASH_COMMENT_MODE:I, +NUMBER_MODE:{scope:"number",begin:_,relevance:0},C_NUMBER_MODE:{scope:"number", +begin:N,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:A,relevance:0}, REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//, -end:/\/[gimuy]*/,illegal:/\n/,contains:[w,{begin:/\[/,end:/\]/,relevance:0, -contains:[w]}]}]},TITLE_MODE:{scope:"title",begin:f,relevance:0}, -UNDERSCORE_TITLE_MODE:{scope:"title",begin:m,relevance:0},METHOD_GUARD:{ +end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0, +contains:[v]}]}]},TITLE_MODE:{scope:"title",begin:y,relevance:0}, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{ begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ "on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{ -n.data._beginMatch!==e[1]&&n.ignoreMatch()}})});function M(e,n){ -"."===e.input[e.index-1]&&n.ignoreMatch()}function R(e,n){ -void 0!==e.className&&(e.scope=e.className,delete e.className)}function I(e,n){ +n.data._beginMatch!==e[1]&&n.ignoreMatch()}})});function T(e,n){ +"."===e.input[e.index-1]&&n.ignoreMatch()}function j(e,n){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function L(e,n){ n&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", -e.__beforeBegin=M,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, -void 0===e.relevance&&(e.relevance=0))}function C(e,n){ -Array.isArray(e.illegal)&&(e.illegal=u(...e.illegal))}function j(e,n){ +e.__beforeBegin=T,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function B(e,n){ +Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function D(e,n){ if(e.match){ if(e.begin||e.end)throw Error("begin & end are not supported with match") -;e.begin=e.match,delete e.match}}function T(e,n){ -void 0===e.relevance&&(e.relevance=1)}const B=(e,n)=>{if(!e.beforeMatch)return +;e.begin=e.match,delete e.match}}function P(e,n){ +void 0===e.relevance&&(e.relevance=1)}const U=(e,n)=>{if(!e.beforeMatch)return ;if(e.starts)throw Error("beforeMatch cannot be used with starts") ;const t=Object.assign({},e);Object.keys(e).forEach((n=>{delete e[n] -})),e.keywords=t.keywords, -e.begin=d(t.beforeMatch,d("(?=",t.begin,")")),e.starts={relevance:0, -contains:[Object.assign(t,{endsParent:!0})]},e.relevance=0,delete t.beforeMatch -},L=["of","and","for","in","not","or","if","then","parent","list","value"] -;function D(e,n,t="keyword"){const i=Object.create(null) +})),e.keywords=t.keywords,e.begin=b(t.beforeMatch,u(t.begin)),e.starts={ +relevance:0,contains:[Object.assign(t,{endsParent:!0})] +},e.relevance=0,delete t.beforeMatch +},$=["of","and","for","in","not","or","if","then","parent","list","value"] +;function H(e,n,t="keyword"){const i=Object.create(null) ;return"string"==typeof e?r(t,e.split(" ")):Array.isArray(e)?r(t,e):Object.keys(e).forEach((t=>{ -Object.assign(i,D(e[t],n,t))})),i;function r(e,t){ +Object.assign(i,H(e[t],n,t))})),i;function r(e,t){ n&&(t=t.map((e=>e.toLowerCase()))),t.forEach((n=>{const t=n.split("|") -;i[t[0]]=[e,P(t[0],t[1])]}))}}function P(e,n){ -return n?Number(n):(e=>L.includes(e.toLowerCase()))(e)?0:1}const U={},F=e=>{ -console.error(e)},H=(e,...n)=>{console.log("WARN: "+e,...n)},$=(e,n)=>{ -U[`${e}/${n}`]||(console.log(`Deprecated as of ${e}. ${n}`),U[`${e}/${n}`]=!0) -},z=Error();function K(e,n,{key:t}){let i=0;const r=e[t],a={},s={} -;for(let e=1;e<=n.length;e++)s[e+i]=r[e],a[e+i]=!0,i+=h(n[e-1]) -;e[t]=s,e[t]._emit=a,e[t]._multi=!0}function V(e){(e=>{ +;i[t[0]]=[e,F(t[0],t[1])]}))}}function F(e,n){ +return n?Number(n):(e=>$.includes(e.toLowerCase()))(e)?0:1}const z={},V=e=>{ +console.error(e)},K=(e,...n)=>{console.log("WARN: "+e,...n)},Z=(e,n)=>{ +z[`${e}/${n}`]||(console.log(`Deprecated as of ${e}. ${n}`),z[`${e}/${n}`]=!0) +},W=Error();function G(e,n,{key:t}){let i=0;const r=e[t],a={},s={} +;for(let e=1;e<=n.length;e++)s[e+i]=r[e],a[e+i]=!0,i+=m(n[e-1]) +;e[t]=s,e[t]._emit=a,e[t]._multi=!0}function X(e){(e=>{ e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ _wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope }),(e=>{if(Array.isArray(e.begin)){ -if(e.skip||e.excludeBegin||e.returnBegin)throw F("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), -z -;if("object"!=typeof e.beginScope||null===e.beginScope)throw F("beginScope must be object"), -z;K(e,e.begin,{key:"beginScope"}),e.begin=p(e.begin,{joinWith:""})}})(e),(e=>{ +if(e.skip||e.excludeBegin||e.returnBegin)throw V("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +W +;if("object"!=typeof e.beginScope||null===e.beginScope)throw V("beginScope must be object"), +W;G(e,e.begin,{key:"beginScope"}),e.begin=x(e.begin,{joinWith:""})}})(e),(e=>{ if(Array.isArray(e.end)){ -if(e.skip||e.excludeEnd||e.returnEnd)throw F("skip, excludeEnd, returnEnd not compatible with endScope: {}"), -z -;if("object"!=typeof e.endScope||null===e.endScope)throw F("endScope must be object"), -z;K(e,e.end,{key:"endScope"}),e.end=p(e.end,{joinWith:""})}})(e)}function Z(e){ -function n(n,t){return RegExp(g(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))} -class t{constructor(){ +if(e.skip||e.excludeEnd||e.returnEnd)throw V("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +W +;if("object"!=typeof e.endScope||null===e.endScope)throw V("endScope must be object"), +W;G(e,e.end,{key:"endScope"}),e.end=x(e.end,{joinWith:""})}})(e)}function q(e){ +function n(n,t){ +return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(t?"g":"")) +}class t{constructor(){ this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} addRule(e,n){ n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]), -this.matchAt+=h(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) -;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(p(e,{joinWith:"|" +this.matchAt+=m(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(x(e,{joinWith:"|" }),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex ;const n=this.matcherRe.exec(e);if(!n)return null ;const t=n.findIndex(((e,n)=>n>0&&void 0!==e)),i=this.matchIndexes[t] @@ -147,154 +150,161 @@ if(e.compilerExtensions||(e.compilerExtensions=[]), e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") ;return e.classNameAliases=a(e.classNameAliases||{}),function t(r,s){const o=r ;if(r.isCompiled)return o -;[R,j,V,B].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))), -r.__beforeBegin=null,[I,C,T].forEach((e=>e(r,s))),r.isCompiled=!0;let l=null +;[j,D,X,U].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))), +r.__beforeBegin=null,[L,B,P].forEach((e=>e(r,s))),r.isCompiled=!0;let c=null ;return"object"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords), -l=r.keywords.$pattern, -delete r.keywords.$pattern),l=l||/\w+/,r.keywords&&(r.keywords=D(r.keywords,e.case_insensitive)), -o.keywordPatternRe=n(l,!0), -s&&(r.begin||(r.begin=/\B|\b/),o.beginRe=n(r.begin),r.end||r.endsWithParent||(r.end=/\B|\b/), -r.end&&(o.endRe=n(r.end)), -o.terminatorEnd=g(r.end)||"",r.endsWithParent&&s.terminatorEnd&&(o.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)), +c=r.keywords.$pattern, +delete r.keywords.$pattern),c=c||/\w+/,r.keywords&&(r.keywords=H(r.keywords,e.case_insensitive)), +o.keywordPatternRe=n(c,!0), +s&&(r.begin||(r.begin=/\B|\b/),o.beginRe=n(o.begin),r.end||r.endsWithParent||(r.end=/\B|\b/), +r.end&&(o.endRe=n(o.end)), +o.terminatorEnd=d(o.end)||"",r.endsWithParent&&s.terminatorEnd&&(o.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)), r.illegal&&(o.illegalRe=n(r.illegal)), r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((n=>a(e,{ -variants:null},n)))),e.cachedVariants?e.cachedVariants:W(e)?a(e,{ +variants:null},n)))),e.cachedVariants?e.cachedVariants:J(e)?a(e,{ starts:e.starts?a(e.starts):null }):Object.isFrozen(e)?a(e):e))("self"===e?r:e)))),r.contains.forEach((e=>{t(e,o) })),r.starts&&t(r.starts,s),o.matcher=(e=>{const n=new i ;return e.contains.forEach((e=>n.addRule(e.begin,{rule:e,type:"begin" }))),e.terminatorEnd&&n.addRule(e.terminatorEnd,{type:"end" -}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n})(o),o}(e)}function W(e){ -return!!e&&(e.endsWithParent||W(e.starts))}const G=r,X=a,q=Symbol("nomatch") -;var J=(e=>{const n=Object.create(null),r=Object.create(null),a=[];let s=!0 -;const o="Could not find the language '{}', did you forget to load/include a language module?",l={ -disableAutodetect:!0,name:"Plain text",contains:[]};let g={ -ignoreUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n})(o),o}(e)}function J(e){ +return!!e&&(e.endsWithParent||J(e.starts))}class Q extends Error{ +constructor(e,n){super(e),this.name="HTMLInjectionError",this.html=n}} +const Y=r,ee=a,ne=Symbol("nomatch");var te=(e=>{ +const n=Object.create(null),r=Object.create(null),a=[];let s=!0 +;const o="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let l={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", -cssSelector:"pre code",languages:null,__emitter:c};function d(e){ -return g.noHighlightRe.test(e)}function u(e,n,t){let i="",r="" +cssSelector:"pre code",languages:null,__emitter:g};function d(e){ +return l.noHighlightRe.test(e)}function m(e,n,t){let i="",r="" ;"object"==typeof n?(i=e, -t=n.ignoreIllegals,r=n.language):($("10.7.0","highlight(lang, code, ...args) has been deprecated."), -$("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), -r=e,i=n),void 0===t&&(t=!0);const a={code:i,language:r};w("before:highlight",a) -;const s=a.result?a.result:h(a.language,a.code,t) -;return s.code=a.code,w("after:highlight",s),s}function h(e,t,r,a){ -const l=Object.create(null);function c(){if(!S.keywords)return void O.addText(k) -;let e=0;S.keywordPatternRe.lastIndex=0;let n=S.keywordPatternRe.exec(k),t="" -;for(;n;){t+=k.substring(e,n.index) -;const r=_.case_insensitive?n[0].toLowerCase():n[0],a=(i=r,S.keywords[i]);if(a){ +t=n.ignoreIllegals,r=n.language):(Z("10.7.0","highlight(lang, code, ...args) has been deprecated."), +Z("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +r=e,i=n),void 0===t&&(t=!0);const a={code:i,language:r};S("before:highlight",a) +;const s=a.result?a.result:E(a.language,a.code,t) +;return s.code=a.code,S("after:highlight",s),s}function E(e,t,r,a){ +const c=Object.create(null);function g(){if(!v.keywords)return void M.addText(O) +;let e=0;v.keywordPatternRe.lastIndex=0;let n=v.keywordPatternRe.exec(O),t="" +;for(;n;){t+=O.substring(e,n.index) +;const r=w.case_insensitive?n[0].toLowerCase():n[0],a=(i=r,v.keywords[i]);if(a){ const[e,i]=a -;if(O.addText(t),t="",l[r]=(l[r]||0)+1,l[r]<=7&&(M+=i),e.startsWith("_"))t+=n[0];else{ -const t=_.classNameAliases[e]||e;O.addKeyword(n[0],t)}}else t+=n[0] -;e=S.keywordPatternRe.lastIndex,n=S.keywordPatternRe.exec(k)}var i -;t+=k.substr(e),O.addText(t)}function d(){null!=S.subLanguage?(()=>{ -if(""===k)return;let e=null;if("string"==typeof S.subLanguage){ -if(!n[S.subLanguage])return void O.addText(k) -;e=h(S.subLanguage,k,!0,v[S.subLanguage]),v[S.subLanguage]=e._top -}else e=b(k,S.subLanguage.length?S.subLanguage:null) -;S.relevance>0&&(M+=e.relevance),O.addSublanguage(e._emitter,e.language) -})():c(),k=""}function u(e,n){let t=1;for(;void 0!==n[t];){if(!e._emit[t]){t++ -;continue}const i=_.classNameAliases[e[t]]||e[t],r=n[t] -;i?O.addKeyword(r,i):(k=r,c(),k=""),t++}}function p(e,n){ -return e.scope&&"string"==typeof e.scope&&O.openNode(_.classNameAliases[e.scope]||e.scope), -e.beginScope&&(e.beginScope._wrap?(O.addKeyword(k,_.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), -k=""):e.beginScope._multi&&(u(e.beginScope,n),k="")),S=Object.create(e,{parent:{ -value:S}}),S}function f(e,n,t){let r=((e,n)=>{const t=e&&e.exec(n) +;if(M.addText(t),t="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))t+=n[0];else{ +const t=w.classNameAliases[e]||e;M.addKeyword(n[0],t)}}else t+=n[0] +;e=v.keywordPatternRe.lastIndex,n=v.keywordPatternRe.exec(O)}var i +;t+=O.substring(e),M.addText(t)}function d(){null!=v.subLanguage?(()=>{ +if(""===O)return;let e=null;if("string"==typeof v.subLanguage){ +if(!n[v.subLanguage])return void M.addText(O) +;e=E(v.subLanguage,O,!0,S[v.subLanguage]),S[v.subLanguage]=e._top +}else e=x(O,v.subLanguage.length?v.subLanguage:null) +;v.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language) +})():g(),O=""}function u(e,n){let t=1;const i=n.length-1;for(;t<=i;){ +if(!e._emit[t]){t++;continue}const i=w.classNameAliases[e[t]]||e[t],r=n[t] +;i?M.addKeyword(r,i):(O=r,g(),O=""),t++}}function h(e,n){ +return e.scope&&"string"==typeof e.scope&&M.openNode(w.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(M.addKeyword(O,w.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +O=""):e.beginScope._multi&&(u(e.beginScope,n),O="")),v=Object.create(e,{parent:{ +value:v}}),v}function p(e,n,t){let r=((e,n)=>{const t=e&&e.exec(n) ;return t&&0===t.index})(e.endRe,t);if(r){if(e["on:end"]){const t=new i(e) ;e["on:end"](n,t),t.isMatchIgnored&&(r=!1)}if(r){ for(;e.endsParent&&e.parent;)e=e.parent;return e}} -if(e.endsWithParent)return f(e.parent,n,t)}function m(e){ -return 0===S.matcher.regexIndex?(k+=e[0],1):(C=!0,0)}function x(e){ -const n=e[0],i=t.substr(e.index),r=f(S,e,i);if(!r)return q;const a=S -;S.endScope&&S.endScope._wrap?(d(), -O.addKeyword(n,S.endScope._wrap)):S.endScope&&S.endScope._multi?(d(), -u(S.endScope,e)):a.skip?k+=n:(a.returnEnd||a.excludeEnd||(k+=n), -d(),a.excludeEnd&&(k=n));do{ -S.scope&&!S.isMultiClass&&O.closeNode(),S.skip||S.subLanguage||(M+=S.relevance), -S=S.parent}while(S!==r.parent) -;return r.starts&&p(r.starts,e),a.returnEnd?0:n.length}let y={};function w(n,a){ -const o=a&&a[0];if(k+=n,null==o)return d(),0 -;if("begin"===y.type&&"end"===a.type&&y.index===a.index&&""===o){ -if(k+=t.slice(a.index,a.index+1),!s){const n=Error(`0 width match regex (${e})`) -;throw n.languageName=e,n.badRule=y.rule,n}return 1} -if(y=a,"begin"===a.type)return(e=>{ +if(e.endsWithParent)return p(e.parent,n,t)}function b(e){ +return 0===v.matcher.regexIndex?(O+=e[0],1):(C=!0,0)}function f(e){ +const n=e[0],i=t.substring(e.index),r=p(v,e,i);if(!r)return ne;const a=v +;v.endScope&&v.endScope._wrap?(d(), +M.addKeyword(n,v.endScope._wrap)):v.endScope&&v.endScope._multi?(d(), +u(v.endScope,e)):a.skip?O+=n:(a.returnEnd||a.excludeEnd||(O+=n), +d(),a.excludeEnd&&(O=n));do{ +v.scope&&M.closeNode(),v.skip||v.subLanguage||(R+=v.relevance),v=v.parent +}while(v!==r.parent);return r.starts&&h(r.starts,e),a.returnEnd?0:n.length} +let m={};function y(n,a){const o=a&&a[0];if(O+=n,null==o)return d(),0 +;if("begin"===m.type&&"end"===a.type&&m.index===a.index&&""===o){ +if(O+=t.slice(a.index,a.index+1),!s){const n=Error(`0 width match regex (${e})`) +;throw n.languageName=e,n.badRule=m.rule,n}return 1} +if(m=a,"begin"===a.type)return(e=>{ const n=e[0],t=e.rule,r=new i(t),a=[t.__beforeBegin,t["on:begin"]] -;for(const t of a)if(t&&(t(e,r),r.isMatchIgnored))return m(n) -;return t.skip?k+=n:(t.excludeBegin&&(k+=n), -d(),t.returnBegin||t.excludeBegin||(k=n)),p(t,e),t.returnBegin?0:n.length})(a) +;for(const t of a)if(t&&(t(e,r),r.isMatchIgnored))return b(n) +;return t.skip?O+=n:(t.excludeBegin&&(O+=n), +d(),t.returnBegin||t.excludeBegin||(O=n)),h(t,e),t.returnBegin?0:n.length})(a) ;if("illegal"===a.type&&!r){ -const e=Error('Illegal lexeme "'+o+'" for mode "'+(S.scope||"")+'"') -;throw e.mode=S,e}if("end"===a.type){const e=x(a);if(e!==q)return e} +const e=Error('Illegal lexeme "'+o+'" for mode "'+(v.scope||"")+'"') +;throw e.mode=v,e}if("end"===a.type){const e=f(a);if(e!==ne)return e} if("illegal"===a.type&&""===o)return 1 ;if(I>1e5&&I>3*a.index)throw Error("potential infinite loop, way more iterations than matches") -;return k+=o,o.length}const _=E(e) -;if(!_)throw F(o.replace("{}",e)),Error('Unknown language: "'+e+'"') -;const N=Z(_);let A="",S=a||N;const v={},O=new g.__emitter(g);(()=>{const e=[] -;for(let n=S;n!==_;n=n.parent)n.scope&&e.unshift(n.scope) -;e.forEach((e=>O.openNode(e)))})();let k="",M=0,R=0,I=0,C=!1;try{ -for(S.matcher.considerAll();;){ -I++,C?C=!1:S.matcher.considerAll(),S.matcher.lastIndex=R -;const e=S.matcher.exec(t);if(!e)break;const n=w(t.substring(R,e.index),e) -;R=e.index+n}return w(t.substr(R)),O.closeAllNodes(),O.finalize(),A=O.toHTML(),{ -language:e,value:A,relevance:M,illegal:!1,_emitter:O,_top:S}}catch(n){ -if(n.message&&n.message.includes("Illegal"))return{language:e,value:G(t), -illegal:!0,relevance:0,_illegalBy:{message:n.message,index:R, -context:t.slice(R-100,R+100),mode:n.mode,resultSoFar:A},_emitter:O};if(s)return{ -language:e,value:G(t),illegal:!1,relevance:0,errorRaised:n,_emitter:O,_top:S} -;throw n}}function b(e,t){t=t||g.languages||Object.keys(n);const i=(e=>{ -const n={value:G(e),illegal:!1,relevance:0,_top:l,_emitter:new g.__emitter(g)} -;return n._emitter.addText(e),n})(e),r=t.filter(E).filter(y).map((n=>h(n,e,!1))) +;return O+=o,o.length}const w=N(e) +;if(!w)throw V(o.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const _=q(w);let A="",v=a||_;const S={},M=new l.__emitter(l);(()=>{const e=[] +;for(let n=v;n!==w;n=n.parent)n.scope&&e.unshift(n.scope) +;e.forEach((e=>M.openNode(e)))})();let O="",R=0,k=0,I=0,C=!1;try{ +for(v.matcher.considerAll();;){ +I++,C?C=!1:v.matcher.considerAll(),v.matcher.lastIndex=k +;const e=v.matcher.exec(t);if(!e)break;const n=y(t.substring(k,e.index),e) +;k=e.index+n} +return y(t.substring(k)),M.closeAllNodes(),M.finalize(),A=M.toHTML(),{ +language:e,value:A,relevance:R,illegal:!1,_emitter:M,_top:v}}catch(n){ +if(n.message&&n.message.includes("Illegal"))return{language:e,value:Y(t), +illegal:!0,relevance:0,_illegalBy:{message:n.message,index:k, +context:t.slice(k-100,k+100),mode:n.mode,resultSoFar:A},_emitter:M};if(s)return{ +language:e,value:Y(t),illegal:!1,relevance:0,errorRaised:n,_emitter:M,_top:v} +;throw n}}function x(e,t){t=t||l.languages||Object.keys(n);const i=(e=>{ +const n={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new l.__emitter(l)} +;return n._emitter.addText(e),n})(e),r=t.filter(N).filter(v).map((n=>E(n,e,!1))) ;r.unshift(i);const a=r.sort(((e,n)=>{ if(e.relevance!==n.relevance)return n.relevance-e.relevance -;if(e.language&&n.language){if(E(e.language).supersetOf===n.language)return 1 -;if(E(n.language).supersetOf===e.language)return-1}return 0})),[s,o]=a,c=s -;return c.secondBest=o,c}function p(e){let n=null;const t=(e=>{ +;if(e.language&&n.language){if(N(e.language).supersetOf===n.language)return 1 +;if(N(n.language).supersetOf===e.language)return-1}return 0})),[s,o]=a,g=s +;return g.secondBest=o,g}function y(e){let n=null;const t=(e=>{ let n=e.className+" ";n+=e.parentNode?e.parentNode.className:"" -;const t=g.languageDetectRe.exec(n);if(t){const n=E(t[1]) -;return n||(H(o.replace("{}",t[1])), -H("Falling back to no-highlight mode for this block.",e)),n?t[1]:"no-highlight"} -return n.split(/\s+/).find((e=>d(e)||E(e)))})(e);if(d(t))return -;w("before:highlightElement",{el:e,language:t -}),!g.ignoreUnescapedHTML&&e.children.length>0&&(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), -console.warn("https://github.com/highlightjs/highlight.js/issues/2886"), -console.warn(e)),n=e;const i=n.textContent,a=t?u(i,{language:t,ignoreIllegals:!0 -}):b(i);e.innerHTML=a.value,((e,n,t)=>{const i=n&&r[n]||t +;const t=l.languageDetectRe.exec(n);if(t){const n=N(t[1]) +;return n||(K(o.replace("{}",t[1])), +K("Falling back to no-highlight mode for this block.",e)),n?t[1]:"no-highlight"} +return n.split(/\s+/).find((e=>d(e)||N(e)))})(e);if(d(t))return +;if(S("before:highlightElement",{el:e,language:t +}),e.children.length>0&&(l.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),l.throwUnescapedHTML))throw new Q("One of your code blocks includes unescaped HTML.",e.innerHTML) +;n=e;const i=n.textContent,a=t?m(i,{language:t,ignoreIllegals:!0}):x(i) +;e.innerHTML=a.value,((e,n,t)=>{const i=n&&r[n]||t ;e.classList.add("hljs"),e.classList.add("language-"+i) })(e,t,a.language),e.result={language:a.language,re:a.relevance, relevance:a.relevance},a.secondBest&&(e.secondBest={ language:a.secondBest.language,relevance:a.secondBest.relevance -}),w("after:highlightElement",{el:e,result:a,text:i})}let f=!1;function m(){ -"loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(p):f=!0 -}function E(e){return e=(e||"").toLowerCase(),n[e]||n[r[e]]} -function x(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ -r[e.toLowerCase()]=n}))}function y(e){const n=E(e) -;return n&&!n.disableAutodetect}function w(e,n){const t=e;a.forEach((e=>{ +}),S("after:highlightElement",{el:e,result:a,text:i})}let w=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(l.cssSelector).forEach(y):w=!0 +}function N(e){return e=(e||"").toLowerCase(),n[e]||n[r[e]]} +function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +r[e.toLowerCase()]=n}))}function v(e){const n=N(e) +;return n&&!n.disableAutodetect}function S(e,n){const t=e;a.forEach((e=>{ e[t]&&e[t](n)}))} "undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ -f&&m()}),!1),Object.assign(e,{highlight:u,highlightAuto:b,highlightAll:m, -highlightElement:p, -highlightBlock:e=>($("10.7.0","highlightBlock will be removed entirely in v12.0"), -$("10.7.0","Please use highlightElement now."),p(e)),configure:e=>{g=X(g,e)}, +w&&_()}),!1),Object.assign(e,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:y, +highlightBlock:e=>(Z("10.7.0","highlightBlock will be removed entirely in v12.0"), +Z("10.7.0","Please use highlightElement now."),y(e)),configure:e=>{l=ee(l,e)}, initHighlighting:()=>{ -m(),$("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +_(),Z("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, initHighlightingOnLoad:()=>{ -m(),$("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +_(),Z("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") },registerLanguage:(t,i)=>{let r=null;try{r=i(e)}catch(e){ -if(F("Language definition for '{}' could not be registered.".replace("{}",t)), -!s)throw e;F(e),r=l} -r.name||(r.name=t),n[t]=r,r.rawDefinition=i.bind(null,e),r.aliases&&x(r.aliases,{ +if(V("Language definition for '{}' could not be registered.".replace("{}",t)), +!s)throw e;V(e),r=c} +r.name||(r.name=t),n[t]=r,r.rawDefinition=i.bind(null,e),r.aliases&&A(r.aliases,{ languageName:t})},unregisterLanguage:e=>{delete n[e] ;for(const n of Object.keys(r))r[n]===e&&delete r[n]}, -listLanguages:()=>Object.keys(n),getLanguage:E,registerAliases:x, -autoDetection:y,inherit:X,addPlugin:e=>{(e=>{ +listLanguages:()=>Object.keys(n),getLanguage:N,registerAliases:A, +autoDetection:v,inherit:ee,addPlugin:e=>{(e=>{ e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=n=>{ e["before:highlightBlock"](Object.assign({block:n.el},n)) }),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=n=>{ e["after:highlightBlock"](Object.assign({block:n.el},n))})})(e),a.push(e)} -}),e.debugMode=()=>{s=!1},e.safeMode=()=>{s=!0},e.versionString="11.0.0" -;for(const e in k)"object"==typeof k[e]&&t(k[e]);return Object.assign(e,k),e -})({}),Q=Object.freeze({__proto__:null,grmr_julia:e=>{ -var n="(?:[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF!]*)",t={$pattern:n, +}),e.debugMode=()=>{s=!1},e.safeMode=()=>{s=!0 +},e.versionString="11.6.0",e.regex={concat:b,lookahead:u,either:f,optional:p, +anyNumberOfTimes:h};for(const e in C)"object"==typeof C[e]&&t(C[e]) +;return Object.assign(e,C),e})({}),ie=Object.freeze({__proto__:null, +grmr_julia:e=>{ +const n="(?:[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF!]*)",t={ +$pattern:n, keyword:["baremodule","begin","break","catch","ccall","const","continue","do","else","elseif","end","export","false","finally","for","function","global","if","import","in","isa","let","local","macro","module","quote","return","true","try","using","where","while"], literal:["ARGS","C_NULL","DEPOT_PATH","ENDIAN_BOM","ENV","Inf","Inf16","Inf32","Inf64","InsertionSort","LOAD_PATH","MergeSort","NaN","NaN16","NaN32","NaN64","PROGRAM_FILE","QuickSort","RoundDown","RoundFromZero","RoundNearest","RoundNearestTiesAway","RoundNearestTiesUp","RoundToZero","RoundUp","VERSION|0","devnull","false","im","missing","nothing","pi","stderr","stdin","stdout","true","undef","\u03c0","\u212f"], type:["AbstractArray","AbstractChannel","AbstractChar","AbstractDict","AbstractDisplay","AbstractFloat","AbstractIrrational","AbstractMatrix","AbstractRange","AbstractSet","AbstractString","AbstractUnitRange","AbstractVecOrMat","AbstractVector","Any","ArgumentError","Array","AssertionError","BigFloat","BigInt","BitArray","BitMatrix","BitSet","BitVector","Bool","BoundsError","CapturedException","CartesianIndex","CartesianIndices","Cchar","Cdouble","Cfloat","Channel","Char","Cint","Cintmax_t","Clong","Clonglong","Cmd","Colon","Complex","ComplexF16","ComplexF32","ComplexF64","CompositeException","Condition","Cptrdiff_t","Cshort","Csize_t","Cssize_t","Cstring","Cuchar","Cuint","Cuintmax_t","Culong","Culonglong","Cushort","Cvoid","Cwchar_t","Cwstring","DataType","DenseArray","DenseMatrix","DenseVecOrMat","DenseVector","Dict","DimensionMismatch","Dims","DivideError","DomainError","EOFError","Enum","ErrorException","Exception","ExponentialBackOff","Expr","Float16","Float32","Float64","Function","GlobalRef","HTML","IO","IOBuffer","IOContext","IOStream","IdDict","IndexCartesian","IndexLinear","IndexStyle","InexactError","InitError","Int","Int128","Int16","Int32","Int64","Int8","Integer","InterruptException","InvalidStateException","Irrational","KeyError","LinRange","LineNumberNode","LinearIndices","LoadError","MIME","Matrix","Method","MethodError","Missing","MissingException","Module","NTuple","NamedTuple","Nothing","Number","OrdinalRange","OutOfMemoryError","OverflowError","Pair","PartialQuickSort","PermutedDimsArray","Pipe","ProcessFailedException","Ptr","QuoteNode","Rational","RawFD","ReadOnlyMemoryError","Real","ReentrantLock","Ref","Regex","RegexMatch","RoundingMode","SegmentationFault","Set","Signed","Some","StackOverflowError","StepRange","StepRangeLen","StridedArray","StridedMatrix","StridedVecOrMat","StridedVector","String","StringIndexError","SubArray","SubString","SubstitutionString","Symbol","SystemError","Task","TaskFailedException","Text","TextDisplay","Timer","Tuple","Type","TypeError","TypeVar","UInt","UInt128","UInt16","UInt32","UInt64","UInt8","UndefInitializer","UndefKeywordError","UndefRefError","UndefVarError","Union","UnionAll","UnitRange","Unsigned","Val","Vararg","VecElement","VecOrMat","Vector","VersionNumber","WeakKeyDict","WeakRef"] @@ -308,41 +318,41 @@ end:"}"},{begin:"(?<=[<>:]:\\s?"+n+"\\.)"+n},{begin:"(?<=[<>:]:\\s?)"+n},{ begin:n+"(?=\\."+n+"\\s?[<>]:)"},{begin:n+"(?=\\s?[<>]:)"}], contains:[a,r,"self"]},o={className:"",variants:[{begin:"(?<=where\\s+){", end:"}",excludeBegin:!0,excludeEnd:!0},{className:"type", -begin:"(?<=where\\s+)"+n}],contains:[s,a,r,{begin:n,className:"type"}]},l={ -className:"subst",begin:/\$\(/,end:/\)/,keywords:t},c={className:"variable", +begin:"(?<=where\\s+)"+n}],contains:[s,a,r,{begin:n,className:"type"}]},c={ +className:"subst",begin:/\$\(/,end:/\)/,keywords:t},l={className:"variable", begin:"\\$"+n},g=[{className:"number", begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/, relevance:0},{className:"string",variants:[{begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/ -},{begin:/'\\[rn\\$]'/}]},{className:"string",contains:[e.BACKSLASH_ESCAPE,l,c], +},{begin:/'\\[rn\\$]'/}]},{className:"string",contains:[e.BACKSLASH_ESCAPE,c,l], variants:[{begin:/r"""/,end:/"""\w*/,relevance:10,className:"regexp", contains:[e.BACKSLASH_ESCAPE]},{begin:/\w*"""/,end:/"""\w*/,relevance:10},{ begin:"```",end:"```"},{begin:/r"/,end:/"\w*/,className:"regexp", contains:[e.BACKSLASH_ESCAPE]},{begin:/\w*"/,end:/"\w*/},{begin:"`",end:"`"}]},{ className:"symbol",begin:"(?]+)\\s*)(:"+n+")"},{ className:"meta",begin:"@"+n}],d={className:"comment",variants:[{begin:"#=", -end:"=#",relevance:10},{begin:"#",end:"$"}]},u={className:"", +end:"=#",relevance:10},{begin:"#",end:"$"}]};var u={className:"", begin:"(\\b"+n+"({.*?}|\\.)?\\()",end:"\\)",returnBegin:!0,keywords:t, contains:[{begin:"\\b(ccall|new({.*?})?)(?=\\()",className:"keyword"},{ begin:"\\b"+n+"({.*?})?(?=\\.?\\()",className:"built_in"},...g,"self",s,a,r,d] },h={begin:"(?<="+n+"({.*?})?)\\(",end:"\\)",className:"",keywords:t, -contains:[...g,s,u,a,r,{begin:"(?]:\\s+"+n+"({.*})?)?))*", returnBegin:!0,contains:[{begin:"\\bfunction\\s",className:"keyword"},{ begin:"("+n+")(?=\\.)",end:"(?="+n+"({.*?})?\\()",contains:[{begin:"\\.", -className:"built_in"}]},{begin:n+"({.*?})?(?=\\()",className:"title"},h,o]},p={ +className:"built_in"}]},{begin:n+"({.*?})?(?=\\()",className:"title"},h,o]},b={ className:"",begin:n+"\\(.*\\)(\\s+where.*?)?\\s*(?==(?!=))",returnBegin:!0, contains:[{begin:n+"(?=\\()",className:"title"},h,o]},f={className:"class", variants:[{begin:"(?<=primitive[ \\t]+type[ \\t]+)"+n},{ begin:"(?<=abstract[ \\t]+type[ \\t]+)"+n+"({.*?})?"},{ begin:"(?<=(mutable[ \\t]+)?struct[ \\t]+)"+n+"({.*?})?"}]} -;return i.name="Julia",i.contains=[...g,b,p,u,f,o,s,d,e.HASH_COMMENT_MODE,{ +;return i.name="Julia",i.contains=[...g,p,b,u,f,o,s,d,e.HASH_COMMENT_MODE,{ className:"keyword", begin:"\\b(((abstract|primitive)[ \\t]+)type|(mutable[ \\t]+)?struct)\\b"},a,r,{ -begin:/<:/}],l.contains=i.contains,i},grmr_julia_repl:e=>({name:"Julia REPL", -contains:[{className:"meta",begin:/^julia>/,relevance:10,starts:{ -end:/^(?![ ]{6})/,subLanguage:"julia"},aliases:["jldoctest"]}]}), -grmr_matlab:e=>{var n={relevance:0,contains:[{begin:"('|\\.')+"}]};return{ +begin:/<:/}],c.contains=i.contains,i},grmr_julia_repl:e=>({name:"Julia REPL", +contains:[{className:"meta.prompt",begin:/^julia>/,relevance:10,starts:{ +end:/^(?![ ]{6})/,subLanguage:"julia"}}],aliases:["jldoctest"]}), +grmr_matlab:e=>{const n={relevance:0,contains:[{begin:"('|\\.')+"}]};return{ name:"Matlab",keywords:{ keyword:"arguments break case catch classdef continue else elseif end enumeration events for function global if methods otherwise parfor persistent properties return spmd switch try while", built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i|0 inf nan isnan isinf isfinite j|0 why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson max min nanmax nanmin mean nanmean type table readtable writetable sortrows sort figure plot plot3 scatter scatter3 cellfun legend intersect ismember procrustes hold num2cell " @@ -352,10 +362,17 @@ className:"params",variants:[{begin:"\\(",end:"\\)"},{begin:"\\[",end:"\\]"}]}] },{className:"built_in",begin:/true|false/,relevance:0,starts:n},{ begin:"[a-zA-Z][a-zA-Z_0-9]*('|\\.')+",relevance:0},{className:"number", begin:e.C_NUMBER_RE,relevance:0,starts:n},{className:"string",begin:"'",end:"'", -contains:[e.BACKSLASH_ESCAPE,{begin:"''"}]},{begin:/\]|\}|\)/,relevance:0, -starts:n},{className:"string",begin:'"',end:'"',contains:[e.BACKSLASH_ESCAPE,{ -begin:'""'}],starts:n -},e.COMMENT("^\\s*%\\{\\s*$","^\\s*%\\}\\s*$"),e.COMMENT("%","$")]}}});const Y=J -;for(const e of Object.keys(Q)){const n=e.replace("grmr_","").replace("_","-") -;Y.registerLanguage(n,Q[e])}return Y}() +contains:[{begin:"''"}]},{begin:/\]|\}|\)/,relevance:0,starts:n},{ +className:"string",begin:'"',end:'"',contains:[{begin:'""'}],starts:n +},e.COMMENT("^\\s*%\\{\\s*$","^\\s*%\\}\\s*$"),e.COMMENT("%","$")]}}, +grmr_diff:e=>{const n=e.regex;return{name:"Diff",aliases:["patch"],contains:[{ +className:"meta",relevance:10, +match:n.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/) +},{className:"comment",variants:[{ +begin:n.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/), +end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{ +className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/, +end:/$/}]}}});const re=te;for(const e of Object.keys(ie)){ +const n=e.replace("grmr_","").replace("_","-");re.registerLanguage(n,ie[e])} +return re}() ;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); \ No newline at end of file From 2779e420937ae0a441851f69d90c13a6c33b4d9c Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Mon, 24 Oct 2022 14:30:04 -0400 Subject: [PATCH 10/11] [NFC] Tweak alignment of examples and phrasings --- src/trial.jl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/trial.jl b/src/trial.jl index b6b0ecb..d56115d 100644 --- a/src/trial.jl +++ b/src/trial.jl @@ -30,18 +30,19 @@ DataSubset("events", Source{Events}, "/path/to/subset", "Subject [0-9]*/events/* ```julia-repl julia> labels = Dict( - :subject => r"(?<=Patient )\\d+", - :session => r"(?<=Session )\\d+", - :mvic => r"mvic_[rl](bic|tric)", # Defines possible MVIC "trial" names - ); + :subject => r"(?<=Patient )\\d+", + :session => r"(?<=Session )\\d+", + :mvic => r"mvic_[rl](bic|tric)", # Defines possible MVIC "trial" names + ); julia> # Only :subject and :session are required conditions (for matching existing trials) julia> conds = TrialConditions((:subject,:session,:mvic), labels; required=(:subject,:session,)); julia> # Note the DataSubset name matches the "condition" name in `labels` julia> subsets = [ - DataSubset("mvic", Source{C3DFile}, c3dpath, "Subject [0-9]*/Session [0-2]/*.c3d"; dependent=true) -]; + DataSubset("mvic", Source{C3DFile}, c3dpath, "Subject [0-9]*/Session [0-2]/*.c3d"; + dependent=true) + ]; julia> findtrials!(trials, subsets, conds) ``` @@ -74,7 +75,7 @@ end TrialConditions(conditions, labels; [required, types, defaults, subject_fmt]) Define the names of experimental `conditions` (aka factors) and the possible `labels` -within each condition. Conditions are determined from the absolute path of potential +within each condition. Conditions are searched for in the absolute path of potential sources. `subject` is a reserved condition name for the unique identifier (ID) of individual @@ -109,13 +110,14 @@ subjects/participants in the dataset. If `:subject` is not explicitly included i # Examples ```julia-repl julia> labels = Dict( - :subject => r"(?<=Patient )\\d+", - :group => ["Placebo" => "Control", "Group A", "Group B"], - :posture => r"(sit|stand)"i => lowercase, - :cue => r"cue[-_](fast|slow)" => s"\\\\1 cue" => r"(fast|slow) cue"); + :subject => r"(?<=Patient )\\d+", + :group => ["Placebo" => "Control", "Group A", "Group B"], + :posture => r"(sit|stand)"i => lowercase, + :cue => r"cue[-_](fast|slow)" => s"\\\\1 cue" => r"(fast|slow) cue" + ); julia> conds = TrialConditions((:subject,:group,:posture,:cue), labels; - types=Dict(:subject => Int)); + types=Dict(:subject => Int)); ``` """ struct TrialConditions @@ -236,7 +238,7 @@ end Characterizes a single instance of data collected from a specific `subject`. The Trial has a `name`, and may have one or more `conditions` which describe experimental conditions and/or -subject specific charateristics which are relevant to subsequent analyses. A Trial may have +subject specific characteristics which are relevant to subsequent analyses. A Trial may have one or more complementary `sources` of data (e.g. simultaneous recordings from separate equipment stored in separate files, supplementary data for a primary data source, etc). @@ -861,8 +863,8 @@ subdirectories, using the naming schema "\$trial.subject_\$srcname_\$basename(so ```julia-repl julia> export_trials(trials, pwd()) do trial, source - "\$(subject(trial))_\$(conditions(trial)[:group]).\$(srcext(source))" -end + "\$(subject(trial))_\$(conditions(trial)[:group]).\$(srcext(source))" + end ``` """ function export_trials(trials::Vector{<:Trial}, outdir, srcs=unique_sources(trials)) From 28e4793f3ea566e0b991f697be626ba8fbe8ccba Mon Sep 17 00:00:00 2001 From: Allen Hill Date: Mon, 24 Oct 2022 18:06:13 -0400 Subject: [PATCH 11/11] Improve docs --- docs/src/concepts/conditions.md | 10 +- docs/src/concepts/sources.md | 2 +- matlab/TrialConditions.m | 197 ++++++++++++++++---------------- 3 files changed, 107 insertions(+), 102 deletions(-) diff --git a/docs/src/concepts/conditions.md b/docs/src/concepts/conditions.md index 6c1c374..b1222f4 100644 --- a/docs/src/concepts/conditions.md +++ b/docs/src/concepts/conditions.md @@ -1,8 +1,8 @@ # TrialConditions A `TrialConditions` is a [type -[Julia]](../../julia-reference#DatasetManager.TrialConditions) or [class -[MATLAB]](../../matlab-reference#TrialConditions) that describes the names and possible +[Julia]](../../../julia-reference/#DatasetManager.TrialConditions) or [class +[MATLAB]](../../../matlab-reference/#TrialConditions) that describes the names and possible values for the experimental conditions (aka factors) and other characteristics (e.g. subject ID, etc) which are needed to describe and recognize multiple sources as being associated with a single, unique `Trial`. @@ -21,7 +21,7 @@ Therefore `TrialConditions` is designed to be capable of describing such complex **label**: - 1. A value of a condition + 1. A value of a condition, aka a level of a factor Example: `"control"` is a valid label for the condition "*group*" @@ -40,6 +40,10 @@ The simplest datasets can be described by listing all valid labels for each cond
``` +```@setup conditions +using DatasetManager +``` + ```@repl conditions labels = Dict( :subject => ["1", "2", "3", "4", "5"], diff --git a/docs/src/concepts/sources.md b/docs/src/concepts/sources.md index 6e4b2b2..1b353eb 100644 --- a/docs/src/concepts/sources.md +++ b/docs/src/concepts/sources.md @@ -3,7 +3,7 @@ A `Source` is a [type [Julia]](/julia-reference.html#DatasetManager.Source) or [class [MATLAB]](/matlab-reference.html#Source) that refers to the location of a source of data (typically a path to a file). DatasetManager normally assumes that sources contain -time-series data (e.g. in [`readsegment`](@ref), however this is not a requirement. +time-series data (e.g. in [`readsegment`](@ref), however this is not a requirement). Datasets often have more than one kind of source (e.g. if multiple systems were used to collect different kinds of data, such as EMG and motion capture). These different diff --git a/matlab/TrialConditions.m b/matlab/TrialConditions.m index cc05eb7..7726f30 100644 --- a/matlab/TrialConditions.m +++ b/matlab/TrialConditions.m @@ -1,118 +1,119 @@ classdef TrialConditions - % TRIALCONDITIONS Defines the names and levels of experimental conditions. - % - % See also TrialConditions.generate + % TRIALCONDITIONS Defines the names and levels of experimental conditions. + % + % See also TrialConditions.generate - properties - condnames(1,:) - required(1,:) - labels_rg - subst(:,2) - end + properties + condnames(1,:) + required(1,:) + labels_rg + subst(:,2) + end - methods - function obj = TrialConditions(condnames, required, labels_rg, subst) - if nargin > 0 - obj.condnames = condnames; - obj.required = required; - obj.labels_rg = labels_rg; - obj.subst = subst; - end - end + methods + function obj = TrialConditions(condnames, required, labels_rg, subst) + if nargin > 0 + obj.condnames = condnames; + obj.required = required; + obj.labels_rg = labels_rg; + obj.subst = subst; + end end + end - methods(Static) - function obj = generate(conditions, labels, varargin) - % GENERATE Define the names of experimental `conditions` (aka factors) and the - % possible `labels` within each condition. Conditions are determined from the - % absolute path of potential sources. - % - % trialconds = generate(conditions, labels) - % trialconds = generate(conditions, labels, Name, Value) - % - % # Input arguments - % - % - `conditions` is a cell array of condition names (eg `{'medication', 'dose'}`) - % in the order they must appear in the file paths of trial sources - % - `labels` is a struct with a field for each condition name (eg `isfield(labels, - % 'medication')`). Each condition field must have 'to' and 'from' fields which - % contain the final names and all the name possibilities, respectively. The 'from' - % field is optional if the terminology in the filenames is the desired terminology. - % - % # Name-value arguments - % - % - `'Required'` (defaults to all conditions): The conditions which every trial must - % have (in the case of some trials having optional/additional conditions). - % - `'Separator'` (defaults to `'[_-]'`): The character separating condition labels - % - % # Examples - % - % ```matlab - % labels.session(1).to = '\d'; - % labels.stim(1).to = 'stim'; - % labels.stim(2).to = 'placebo'; - % % or equivalently: - % labels.session.to = '\d'; - % labels.stim = struct('to', { 'stim'; 'placebo' }); - % - % conds = TrialConditions.generate({'session';'stim'}, labels) - % ``` + methods(Static) + function obj = generate(conditions, labels, varargin) + % GENERATE Define the names of experimental `conditions` (aka factors) and the + % possible `labels` (aka levels) within each condition. Conditions are determined from the + % absolute path of potential sources. + % + % trialconds = generate(conditions, labels) + % trialconds = generate(conditions, labels, Name, Value) + % + % # Input arguments + % + % - `conditions` is a cell array of condition names (eg `{'medication', 'dose'}`) + % in the order they must appear in the file paths of trial sources + % - `labels` is a struct with a field for each condition name (eg `isfield(labels, + % 'medication')`). Each condition must be a a struct with a 'to' field which + % contains a cell array of the acceptable labels. A 'from' field in the struct is used + % to match non-standard labels and convert to the standard form (e.g. typos, + % inconsistent capitalization, etc). + % + % # Name-value arguments + % + % - `'Required'` (defaults to all conditions): The conditions which every trial must + % have (in the case of some trials having optional/additional conditions). + % - `'Separator'` (defaults to `'[_-]'`): The character separating condition labels + % + % # Examples + % + % ```matlab + % labels.session(1).to = '\d'; + % labels.stim(1).to = 'stim'; + % labels.stim(2).to = 'placebo'; + % % or equivalently: + % labels.session.to = '\d'; + % labels.stim = struct('to', { 'stim'; 'placebo' }); + % + % conds = TrialConditions.generate({'session';'stim'}, labels) + % ``` - p = inputParser; - addRequired(p, 'conditions', @iscell) - addRequired(p, 'labels', @isstruct) - addParameter(p, 'Required', conditions, @iscell) - addParameter(p, 'Separator', '[_-]', @ischar) + p = inputParser; + addRequired(p, 'conditions', @iscell) + addRequired(p, 'labels', @isstruct) + addParameter(p, 'Required', conditions, @iscell) + addParameter(p, 'Separator', '[_-]', @ischar) - parse(p, conditions, labels, varargin{:}) - required = p.Results.Required; - sep = strcat(p.Results.Separator, '?'); + parse(p, conditions, labels, varargin{:}) + required = p.Results.Required; + sep = strcat(p.Results.Separator, '?'); - labels_rg = ''; - subst = cell(0,2); + labels_rg = ''; + subst = cell(0,2); - for condi = 1:length(conditions) - cond = conditions{condi}; + for condi = 1:length(conditions) + cond = conditions{condi}; - labels_rg = strcat(labels_rg, '(?<', cond, '>'); + labels_rg = strcat(labels_rg, '(?<', cond, '>'); - if ~isfield(labels, cond) - error('Error: ''%s'' was not found in ''labels''', cond) - end - if ~isfield(labels.(cond), 'to') - error('Error: ''to'' was not found in ''labels.%s''', cond) - end + if ~isfield(labels, cond) + error('Error: ''%s'' was not found in ''labels''', cond) + end + if ~isfield(labels.(cond), 'to') + error('Error: ''to'' was not found in ''labels.%s''', cond) + end - labels_rg = strcat(labels_rg, strjoin({ labels.(cond).('to') }, '|')); + labels_rg = strcat(labels_rg, strjoin({ labels.(cond).('to') }, '|')); - optchar = '?'; + optchar = '?'; - if condi == length(conditions) - SEP = ''; - else - SEP = sep; - end + if condi == length(conditions) + SEP = ''; + else + SEP = sep; + end - labels_rg = strcat(labels_rg, ')', optchar, SEP); + labels_rg = strcat(labels_rg, ')', optchar, SEP); - for i = 1:length(labels.(cond)) - condpair = labels.(cond); - if isfield(condpair(i), 'from') - condlabel = condpair(i).('from'); - if ~isempty(condlabel) - if isa(condlabel, 'char') - altlabels = {condlabel}; - else - altlabels = condlabel; - end - subst = [ subst; { strcat('(?:', strjoin(altlabels, '|'), ')'), ... - condpair(i).('to') } ]; - end - end - end + for i = 1:length(labels.(cond)) + condpair = labels.(cond); + if isfield(condpair(i), 'from') + condlabel = condpair(i).('from'); + if ~isempty(condlabel) + if isa(condlabel, 'char') + altlabels = {condlabel}; + else + altlabels = condlabel; + end + subst = [ subst; { strcat('(?:', strjoin(altlabels, '|'), ')'), ... + condpair(i).('to') } ]; end - - obj = TrialConditions(conditions, required, labels_rg, subst); + end end + end + + obj = TrialConditions(conditions, required, labels_rg, subst); end + end end