Derived(parameters::Vector{Union{String,Symbol}}, function::Function) Derived(parameter::Union{String,Symbol}, function::Function)

Wrap the name(s) of a parameter(s) and a function. After the possible parameter combinations are created, dict_list will replace instances of Derived by the result of the function func, evaluated with the value of the parameter(s).


julia> p = Dict(:α => [1, 2],
           :solver => ["SolverA","SolverB"],
           :β => Derived(:α, x -> x^2),
Dict{Symbol, Any} with 3 entries:
  :α      => [1, 2]
  :solver => ["SolverA", "SolverB"]
  :β      => Derived{Symbol}(:α, #51)

julia> dict_list(p)
4-element Vector{Dict{Symbol, Any}}:
 Dict(:α => 1, :solver => "SolverA", :β => 1)
 Dict(:α => 2, :solver => "SolverA", :β => 4)
 Dict(:α => 1, :solver => "SolverB", :β => 1)
 Dict(:α => 2, :solver => "SolverB", :β => 4)

A vector of parameter names can also be passed when the accompanying function uses multiple arguments:

 julia> p2 = Dict(:α => [1, 2],
           :β => [10,100],
           :solver => ["SolverA","SolverB"],
           :γ => Derived([:α,:β], (x,y) -> x^2 + 2y),
Dict{Symbol, Any} with 4 entries:
  :α      => [1, 2]
  :γ      => Derived{Symbol}([:α, :β], #7)
  :solver => ["SolverA", "SolverB"]
  :β      => [10, 100]

julia> dict_list(p2)
8-element Vector{Dict{Symbol, Any}}:
 Dict(:α => 1, :γ => 21, :solver => "SolverA", :β => 10)
 Dict(:α => 2, :γ => 24, :solver => "SolverA", :β => 10)
 Dict(:α => 1, :γ => 21, :solver => "SolverB", :β => 10)
 Dict(:α => 2, :γ => 24, :solver => "SolverB", :β => 10)
 Dict(:α => 1, :γ => 201, :solver => "SolverA", :β => 100)
 Dict(:α => 2, :γ => 204, :solver => "SolverA", :β => 100)
 Dict(:α => 1, :γ => 201, :solver => "SolverB", :β => 100)
 Dict(:α => 2, :γ => 204, :solver => "SolverB", :β => 100)
access(c, key)

Access c with given key. For AbstractDict this is getindex, for anything else it is getproperty.

access(c, keys...)

When given multiple keys, access is called recursively, i.e. access(c, key1, key2) = access(access(c, key1), key2) and so on. For example, if c, c.k1 are NamedTuples then access(c, k1, k2) == c.k1.k2.


Please only extend the single key method when customizing access for your own Types.


Return all the keys c can be accessed using access. For dictionaries/named tuples this is keys(c), for everything else it is fieldnames(typeof(c)).

checktagtype!(d::AbstractDict{K,T}) where {K<:Union{Symbol,String},T}

Check if the value type of d allows String and promote it to do so if not.

default_expand(c) = String[]

Keys that should be expanded in their savename within savename. Must be Vector{String} (as all keys are first translated into strings inside savename).

default_prefix(c) = ""

Return the prefix that will be used by default in savename.

Notice that if default_prefix is defined for c but a prefix is also given to savename then the two values are merged via joinpath for convenience (if they are not the same of course).

E.g. defining default_prefix(c::MyType) = "lala" and calling

savename(datadir(), mytype)

will in fact return a string that looks like


This allows savename to work well with produce_or_load.

dict2ntuple(dict) -> ntuple

Convert a dictionary (with Symbol or String as key type) to a NamedTuple.


Expand the dictionary c into a vector of dictionaries. Each entry has a unique combination from the product of the Vector values of the dictionary while the non-Vector values are kept constant for all possibilities. The keys of the entries are the same.

Whether the values of c are iterable or not is of no concern; the function considers as "iterable" only subtypes of Vector.

To restrict some values in the dictionary so that they only appear in the resulting dictionaries, if a certain condition is met, the macro @onlyif can be used on those values.

To compute some parameters on creation of dict_list as a function of other specified parameters, use the type Derived.

Use the function dict_list_count to get the number of dictionaries that dict_list will produce.


julia> c = Dict(:a => [1, 2], :b => 4);

julia> dict_list(c)
2-element Array{Dict{Symbol,Int64},1}:

julia> c[:model] = "linear"; c[:run] = ["bi", "tri"];

julia> dict_list(c)
4-element Array{Dict{Symbol,Any},1}:

julia> c[:e] = [[1, 2], [3, 5]];

julia> dict_list(c)
8-element Array{Dict{Symbol,Any},1}:
 Dict(:a=>1,:b=>4,:run=>"bi",:e=>[1, 2],:model=>"linear")
 Dict(:a=>2,:b=>4,:run=>"bi",:e=>[1, 2],:model=>"linear")
 Dict(:a=>1,:b=>4,:run=>"tri",:e=>[1, 2],:model=>"linear")
 Dict(:a=>2,:b=>4,:run=>"tri",:e=>[1, 2],:model=>"linear")
 Dict(:a=>1,:b=>4,:run=>"bi",:e=>[3, 5],:model=>"linear")
 Dict(:a=>2,:b=>4,:run=>"bi",:e=>[3, 5],:model=>"linear")
 Dict(:a=>1,:b=>4,:run=>"tri",:e=>[3, 5],:model=>"linear")
 Dict(:a=>2,:b=>4,:run=>"tri",:e=>[3, 5],:model=>"linear")

Example using Derived

julia> p = Dict(:α => [1, 2],
           :solver => ["SolverA","SolverB"],
           :β => Derived(:α, x -> x^2),
Dict{Symbol, Any} with 3 entries:
  :α      => [1, 2]
  :solver => ["SolverA", "SolverB"]
  :β      => Derived{Symbol}(:α, #51)

julia> dict_list(p)
4-element Vector{Dict{Symbol, Any}}:
 Dict(:α => 1, :solver => "SolverA", :β => 1)
 Dict(:α => 2, :solver => "SolverA", :β => 4)
 Dict(:α => 1, :solver => "SolverB", :β => 1)
 Dict(:α => 2, :solver => "SolverB", :β => 4)
dict_list_count(c) -> N

Return the number of dictionaries that will be created by calling dict_list(c).


Transform a Tuple of Symbol and assignments (a=b) into a dictionary where each Symbol in vars defines a key-value pair. The value is obtained by evaluating the Symbol in the macro calling environment.

This should only be called when producing an expression intended to be returned by a macro.

findproject(dir = pwd()) -> project_path

Recursively search dir and its parents for a valid Julia project file (anything in Base.project_names). If it is found return its path, otherwise issue a warning and return nothing.

The function stops searching if it hits either the home directory or the root directory.

get_rawtype(D::DataType) = getproperty(parentmodule(D), nameof(D))

Return Concrete DataType from an AbstractDict D. Found online at: https://discourse.julialang.org/t/retrieve-the-type-of-abstractdict-without-parameters-from-a-concrete-dictionary-type/67567/3

gitdescribe(gitpath = projectdir(); dirty_suffix = "-dirty") -> gitstr

Return a string gitstr with the output of git describe if an annotated git tag exists, otherwise the current active commit id of the Git repository present in gitpath, which by default is the currently active project. If the repository is dirty when this function is called the string will end with dirty_suffix.

Return nothing if gitpath is not a Git repository, i.e. a directory within a git repository.

The format of the git describe output in general is


If the latest tag is v1.2.3 and there are 5 additional commits while the latest commit hash is 334a0f225d9fba86161ab4c8892d4f023688159c, the output will be v1.2.3-5-g334a0f. Notice that git will shorten the hash if there are no ambiguous commits.

More information about the git describe output can be found on (https://git-scm.com/docs/git-describe)

See also tag!.


julia> gitdescribe() # a tag exists

julia> gitdescribe() # a tag doesn't exist

julia> gitdescribe(path_to_a_dirty_repo)
gitpatch(gitpath = projectdir())

Generates a patch describing the changes of a dirty repository compared to its last commit; i.e. what git diff HEAD produces. The gitpath needs to point to a directory within a git repository, otherwise nothing is returned.

Be aware that gitpatch needs a working installation of Git, that can be found in the current PATH.

initialize_project(path [, name]; kwargs...)

Initialize a scientific project expected by DrWatson in path (directory representing an empty folder). If name is not given, it is assumed to be the folder's name.

The new project remains activated for you to immediately add packages.


  • readme = true : adds a README.md file.

  • authors = nothing : if a string or container of strings, adds the authors in the Project.toml file and README.md.

  • force = false : If the path is not empty then throw an error. If however force is true then recursively delete everything in the path and create the project.

  • git = true : Make the project a Git repository.

  • add_test = true : Add some additional files for testing the project. This is done automatically during continuous integration (if hosted on GitHub), or manually by running the contents of the test/runtests.jl file.

  • add_docs = false : Add some additional files for generating documentation for the project, which can be generated locally by running docs/make.jl but is also generated and hosted during continuous integration using Documenter.jl (if hosted on GitHub). If this option is enabled, Documenter also becomes a dependency of the project.

    To host the docs online, set the keyword github_name with the name of the GitHub account you plan to upload at, and then manually enable the gh-pages deployment by going to settings/pages of the GitHub repo, and choosing as "Source" the gh-pages branch.

    Typically, a full documentation is not necessary for most projects, because README.md can serve as the documentation, hence this feature is false by default.

  • template = DrWatson.DEFAULT_TEMPLATE : A template containing the folder structure of the project. It should be a vector containing strings (folders) or pairs of String => Vector{String}, containg a folder and subfolders (this can be nested further). Example:

      "data" => ["sims", "exp_raw", "exp_pro"],

    Obviously, the default derivative functions of projectdir, such as datadir, have been written with the default template in mind.

  • placeholder = false : Add "hidden" placeholder files in each default folder to ensure that project folder structure is maintained when the directory is cloned (because empty folders are not pushed to a remote). Only used when git = true.

  • folders_to_gitignore = ["data", "videos","plots","notebooks","_research"] : Folders to include in the created .gitignore

isdirty(gitpath = projectdir()) -> Bool

Return true if gitpath is the path to a dirty Git repository, false otherwise.

Note that unlike tag!, isdirty can error (for example, if the path passed to it doesn't exist, or isn't a Git repository). The purpose of isdirty is to be used as a check before running simulations, for users that do not wish to tag data while having a dirty git repo.

istaggable(file::AbstractStrig) → bool

Return true if the file save format (file ending) is "taggable", i.e. allows adding additional data fields as strings. Currently endings that can do this are:

("bson", "jld", "jld2")
istaggable(x) = x isa AbstractDict

For non-string input the function just checks if input is dictionary.

keyname(d::AbstractDict{K,T}, key) where {K<:Union{Symbol,String},T}

Check the key type of d and convert key to the appropriate type.

lookup_candidate(original_dict, d, name)

If name is a key name from original_dict either return it's value from d or throw a KeyDeletedFromDictError error if it's not in d.

ntuple2dict([type = Dict,] nt) -> dict

Convert a NamedTuple to a dictionary.


Try parsing str with the types given in types. The first working parse is returned. Fallback is String ie. str is returned.

parse_savename(filename::AbstractString; kwargs...)

Try to convert a shorthand name produced with savename into a dictionary containing the parameters and their values, a prefix and suffix string. Return prefix, parameters, suffix.

Parsing the key-value parts of filename is performed under the assumption that the value is delimited by = and the closest connector. This allows the user to have connector (eg. _) in a key name (variable name) but not in the value part.


  • connector = "_" : string used to connect the various entries.
  • parsetypes = (Int, Float64) : tuple used to define the types which should be tried when parsing the values given in filename. Fallback is String.


Receive an array of parameter dictionaries, and for each one, evaluate the computed parameters after the possible combination of parameters has been created.

produce_or_load(f::Function, config, path = ""; kwargs...) -> data, file

The goal of produce_or_load is to avoid running some data-producing code that has already been run with a given configuration container config. If the output of some function f(config) exists on disk, produce_or_load will load it and return it, and if not, it will produce it, save it, and then return it.

Here is how it works:

  1. The output data are saved in a file named name = filename(config). I.e., the output file's name is created from the configuration container config. By default, this is name =savename(config), but can be configured differently, using e.g. hash, see keyword filename below. See also produce_or_load with hash codes for an example where config would be hard to put into name with savename, and hash is used instead.
  2. Now, let file = joinpath(path, name).
  3. If file exists, load it and return the contained data, along with the global path that it is saved at (file).
  4. If the file does not exist then call data = f(config), with f your function that produces your data from the configuration container.
  5. Then save the data as file and then return data, file.

The function f should return a string-keyed dictionary if the data are saved in the default format of JLD2.jl., the macro @strdict can help with that.

You can use a do-block instead of defining a function to pass in. For example,

produce_or_load(config, path) do config
    # code using `config` runs here
    # and then returns a dictionary to be saved


Name deciding

  • filename::Union{Function, String} = savename : Configures the name of the file to produce or load given the configuration container. It may be a one-argument function of config, savename by default, so that name = filename(config). Useful alternative to savename is hash. The keyword filename could also be a String directly, possibly extracted from config before calling produce_or_load, in which case name = filename.
  • suffix = "jld2", prefix = default_prefix(config) : If not empty, added to name as name = prefix*'_'*name*'.'*suffix (i.e., like in savename).


  • tag::Bool = DrWatson.readenv("DRWATSON_TAG", istaggable(suffix)) : Save the file using tagsave if true (which is the default).
  • gitpath, storepatch : Given to tagsave if tag is true.
  • force = false : If true then don't check if file exists and produce it and save it anyway.
  • loadfile = true : If false, this function does not actually load the file, but only checks if it exists. The return value in this case is always nothing, file, regardless of whether the file exists or not. If it doesn't exist it is still produced and saved.
  • verbose = true : print info about the process, if the file doesn't exist.
  • wsave_kwargs = Dict() : Keywords to pass to wsave (e.g. to enable compression).

Return the directory of the currently active project.

projectdir(args...) = joinpath(projectdir(), args...)

Join the path of the currently active project with args (typically other subfolders).

quickactivate(path [, name::String])

Activate the project found by recursively searching the path and its parents for a valid Julia project file via the findproject function. Optionally check if name is the same as the activated project's name. If it is not, throw an error. See also @quickactivate. Do nothing if the project found is already active, or if no project file is found.


using DrWatson
quickactivate("path/to/project", "Best project in the WOLRLD")

Notice that this function is first activating the project and then checking if it matches the name.


Note that to access quickactivate you need to be using DrWatson. For this to be possible DrWatson must be already added in the existing global environment. If you use quickactivate and share your project, do note to your co-workers that they need to add DrWatson globally (the default README.md created by initialize_project says this automatically).

In addition, in your scripts write:

using DrWatson # YES
using Package1, Package2
# do stuff

instead of the erroneous:

using DrWatson, Package1, Package2 # NO!
# do stuff

This ensures that the packages you use will all have the versions dictated by your activated project (besides DrWatson, since this is impossible to do using quickactivate).


Run cmd synchronously and capture stdout, stdin and a possible error exception. Return a NamedTuple with the fields exception, out and err.

readenv(var, default::T)

Try to read the environment variable var and parse it as type T. If that fails, return default.

safesave(filename, data...; kwargs...)

Safely save data in filename by ensuring that no existing files are overwritten. Do this by renaming already existing data with a backup-number ending like #1, #2, .... For example if filename = test.jld2, the first time you safesave it, the file is saved normally. The second time the existing save is renamed to test_#1.jld2 and a new file test.jld2 is then saved.

If a backup file already exists then its backup-number is incremented (e.g. going from #2 to #3). For example safesaving test.jld2 a third time will rename the old test_#1.jld2 to test_#2.jld2, rename the old test.jld2 to test_#1.jld2 and then save a new test.jld2 with the latest data.

Any additional keyword arguments are passed through to wsave (to e.g. enable compression).

See also tagsave.

savename([prefix,], c [, suffix]; kwargs...)

Create a shorthand name, commonly used for saving a file or as a figure title, based on the parameters in the container c (Dict, NamedTuple or any other Julia composite type). If provided use the prefix and end the name with .suffix (i.e. you don't have to include the . in your suffix).

The function chains keys and values into a string of the form:


while the keys are sorted alphabetically by default. If you provide the prefix/suffix the function will do:


assuming you chose the default connector, see below. Notice that prefix should not contain path separators to avoid compatibility issues on different operating systems. For constructing paths, use the *dir() methods or joinpath with savename() as the last parameter. See default_prefix for more.

savename can be conveniently combined with @dict or @ntuple. See also parse_savename and @savename.

Standard keywords

  • sort = true : Indicate whether the pairs are sorted alphabetically by keys. If not, they are sorted by the order of accesses. WARNING: the default accesses is not deterministic for Dict inputs.
  • digits = nothing, sigdigits = 3 : Floating point values are rounded using the round function with these keywords.
  • connector = "_" : string used to connect the various entries.

Customization keywords

  • allowedtypes = default_allowed(c) : Only values of type subtyping anything in allowedtypes are used in the name. By default this is (Real, String, Symbol, TimeType).
  • accesses = allaccess(c) : specify which specific keys you want to use with the keyword accesses. By default this is all possible keys c can be accessed with, see allaccess.
  • ignores = allignore(c) : You can also specify keys that you want to ignore with the keyword ignores. By default this is an empty tuple, see allignore. (keys in ignore are ignored even if they are in accesses)
  • val_to_string = nothing : If not nothing, this is a function that converts any given value to a string representation, and allows for custom formatting. If given, digits, sigidigits are ignored.
  • expand::Vector{String} = default_expand(c) : keys that will be expanded to the savename of their contents, to allow for nested containers. By default is empty. Notice that the type of the container must also be allowed in allowedtypes for expand to take effect! The savename of the nested arguments is always called with its default arguments (so customization here is possible only by rolling your own container type). Containers leading to empty savename are skipped.
  • equals = "=" : Connector between name and value. Can be useful to modify for adding space " = ".


d = (a = 0.153456453, b = 5, mode = "double")
savename(d; digits = 4) == "a=0.1535_b=5_mode=double"
savename("n", d) == "n_a=0.153_b=5_mode=double"
savename(d, "n") == "a=0.153_b=5_mode=double.n"
savename("n", d, "n"; connector = "-") == "n-a=0.153-b=5-mode=double.n"
savename(d, allowedtypes = (String,)) == "mode=double"
savename(d, connector=" | ", equals=" = ") == "a = 0.153 | b = 5 | mode = double"

rick = (never = "gonna", give = "you", up = "!");
savename(rick) == "give=you_never=gonna_up=!" # keys are sorted!
savename(rick; ignores = ["up"]) == "give=you_never=gonna"
scripttag!(d::AbstractDict{K,T}, source::LineNumberNode; gitpath = projectdir(), force = false) where {K<:Union{Symbol,String},T}

Include a script field in d, containing the source file and line number in source. Do nothing if the field is already present unless force = true. Uses gitpath to make the source file path relative.

struct2dict([type = Dict,] s) -> d

Convert a Julia composite type s to a dictionary d with key type Symbol that maps each field of s to its value. Simply passing s will return a regular dictionary. This can be useful in e.g. saving:

tagsave(savename(s), struct2dict(s))
tag!(d::AbstractDict; kwargs...) -> d

Tag d by adding an extra field gitcommit which will have as value the gitdescribe of the repository at gitpath (by default the project's gitpath). Do nothing if a key gitcommit already exists (unless force=true then replace with the new value) or if the Git repository is not found. If the git repository is dirty, i.e. there are un-commited changes, and storepatch is true, then the output of git diff HEAD is stored in the field gitpatch. Note that patches for binary files are not stored. You can use isdirty to check if a repo is dirty. If the commit message is set to true, then the dictionary d will include an additional field "gitmessage" and will contain the git message associated with the commit.

Notice that the key-type of the dictionary must be String or Symbol. If String is a subtype of the value type of the dictionary, this operation is in-place. Otherwise a new dictionary is created and returned.

To restore a repository to the state of a particular model-run do:

  1. checkout the relevant commit with git checkout xyz where xyz is the value stored
  2. apply the patch git apply patch, where the string stored in the gitpatch field needs to be written to the file patch.


  • gitpath = projectdir()
  • force = false
  • storepatch = DrWatson.readenv("DRWATSON_STOREPATCH", false): Whether to collect and store the output of gitpatch as well.


julia> d = Dict(:x => 3, :y => 4)
Dict{Symbol,Int64} with 2 entries:
  :y => 4
  :x => 3

julia> tag!(d; commit_message=true)
Dict{Symbol,Any} with 3 entries:
  :y => 4
  :gitmessage => "File set up by DrWatson"
  :gitcommit => "96df587e45b29e7a46348a3d780db1f85f41de04"
  :x => 3
tagsave(file::String, d::AbstractDict; kwargs...)

First tag! dictionary d and then save d in file.

"Tagging" means that when saving the dictionary, an extra field :gitcommit is added to establish reproducibility of results using Git. If the Git repository is dirty and storepatch=true, one more field :gitpatch is added that stores the difference string. If a dictionary already contains a key :gitcommit, it is not overwritten, unless force=true. For more details, see tag!.

Keywords gitpath, storepatch, force, are propagated to tag!. Any additional keyword arguments are propagated to wsave, to e.g. enable compression.

The keyword safe = DrWatson.readenv("DRWATSON_SAFESAVE", false) decides whether to save the file using safesave.

tmpsave(dicts::Vector{Dict} [, tmp]; kwargs...) -> r

Save each entry in dicts into a unique temporary file in the directory tmp. Then return the list of file names (relative to tmp) that were used for saving each dictionary. Each dictionary can then be loaded back by calling

wload(nth_tmpfilename, "params")

tmp defaults to projectdir("_research", "tmp").

See also dict_list.


  • l = 8 : number of characters in the random string.
  • prefix = "" : prefix each temporary name will have.
  • suffix = "jld2" : ending of the temporary names (no need for the dot).
  • kwargs... : Any additional keywords are passed through to wsave (e.g. compression).

Change a dictionary with key type Symbol to have key type String.


Change a dictionary with key type String to have key type Symbol.


Return a dict where nested @@onlyif vectors are removed. This is necessary, because @onlyif automatically broadcasts vector arguments. In a case like this:

   :b => [@onlyif(:a==10,[10,11]), [12,13]]

Broadcasting is obviously not wanted as :b should retain it's type of Vector{Int}.


Convert val to a string with the smallest possible representation of val that allows recovering val from valtostring(val).

wsave(filename, data...; kwargs...)

Save data at filename by first creating the appropriate paths. Default fallback is FileIO.save. Extend this for your types by extending DrWatson._wsave(filename, data::YourType, args...; kwargs...).

@dict vars...

Create a dictionary out of the given variables that has as keys the variable names and as values their values.

Notice: @dict a b is the correct way to call the macro. @dict a, b is incorrect. If you want to use commas you have to do @dict(a, b).


julia> ω = 5; χ = "test"; ζ = π/3;

julia> @dict ω y=χ ζ
Dict{Symbol,Any} with 3 entries:
  :ω => 5
  :y => "test"
  :ζ => 1.0472
@ntuple vars...

Create a NamedTuple out of the given variables that has as keys the variable names and as values their values.


julia> ω = 5; χ = "test"; ζ = 3.14;

julia> @ntuple ω χ π=ζ
(ω = 5, χ = "test", π = 3.14)
@onlyif(ex, value)

Tag value to only appear in a dictionary created with dict_list if the Julia expression ex (see below) is evaluated as true. If value is a subtype of Vector, @onlyif is applied to each entry. Since @onlyif is applied to a value and not to a dictionary key, it is possible to restrict only some of the values of a vector. This means that based on ex the number of options for a particular key varies.

Within ex it is possible to extract values of the dictionary passed to dict_list by a shorthand notation where only the key must be provided. For example ex = :(:N == 1) is tranformed in the call dict_list(d) to an expression analogous to :(d[:N] == 1) by using the function lookup_candidate. This is supported for Symbol and String keys.


julia> d = Dict(:a => [1, 2], :b => 4, :c => @onlyif(:a == 1, [10, 11]));

julia> dict_list(d) # only in case `:a` is `1` the dictionary will get key `:c`
3-element Array{Dict{Symbol,Int64},1}:
 Dict(:a => 1,:b => 4,:c => 10)
 Dict(:a => 1,:b => 4,:c => 11)
 Dict(:a => 2,:b => 4)

 julia> d = Dict(:a => [1, 2], :b => 4, :c => [10, @onlyif(:a == 1, 11)]);

 julia> dict_list(d) # only in case `:a` is `1` the dictionary will get extra value `11` for key `:c`
 3-element Array{Dict{Symbol,Int64},1}:
 Dict(:a => 1,:b => 4,:c => 10)
 Dict(:a => 1,:b => 4,:c => 11)
 Dict(:a => 2,:b => 4,:c => 10)

See the Defining parameter sets with restrictions section for more examples.

@produce_or_load(f, config, path; kwargs...)

Same as produce_or_load but one more field :script is added that records the local path of the script and line number that called @produce_or_load, see @tag!.

Notice that path here is mandatory in contrast to produce_or_load.


Equivalent with quickactivate(@__DIR__).

@quickactivate name::String

Equivalent with quickactivate(@__DIR__, name).

Notice that since @quickactivate is a macro, standard caveats apply when using Distributed computing. Specifically, you need to import DrWatson and use @quickactivate in different begin blocks as follows:

using Distributed
@everywhere using DrWatson

@everywhere begin
    @quickactivate "TestEnv"
    using Distributions, ...
    # remaining imports
Usage in Pluto.jl

Pluto.jl understands the @quickactivate macro and will switch to using the standard Julia package manager once it encounters it (or quickactivate). But, because @quickactivate is a macro it needs to be executed in a new cell, after using DrWatson. I.e., you need to split

    using DrWatson
    @quickactivate "Whatever"

to two different cells:

using DrWatson
@quickcativate "Whatever"
@quickactivate ProjectName::Symbol

If given a Symbol then first quickactivate(@__DIR__, string(ProjectName)), and then do using ProjectName, as if the symbol was representing a module name.

This ties with Making your project a usable module functionality, see the docs for an example.

@savename vars...

Convenient combination of chaining a call to @dict on vars and savename.


julia> a = 0.153456453; b = 5.0; mode = "double"
julia> @savename a b mode
@tag!(d, gitpath = projectdir(), storepatch = true, force = false) -> d

Do the same as tag! but also add another field script that has the path of the script that called @tag!, relative with respect to gitpath. The saved string ends with #line_number, which indicates the line number within the script that @tag! was called at.


julia> d = Dict(:x => 3)Dict{Symbol,Int64} with 1 entry:
  :x => 3

julia> @tag!(d) # running from a script or inline evaluation of Juno
Dict{Symbol,Any} with 3 entries:
  :gitcommit => "618b72bc0936404ab6a4dd8d15385868b8299d68"
  :script => "test\stools_tests.jl#10"
  :x      => 3
@tagsave(file::String, d::AbstractDict; kwargs...)

Same as tagsave but one more field :script is added that records the local path of the script and line number that called @tagsave, see @tag!.