├── VERSION ├── .matlab_version ├── docs-src-mkdocs ├── mkdocs.yml ├── make_doc ├── docs │ └── index.md └── make_doc.m ├── .mlproject.json ├── examples ├── helloworld.m └── .gitkeep ├── src ├── c │ └── .gitkeep └── java │ └── myproject-java │ ├── src │ └── main │ │ └── java │ │ └── com │ │ └── example │ │ └── mypackage │ │ └── HelloWorld.java │ └── pom.xml ├── doc ├── examples │ ├── .gitkeep │ └── helloworld.m └── index.html ├── docs ├── examples │ ├── helloworld.m │ └── .gitkeep ├── make_doc ├── make_doc.m └── index.html ├── lib ├── java │ └── .gitkeep └── matlab │ └── .gitkeep ├── Mcode └── +mypackage │ ├── +examples │ ├── helloworld.m │ ├── helloworldmex.mexw64 │ ├── helloworldmex.mexmaci64 │ ├── helloworldmexcpp.mexmaci64 │ ├── helloworldmex.c │ └── helloworldmexcpp.cpp │ ├── +internal │ ├── +util │ │ ├── frewind2.m │ │ ├── withcd.m │ │ ├── strings2java.m │ │ ├── rmdir2.m │ │ ├── mv.m │ │ ├── fseek2.m │ │ ├── withwarnoff.m │ │ ├── private │ │ │ └── reportBadValue.m │ │ ├── system2.m │ │ ├── fclose2.m │ │ ├── mkdir2.m │ │ ├── copyfile2.m │ │ ├── size2str.m │ │ ├── fopen2.m │ │ ├── writetext.m │ │ ├── readtext.m │ │ ├── rmrf.m │ │ ├── dir2.m │ │ ├── mustBeA.m │ │ └── todatetime.m │ ├── LibraryInitializer.m │ ├── MypackageBase.m │ ├── MypackageBaseHandle.m │ ├── Contents.m │ ├── misc.m │ └── initializePackage.m │ ├── +test │ ├── HelloWorldTest.m │ └── runtests.m │ ├── +logger │ ├── private │ │ ├── size2str.m │ │ ├── parseOpts.m │ │ ├── mustBeA.m │ │ ├── loggerCallImpl.m │ │ ├── dispstrs.m │ │ └── dispstr.m │ ├── +internal │ │ └── private │ │ │ ├── size2str.m │ │ │ ├── parseOpts.m │ │ │ ├── dispstrs.m │ │ │ └── dispstr.m │ ├── infoj.m │ ├── warnj.m │ ├── debugj.m │ ├── tracej.m │ ├── errorj.m │ ├── info.m │ ├── warn.m │ ├── debug.m │ ├── error.m │ ├── trace.m │ ├── globals.m │ ├── version.m │ ├── Log4jConfigurator.m │ └── Logger.m │ ├── Settings.m │ ├── globals.m │ └── private │ └── todatetime.m ├── myproject-LICENSE ├── dev-kit ├── util-shims │ ├── fclose2.m │ ├── frewind2.m │ ├── withcd.m │ ├── mkdir2.m │ ├── mv.m │ ├── fseek2.m │ ├── rmdir2.m │ ├── strings2java.m │ ├── rmrf.m │ ├── withwarnoff.m │ ├── copyfile2.m │ ├── system2.m │ ├── size2str.m │ ├── writetext.m │ ├── fopen2.m │ ├── readtext.m │ ├── dir2.m │ ├── mustBeA.m │ └── todatetime.m ├── private │ ├── reporoot.m │ ├── withcd.m │ ├── rmdir2.m │ ├── mv.m │ ├── withwarnoff.m │ ├── system2.m │ ├── copyfile2.m │ ├── mkdir2.m │ ├── writetext.m │ ├── readtext.m │ ├── rmrf.m │ ├── mypackage_build_all_mex.m │ ├── dir2.m │ └── mypackage_build.m ├── load_mypackage.m ├── mypackage_launchtests.m ├── mypackage_batch_package_toolbox.m ├── README.md ├── mypackage_package_toolbox.m ├── run_matlab ├── make_release ├── pull_in_homebrew_ruby.m ├── mypackage_make_release.m └── mypackage_make.m ├── .editorconfig ├── myproject-CHANGES.md ├── MatlabProjectTemplate ├── TODO.md ├── make_gitkeeps ├── test_project_initialization ├── project-README.md ├── README.md └── Description for File Exchange.txt ├── doc-project ├── TODO.md ├── Developer Notes.md ├── Description for File Exchange.txt └── Release Checklist.md ├── docs-src-jekyll ├── make_doc ├── 404.html ├── _includes │ └── mathjax.html ├── README.md ├── Gemfile ├── _config.yml ├── index.md ├── make_doc.m ├── Use-AsciiDoc.adoc ├── _layouts │ └── default.html └── css │ └── asciidoc-pygments.css ├── docs-src-gh-pages-raw ├── make_doc.m ├── make_doc ├── 404.html ├── _includes │ └── mathjax.html ├── index.md ├── _config.yml └── Gemfile ├── docs-src-gh-pages ├── make_doc ├── 404.html ├── _includes │ └── mathjax.html ├── index.md ├── _config.yml ├── make_doc.m └── Gemfile ├── rollback_init ├── .travis.yml ├── info.xml ├── mypackage_toolbox_info.m ├── azure-pipelines.yml ├── LICENSE-MatlabProjectTemplate.md ├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── Makefile ├── project_settings.m ├── CHANGES.md ├── README.md ├── myproject.prj.in └── init_project_from_template.m /VERSION: -------------------------------------------------------------------------------- 1 | 0.4.2+ 2 | -------------------------------------------------------------------------------- /.matlab_version: -------------------------------------------------------------------------------- 1 | R2019b 2 | -------------------------------------------------------------------------------- /docs-src-mkdocs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: __myproject__ 2 | theme: mkdocs 3 | -------------------------------------------------------------------------------- /.mlproject.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "do_pcode": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/helloworld.m: -------------------------------------------------------------------------------- 1 | function helloworld 2 | fprintf('Hello, world\n!') 3 | end -------------------------------------------------------------------------------- /src/c/.gitkeep: -------------------------------------------------------------------------------- 1 | This file just exists to keep the empty directory alive in git 2 | -------------------------------------------------------------------------------- /doc/examples/.gitkeep: -------------------------------------------------------------------------------- 1 | This file just exists to keep the empty directory alive in git 2 | -------------------------------------------------------------------------------- /doc/examples/helloworld.m: -------------------------------------------------------------------------------- 1 | function helloworld 2 | fprintf('Hello, world\n!') 3 | end -------------------------------------------------------------------------------- /docs/examples/helloworld.m: -------------------------------------------------------------------------------- 1 | function helloworld 2 | fprintf('Hello, world\n!') 3 | end -------------------------------------------------------------------------------- /examples/.gitkeep: -------------------------------------------------------------------------------- 1 | This file just exists to keep the empty directory alive in git 2 | -------------------------------------------------------------------------------- /lib/java/.gitkeep: -------------------------------------------------------------------------------- 1 | This file just exists to keep the empty directory alive in git 2 | -------------------------------------------------------------------------------- /lib/matlab/.gitkeep: -------------------------------------------------------------------------------- 1 | This file just exists to keep the empty directory alive in git 2 | -------------------------------------------------------------------------------- /docs/examples/.gitkeep: -------------------------------------------------------------------------------- 1 | This file just exists to keep the empty directory alive in git 2 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+examples/helloworld.m: -------------------------------------------------------------------------------- 1 | function helloworld 2 | fprintf('Hello, world!\n') 3 | end 4 | -------------------------------------------------------------------------------- /myproject-LICENSE: -------------------------------------------------------------------------------- 1 | The license for this project has not been defined yet. 2 | 3 | TODO: Choose a license and put it here. -------------------------------------------------------------------------------- /dev-kit/util-shims/fclose2.m: -------------------------------------------------------------------------------- 1 | function fclose2(fid) 2 | % A version of fclose that errors on failure 3 | mypackage.internal.util.fclose2(fid); 4 | end 5 | -------------------------------------------------------------------------------- /dev-kit/util-shims/frewind2.m: -------------------------------------------------------------------------------- 1 | function frewind2(fid) 2 | % A version of frewind that errors on failure 3 | mypackage.internal.util.frewind2(fid); 4 | end 5 | -------------------------------------------------------------------------------- /dev-kit/util-shims/withcd.m: -------------------------------------------------------------------------------- 1 | function out = withcd(dir) 2 | % Temporarily change to a new directory 3 | out = mypackage.internal.util.withcd(dir); 4 | end 5 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+examples/helloworldmex.mexw64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janklab/MatlabProjectTemplate/HEAD/Mcode/+mypackage/+examples/helloworldmex.mexw64 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.m] 8 | indent_style = space 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+examples/helloworldmex.mexmaci64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janklab/MatlabProjectTemplate/HEAD/Mcode/+mypackage/+examples/helloworldmex.mexmaci64 -------------------------------------------------------------------------------- /dev-kit/util-shims/mkdir2.m: -------------------------------------------------------------------------------- 1 | function mkdir2(varargin) 2 | % A version of mkdir that raises error on failure 3 | mypackage.internal.util.mkdir2(varargin{:}); 4 | end 5 | -------------------------------------------------------------------------------- /dev-kit/util-shims/mv.m: -------------------------------------------------------------------------------- 1 | function mv(source, dest) 2 | % A version of movefile that raises an error on failure 3 | mypackage.internal.util.mv(source, dest); 4 | end 5 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+examples/helloworldmexcpp.mexmaci64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janklab/MatlabProjectTemplate/HEAD/Mcode/+mypackage/+examples/helloworldmexcpp.mexmaci64 -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/frewind2.m: -------------------------------------------------------------------------------- 1 | function frewind2(fid) 2 | % A version of frewind that errors on failure 3 | mypackage.internal.util.fseek(fid, 0, 'bof'); 4 | end -------------------------------------------------------------------------------- /dev-kit/private/reporoot.m: -------------------------------------------------------------------------------- 1 | function out = reporoot 2 | % The root dir of the myproject repo 3 | out = string(fileparts(fileparts(fileparts(mfilename('fullpath'))))); 4 | end -------------------------------------------------------------------------------- /dev-kit/private/withcd.m: -------------------------------------------------------------------------------- 1 | function out = withcd(dir) 2 | % Temporarily change to a new directory 3 | origDir = pwd; 4 | cd(dir); 5 | out.RAII = onCleanup(@() cd(origDir)); 6 | end -------------------------------------------------------------------------------- /dev-kit/load_mypackage.m: -------------------------------------------------------------------------------- 1 | function load_mypackage 2 | 3 | repoDir = fileparts(fileparts(mfilename('fullpath'))); 4 | toplevelDir = [repoDir '/Mcode']; 5 | addpath(toplevelDir); 6 | -------------------------------------------------------------------------------- /dev-kit/util-shims/fseek2.m: -------------------------------------------------------------------------------- 1 | function fseek2(fid, offset, origin) 2 | % A version of fseek that errors on failure 3 | mypackage.internal.util.fseek2(fid, offset, origin); 4 | end 5 | -------------------------------------------------------------------------------- /dev-kit/util-shims/rmdir2.m: -------------------------------------------------------------------------------- 1 | function rmdir2(dir, varargin) 2 | % A version of rmdir that raises errors on failure 3 | mypackage.internal.util.rmdir2(dir, varargin{:}); 4 | end 5 | -------------------------------------------------------------------------------- /dev-kit/util-shims/strings2java.m: -------------------------------------------------------------------------------- 1 | function out = strings2java(str) 2 | % Convert Matlab string array to Java string array 3 | out = mypackage.internal.util.strings2java(str); 4 | end 5 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/withcd.m: -------------------------------------------------------------------------------- 1 | function out = withcd(dir) 2 | % Temporarily change to a new directory 3 | origDir = pwd; 4 | cd(dir); 5 | out.RAII = onCleanup(@() cd(origDir)); 6 | end -------------------------------------------------------------------------------- /myproject-CHANGES.md: -------------------------------------------------------------------------------- 1 | __myproject__ Changelog 2 | ================================ 3 | 4 | Version 0.1.0 (date goes here) 5 | ------------------------------ 6 | 7 | * Initial project release 8 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/strings2java.m: -------------------------------------------------------------------------------- 1 | function out = strings2java(str) 2 | out = javaArray('java.lang.String', numel(str)); 3 | for i = 1:numel(str) 4 | out(i) = java.lang.String(str(i)); 5 | end 6 | end -------------------------------------------------------------------------------- /dev-kit/util-shims/rmrf.m: -------------------------------------------------------------------------------- 1 | function rmrf(files) 2 | % Recursively delete files and directories 3 | % 4 | % rmrf(files) 5 | arguments 6 | files string 7 | end 8 | mypackage.internal.util.rmrf(files); 9 | end 10 | -------------------------------------------------------------------------------- /dev-kit/util-shims/withwarnoff.m: -------------------------------------------------------------------------------- 1 | function out = withwarnoff(warningId) 2 | % Temporarily disable warnings 3 | arguments 4 | warningId string 5 | end 6 | out = mypackage.internal.util.withwarnoff(warningId); 7 | end 8 | -------------------------------------------------------------------------------- /dev-kit/private/rmdir2.m: -------------------------------------------------------------------------------- 1 | function rmdir2(dir, varargin) 2 | % A version of rmdir that raises errors on failure 3 | [ok,msg] = rmdir(dir, varargin{:}); 4 | if ~ok 5 | error('rmdir of "%s" failed: %s', dir, msg); 6 | end 7 | end -------------------------------------------------------------------------------- /dev-kit/private/mv.m: -------------------------------------------------------------------------------- 1 | function mv(source, dest) 2 | % A version of movefile that raises an error on failure 3 | [ok,msg] = movefile(source, dest); 4 | if ~ok 5 | error('Failed moving "%s" to "%s": %s', source, dest, msg); 6 | end 7 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+examples/helloworldmex.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) 6 | { 7 | mexPrintf("Hello World!\n"); 8 | } 9 | -------------------------------------------------------------------------------- /MatlabProjectTemplate/TODO.md: -------------------------------------------------------------------------------- 1 | # MatlabProjectTemplate TODO 2 | 3 | * Testing/CI 4 | * CircleCI support 5 | * Azure Pipelines support 6 | * Decide whether there should be another level of "lib/" under Mcode/ 7 | * Matlab Project support 8 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/rmdir2.m: -------------------------------------------------------------------------------- 1 | function rmdir2(dir, varargin) 2 | % A version of rmdir that raises errors on failure 3 | [ok,msg] = rmdir(dir, varargin{:}); 4 | if ~ok 5 | error('rmdir of "%s" failed: %s', dir, msg); 6 | end 7 | end -------------------------------------------------------------------------------- /dev-kit/util-shims/copyfile2.m: -------------------------------------------------------------------------------- 1 | function copyfile2(src, dest) 2 | % A version of copyfile that raises an error on failure 3 | arguments 4 | src (1,:) string 5 | dest (1,1) string 6 | end 7 | mypackage.internal.util.copyfile2(src, dest); 8 | end 9 | -------------------------------------------------------------------------------- /src/java/myproject-java/src/main/java/com/example/mypackage/HelloWorld.java: -------------------------------------------------------------------------------- 1 | package com.example.mypackage; 2 | 3 | public class HelloWorld { 4 | public static void main(String[] args) { 5 | System.out.println("Hello, World!"); 6 | } 7 | } -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/mv.m: -------------------------------------------------------------------------------- 1 | function mv(source, dest) 2 | % A version of movefile that raises an error on failure 3 | [ok,msg] = movefile(source, dest); 4 | if ~ok 5 | error('Failed moving "%s" to "%s": %s', source, dest, msg); 6 | end 7 | end -------------------------------------------------------------------------------- /dev-kit/util-shims/system2.m: -------------------------------------------------------------------------------- 1 | function out = system2(cmd) 2 | % A version of system that raises an error on failure 3 | if nargout == 0 4 | mypackage.internal.util.system2(cmd); 5 | else 6 | out = mypackage.internal.util.system2(cmd); 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/LibraryInitializer.m: -------------------------------------------------------------------------------- 1 | classdef LibraryInitializer 2 | 3 | methods 4 | 5 | function this = LibraryInitializer() 6 | mypackage.internal.initializePackage; 7 | end 8 | 9 | end 10 | 11 | end 12 | 13 | -------------------------------------------------------------------------------- /doc-project/TODO.md: -------------------------------------------------------------------------------- 1 | # myproject TODO 2 | 3 | Put your TODOs here! 4 | 5 | ## Subject Area 1 6 | 7 | * One thing 8 | * Another thing 9 | * And another thing after that 10 | 11 | ## Subject Area 2 12 | 13 | * Some more stuff 14 | * And yet more stuff 15 | -------------------------------------------------------------------------------- /docs/make_doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This is the script that builds the project documentation into ../doc. 4 | # 5 | # Generally you don't call this directly; instead you run `make doc`. 6 | 7 | set -e 8 | 9 | mkdir -p ../doc 10 | cp *.html ../doc -------------------------------------------------------------------------------- /docs-src-mkdocs/make_doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This is the script that builds the project documentation into ../doc. 4 | # 5 | # Generally you don't call this directly; instead you run `make doc`. 6 | 7 | mkdocs build 8 | rm -rf ../doc/* 9 | cp -R site/* ../doc 10 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+test/HelloWorldTest.m: -------------------------------------------------------------------------------- 1 | classdef HelloWorldTest < matlab.unittest.TestCase 2 | 3 | methods (Test) 4 | 5 | function testHelloWorld(t) 6 | fprintf('Hello, World!') 7 | t.verifyNotEmpty('Hello, World!') 8 | end 9 | 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/fseek2.m: -------------------------------------------------------------------------------- 1 | function fseek2(fid, offset, origin) 2 | % A version of fseek that errors on failure 3 | status = fseek(fid, offset, origin); 4 | if status ~= 0 5 | file = fopen(fid); 6 | error('Failed doing fseek on file %s. Error message unavailable.', file); 7 | end 8 | end -------------------------------------------------------------------------------- /doc-project/Developer Notes.md: -------------------------------------------------------------------------------- 1 | # __myproject__ Developer Notes 2 | 3 | Here is where you put notes for the developers working on your project! Recommended sections: 4 | 5 | ## Code Style Guide 6 | 7 | ## Prose Style Guide 8 | 9 | ## See also 10 | 11 | * The `Release Checklist.md` file 12 | -------------------------------------------------------------------------------- /dev-kit/mypackage_launchtests.m: -------------------------------------------------------------------------------- 1 | function mypackage_launchtests 2 | % Entry point for running full test suite from command line or automation 3 | 4 | rootdir = fileparts(fileparts(mfilename('fullpath'))); 5 | addpath(fullfile(rootdir, 'Mcode')) 6 | 7 | results = mypackage.test.runtests %#ok 8 | 9 | end 10 | -------------------------------------------------------------------------------- /dev-kit/private/withwarnoff.m: -------------------------------------------------------------------------------- 1 | function out = withwarnoff(warningId) 2 | % Temporarily disable warnings 3 | arguments 4 | warningId string 5 | end 6 | origWarnState = warning; 7 | out.RAII = onCleanup(@() warning(origWarnState)); 8 | for i = 1:numel(warningId) 9 | warning('off', warningId(i)); 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /docs/make_doc.m: -------------------------------------------------------------------------------- 1 | function make_doc 2 | % Build these doc sources into the final doc/ directory 3 | 4 | %#ok<*STRNU> 5 | 6 | import mypackage.internal.util.* 7 | 8 | RAII.cd = withcd(fileparts(mfilename('fullpath'))); 9 | rmdir2('../doc', 's'); 10 | mkdir2('../doc'); 11 | copyfile2('*.*', '../doc'); 12 | 13 | end 14 | 15 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/withwarnoff.m: -------------------------------------------------------------------------------- 1 | function out = withwarnoff(warningId) 2 | % Temporarily disable warnings 3 | arguments 4 | warningId string 5 | end 6 | origWarnState = warning; 7 | out.RAII = onCleanup(@() warning(origWarnState)); 8 | for i = 1:numel(warningId) 9 | warning('off', warningId(i)); 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /dev-kit/private/system2.m: -------------------------------------------------------------------------------- 1 | function out = system2(cmd) 2 | % A version of system that raises an error on failure 3 | 4 | if nargout == 0 5 | status = system(cmd); 6 | else 7 | [status,out] = system(cmd); 8 | end 9 | 10 | if status ~= 0 11 | error('Command failed (exit status %d). Command: %s', status, cmd); 12 | end 13 | 14 | end -------------------------------------------------------------------------------- /dev-kit/util-shims/size2str.m: -------------------------------------------------------------------------------- 1 | function out = size2str(sz) 2 | %SIZE2STR Format an array size for display 3 | % 4 | % out = size2str(sz) 5 | % 6 | % Sz is an array of dimension sizes, in the format returned by SIZE. 7 | % 8 | % Examples: 9 | % 10 | % size2str(size(magic(3))) 11 | out = mypackage.internal.util.size2str(sz); 12 | end 13 | -------------------------------------------------------------------------------- /docs-src-jekyll/make_doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This is the script that builds the project documentation into ../doc. 4 | # 5 | # Generally you don't call this directly; instead you run `make doc`. 6 | 7 | set -e 8 | 9 | bundle install >/dev/null 10 | bundle exec jekyll build 11 | rm -rf ../doc/* 12 | cp -R _site/* ../doc 13 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/private/reportBadValue.m: -------------------------------------------------------------------------------- 1 | function reportBadValue(valueName, expectedThing, actualThing) 2 | if isempty(valueName) 3 | valueLabel = 'Value'; 4 | else 5 | valueLabel = sprintf('Value ''%s''', valueName); 6 | end 7 | error('%s must be a %s, but got a %s.', valueLabel,... 8 | expectedThing, actualThing); 9 | end -------------------------------------------------------------------------------- /docs-src-gh-pages-raw/make_doc.m: -------------------------------------------------------------------------------- 1 | function make_doc 2 | % Build these doc sources into the final doc/ directory 3 | 4 | %#ok<*STRNU> 5 | 6 | import mypackage.internal.util.* 7 | 8 | RAII.cd = withcd(fileparts(mfilename('fullpath'))); 9 | rmdir2('../doc', 's'); 10 | mkdir2('../doc'); 11 | copyfile2('*.*', '../doc'); 12 | 13 | end 14 | 15 | -------------------------------------------------------------------------------- /docs-src-gh-pages/make_doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This is the script that builds the project documentation into ../doc. 4 | # 5 | # Generally you don't call this directly; instead you run `make doc`. 6 | 7 | set -e 8 | 9 | bundle install >/dev/null 10 | bundle exec jekyll build 11 | rm -rf ../doc/* 12 | cp -R _site/* ../doc 13 | -------------------------------------------------------------------------------- /docs-src-gh-pages-raw/make_doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This is the script that builds the project documentation into ../doc. 4 | # 5 | # Generally you don't call this directly; instead you run `make doc`. 6 | 7 | set -e 8 | 9 | bundle install >/dev/null 10 | bundle exec jekyll build 11 | rm -rf ../doc/* 12 | cp -R _site/* ../doc 13 | -------------------------------------------------------------------------------- /rollback_init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | git reset --hard 6 | rm -rf M-doc Mcode/+mycoolpackage docs/* doc/* \ 7 | src/java/MyCoolProject-java \ 8 | dev-kit/*mycoolpackage* dev-kit/*MyCoolProject* MyCoolProject.mltbx MyCoolProject.prj.in \ 9 | mypackage* lib/java/MyCoolProject-java mycoolpackage_* 10 | git reset --hard -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/system2.m: -------------------------------------------------------------------------------- 1 | function out = system2(cmd) 2 | % A version of system that raises an error on failure 3 | 4 | if nargout == 0 5 | status = system(cmd); 6 | else 7 | [status,out] = system(cmd); 8 | end 9 | 10 | if status ~= 0 11 | error('Command failed (exit status %d). Command: %s', status, cmd); 12 | end 13 | 14 | end -------------------------------------------------------------------------------- /dev-kit/private/copyfile2.m: -------------------------------------------------------------------------------- 1 | function copyfile2(src, dest) 2 | % A version of copyfile that raises an error on failure 3 | arguments 4 | src (1,:) string 5 | dest (1,1) string 6 | end 7 | for file = src 8 | [ok,msg] = copyfile(file, dest); 9 | if ~ok 10 | error('Failed copying file "%s" to "%s": %s', file, dest, msg); 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /dev-kit/private/mkdir2.m: -------------------------------------------------------------------------------- 1 | function mkdir2(varargin) 2 | % A version of mkdir that raises error on failure 3 | 4 | [ok,msg] = mkdir(varargin{:}); 5 | if ~ok 6 | if nargin == 1 7 | target = varargin{1}; 8 | else 9 | target = fullfile(varargin{:}); 10 | end 11 | error('Failed creating directory "%s": %s', target, msg); 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/fclose2.m: -------------------------------------------------------------------------------- 1 | function fclose2(fid) 2 | % A version of fclose that errors on failure 3 | status = fclose(fid); 4 | if status ~= 0 5 | if isequal(fid, 'all') 6 | file = ''; 7 | else 8 | file = fopen(fid); 9 | end 10 | error('Failed doing fclose on file %s. Error message unavailable.', file); 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/mkdir2.m: -------------------------------------------------------------------------------- 1 | function mkdir2(varargin) 2 | % A version of mkdir that raises error on failure 3 | 4 | [ok,msg] = mkdir(varargin{:}); 5 | if ~ok 6 | if nargin == 1 7 | target = varargin{1}; 8 | else 9 | target = fullfile(varargin{:}); 10 | end 11 | error('Failed creating directory "%s": %s', target, msg); 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/private/size2str.m: -------------------------------------------------------------------------------- 1 | function out = size2str(sz) 2 | %SIZE2STR Format a matrix size for display 3 | % 4 | % out = size2str(sz) 5 | % 6 | % Sz is an array of dimension sizes, in the format returned by SIZE. 7 | 8 | strs = cell(size(sz)); 9 | for i = 1:numel(sz) 10 | strs{i} = sprintf('%d', sz(i)); 11 | end 12 | 13 | out = strjoin(strs, '-by-'); 14 | end -------------------------------------------------------------------------------- /MatlabProjectTemplate/make_gitkeeps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Create the dummy .gitkeep files to keep the template repo structure alive in git 4 | 5 | doit () { 6 | for i in doc examples \ 7 | lib/java lib/matlab src/c src/java; do 8 | echo "This file just exists to keep the empty directory alive in git." > $i/.gitkeep 9 | done 10 | } 11 | 12 | doit 2>/dev/null 13 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/copyfile2.m: -------------------------------------------------------------------------------- 1 | function copyfile2(src, dest) 2 | % A version of copyfile that raises an error on failure 3 | arguments 4 | src (1,:) string 5 | dest (1,1) string 6 | end 7 | for file = src 8 | [ok,msg] = copyfile(file, dest); 9 | if ~ok 10 | error('Failed copying file "%s" to "%s": %s', file, dest, msg); 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/+internal/private/size2str.m: -------------------------------------------------------------------------------- 1 | function out = size2str(sz) 2 | %SIZE2STR Format a matrix size for display 3 | % 4 | % out = size2str(sz) 5 | % 6 | % Sz is an array of dimension sizes, in the format returned by SIZE. 7 | 8 | strs = cell(size(sz)); 9 | for i = 1:numel(sz) 10 | strs{i} = sprintf('%d', sz(i)); 11 | end 12 | 13 | out = strjoin(strs, '-by-'); 14 | end -------------------------------------------------------------------------------- /dev-kit/util-shims/writetext.m: -------------------------------------------------------------------------------- 1 | function writetext(text, file, encoding) 2 | % Write text to a file 3 | % 4 | % writetext(text, file, encoding) 5 | % 6 | % Encoding deaults to UTF-8. 7 | % 8 | % Replaces the original file contents. 9 | arguments 10 | text (1,1) string 11 | file (1,1) string 12 | encoding (1,1) string = 'UTF-8' 13 | end 14 | mypackage.internal.util.writetext(text, file, encoding); 15 | end 16 | -------------------------------------------------------------------------------- /dev-kit/mypackage_batch_package_toolbox.m: -------------------------------------------------------------------------------- 1 | function mypackage_batch_package_toolbox 2 | % An entry point for building the toolbox from a "matlab -batch" call 3 | % 4 | % This has error handling to set matlab's exit status appropriately. 5 | 6 | try 7 | load_mypackage; 8 | mypackage_package_toolbox; 9 | catch err 10 | fprintf('Error occurred:\n'); 11 | fprintf('%s\n', getReport(err)); 12 | exit(1); 13 | end 14 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/MypackageBase.m: -------------------------------------------------------------------------------- 1 | classdef MypackageBase 2 | % This class is a trick to support automatic library initialization 3 | % 4 | % To use it, have all your classes that depend on the library being 5 | % initialized inherit from this or MypackageBaseHandle. 6 | 7 | properties (Constant, Hidden) 8 | initializer = mypackage.internal.LibraryInitializer; 9 | end 10 | 11 | end 12 | 13 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/MypackageBaseHandle.m: -------------------------------------------------------------------------------- 1 | classdef MypackageBaseHandle < handle 2 | % This class is a trick to support automatic library initialization 3 | % 4 | % To use it, have all your classes that depend on the library being 5 | % initialized inherit from this or MypackageBase. 6 | 7 | properties (Constant, Hidden) 8 | initializer = mypackage.internal.LibraryInitializer; 9 | end 10 | 11 | end 12 | 13 | -------------------------------------------------------------------------------- /dev-kit/util-shims/fopen2.m: -------------------------------------------------------------------------------- 1 | function out = fopen2(filename, permission, machinefmt, encodingIn) 2 | % A version of fopen that raises an error on failure and defaults to Unicode 3 | arguments 4 | filename (1,1) string 5 | permission (1,1) string = 'r' 6 | machinefmt (1,1) string = 'n' 7 | encodingIn (1,1) string = 'UTF-8' 8 | end 9 | out = mypackage.internal.util.fopen2(filename, permission, machinefmt, encodingIn); 10 | end 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: matlab 2 | matlab: 3 | - R2021a 4 | script: 5 | - make test 6 | - make dist 7 | - make clean 8 | # start_MPT_targets 9 | # make dist does make doc, which alters the contents of doc/. Reset to revert. 10 | - git reset --hard 11 | - ./MatlabProjectTemplate/test_project_initialization 12 | # end_MPT_targets 13 | # Remove this if you don't use codecov.io 14 | after_script: bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/size2str.m: -------------------------------------------------------------------------------- 1 | function out = size2str(sz) 2 | %SIZE2STR Format an array size for display 3 | % 4 | % out = size2str(sz) 5 | % 6 | % Sz is an array of dimension sizes, in the format returned by SIZE. 7 | % 8 | % Examples: 9 | % 10 | % size2str(size(magic(3))) 11 | 12 | strs = repmat(string(missing), size(sz)); 13 | for i = 1:numel(sz) 14 | strs(i) = sprintf("%d", sz(i)); 15 | end 16 | 17 | out = strjoin(strs, '-by-'); 18 | 19 | end 20 | -------------------------------------------------------------------------------- /dev-kit/util-shims/readtext.m: -------------------------------------------------------------------------------- 1 | function out = readtext(file, encoding) 2 | % Read the contents of a text file as a string 3 | % 4 | % This is analagous to Matlab's readcsv and readtable, and exists because Matlab 5 | % doesn't provide a basic file-slurping mechanism. 6 | arguments 7 | file (1,1) string 8 | encoding (1,1) string = 'UTF-8' % TODO: auto-detect file encoding via sniffing 9 | end 10 | out = mypackage.internal.util.readtext(file, encoding); 11 | end 12 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/Contents.m: -------------------------------------------------------------------------------- 1 | % The +internal package contains code that is for the internal use of this 2 | % program. It is not part of its public API, and may change at any time. 3 | % 4 | % The +internal package exists because it is not practical to protect 5 | % internal-use code with "Access=private" access controls; that makes it hard to 6 | % break code up into multiple classes, makes it hard to test, and can't be 7 | % applied to functions outside classes anyway. -------------------------------------------------------------------------------- /info.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 2019b 7 | myproject 8 | toolbox 9 | 10 | build/M-doc 11 | 12 | 13 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/infoj.m: -------------------------------------------------------------------------------- 1 | function infoj(msg, varargin) 2 | % Log an INFO level message from caller, using SLF4J style formatting. 3 | % 4 | % logger.infoj(msg, varargin) 5 | % 6 | % This accepts a message with SLF4J style formatting, using '{}' as placeholders for 7 | % values to be interpolated into the message. 8 | % 9 | % Examples: 10 | % 11 | % logger.infoj('Some message. value1={} value2={}', 'foo', 42); 12 | 13 | loggerCallImpl('info', msg, varargin, 'j'); 14 | 15 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/warnj.m: -------------------------------------------------------------------------------- 1 | function warnj(msg, varargin) 2 | % Log a WARN level message from caller, using SLF4J style formatting. 3 | % 4 | % logger.warnj(msg, varargin) 5 | % 6 | % This accepts a message with SLF4J style formatting, using '{}' as placeholders for 7 | % values to be interpolated into the message. 8 | % 9 | % Examples: 10 | % 11 | % logger.warnj('Some message. value1={} value2={}', 'foo', 42); 12 | 13 | loggerCallImpl('warn', msg, varargin, 'j'); 14 | 15 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/debugj.m: -------------------------------------------------------------------------------- 1 | function debugj(msg, varargin) 2 | % Log a DEBUG level message from caller, using SLF4J style formatting. 3 | % 4 | % logger.debug(msg, varargin) 5 | % 6 | % This accepts a message with SLF4J style formatting, using '{}' as placeholders for 7 | % values to be interpolated into the message. 8 | % 9 | % Examples: 10 | % 11 | % logger.debugj('Some message. value1={} value2={}', 'foo', 42); 12 | 13 | loggerCallImpl('debug', msg, varargin, 'j'); 14 | 15 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/tracej.m: -------------------------------------------------------------------------------- 1 | function tracej(msg, varargin) 2 | % Log a TRACE level message from caller, using SLF4J style formatting. 3 | % 4 | % logger.tracej(msg, varargin) 5 | % 6 | % This accepts a message with SLF4J style formatting, using '{}' as placeholders for 7 | % values to be interpolated into the message. 8 | % 9 | % Examples: 10 | % 11 | % logger.tracej('Some message. value1={} value2={}', 'foo', 42); 12 | 13 | loggerCallImpl('trace', msg, varargin, 'j'); 14 | 15 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/errorj.m: -------------------------------------------------------------------------------- 1 | function errorj(msg, varargin) 2 | % Log an ERROR level message from caller, using SLF4J style formatting. 3 | % 4 | % logger.errorj(msg, varargin) 5 | % 6 | % This accepts a message with SLF4J style formatting, using '{}' as placeholders for 7 | % values to be interpolated into the message. 8 | % 9 | % Examples: 10 | % 11 | % logger.errorj('Some message. value1={} value2={}', 'foo', 42); 12 | 13 | loggerCallImpl('error', msg, varargin, 'j'); 14 | 15 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/info.m: -------------------------------------------------------------------------------- 1 | function info(msg, varargin) 2 | % Log an INFO level message from caller, with printf style formatting. 3 | % 4 | % logger.info(msg, varargin) 5 | % logger.info(exception, msg, varargin) 6 | % 7 | % This accepts a message with printf style formatting, using '%...' formatting 8 | % controls as placeholders. 9 | % 10 | % Examples: 11 | % 12 | % logger.info('Some message. value1=%s value2=%d', 'foo', 42); 13 | 14 | loggerCallImpl('info', msg, varargin); 15 | 16 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/warn.m: -------------------------------------------------------------------------------- 1 | function warn(msg, varargin) 2 | % Log a WARN level message from caller, with printf style formatting. 3 | % 4 | % logger.warn(msg, varargin) 5 | % logger.warn(exception, msg, varargin) 6 | % 7 | % This accepts a message with printf style formatting, using '%...' formatting 8 | % controls as placeholders. 9 | % 10 | % Examples: 11 | % 12 | % logger.warn('Some message. value1=%s value2=%d', 'foo', 42); 13 | 14 | loggerCallImpl('warn', msg, varargin); 15 | 16 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/debug.m: -------------------------------------------------------------------------------- 1 | function debug(msg, varargin) 2 | % Log a DEBUG level message from caller, with printf style formatting. 3 | % 4 | % logger.debug(msg, varargin) 5 | % logger.debug(exception, msg, varargin) 6 | % 7 | % This accepts a message with printf style formatting, using '%...' formatting 8 | % controls as placeholders. 9 | % 10 | % Examples: 11 | % 12 | % logger.debug('Some message. value1=%s value2=%d', 'foo', 42); 13 | 14 | loggerCallImpl('debug', msg, varargin); 15 | 16 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/error.m: -------------------------------------------------------------------------------- 1 | function error(msg, varargin) 2 | % Log an ERROR level message from caller, with printf style formatting. 3 | % 4 | % logger.error(msg, varargin) 5 | % logger.error(exception, msg, varargin) 6 | % 7 | % This accepts a message with printf style formatting, using '%...' formatting 8 | % controls as placeholders. 9 | % 10 | % Examples: 11 | % 12 | % logger.error('Some message. value1=%s value2=%d', 'foo', 42); 13 | 14 | loggerCallImpl('error', msg, varargin); 15 | 16 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/trace.m: -------------------------------------------------------------------------------- 1 | function trace(msg, varargin) 2 | % Log a TRACE level message from caller, with printf style formatting. 3 | % 4 | % logger.trace(msg, varargin) 5 | % logger.trace(exception, msg, varargin) 6 | % 7 | % This accepts a message with printf style formatting, using '%...' formatting 8 | % controls as placeholders. 9 | % 10 | % Examples: 11 | % 12 | % logger.trace('Some message. value1=%s value2=%d', 'foo', 42); 13 | 14 | loggerCallImpl('trace', msg, varargin); 15 | 16 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/fopen2.m: -------------------------------------------------------------------------------- 1 | function out = fopen2(filename, permission, machinefmt, encodingIn) 2 | % A version of fopen that raises an error on failure and defaults to Unicode 3 | arguments 4 | filename (1,1) string 5 | permission (1,1) string = 'r' 6 | machinefmt (1,1) string = 'n' 7 | encodingIn (1,1) string = 'UTF-8' 8 | end 9 | [out,msg] = fopen(filename, permission, machinefmt, encodingIn); 10 | if out < 0 11 | error('Failed opening file "%s": %s', filename, msg); 12 | end 13 | end -------------------------------------------------------------------------------- /dev-kit/private/writetext.m: -------------------------------------------------------------------------------- 1 | function writetext(text, file, encoding) 2 | % Write text to a file 3 | % 4 | % writetext(text, file, encoding) 5 | % 6 | % Encoding deaults to UTF-8. 7 | % 8 | % Replaces the original file contents. 9 | arguments 10 | text (1,1) string 11 | file (1,1) string 12 | encoding (1,1) string = 'UTF-8' 13 | end 14 | [fid,msg] = fopen(file, 'w', 'n', encoding); 15 | if fid < 1 16 | error('Failed opening file %s: %s', file, msg); 17 | end 18 | RAII.fh = onCleanup(@() fclose(fid)); 19 | fprintf(fid, '%s', text); 20 | end 21 | -------------------------------------------------------------------------------- /docs-src-gh-pages/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs-src-jekyll/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs-src-gh-pages-raw/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/writetext.m: -------------------------------------------------------------------------------- 1 | function writetext(text, file, encoding) 2 | % Write text to a file 3 | % 4 | % writetext(text, file, encoding) 5 | % 6 | % Encoding deaults to UTF-8. 7 | % 8 | % Replaces the original file contents. 9 | arguments 10 | text (1,1) string 11 | file (1,1) string 12 | encoding (1,1) string = 'UTF-8' 13 | end 14 | [fid,msg] = fopen(file, 'w', 'n', encoding); 15 | if fid < 1 16 | error('Failed opening file %s: %s', file, msg); 17 | end 18 | RAII.fh = onCleanup(@() fclose(fid)); 19 | fprintf(fid, '%s', text); 20 | end 21 | -------------------------------------------------------------------------------- /docs-src-gh-pages/_includes/mathjax.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /docs-src-jekyll/_includes/mathjax.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /docs-src-gh-pages-raw/_includes/mathjax.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /mypackage_toolbox_info.m: -------------------------------------------------------------------------------- 1 | function out = mypackage_toolbox_info 2 | % Info about your toolbox, for use during the build 3 | % 4 | % This is the file you edit to have your toolbox-specific information. 5 | % 6 | % It is expected to be run while your toolbox is loaded, so you can use your 7 | % toolbox functions inside it. 8 | % 9 | % It must return a struct with the fields: 10 | % name - a charvec 11 | % version - a charvec 12 | % 13 | % The version may have "-[\w+]" suffixes in addition to the standard 14 | % "0.0[.0[.0]]" numeric version format. 15 | 16 | out.name = 'myproject'; 17 | out.version = mypackage.globals.version; 18 | -------------------------------------------------------------------------------- /dev-kit/private/readtext.m: -------------------------------------------------------------------------------- 1 | function out = readtext(file, encoding) 2 | % Read the contents of a text file as a string 3 | % 4 | % This is analagous to Matlab's readcsv and readtable, and exists because Matlab 5 | % doesn't provide a basic file-slurping mechanism. 6 | 7 | arguments 8 | file (1,1) string 9 | encoding (1,1) string = 'UTF-8' % TODO: auto-detect file encoding via sniffing 10 | end 11 | [fid,msg] = fopen(file, 'r', 'n', encoding); 12 | if fid < 1 13 | error('Failed opening file %s: %s', file, msg); 14 | end 15 | RAII.fh = onCleanup(@() fclose(fid)); 16 | c = fread(fid, Inf, 'uint8=>char'); 17 | out = string(c'); 18 | end 19 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/readtext.m: -------------------------------------------------------------------------------- 1 | function out = readtext(file, encoding) 2 | % Read the contents of a text file as a string 3 | % 4 | % This is analagous to Matlab's readcsv and readtable, and exists because Matlab 5 | % doesn't provide a basic file-slurping mechanism. 6 | 7 | arguments 8 | file (1,1) string 9 | encoding (1,1) string = 'UTF-8' % TODO: auto-detect file encoding via sniffing 10 | end 11 | [fid,msg] = fopen(file, 'r', 'n', encoding); 12 | if fid < 1 13 | error('Failed opening file %s: %s', file, msg); 14 | end 15 | RAII.fh = onCleanup(@() fclose(fid)); 16 | c = fread(fid, Inf, 'uint8=>char'); 17 | out = string(c'); 18 | end 19 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: InstallMATLAB@0 3 | inputs: 4 | release: R2020a 5 | - task: RunMATLABCommand@0 6 | inputs: 7 | command: addpath dev-kit; mypackage_launchtests 8 | - task: RunMATLABTests@0 9 | inputs: 10 | testResultsJUnit: test-results/results.xml 11 | codeCoverageCobertura: code-coverage/coverage.xml 12 | - task: PublishTestResults@2 13 | condition: succeededOrFailed() 14 | inputs: 15 | testResultsFiles: test-results/results.xml 16 | - task: PublishCodeCoverageResults@1 17 | inputs: 18 | codeCoverageTool: Cobertura 19 | summaryFileLocation: code-coverage/coverage.xml 20 | -------------------------------------------------------------------------------- /doc-project/Description for File Exchange.txt: -------------------------------------------------------------------------------- 1 | If you're going to post your project to MathWorks File Exchange, edit this file to contain what 2 | you want to go in the Summary and Description fields of its posting. That way you don't have to worry 3 | about editing and formatting text in their little text field on the web page, and you'll have a 4 | history of what you posted. Plus you can edit it ahead of time to your heart's content and then 5 | just copy and paste. 6 | 7 | ------------------------------------------------------------------------------- 8 | 9 | Summary: 10 | 11 | __myproject_summary__ 12 | 13 | 14 | Description: 15 | 16 | __myproject_description__ 17 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example MatlabProjectTemplate Documentation 5 | 6 | 7 |

Hello, world!

8 |

9 | This is a dummy example doco file for MatlabProjectTemplate's use. It exists so that make dist can work on an uninitialized project. This whole doc-src/ directory should be blown away and replaced with the contents of one of the other doc-src-* directories. This is done by init_project_from_template. 10 |

11 |

12 | You should not see this file in an actual initialized project. 13 |

14 | 15 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example MatlabProjectTemplate Documentation 5 | 6 | 7 |

Hello, world!

8 |

9 | This is a dummy example doco file for MatlabProjectTemplate's use. It exists so that make dist can work on an uninitialized project. This whole doc-src/ directory should be blown away and replaced with the contents of one of the other doc-src-* directories. This is done by init_project_from_template. 10 |

11 |

12 | You should not see this file in an actual initialized project. 13 |

14 | 15 | -------------------------------------------------------------------------------- /LICENSE-MatlabProjectTemplate.md: -------------------------------------------------------------------------------- 1 | # MatlabProjectTemplate License 2 | 3 | Copyright 2019, 2020, 2021 Andrew Janke 4 | 5 | MatlabProjectTemplate is multi-licensed under all of the following licenses: 6 | 7 | * BSD 3-Clause License 8 | * BSD 2-Clause License 9 | * Apache License 10 | * MIT License 11 | * GPLv3 12 | 13 | The main LICENSE file in this repo is the BSD 3-Clause License (instead of a note about multi-licensing) so that GitHub can auto-detect it and display it on the repo web page, and it's easy for MathWorks File Exchange to automatically determine license compatibility. The BSD 3-Clause License was chosen because that is the standard license required by MathWorks File Exchange. 14 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+examples/helloworldmexcpp.cpp: -------------------------------------------------------------------------------- 1 | /* Hello world in C++ MEX */ 2 | 3 | #include "mex.hpp" 4 | #include "mexAdapter.hpp" 5 | 6 | using namespace matlab::data; 7 | using matlab::mex::ArgumentList; 8 | 9 | class MexFunction : public matlab::mex::Function { 10 | // Sooooo... this MEX function _would_ just output "Hello, world!" on stdout, 11 | // but it turns out that's non-trivial to do using Matlab's C++ MEX API. 12 | // * https://www.mathworks.com/matlabcentral/answers/438100-how-to-use-mexprintf-with-c-mex-functions 13 | // * https://www.mathworks.com/help/matlab/matlab_external/displaying-output-in-matlab-command-window.html 14 | // Go figure. 15 | // 16 | // So, just do nothing. 17 | }; 18 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/globals.m: -------------------------------------------------------------------------------- 1 | classdef globals 2 | % Global library properties and settings for SLF4M 3 | 4 | properties (Constant) 5 | % Path to the root directory of this SLF4M distribution 6 | distroot = string(fileparts(fileparts(fileparts(mfilename('fullpath'))))); 7 | end 8 | 9 | methods (Static) 10 | 11 | function out = version 12 | % The version of the SLF4M library 13 | % 14 | % Returns a string. 15 | persistent val 16 | if isempty(val) 17 | versionFile = fullfile(logger.globals.distroot, 'VERSION'); 18 | val = strtrim(mypackage.internal.util.readtext(versionFile)); 19 | end 20 | out = val; 21 | end 22 | 23 | end 24 | 25 | end 26 | 27 | -------------------------------------------------------------------------------- /docs-src-gh-pages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | __myproject_summary__ 6 | 7 | The main index page for your project documentation goes here! 8 | 9 | ## About 10 | 11 | __myproject_description__ 12 | 13 | ## Requirements 14 | 15 | TODO! 16 | 17 | ## Installation 18 | 19 | TODO! 20 | 21 | ## Usage 22 | 23 | TODO! 24 | 25 | ### Examples 26 | 27 | TODO! 28 | 29 | ## Author 30 | 31 | __myproject__ is written and maintained by [__YOUR_NAME_HERE__](__author_homepage__). The project home page is . 32 | 33 | ## Acknowledgments 34 | 35 | This project was created with [MatlabProjectTemplate](https://github.com/apjanke/MatlabProjectTemplate) by [Andrew Janke](https://apjanke.net). 36 | -------------------------------------------------------------------------------- /docs-src-gh-pages-raw/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | __myproject_summary__ 6 | 7 | The main index page for your project documentation goes here! 8 | 9 | ## About 10 | 11 | __myproject_description__ 12 | 13 | ## Requirements 14 | 15 | TODO! 16 | 17 | ## Installation 18 | 19 | TODO! 20 | 21 | ## Usage 22 | 23 | TODO! 24 | 25 | ### Examples 26 | 27 | TODO! 28 | 29 | ## Author 30 | 31 | __myproject__ is written and maintained by [__YOUR_NAME_HERE__](__author_homepage__). The project home page is . 32 | 33 | ## Acknowledgments 34 | 35 | This project was created with [MatlabProjectTemplate](https://github.com/apjanke/MatlabProjectTemplate) by [Andrew Janke](https://apjanke.net). 36 | -------------------------------------------------------------------------------- /Mcode/+mypackage/Settings.m: -------------------------------------------------------------------------------- 1 | classdef Settings < mypackage.internal.MypackageBaseHandle 2 | % Global settings for the mypackage package 3 | % 4 | % Don't use this class directly. If you want to get or set the settings, 5 | % work with the instance of this in the mypackage.globals.settings field. 6 | 7 | properties 8 | end 9 | 10 | methods (Static=true) 11 | 12 | function out = discover() 13 | % Discovery of initial values for package settings. 14 | % 15 | % This could look at config files, environment variables, Matlab appdata, and 16 | % so on. 17 | % 18 | % This needs to avoid referencing mypackage.globals, to avoid a circular dependency. 19 | out = mypackage.Settings; 20 | end 21 | 22 | end 23 | 24 | end -------------------------------------------------------------------------------- /dev-kit/README.md: -------------------------------------------------------------------------------- 1 | # __myproject__ Dev Kit 2 | 3 | This directory contains tools for doing development on __myproject__ itself. It is for use by the developers of __myproject__, and is not for use by users of it, and does not ship with the project distribution. 4 | 5 | __myproject__ developers should add this directory to their Matlab path when working on the project. 6 | 7 | The whole thing about this directory is that it doesn't care about name collisions in its class and function names; it assumes that __myproject__ is the only thing you're working on in your session. It names functions whatever it wants, and doesn't put them inside packages. (This is intentional, to make it convenient for __myproject__ developers to call them without having to type out the package name all the time.) So it is _not_ suitable for redistribution to users. 8 | -------------------------------------------------------------------------------- /dev-kit/util-shims/dir2.m: -------------------------------------------------------------------------------- 1 | function [names, details] = dir2(dirPath) 2 | % Like DIR, but better 3 | % 4 | % [names, details] = dir2(path) 5 | % 6 | % Like DIR, but better: Doesn't include . and .. in the results. Returns the 7 | % details as a table, and time values as datetimes. 8 | % 9 | % Path is the path to the directory, file, or fileglob pattern to list. 10 | % 11 | % Returns: 12 | % names - a list of the child files and directories under path, as string 13 | % array 14 | % details - entry details, as a table array, with variables: 15 | % name - base name of file (string) 16 | % path - full path to file (string) 17 | % mtime - last modification time of file (datetime with TimeZone) 18 | % bytes - size of file in bytes (double) 19 | % isdir - whether file is a directory (logical) 20 | 21 | [names, details] = mypackage.internal.util.dir2(dirPath); 22 | 23 | end 24 | -------------------------------------------------------------------------------- /dev-kit/private/rmrf.m: -------------------------------------------------------------------------------- 1 | function rmrf(files) 2 | % Recursively delete files and directories 3 | % 4 | % rmrf(files) 5 | arguments 6 | files string 7 | end 8 | 9 | %#ok<*STRNU> 10 | 11 | RAII.warn = withwarnoff('MATLAB:DELETE:FileNotFound'); 12 | 13 | % Sigh. We have to glob out the files ourselves because Matlab doesn't have a 14 | % delete operation that will work on both files and directories. 15 | for iGlob = 1:numel(files) 16 | glob = files(iGlob); 17 | if glob.contains("*") 18 | [~,details] = dir2(files(iGlob)); 19 | paths = details.path; 20 | for file = paths' 21 | rmrf_one_file(file); 22 | end 23 | else 24 | file = glob; 25 | rmrf_one_file(file); 26 | end 27 | end 28 | 29 | end 30 | 31 | function rmrf_one_file(file) 32 | if isfile(file) 33 | delete(file); 34 | elseif isfolder(file) 35 | rmdir2(file, 's'); 36 | else 37 | % Ignore nonexistent files. 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /docs-src-jekyll/README.md: -------------------------------------------------------------------------------- 1 | # docs-src 2 | 3 | This directory contains stuff for generating the project documentation using Jekyll, a static site generator that uses Markdown. 4 | 5 | ## Requirements 6 | 7 | You will need to have Ruby and Bundler installed on your machine. This part you'll have to figure out yourself. 8 | 9 | Then do "bundle install" in this directory before it will work. 10 | 11 | ## Usage 12 | 13 | Get set up: 14 | 15 | ```bash 16 | bundle install 17 | ``` 18 | 19 | Preview the site in a local web server: 20 | 21 | ```bash 22 | bundle exec jekyll serve 23 | ``` 24 | 25 | To actually build the documentation for use in your project's distribution, go back up a directory and run `make doc`. 26 | 27 | ## Writing documentation 28 | 29 | You can use either Markdown or AsciiDoc to write your documentation files. Markdown files must have a `.md` extension. Asciidoc files must have `.adoc` or another standard AsciiDoc file extension. See `Use-AsciiDoc.adoc` for examples. 30 | -------------------------------------------------------------------------------- /docs-src-mkdocs/docs/index.md: -------------------------------------------------------------------------------- 1 | # __myproject__ Documentation 2 | 3 | __myproject_summary__ 4 | 5 | ## About 6 | 7 | __myproject_description__ 8 | 9 | ## Installation 10 | 11 | ## Usage 12 | 13 | ### Examples 14 | 15 | ```matlab 16 | classdef SomeClass < SomeOtherClass 17 | 18 | properties 19 | x (1,1) double = 42 20 | y 21 | end 22 | 23 | methods 24 | function this = SomeClass() 25 | end 26 | end 27 | 28 | end 29 | 30 | function anExampleFunction(foo, bar) 31 | arguments 32 | foo (1,1) double 33 | bar string = "whatever" 34 | end 35 | 36 | fprintf('Hello, world!') 37 | end 38 | ``` 39 | 40 | ## Author 41 | 42 | __myproject__ is written and maintained by [__YOUR_NAME_HERE__](__author_homepage__). The project home page is . 43 | 44 | ## Acknowledgments 45 | 46 | This project was created with [MatlabProjectTemplate](https://github.com/apjanke/MatlabProjectTemplate) by [Andrew Janke](https://apjanke.net). 47 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/misc.m: -------------------------------------------------------------------------------- 1 | classdef misc 2 | % Miscellaneous utilities 3 | % 4 | % The ones in this class are ones you typically wouldn't want to import, and are mostly 5 | % for the +internal package's own internal use. 6 | 7 | methods (Static=true) 8 | 9 | function mkdir(dir) 10 | [ok,msg] = mkdir(dir); 11 | if ~ok 12 | error('Failed creating directory "%s": %s', dir, msg); 13 | end 14 | end 15 | 16 | function out = getpackageappdata(key) 17 | ad = getappdata(0, 'mypackage'); 18 | if isempty(ad) 19 | ad = struct; 20 | end 21 | if isfield(ad, key) 22 | out = ad.(key); 23 | else 24 | out = []; 25 | end 26 | end 27 | 28 | function setpackageappdata(key, value) 29 | ad = getappdata(0, 'mypackage'); 30 | if isempty(ad) 31 | ad = struct; 32 | end 33 | ad.(key) = value; 34 | setappdata(0, 'mypackage', ad); 35 | end 36 | 37 | end 38 | 39 | end -------------------------------------------------------------------------------- /dev-kit/util-shims/mustBeA.m: -------------------------------------------------------------------------------- 1 | function mustBeA(value, type) 2 | %MUSTBEA Validate that an input is of a particular data type 3 | % 4 | % MUSTBEA(value, type) 5 | % 6 | % Validates that the input Value is of the specified Type or a 7 | % subtype. If Value is not of Type, an error is raised. If Value is 8 | % of Type, does nothing and returns. 9 | % 10 | % Value is the value to validates the type of. It may be anything. If 11 | % you call it using a variable (as opposed to a longer expression), 12 | % the variable name is included in any error messages. 13 | % 14 | % Type (char) is the name of the type that Value must be. A type 15 | % name may be one of: 16 | % * A class, such as 'double', 'cell', or 'containers.Map' 17 | % * A Janklab pseudotype, such as 'cellstr' or 'numeric' 18 | % 19 | % Note: The cellstr pseudotype is nontrivial to check for, as it 20 | % must call iscellstr() and check all cell contents. Avoid calling it in 21 | % performance-critical code. 22 | mypackage.internal.util.mustBeA(value, type); 23 | end 24 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/private/parseOpts.m: -------------------------------------------------------------------------------- 1 | function out = parseOpts(opts, defaults) 2 | %PARSEOPTS Parse a Janklab-style "option" argument 3 | % 4 | % out = parseOpts(opts, defaults) 5 | % 6 | % Parses a Janklab-style "option" argument into a struct, optionally applying 7 | % defaults. Generally you will want to supply a defaults with all fields 8 | % present, so you don't have to check for the existence of a field before 9 | % referencing it. 10 | % 11 | % Options and defaults may both be of type: 12 | % * struct 13 | % * cellrec 14 | % 15 | out = parseOptsArg(opts); 16 | if nargin > 1 17 | defaults = parseOptsArg(defaults); 18 | out = copyfields(defaults, out); 19 | end 20 | end 21 | 22 | function out = parseOptsArg(opts) 23 | if isempty(opts) 24 | opts = struct; 25 | end 26 | if isstruct(opts) 27 | out = opts; 28 | elseif iscell(opts) 29 | opts = cellrec(opts); 30 | out = cellrec2struct(opts); 31 | else 32 | error('dispstr:InvalidInput', 'Unsupported input type: %s', class(opts)); 33 | end 34 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/+internal/private/parseOpts.m: -------------------------------------------------------------------------------- 1 | function out = parseOpts(opts, defaults) 2 | %PARSEOPTS Parse a Janklab-style "option" argument 3 | % 4 | % out = parseOpts(opts, defaults) 5 | % 6 | % Parses a Janklab-style "option" argument into a struct, optionally applying 7 | % defaults. Generally you will want to supply a defaults with all fields 8 | % present, so you don't have to check for the existence of a field before 9 | % referencing it. 10 | % 11 | % Options and defaults may both be of type: 12 | % * struct 13 | % * cellrec 14 | % 15 | out = parseOptsArg(opts); 16 | if nargin > 1 17 | defaults = parseOptsArg(defaults); 18 | out = copyfields(defaults, out); 19 | end 20 | end 21 | 22 | function out = parseOptsArg(opts) 23 | if isempty(opts) 24 | opts = struct; 25 | end 26 | if isstruct(opts) 27 | out = opts; 28 | elseif iscell(opts) 29 | opts = cellrec(opts); 30 | out = cellrec2struct(opts); 31 | else 32 | error('dispstr:InvalidInput', 'Unsupported input type: %s', class(opts)); 33 | end 34 | end -------------------------------------------------------------------------------- /docs-src-mkdocs/make_doc.m: -------------------------------------------------------------------------------- 1 | function make_doc 2 | % Build these doc sources into the final doc/ directory 3 | % 4 | % make_doc 5 | % make_doc --preview 6 | % make_doc --build-only 7 | % 8 | % This will require special configuration on Windows to get it working. 9 | % 10 | % Requires mkdocs to be installed. See https://www.mkdocs.org/. 11 | 12 | action = "install"; 13 | args = string(varargin); 14 | if ismember("--preview", args) 15 | action = "preview"; 16 | elseif ismember("--build-only", args) 17 | action = "build"; 18 | end 19 | 20 | %#ok<*STRNU> 21 | 22 | import mypackage.internal.util.* 23 | 24 | RAII.cd = withcd(fileparts(mfilename('fullpath'))); 25 | 26 | system2('bundle install >/dev/null'); 27 | 28 | if action == "preview" 29 | % Use plain system() and quash because we expect this to error out when user Ctrl-C's it 30 | system('mkdocs serve'); 31 | else 32 | system2('mkdocs build'); 33 | if action == "install" 34 | rmdir2('../doc', 's'); 35 | copyfile2('_site/*.*', '../doc'); 36 | end 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /docs-src-gh-pages/_config.yml: -------------------------------------------------------------------------------- 1 | title: myproject Documentation 2 | email: __myproject_email__ 3 | description: __myproject_summary__ 4 | 5 | baseurl: "/" 6 | url: "" 7 | 8 | theme: jekyll-theme-cayman 9 | 10 | github_username: __myghuser__ 11 | 12 | # Locked-down GitHub Pages configuration 13 | markdown: kramdown 14 | lsi: false 15 | safe: true 16 | incremental: false 17 | highlighter: rouge 18 | gist: 19 | noscript: false 20 | kramdown: 21 | math_engine: mathjax 22 | syntax_highlighter: rouge 23 | 24 | exclude: 25 | - Gemfile* 26 | 27 | keep_files: 28 | - images 29 | 30 | plugins: 31 | # Forced by GitHub Pages: 32 | - jekyll-asciidoc 33 | - jekyll-coffeescript 34 | - jekyll-default-layout 35 | - jekyll-gist 36 | - jekyll-github-metadata 37 | - jekyll-last-modified-at 38 | - jekyll-optional-front-matter 39 | - jekyll-paginate 40 | - jekyll-readme-index 41 | - jekyll-relative-links 42 | - jekyll-titles-from-headings 43 | # Supported by GitHub Pages, but optional: 44 | - jekyll-feed 45 | - jekyll-seo-tag 46 | - jekyll-sitemap 47 | -------------------------------------------------------------------------------- /docs-src-gh-pages-raw/_config.yml: -------------------------------------------------------------------------------- 1 | title: myproject Documentation 2 | email: __myproject_email__ 3 | description: __myproject_summary__ 4 | 5 | baseurl: "/" 6 | url: "" 7 | 8 | theme: jekyll-theme-cayman 9 | 10 | github_username: __myghuser__ 11 | 12 | # Locked-down GitHub Pages configuration 13 | markdown: kramdown 14 | lsi: false 15 | safe: true 16 | incremental: false 17 | highlighter: rouge 18 | gist: 19 | noscript: false 20 | kramdown: 21 | math_engine: mathjax 22 | syntax_highlighter: rouge 23 | 24 | exclude: 25 | - Gemfile* 26 | 27 | keep_files: 28 | - images 29 | 30 | plugins: 31 | # Forced by GitHub Pages: 32 | - jekyll-asciidoc 33 | - jekyll-coffeescript 34 | - jekyll-default-layout 35 | - jekyll-gist 36 | - jekyll-github-metadata 37 | - jekyll-last-modified-at 38 | - jekyll-optional-front-matter 39 | - jekyll-paginate 40 | - jekyll-readme-index 41 | - jekyll-relative-links 42 | - jekyll-titles-from-headings 43 | # Supported by GitHub Pages, but optional: 44 | - jekyll-feed 45 | - jekyll-seo-tag 46 | - jekyll-sitemap 47 | -------------------------------------------------------------------------------- /MatlabProjectTemplate/test_project_initialization: -------------------------------------------------------------------------------- 1 | #/usr/bin/env bash 2 | # 3 | # test_project_initialization 4 | # 5 | # Tests whether the MPT project initialization code works by initializing this 6 | # repo, doing some builds, and then trying to roll back. 7 | # 8 | # The rollback is not guaranteed to work, so only do this in a repo copy that you 9 | # can afford to lose or manually clean up! 10 | 11 | # Error out on any failure to indicate the test failed 12 | set -e 13 | 14 | # Do not run if there are local changes, because the rollback is destructive 15 | if [[ $(git status --porcelain) ]]; then 16 | echo >&2 "Error: Cannot run $0 when there are uncommitted changes." 17 | echo >&2 "Changes:" 18 | git status --porcelain >&2 19 | exit 1 20 | fi 21 | 22 | ./dev-kit/run_matlab "init_project_from_template --dev" 23 | 24 | echo "" 25 | echo "Running tests..." 26 | make test 27 | 28 | echo "" 29 | echo "Building artifacts..." 30 | make docs 31 | make doc 32 | make dist 33 | 34 | echo "" 35 | echo "Cleaning up..." 36 | make clean 37 | ./rollback_init 38 | 39 | echo "" 40 | echo "Test passed." 41 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for CircleCI 2 | version: 2.1 3 | orbs: 4 | matlab: mathworks/matlab@0.4.0 5 | jobs: 6 | build: 7 | machine: 8 | image: 'ubuntu-1604:201903-01' 9 | steps: 10 | - checkout 11 | - run: 12 | name: "Switch to Python 3.7" 13 | command: | 14 | pyenv versions 15 | pyenv global 3.7.0 16 | - matlab/install 17 | - matlab/run-command: 18 | command: addpath dev-kit; mypackage_launchtests 19 | - matlab/run-tests: 20 | test-results-junit: test-results/matlab/results.xml 21 | code-coverage-cobertura: coverage 22 | source-folder: Mcode 23 | - store_test_results: 24 | path: test-results 25 | - run: 26 | command: make test 27 | - run: 28 | command: make dist 29 | - run: 30 | command: make clean 31 | # start_MPT_targets 32 | - run: 33 | command: git reset --hard 34 | - run: 35 | command: ./MatlabProjectTemplate/test_project_initialization 36 | # end_MPT_targets 37 | -------------------------------------------------------------------------------- /dev-kit/private/mypackage_build_all_mex.m: -------------------------------------------------------------------------------- 1 | function mypackage_build_all_mex 2 | % Builds all the MEX files in __myproject__'s source tree 3 | % 4 | % This is for use during development. 5 | % 6 | % You should do this at code authoring time, and then check your built MEX files back in 7 | % to git! This is not part of the project build process. 8 | % 9 | % This function builds your mex files with the default mex() options. There's no mechanism 10 | % to specify other options, either globally or on a per-file basis. We should probably 11 | % add something to do that. 12 | 13 | coderoot = fullfile(mypackage.globals.distroot, 'Mcode'); 14 | mexSourceFiles = [ 15 | searchFilesRecursively(coderoot, '**/*.cpp') 16 | searchFilesRecursively(coderoot, '**/*.c') 17 | searchFilesRecursively(coderoot, '**/*.f') 18 | ]; 19 | 20 | for sourceFile = mexSourceFiles' 21 | fprintf('Building MEX: %s\n', sourceFile) 22 | mex(sourceFile) 23 | end 24 | 25 | end 26 | 27 | function out = searchFilesRecursively(base, pattern) 28 | d = dir(fullfile(base, pattern)); 29 | out = fullfile({d.folder}, {d.name})'; 30 | end 31 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/rmrf.m: -------------------------------------------------------------------------------- 1 | function rmrf(files) 2 | % Recursively delete files and directories 3 | % 4 | % rmrf(files) 5 | % 6 | % files is a list of files and directories to delete. They may contain "*" 7 | % wildcards. 8 | % 9 | % Nonexistent directories and non-matching patterns are silently ignored. 10 | arguments 11 | files string 12 | end 13 | 14 | %#ok<*STRNU> 15 | 16 | RAII.warn = withwarnoff('MATLAB:DELETE:FileNotFound'); 17 | 18 | % Sigh. We have to glob out the files ourselves because Matlab doesn't have a 19 | % delete operation that will work on both files and directories. 20 | for iGlob = 1:numel(files) 21 | glob = files(iGlob); 22 | if glob.contains("*") 23 | [~,details] = mypackage.internal.util.dir2(files(iGlob)); 24 | paths = details.path; 25 | for file = paths' 26 | rmrf_one_file(file); 27 | end 28 | else 29 | file = glob; 30 | rmrf_one_file(file); 31 | end 32 | end 33 | 34 | end 35 | 36 | function rmrf_one_file(file) 37 | if isfile(file) 38 | delete(file); 39 | elseif isfolder(file) 40 | mypackage.internal.util.rmdir2(file, 's'); 41 | else 42 | % Ignore nonexistent files. 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /docs-src-jekyll/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Hello! This is where you manage which Jekyll version is used to run. 4 | # When you want to use a different version, change it below, save the 5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 6 | # 7 | # bundle exec jekyll serve 8 | # 9 | # This will help ensure the proper Jekyll version is running. 10 | # Happy Jekylling! 11 | 12 | gem 'jekyll', '~> 3.9.0' 13 | 14 | gem 'minima' #, '~> 2.0' 15 | 16 | group :jekyll_plugins do 17 | gem 'asciidoctor-diagram' 18 | gem 'jekyll-asciidoc' 19 | gem 'jekyll-feed', '~> 0.6' 20 | gem 'jekyll-seo-tag' 21 | gem 'jekyll-theme-slate' 22 | gem 'jekyll-last-modified-at' 23 | gem 'jekyll-toc' 24 | gem 'jekyll-sitemap' 25 | end 26 | 27 | # Asciidoctor uses pygments for source highlighting 28 | gem 'pygments.rb' 29 | 30 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 31 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 32 | # Performance-booster for watching directories on Windows 33 | gem 'wdm', '~> 0.1.0' if Gem.win_platform? 34 | 35 | # I don't know why we need these 36 | gem 'rexml' 37 | gem 'webrick' 38 | -------------------------------------------------------------------------------- /docs-src-jekyll/_config.yml: -------------------------------------------------------------------------------- 1 | title: __myproject__ Documentation 2 | email: __myprojectemail__ 3 | description: Documentation for __myproject__ 4 | 5 | baseurl: "" 6 | url: "" 7 | 8 | github_username: __myghuser__ 9 | github: 10 | repository_nwo: __myghuser__/__myproject__ 11 | forkme_nwo: __myghuser__/__myproject__ 12 | footer-links: 13 | github: __myghuser__/__myproject__ 14 | 15 | permalink: pretty 16 | 17 | exclude: 18 | - Gemfile* 19 | - README.md 20 | - make_doc 21 | 22 | markdown: kramdown 23 | theme: jekyll-theme-slate 24 | highlighter: rouge 25 | kramdown: 26 | syntax_highlighter_opts: 27 | default_lang: matlab 28 | asciidoc: {} 29 | asciidoctor: 30 | base_dir: :docdir 31 | safe: unsafe 32 | attributes: 33 | stem: 34 | imagesdir: /images 35 | source-highlighter: pygments 36 | pygments-stylesheet: css/asciidoc-pygments.css 37 | 38 | keep_files: 39 | - images 40 | plugins: 41 | - asciidoctor-diagram 42 | - jekyll-asciidoc 43 | - jekyll-last-modified-at 44 | - jekyll-toc # https://github.com/toshimaru/jekyll-toc 45 | - jekyll-sitemap # https://github.com/jekyll/jekyll-sitemap 46 | # Not sure about this one: 47 | # - jekyll-paginate 48 | -------------------------------------------------------------------------------- /docs-src-jekyll/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # __myproject__ Documentation 6 | 7 | __myproject_summary__ 8 | 9 | ## About 10 | 11 | __myproject_description__ 12 | 13 | ## Installation 14 | 15 | ## Usage 16 | 17 | ### Examples 18 | 19 | ```matlab 20 | classdef SomeClass < SomeOtherClass 21 | 22 | properties 23 | x (1,1) double = 42 24 | y 25 | end 26 | 27 | methods 28 | function this = SomeClass() 29 | end 30 | end 31 | 32 | end 33 | 34 | function anExampleFunction(foo, bar, baz, qux) 35 | arguments 36 | foo 37 | bar (1,1) double 38 | baz string = "whatever" 39 | qux string = "foo" {mustBeMember(qux, ["foo" "bar" "baz"])} 40 | end 41 | 42 | fprintf('Hello, world!\n') 43 | end 44 | ``` 45 | 46 | ## AsciiDoc 47 | 48 | Some of the documentation pages use AsciiDoc. See [here](Use-AsciiDoc/index.html) for an example. 49 | 50 | ## Author 51 | 52 | __myproject__ is written and maintained by [__YOUR_NAME_HERE__](__author_homepage__). The project home page is . 53 | 54 | ## Acknowledgments 55 | 56 | This project was created with [MatlabProjectTemplate](https://github.com/apjanke/MatlabProjectTemplate) by [Andrew Janke](https://apjanke.net). 57 | -------------------------------------------------------------------------------- /dev-kit/private/dir2.m: -------------------------------------------------------------------------------- 1 | function [names, details] = dir2(dirPath) 2 | % Like DIR, but better 3 | % 4 | % [names, details] = dir2(path) 5 | % 6 | % Like DIR, but better: Doesn't include . and .. in the results. Returns the 7 | % details as a table, and time values as datetimes. 8 | % 9 | % Path is the path to the directory, file, or fileglob pattern to list. 10 | % 11 | % Returns: 12 | % names - a list of the child files and directories under path, as string 13 | % array 14 | % details - entry details, as a table array, with variables: 15 | % name - base name of file (string) 16 | % path - full path to file (string) 17 | % mtime - last modification time of file (datetime with TimeZone) 18 | % bytes - size of file in bytes (double) 19 | % isdir - whether file is a directory (logical) 20 | 21 | d = dir(dirPath); 22 | tfDots = ismember({d.name}, {'.', '..'}); 23 | d(tfDots) = []; 24 | 25 | names = string({d.name}); 26 | 27 | if nargout > 1 28 | name = names'; 29 | folder = string({d.folder}'); 30 | path = fullfile(folder, name); 31 | mtime = datetime([d.datenum]', 'ConvertFrom','datenum'); 32 | mtime.TimeZone = datetime.SystemTimeZone; 33 | bytes = [d.bytes]'; 34 | isdir = [d.isdir]'; 35 | details = table(name, mtime, bytes, isdir, path, folder); 36 | % TODO: add ishidden var to details? 37 | end 38 | 39 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/dir2.m: -------------------------------------------------------------------------------- 1 | function [names, details] = dir2(dirPath) 2 | % Like DIR, but better 3 | % 4 | % [names, details] = dir2(path) 5 | % 6 | % Like DIR, but better: Doesn't include . and .. in the results. Returns the 7 | % details as a table, and time values as datetimes. 8 | % 9 | % Path is the path to the directory, file, or fileglob pattern to list. 10 | % 11 | % Returns: 12 | % names - a list of the child files and directories under path, as string 13 | % array 14 | % details - entry details, as a table array, with variables: 15 | % name - base name of file (string) 16 | % path - full path to file (string) 17 | % mtime - last modification time of file (datetime with TimeZone) 18 | % bytes - size of file in bytes (double) 19 | % isdir - whether file is a directory (logical) 20 | 21 | d = dir(dirPath); 22 | tfDots = ismember({d.name}, {'.', '..'}); 23 | d(tfDots) = []; 24 | 25 | names = string({d.name}); 26 | 27 | if nargout > 1 28 | name = names'; 29 | folder = string({d.folder}'); 30 | path = fullfile(folder, name); 31 | mtime = datetime([d.datenum]', 'ConvertFrom','datenum'); 32 | mtime.TimeZone = datetime.SystemTimeZone; 33 | bytes = [d.bytes]'; 34 | isdir = [d.isdir]'; 35 | details = table(name, mtime, bytes, isdir, path, folder); 36 | % TODO: add ishidden var to details? 37 | end 38 | 39 | end -------------------------------------------------------------------------------- /MatlabProjectTemplate/project-README.md: -------------------------------------------------------------------------------- 1 | # __myproject__ 2 | 3 | 4 | [![Travis Build Status](https://travis-ci.com/myghuser/myproject.svg?branch=main)](https://travis-ci.com/github/myghuser/myproject) [![CircleCI Build Status](https://circleci.com/gh/myghuser/myproject.svg?style=shield)](https://circleci.com/gh/myghuser/myproject) [![Azure Build Status](https://dev.azure.com/myghuser/myproject/_apis/build/status/myghuser.myproject?branchName=main)](https://dev.azure.com/myghuser/myproject/_build/latest?definitionId=1&branchName=main) 5 | 6 | __myproject_summary__ 7 | 8 | ## About 9 | 10 | __myproject_description__ 11 | 12 | ## Installation 13 | 14 | To install __myproject__, download it from the [Releases page](https://github.com/__myghuser__/__myproject__/releases) or clone the [repo](https://github.com/__myghuser__/__myproject__) to get it on your disk. Then add its `Mcode/` folder to your Matlab path. 15 | 16 | ## Usage 17 | 18 | ### Examples 19 | 20 | ## Author 21 | 22 | __myproject__ is written and maintained by [__YOUR_NAME_HERE__](__author_homepage__). The project home page is . 23 | 24 | ## Acknowledgments 25 | 26 | This project was created with [MatlabProjectTemplate](https://github.com/apjanke/MatlabProjectTemplate) by [Andrew Janke](https://apjanke.net). 27 | -------------------------------------------------------------------------------- /Mcode/+mypackage/globals.m: -------------------------------------------------------------------------------- 1 | classdef globals 2 | % Global library properties and settings for myproject. 3 | % 4 | % Note that if you want to change the settings, you can't do this: 5 | % 6 | % mypackage.globals.settings.someSetting = 42; 7 | % 8 | % That will break due to how Matlab Constant properties work. Instead, you need 9 | % to first grab the Settings object and store it in a variable, and then work 10 | % on that: 11 | % 12 | % s = mypackage.globals.settings; 13 | % s.someSetting = 42; 14 | 15 | properties (Constant) 16 | % Path to the root directory of this __myproject__ distribution. 17 | distroot = string(fileparts(fileparts(fileparts(mfilename('fullpath'))))); 18 | % Global settings for mypackage. 19 | settings = mypackage.Settings.discover 20 | end 21 | 22 | methods (Static) 23 | 24 | function out = version 25 | % The version of the __myproject__ library 26 | % 27 | % Returns a string. 28 | persistent val 29 | if isempty(val) 30 | versionFile = fullfile(mypackage.globals.distroot, 'VERSION'); 31 | val = strtrim(mypackage.internal.util.readtext(versionFile)); 32 | end 33 | out = val; 34 | end 35 | 36 | function initialize 37 | % Initialize this library/package 38 | mypackage.internal.initializePackage; 39 | end 40 | 41 | end 42 | 43 | end 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore for Matlab projects 2 | 3 | # Note that the various binary MEX file formats are NOT included in this .gitignore. 4 | # That is because, unlike with most programming languages, the built binary MEX files 5 | # should be checked into the source tree so your code can still run straight from the 6 | # repo without a build step; they are not just distribution artifacts. 7 | 8 | # Build dirs and artifacts 9 | /build 10 | /dist 11 | /target 12 | /*.mltbx 13 | /coverage 14 | # Do NOT check the Matlab Toolbox .prj file in to the repo! This file can *not* be 15 | # shared between multiple users, because Matlab bakes full, absolute file paths 16 | # in to it. 17 | myproject.prj 18 | M-doc/ 19 | 20 | # Editor and IDE droppings 21 | .idea 22 | ~* 23 | *.m~ 24 | /.factorypath 25 | .classpath 26 | /.ijwb/ 27 | # VS Code stuff 28 | .project 29 | .settings 30 | .vscode 31 | 32 | # Doc site server droppings 33 | _site 34 | .sass-cache 35 | .jekyll-cache 36 | Gemfile.lock 37 | doc-src/site/ 38 | doc/site/ 39 | 40 | # Compiled class file 41 | *.class 42 | 43 | # Log file 44 | *.log 45 | 46 | # BlueJ files 47 | *.ctxt 48 | 49 | # Runtime and debug droppings 50 | matlab-crash-dump* 51 | *.pyc 52 | __pycache__ 53 | /venv/ 54 | *.log 55 | .ipynb_checkpoints 56 | hs_err_pid* 57 | /test-output/ 58 | /test-results/ 59 | /coverage/ 60 | /coverage-results/ 61 | 62 | # Editor, OS, and file browser droppings 63 | *.DS_Store 64 | Thumbs.db 65 | ehthumbs.db 66 | Desktop.ini 67 | $RECYCLE.BIN/ 68 | -------------------------------------------------------------------------------- /dev-kit/private/mypackage_build.m: -------------------------------------------------------------------------------- 1 | function mypackage_build 2 | % Build the project in preparation for distribution 3 | % 4 | % You do _not_ need to call this to get the project running from Mcode/. This is just 5 | % for special code transformation steps needed for the distributed project, like p-coding. 6 | 7 | reporoot = mypackage.globals.distroot; 8 | origDir = pwd; 9 | RAII.cd = onCleanup(@() cd(origDir)); 10 | cd(reporoot) 11 | 12 | fprintf('Building myproject...\n') 13 | 14 | cfgFile = fullfile(reporoot, '.mlproject.json'); 15 | config = jsondecode(mypackage.internal.util.readtext(cfgFile)); 16 | 17 | buildMcodeDir = fullfile('build', 'Mcode'); 18 | if isfolder(buildMcodeDir) 19 | rmdir(buildMcodeDir, 's'); 20 | end 21 | mkdir(buildMcodeDir); 22 | copyfile('Mcode', buildMcodeDir) 23 | 24 | % P-code the files if requested in the project definition 25 | if isfield(config, 'build') && isfield(config.build, 'do_pcode') 26 | if config.build.do_pcode 27 | fprintf('Pcoding files...\n') 28 | origWarn = warning; 29 | RAII.warning = onCleanup(@() warning(origWarn)); 30 | warning off MATLAB:pcode:FileNotFound 31 | d = dir('build/Mcode/**/+*'); 32 | dirs = string(['build/Mcode' fullfile({d.folder}, {d.name})]); 33 | for d = dirs 34 | pcode(d, '-inplace') 35 | end 36 | d = dir([buildMcodeDir, '/**/*.m']); 37 | mfilesInBuild = fullfile({d.folder}, {d.name}); 38 | delete(mfilesInBuild{:}) 39 | end 40 | end 41 | 42 | fprintf('myproject built.\n') 43 | 44 | end -------------------------------------------------------------------------------- /dev-kit/mypackage_package_toolbox.m: -------------------------------------------------------------------------------- 1 | function mypackage_package_toolbox 2 | % Packages this toolbox as a Matlab Toolbox .mltbx file 3 | % 4 | % mypackage_package_toolbox 5 | % 6 | % The package must be loaded on to the Matlab path in order for this to work. 7 | 8 | tbxInfo = mypackage_toolbox_info; 9 | tbxName = tbxInfo.name; 10 | 11 | if ~isfolder('dist') 12 | mkdir('dist'); 13 | end 14 | 15 | % Munge the project file 16 | % Toolboxes don't support "-
" or "+" suffixes in versions
17 | tbxVer = tbxInfo.version;
18 | baseTbxVer = strrep(regexprep(tbxVer, '-.*', ''), '+', '');
19 | fprintf('Packaging %s %s (as %s)\n', tbxName, tbxVer, baseTbxVer);
20 | 
21 | prjInFile = sprintf('%s.prj.in', tbxName);
22 | prjFile = sprintf('%s.prj', tbxName);
23 | prjTxt = fileread(prjInFile);
24 | prjTxt = strrep(prjTxt, '${PROJECT_VERSION}', baseTbxVer);
25 | spew(prjFile, prjTxt);
26 | 
27 | % I can't control the output file name from the project file, so we have to move
28 | % it ourselves
29 | builtFile = [tbxName '.mltbx'];
30 | targetFile = sprintf('dist/%s-%s.mltbx', tbxName, tbxVer);
31 | if isfile(targetFile)
32 |   delete(targetFile);
33 | end
34 | 
35 | matlab.addons.toolbox.packageToolbox(prjFile);
36 | movefile(builtFile, targetFile);
37 | delete(prjFile);
38 | 
39 | fprintf('%s %s packaged to %s (as %s)\n', tbxName, tbxVer, targetFile, baseTbxVer);
40 | 
41 | end
42 | 
43 | function spew(file, txt)
44 | [fid,msg] = fopen(file, 'w');
45 | RAII.fid = onCleanup(@() fclose(fid));
46 | if fid < 1
47 |   error('Failed opening file %s for writing: %s', file, msg);
48 | end
49 | fprintf(fid, '%s', txt);
50 | end


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | BSD 3-Clause License
 2 | 
 3 | Copyright (c) 2021, Andrew Janke
 4 | All rights reserved.
 5 | 
 6 | Redistribution and use in source and binary forms, with or without
 7 | modification, are permitted provided that the following conditions are met:
 8 | 
 9 | 1. Redistributions of source code must retain the above copyright notice, this
10 |    list of conditions and the following disclaimer.
11 | 
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 |    this list of conditions and the following disclaimer in the documentation
14 |    and/or other materials provided with the distribution.
15 | 
16 | 3. Neither the name of the copyright holder nor the names of its
17 |    contributors may be used to endorse or promote products derived from
18 |    this software without specific prior written permission.
19 | 
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | 


--------------------------------------------------------------------------------
/Mcode/+mypackage/+logger/version.m:
--------------------------------------------------------------------------------
 1 | function out = version
 2 | % VERSION Get version info for SLF4M
 3 | %
 4 | % logger.version
 5 | % v = logger.version
 6 | %
 7 | % Gets version info for the SLF4M library.
 8 | %
 9 | % If return value is not captured, displays version info for SLF4M and related
10 | % libraries to the console.
11 | %
12 | % If return value is captured, returns the version of the SLF4M library as
13 | % a char vector.
14 | 
15 | slf4mVersion = getSlf4mVersion;
16 | 
17 | if nargout > 0
18 |     out = slf4mVersion;
19 |     return
20 | else
21 |     versions.SLF4M = slf4mVersion;
22 |     versions.SLF4J = '?';
23 |     versions.log4j = '?';
24 |     % TODO: Figure out how to extract the version info from the JARs
25 |     % This probably can't be done without comparing JAR file checksums to
26 |     % Maven Central, which is slow, so maybe this isn't worth doing.
27 |     jars = javaclasspath('-static');
28 |     for i = 1:numel(jars)
29 |         jarFile = jars{i};
30 |         if endsWith(jarFile, 'log4j.jar')
31 |             %jar = java.util.jar.JarFile(jarFile);
32 |             %manifest = jar.getManifest;
33 |         elseif endsWith(jarFile, 'slf4j-api.jar')
34 |         end
35 |     end
36 |     libnames = fieldnames(versions);
37 |     for i = 1:numel(libnames)
38 |         fprintf('%s version %s\n', libnames{i}, versions.(libnames{i}));
39 |     end
40 | end
41 | 
42 | end
43 | 
44 | function out = getSlf4mVersion
45 | thisFile = mfilename('fullpath');
46 | rootDir = fileparts(fileparts(fileparts(thisFile)));
47 | versionFile = fullfile(rootDir, 'VERSION');
48 | txt = fileread(versionFile);
49 | out = regexprep(txt, '\r?\n', '');
50 | end
51 | 


--------------------------------------------------------------------------------
/docs-src-jekyll/make_doc.m:
--------------------------------------------------------------------------------
 1 | function make_doc(varargin)
 2 | % Build these doc sources into the final doc/ directory
 3 | %
 4 | % make_doc
 5 | % make_doc --preview
 6 | % make_doc --build-only
 7 | %
 8 | % This will only work on Linux or Mac, not Windows.
 9 | %
10 | % Requires Ruby and Bundler to be installed, and available on your $PATH.
11 | 
12 | action = "install";
13 | args = string(varargin);
14 | if ismember("--preview", args)
15 |   action = "preview";
16 | elseif ismember("--build-only", args)
17 |   action = "build";
18 | end
19 | 
20 | %#ok<*STRNU>
21 | 
22 | import mypackage.internal.util.*
23 | 
24 | RAII.cd = withcd(fileparts(mfilename('fullpath')));
25 | 
26 | [status, output] = system('ruby --version'); %#ok
27 | if status ~= 0
28 |   if ispc
29 |     installMsg = "Please install it from https://rubyinstaller.org/.";
30 |   elseif ismac
31 |     installMsg = "Please install it using Homebrew: `brew install ruby bundler`";
32 |   else
33 |     installMsg = "Please install it using your OS's package manager.";
34 |   end
35 |   error("It doesn't look like you have Ruby installed.\n%s\n%s", ...
36 |     "Ruby is required for building these docs.", installMsg);
37 | end
38 | 
39 | system2('bundle install >/dev/null');
40 | 
41 | if action == "preview"
42 |   % Use plain system() and quash because we expect this to error out when user Ctrl-C's it
43 |   try
44 |     system('bundle exec jekyll serve');
45 |   catch err %#ok
46 |     % quash
47 |     % Darn, that doesn't work... looks like Ctrl-C causes an un-catchable error?
48 |   end
49 | else
50 |   system2('bundle exec jekyll build');
51 |   if action == "install"
52 |     rmdir2('../doc', 's');
53 |     copyfile2('_site/*.*', '../doc');
54 |   end
55 | end
56 | 
57 | end


--------------------------------------------------------------------------------
/docs-src-gh-pages/make_doc.m:
--------------------------------------------------------------------------------
 1 | function make_doc(varargin)
 2 | % Build these doc sources into the final doc/ directory
 3 | %
 4 | % make_doc
 5 | % make_doc --preview
 6 | % make_doc --build-only
 7 | %
 8 | % This will only work on Linux or Mac, not Windows.
 9 | %
10 | % Requires Ruby and Bundler to be installed, and available on your $PATH.
11 | 
12 | action = "install";
13 | args = string(varargin);
14 | if ismember("--preview", args)
15 |   action = "preview";
16 | elseif ismember("--build-only", args)
17 |   action = "build";
18 | end
19 | 
20 | %#ok<*STRNU>
21 | 
22 | import mypackage.internal.util.*
23 | 
24 | RAII.cd = withcd(fileparts(mfilename('fullpath')));
25 | 
26 | [status, output] = system('ruby --version'); %#ok
27 | if status ~= 0
28 |   if ispc
29 |     installMsg = "Please install it from https://rubyinstaller.org/.";
30 |   elseif ismac
31 |     installMsg = "Please install it using Homebrew: `brew install ruby bundler`";
32 |   else
33 |     installMsg = "Please install it using your OS's package manager.";
34 |   end
35 |   error("It doesn't look like you have Ruby installed.\n%s\n%s", ...
36 |     "Ruby is required for building these docs.", installMsg);
37 | end
38 | 
39 | system2('bundle install >/dev/null');
40 | 
41 | if action == "preview"
42 |   % Use plain system() and quash because we expect this to error out when user Ctrl-C's it
43 |   try
44 |     system('bundle exec jekyll serve');
45 |   catch err %#ok
46 |     % quash
47 |     % Darn, that doesn't work... looks like Ctrl-C causes an un-catchable error?
48 |   end
49 | else
50 |   system2('bundle exec jekyll build');
51 |   if action == "install"
52 |     rmdir2('../doc', 's');
53 |     copyfile2('_site/*.*', '../doc');
54 |   end
55 | end
56 | 
57 | end


--------------------------------------------------------------------------------
/MatlabProjectTemplate/README.md:
--------------------------------------------------------------------------------
 1 | # MatlabProjectTemplate documentation
 2 | 
 3 | This directory contains documentation and tools for the MatlabProjectTemplate template repo itself. (This is the tool that was used to generate the skeleton for this project.)
 4 | 
 5 | You can ignore this stuff if you want, or even delete it: it is not required for the project to function properly. But you probably want to read the stuff in here, and maybe keep it around. It contains useful information!
 6 | 
 7 | ## Publishing to MathWorks File Exchange
 8 | 
 9 | MatlabProjectTemplate _strongly_ recommends that you publish your project to File Exchange by linking your File Exchange post to your GitHub repo, and using their new Release-driven linkage. (This is what File Exchange themselves recommend, too.)
10 | 
11 | ## Logging
12 | 
13 | MatlabProjectTemplate sets up support for logging by defining a standard `mypackage.logger` package, which contains code to do logging in a manner compatible with [SLF4M](https://github.com/janklab/SLF4M) and SLF4J, without actually taking a dependency on the SLF4M library. We do it this way because managing inter-library dependencies in Matlab is really hard, due to its lack of a package manager or virtualenvs. Doing it this way means your code will be compatible with any other Matlab code that uses SLF4M or does its own logging this way, and you won't have name collisions.
14 | 
15 | ## About MatlabProjectTemplate
16 | 
17 | MatlabProjectTemplate was written by [Andrew Janke](https://apjanke.net). The project home page is . You can get support, or even contribute to the project, there.
18 | 
19 | MatlabProjectTemplate is part of the [Janklab](https://github.com/janklab) suite of libraries for Matlab.
20 | 


--------------------------------------------------------------------------------
/docs-src-gh-pages/Gemfile:
--------------------------------------------------------------------------------
 1 | source 'https://rubygems.org'
 2 | 
 3 | # Hello! This is where you manage which Jekyll version is used to run.
 4 | # When you want to use a different version, change it below, save the
 5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
 6 | #
 7 | #     bundle exec jekyll serve
 8 | #
 9 | # This will help ensure the proper Jekyll version is running.
10 | # Happy Jekylling!
11 | 
12 | # Use the specific jekyll version that GitHub Pages is currently on.
13 | # This is both for consistency and to prevent Bundler from getting stuck in
14 | # a degenerate dependency resolution search that takes forever.
15 | gem 'jekyll', '~> 3.9.0'
16 | 
17 | gem 'minima' #, '~> 2.0'
18 | gem 'kramdown-parser-gfm'
19 | gem 'github-pages'
20 | 
21 | group :jekyll_plugins do
22 |   gem 'jekyll-theme-cayman'
23 |   gem 'jekyll-asciidoc'
24 |   gem 'jekyll-coffeescript'
25 |   gem 'jekyll-default-layout'
26 |   gem 'jekyll-gist'
27 |   gem 'jekyll-github-metadata'
28 |   gem 'jekyll-paginate'
29 |   gem 'jekyll-readme-index'
30 |   gem 'jekyll-relative-links'
31 |   gem 'jekyll-titles-from-headings'
32 |   gem 'jekyll-feed'
33 |   gem 'jekyll-seo-tag'
34 |   gem 'jekyll-theme-slate'
35 |   gem 'jekyll-last-modified-at'
36 |   gem 'jekyll-toc'
37 |   gem 'jekyll-sitemap'
38 |   gem 'jekyll-optional-front-matter'
39 | end
40 | 
41 | # Asciidoctor uses pygments for source highlighting
42 | gem 'pygments.rb'
43 | 
44 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
45 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
46 | # Performance-booster for watching directories on Windows
47 | gem 'wdm', '~> 0.1.0' if Gem.win_platform?
48 | 
49 | # I don't know why we need these
50 | gem 'rexml'
51 | gem 'webrick'
52 | 


--------------------------------------------------------------------------------
/docs-src-gh-pages-raw/Gemfile:
--------------------------------------------------------------------------------
 1 | source 'https://rubygems.org'
 2 | 
 3 | # Hello! This is where you manage which Jekyll version is used to run.
 4 | # When you want to use a different version, change it below, save the
 5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
 6 | #
 7 | #     bundle exec jekyll serve
 8 | #
 9 | # This will help ensure the proper Jekyll version is running.
10 | # Happy Jekylling!
11 | 
12 | # Use the specific jekyll version that GitHub Pages is currently on.
13 | # This is both for consistency and to prevent Bundler from getting stuck in
14 | # a degenerate dependency resolution search that takes forever.
15 | gem 'jekyll', '~> 3.9.0'
16 | 
17 | gem 'minima' #, '~> 2.0'
18 | gem 'kramdown-parser-gfm'
19 | gem 'github-pages'
20 | 
21 | group :jekyll_plugins do
22 |   gem 'jekyll-theme-cayman'
23 |   gem 'jekyll-asciidoc'
24 |   gem 'jekyll-coffeescript'
25 |   gem 'jekyll-default-layout'
26 |   gem 'jekyll-gist'
27 |   gem 'jekyll-github-metadata'
28 |   gem 'jekyll-paginate'
29 |   gem 'jekyll-readme-index'
30 |   gem 'jekyll-relative-links'
31 |   gem 'jekyll-titles-from-headings'
32 |   gem 'jekyll-feed'
33 |   gem 'jekyll-seo-tag'
34 |   gem 'jekyll-theme-slate'
35 |   gem 'jekyll-last-modified-at'
36 |   gem 'jekyll-toc'
37 |   gem 'jekyll-sitemap'
38 |   gem 'jekyll-optional-front-matter'
39 | end
40 | 
41 | # Asciidoctor uses pygments for source highlighting
42 | gem 'pygments.rb'
43 | 
44 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
45 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
46 | # Performance-booster for watching directories on Windows
47 | gem 'wdm', '~> 0.1.0' if Gem.win_platform?
48 | 
49 | # I don't know why we need these
50 | gem 'rexml'
51 | gem 'webrick'
52 | 


--------------------------------------------------------------------------------
/Mcode/+mypackage/+test/runtests.m:
--------------------------------------------------------------------------------
 1 | function out = runtests
 2 |   % runtests Run all the tests in mypackage
 3 |   %
 4 |   % rslt = mypackage.test.runtests
 5 |   %
 6 |   % Runs all the tests in mypackage, presenting results on the command
 7 |   % line and producing results output files.
 8 |   %
 9 |   % The results output files are
10 |   % created in a directory named "test-output" under the current directory.
11 |   % Output files:
12 |   % test-output/
13 |   %   junit/
14 |   %     mypackage/
15 |   %       results.xml     - JUnit XML format test results
16 |   %   cobertura/
17 |   %     coverage.xml      - Cobertura format code coverage report
18 |   %
19 |   % Examples:
20 |   % mypackage.test.runtests
21 |   
22 |   import matlab.unittest.TestSuite
23 |   import matlab.unittest.TestRunner
24 |   import matlab.unittest.plugins.CodeCoveragePlugin
25 |   import matlab.unittest.plugins.codecoverage.CoberturaFormat
26 |   import matlab.unittest.plugins.XMLPlugin;
27 |   
28 |   baseDir = pwd;
29 |   testOutDir = [baseDir '/test-output'];
30 |   if exist(testOutDir, 'dir')
31 |       rmdir(testOutDir, 's');
32 |   end
33 |   mkdir(testOutDir);
34 |   
35 |   suite = TestSuite.fromPackage('mypackage.test', 'IncludingSubpackages', true);
36 |   
37 |   runner = TestRunner.withTextOutput;
38 |   mkdir([testOutDir '/cobertura']);
39 |   coberturaOutFile = [testOutDir '/cobertura/coverage.xml'];
40 |   coveragePlugin = CodeCoveragePlugin.forPackage('mypackage', ...
41 |       'Producing',CoberturaFormat(coberturaOutFile ), ...
42 |       'IncludingSubpackages', true);
43 |   runner.addPlugin(coveragePlugin);
44 |   mkdir([testOutDir '/junit/mypackage']);
45 |   junitXmlPlugin = XMLPlugin.producingJUnitFormat(...
46 |       [testOutDir '/junit/mypackage/results.xml']);
47 |   runner.addPlugin(junitXmlPlugin);
48 |   
49 |   out = runner.run(suite);
50 |   
51 |   end
52 |   


--------------------------------------------------------------------------------
/MatlabProjectTemplate/Description for File Exchange.txt:
--------------------------------------------------------------------------------
 1 | Summary:
 2 | 
 3 | A template for creating repos for Matlab library and application projects
 4 | 
 5 | 
 6 | 
 7 | 
 8 | Description:
 9 | 
10 | 
11 | MatlabProjectTemplate is a template for creating Matlab library and application projects. It defines a "standard" project structure that should be suitable for many projects, including those intended for redistribution / open source.
12 | 
13 | Why does this exist? Because it's kind of a pain to create new Matlab software development projects. Just have a look at File Exchange: most of the projects there use "just a pile of M-files and maybe a README" as their project structure. That's not great for nontrivial projects.
14 | 
15 | MatlabProjectTemplate is suitable for both libraries and applications, and includes coding and organizational conventions that make it safe to use this project's code in a Matlab environment that uses code from other projects, too.
16 | 
17 | Features:
18 | 
19 | MatlabProjectTemplate supports the following features. You don't _have_ to use any of them; you can just ignore the ones you don't care about. But they're there if you need them!
20 | 
21 | * Collaboration between multiple developers
22 | * Building Matlab Toolboxes
23 | * Matlab Continuous Integration and unit tests
24 | * Distribution as both plain zip files and Matlab Toolbox `.mltbx` files
25 | * Using ("vendoring") third-party Java JAR and Matlab libraries
26 | * Custom Java code
27 | * Automatic library initialization
28 | * Logging, in an SLF4M/SLF4J/Log4j-compatible manner
29 | 
30 | About
31 | 
32 | MatlabProjectTemplate is authored by Andrew Janke (https://apjanke.net). The project home page is https://matlabprojecttemplate.janklab.net. For support, post an Issue on the GitHub repo.
33 | 
34 | MatlabProjectTemplate is part of the Janklab suite of libraries for Matlab. See http://janklab.net.
35 | 
36 | 


--------------------------------------------------------------------------------
/dev-kit/run_matlab:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | #
 3 | # _mypackage_run_matlab 
 4 | #
 5 | # Runs a command in a Matlab session with MyProject (and its dev-kit) on the Matlab path.
 6 | #
 7 | # ENVIRONMENT VARIABLES
 8 | #
 9 | #   MYPACKAGE_PREFERRED_MATLAB_VERSIONS - a space-separated list of Matlab releases to search for if matlab
10 | #       is not on the path, in order of descending preference.
11 | set -e
12 | 
13 | locate_matlab () {
14 |   search_order="${MYPACKAGE_PREFERRED_MATLAB_VERSIONS:-R2024b R2024a R2023b R2023a R2022b R2022a R2021b R2021a R2020b R2020a R2019b R2019a R2018b R2018a}"
15 |   if which matlab &>/dev/null; then
16 |     matlab=matlab
17 |   else
18 |     os=$(uname)
19 |     if [[ $os -eq "Darwin" ]]; then
20 |         for rel in $search_order; do
21 |           app_path="/Applications/MATLAB_${rel}.app"
22 |           # echo _mypackage_run_matlab: Checking for $rel at $app_path
23 |           if [[ -e "$app_path" ]]; then
24 |             # echo _mypackage_run_matlab: Found $rel at $app_path
25 |             matlab="$app_path/bin/matlab"
26 |             break
27 |           fi
28 |         done
29 |     else
30 |         for rel in $search_order; do
31 |           app_path="/usr/local/MATLAB/$rel"
32 |           # echo _mypackage_run_matlab: Checking for $rel at $app_path
33 |           if [[ -e "$app_path" ]]; then
34 |             # echo _mypackage_run_matlab: Found $rel at $app_path
35 |             matlab="$app_path/bin/matlab"
36 |             break
37 |           fi
38 |         done
39 |     fi
40 |   fi
41 | 
42 |   if [[ -z "$matlab" ]]; then
43 |     echo >&2 "_mypackage_run_matlab: Error: matlab is not on the path and it could not be detected."
44 |     exit 1
45 |   fi
46 | }
47 | 
48 | locate_matlab
49 | 
50 | devkit_dir="$(dirname "$0")"
51 | reporoot="$(dirname "$devkit_dir")"
52 | matlab -batch "addpath '$reporoot/Mcode'; addpath '${devkit_dir}'; $1"


--------------------------------------------------------------------------------
/doc-project/Release Checklist.md:
--------------------------------------------------------------------------------
 1 | # __myproject__ Release Checklist
 2 | 
 3 | ## Doing a release with the script
 4 | 
 5 | The `dev-kit/make_release` script can take care of most of the release work for you. To use it:
 6 | 
 7 | * Update `CHANGES.md` with the release date for your version.
 8 | * Commit the changes.
 9 | * Run `./dev-kit/make_release `.
10 | * Go to your GitHub repo site and draft the release.
11 | 
12 | ## Doing a release manually
13 | 
14 | * Run all the tests.
15 |   * `make test`, duh.
16 |   * Wouldn't hurt to do `make clean && git status && make test`/manual-cleanup, just to be sure.
17 | * Update and double-check the version number and date in `VERSION`.
18 | * Update the installation instructions in README to use the upcoming new release tarball URL.
19 |   * Format is: `https://github.com/__myghuser__/__myproject__/releases/download/v/__myproject__-.tar.gz`
20 | * Regenerate the doco.
21 |   * `make doc`
22 | * Commit all the files changed by the above steps.
23 |   * Use form: `git commit -a -m 'Cut release v'`
24 | * Make sure your repo is clean: `git status` should show no local changes.
25 | * Build it!
26 |   * Run `make dist`.
27 |   * Be sure to do this first, before creating the tag.
28 | * Create a git tag and push it and the changes to GitHub.
29 |   * `git tag v`
30 |   * `git push; git push --tags`
31 | * Create a new GitHub release from the tag.
32 |   * Just use `` as the name for the release.
33 |   * Upload the dist tarball as a file for the release.
34 | * Open development for next version.
35 |   * Update version number in `VERSION` to have a "+" suffix.
36 |   * Rebuild the doco.
37 |     * `(cd doc; make maintainer-clean; make all)`
38 |   * `git commit -a -m 'Open development for v'; git push`
39 | 
40 | * If there were any problems following these instructions exactly as written, report it as a bug.
41 | 


--------------------------------------------------------------------------------
/dev-kit/make_release:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | #
 3 | # make_release 
 4 | #
 5 | # Make a release.
 6 | 
 7 | version="$1"
 8 | 
 9 | if [[ ! -f 'myproject.prj.in' ]]; then
10 |   echo >&2 "Error: this script must be run from the root of the repo"
11 |   exit 1
12 | fi
13 | 
14 | function must () {
15 |   if ! "$@"; then
16 |     echo >&2 "Error: Step failed. No release for you!"
17 |     exit 1
18 |   fi
19 | }
20 | function mark_version () {
21 |   echo "$1" > VERSION
22 | }
23 | 
24 | echo "Creating release $version"
25 | 
26 | echo "Checking prerequisites..."
27 | if [[ ! -z "$(git status --porcelain)" ]]; then
28 |   echo >&2 "Error: Your repo is not clean! There are local changes. No release for you!"
29 |   exit 1
30 | fi
31 | 
32 | # TODO: Figure out how to check for pending remote changes on the tracked branch
33 | # in git. Diffing origin/main is incorrect, because we may be releasing a patch
34 | # release from a different branch.
35 | #must git remote update
36 | #if [[ ! -z "$(git diff origin/main)" ]]; then
37 | #  echo >&2 "Error: There are pending remote changes in git. No release for you!"
38 | #  exit 1
39 | #fi
40 | 
41 | # First run the tests before we make any changes
42 | 
43 | echo "Running tests..."
44 | must make test
45 | 
46 | # Okay, let's do the release
47 | 
48 | mark_version "$version"
49 | 
50 | echo "Regenerating doco..."
51 | must make doc
52 | 
53 | echo "Building dist..."
54 | must make dist
55 | echo "Building toolbox..."
56 | must make toolbox
57 | 
58 | echo "Tagging release..."
59 | must git commit -a -m "Version $version"
60 | must git tag "v$version"
61 | mark_version "${version}+"
62 | must git commit -a -m "Open development for next release"
63 | must git push
64 | must git push --tags
65 | 
66 | echo "Release is pushed! Now go to GitHub and draft the actual Release:"
67 | echo "   https://github.com/myghuser/myproject/releases"
68 | uname=$(uname)
69 | if [[ $uname = "Darwin" ]]; then
70 |   open "https://github.com/myghuser/myproject/releases"
71 | fi
72 | 


--------------------------------------------------------------------------------
/docs-src-jekyll/Use-AsciiDoc.adoc:
--------------------------------------------------------------------------------
 1 | ---
 2 | layout: default
 3 | ---
 4 | = Use AsciiDoc
 5 | :stem:
 6 | :url-asciidoctor: http://asciidoctor.org
 7 | :url-jekyll-asciidoc: https://github.com/asciidoctor/jekyll-asciidoc
 8 | :url-asciidoctor-diagram: https://asciidoctor.org/docs/asciidoctor-diagram/
 9 | 
10 | You can write your documentation files in AsciiDoc as well as Markdown.
11 | Jekyll converts it to HTML using {url-asciidoctor}[Asciidoctor], via the {url-jekyll-asciidoc}[jekyll-asciidoc Jekyll plugin].
12 | 
13 | Here's what code examples look like:
14 | 
15 | Some Ruby:
16 | 
17 | [source,ruby]
18 | puts "Hello, World!"
19 | 
20 | And, of course, some Matlab:
21 | 
22 | [source,matlab]
23 | ....
24 | classdef SomeClass < SomeOtherClass
25 |   
26 |   properties
27 |     x (1,1) double = 42
28 |     y
29 |   end
30 |   
31 |   methods
32 |     function this = SomeClass()
33 |     end
34 |     
35 |     function blah(this)
36 |       anExampleFunction(this.x)
37 |     end
38 |   end
39 |   
40 | end
41 |   
42 | function anExampleFunction(foo, bar)
43 |   arguments
44 |     foo (1,1) double
45 |     bar string = "whatever"
46 |   end
47 |   
48 |   fprintf('Hello, world!\n')
49 |   fprintf('foo=%f, bar=%s\n', foo, bar)
50 | end
51 | ....
52 | 
53 | That Matlab syntax highlighting currently isn't right. It's because Pygments, the syntax highlighter that Asciidoctor users, doesn't have good support for modern Matlab OOP language features.
54 | 
55 | You can use "STEM" formatting to display science and engineering math markup. Inline STEM can be written like stem:[sqrt(4) = 2], and block STEM content looks like this:
56 | 
57 | [stem]
58 | ++++
59 | sqrt(4) = 2
60 | ++++
61 | 
62 | (To get this to work, you need to explicitly enable `:stem:` on each `.adoc` page that uses it. I'm working on figuring out how to enable it globally.)
63 | 
64 | You can include GraphViz and UML diagrams using the {url-asciidoctor-diagram}[Asciidoctor Diagram plugin], which is enabled by default.
65 | 
66 | [graphviz,dot-example,svg]
67 | ....
68 | digraph g {
69 |     a -> b
70 |     b -> c
71 |     c -> d
72 |     d -> a
73 | }
74 | ....
75 | 


--------------------------------------------------------------------------------
/dev-kit/pull_in_homebrew_ruby.m:
--------------------------------------------------------------------------------
 1 | function pull_in_homebrew_ruby
 2 | % Gets Homebrew's Ruby on to your $PATH in this Matlab session
 3 | %
 4 | % This searches for Homebrew and modifies your $PATH environment variable
 5 | % to get Ruby and its ruby, bundler, and other commands on it. This is
 6 | % needed on Mac because when you launch Matlab from the /Applications icon,
 7 | % it starts up with a minimal $PATH and does not run your shell startup
 8 | % files.
 9 | %
10 | % If it doesn't find a Homebrewed Ruby installation, it just does nothing
11 | % and returns silently.
12 | %
13 | % Call this from your `startup.m` if you're a Mac Homebrew user. You'll probably
14 | % need to do this if you're using the Jekyll or GitHub Pages documentation
15 | % builder, because they rely on having a new Ruby and Bundler installed, and
16 | % Homebrew is usually the way people do that on Macs.
17 | %
18 | % To use this, stick this in your startup.m (after getting dev-kit/ on your
19 | % Matlab path):
20 | %
21 | % if ~ispc
22 | %   pull_in_homebrew_ruby
23 | % end
24 | 
25 | %#ok<*NBRAK>
26 | 
27 | if ispc
28 |   % fprintf("pull_in_homebrew_ruby: Homebrew doesn't exist on Windows. Not pulling it in.\n");
29 |   return
30 | end
31 | 
32 | installCandidates = [
33 |   "/usr/local"
34 |   ];
35 | found = [];
36 | for cand = installCandidates'
37 |   if isfolder(cand+"/Cellar") && isfolder(cand+"/bin")
38 |     found = cand;
39 |     break
40 |   end
41 | end
42 | 
43 | if isempty(found)
44 |   % No Homebrew installation detected
45 |   return
46 | end
47 | 
48 | p = strsplit(getenv('PATH'), ':');
49 | needBinDirs = [brewPrefix+"/bin", brewPrefix+"/opt/ruby/bin"];
50 | rubyGemsVer = []; %#ok
51 | for minorVer = 5:-1:0
52 |     for patchVer = 10:-1:0
53 |         maybeRubyVer = sprintf("3.%d.%d", minorVer, patchVer);
54 |         if isfolder(brewPrefix+"/lib/ruby/gems/"+maybeRubyVer+"/bin")
55 |             rubyGemsVer = maybeRubyVer;
56 |             needBinDirs(end+1) = brewPrefix+"/lib/ruby/gems/"+rubyGemsVer+"/bin"; %#ok
57 |             break
58 |         end
59 |     end
60 | end
61 | for binDir = needBinDirs
62 |   if ~ismember(binDir, p)
63 |     setenv('PATH', binDir+":"+getenv('PATH'));
64 |   end
65 | end
66 | 
67 | end
68 | 


--------------------------------------------------------------------------------
/dev-kit/util-shims/todatetime.m:
--------------------------------------------------------------------------------
 1 | function out = todatetime(x, zonePolicy)
 2 | % Convert values to datetime array
 3 | %
 4 | % out = todatetime(x)
 5 | % out = todatetime(x, zonePolicy)
 6 | %
 7 | % TODATETIME converts an input array to a datetime array. It is an alternative
 8 | % to the datetime constructor, and is provided because MPT thinks Matlab got the
 9 | % datetime constructor behavior wrong: in the one-arg constructor, numeric inputs are
10 | % interpreted as datevecs instead of datetimes; but we think that datenums are
11 | % in more common use and should have been the default interpretation of numeric
12 | % inputs. In addition, DATETIME does not support Java date/time types as inputs.
13 | %
14 | % TODATETIME also provides a zonePolicy argument for convenient setting
15 | % of the TimeZone on the constructed datetimes.
16 | %
17 | % x may be any of:
18 | %   - numeric, which are interpreted as datenums
19 | %   - string, char or cellstr, which are parsed as datestrs
20 | %   - datetime, which are left as is
21 | %   - java.util.Date or java.util.Date[]
22 | %   - java.time LocalDate, LocalDateTime, ZonedDateTime, OffsetDateTime, Instant, or
23 | %       arrays of same.
24 | %
25 | % zonePolicy is a string indicating how the TimeZone on the constructed
26 | % datetime array should be set. It may be one of:
27 | %   - 'passthrough' (default) - Keep whatever zone was set on the input, if any
28 | %   - 'unzoned' - no TimeZone is set
29 | %   - 'utc' - TimeZone is set to UTC
30 | %   - 'local' - TimeZone is set to the system's local time zone
31 | %   - anything else - the name of a specific time zone to set on the output
32 | %
33 | % The way the zone policy is applied is:
34 | %   * Inputs that do not have zone information are interpreted as being in the
35 | %     zone specified by zonePolicy.
36 | %   * Inputs that do have zone information are converted to the zone specified
37 | %     by zonePolicy.
38 | %
39 | % If the input x is a datetime or zoned Java date and a zonePolicy is provided, 
40 | % but the zonePolicy conflicts with the actual TimeZone set on x, an error is 
41 | % raised.
42 | %
43 | % Returns a datetime array.
44 | arguments
45 |   x
46 |   zonePolicy (1,1) string = 'passthrough'
47 | end
48 | out = mypackage.internal.util.todatetime(x, zonePolicy);
49 | end
50 | 


--------------------------------------------------------------------------------
/Mcode/+mypackage/+internal/initializePackage.m:
--------------------------------------------------------------------------------
 1 | function initializePackage
 2 | % Basic package initialization
 3 | %
 4 | % This should *only* do basic library initialization involving paths and dependency
 5 | % loading and the like. It should *not* discover initial values for library settings;
 6 | % that's done in mypackage.Settings.discover. It has to be that way so the package
 7 | % settings state can handle a `clear classes` gracefully.
 8 | 
 9 | % Do not re-initialize if already initialized
10 | if mypackage.internal.misc.getpackageappdata('initialized')
11 |   return
12 | end
13 | 
14 | % Don't depend on globals, to avoid circular dependency
15 | % distroot = mypackage.globals.distroot;
16 | distroot = string(fileparts(fileparts(fileparts(fileparts(mfilename('fullpath'))))));
17 | 
18 | % Java dependencies
19 | libJava = fullfile(distroot, 'lib', 'java');
20 | if isfolder(libJava)
21 |   javaLibs = readdir(libJava);
22 |   for jlib = javaLibs
23 |     jlibdir = fullfile(libJava, jlib);
24 |     d = dir(fullfile(jlibdir, '*.jar'));
25 |     for jar = {d.name}
26 |       javaaddpath(fullfile(jlibdir, jar));
27 |     end
28 |   end
29 | end
30 | 
31 | % Matlab library dependencies
32 | libMatlab = fullfile(distroot, 'lib', 'matlab');
33 | if isfolder(libMatlab)
34 |   mLibs = readdir(libMatlab);
35 |   for mlib = mLibs
36 |     mlibdir = fullfile(libMatlab, mlib);
37 |     % There's no standard layout for a Matlab project, so we use heuristics to guess
38 |     % where they keep their source files
39 |     candidateSubdirs = ["Mcode" "mcode" "src" "srcfiles"];
40 |     for sub = candidateSubdirs
41 |       if isfolder(fullfile(mlibdir, sub))
42 |         addpath(fullfile(mlibdir, sub))
43 |       end
44 |     end
45 |     d = dir(fullfile(mlibdir, '*.m'));
46 |     if ~isempty(d)
47 |       addpath(mlibdir);
48 |     end
49 |     % TODO: Maybe we should just look at all top-level dirs that aren't `+` package dirs,
50 |     % and add them all if they contain any M-files?
51 |   end
52 | end
53 | 
54 | % Put any other custom library initialization code here
55 | 
56 | % Mark library as initialized
57 | 
58 | mypackage.internal.misc.setpackageappdata('initialized', true);
59 | 
60 | end
61 | 
62 | function out = readdir(theDir)
63 | d = dir(theDir);
64 | out = string(setdiff({d.name}, {'.' '..'}));
65 | end
66 | 


--------------------------------------------------------------------------------
/Mcode/+mypackage/private/todatetime.m:
--------------------------------------------------------------------------------
 1 | function out = todatetime(x, zonePolicy)
 2 | % Convert values to datetime array
 3 | %
 4 | % out = todatetime(x)
 5 | % out = todatetime(x, zonePolicy)
 6 | %
 7 | % TODATETIME converts an input array to a datetime array. It is an alternative
 8 | % to the datetime constructor, and is provided because MPT thinks Matlab got the
 9 | % datetime constructor behavior wrong: in the one-arg constructor, numeric inputs are
10 | % interpreted as datevecs instead of datetimes; but we think that datenums are
11 | % in more common use and should have been the default interpretation of numeric
12 | % inputs. In addition, DATETIME does not support Java date/time types as inputs.
13 | %
14 | % TODATETIME also provides a zonePolicy argument for convenient setting
15 | % of the TimeZone on the constructed datetimes.
16 | %
17 | % x may be any of:
18 | %   - numeric, which are interpreted as datenums
19 | %   - string, char or cellstr, which are parsed as datestrs
20 | %   - datetime, which are left as is
21 | %   - java.util.Date or java.util.Date[]
22 | %   - java.time LocalDate, LocalDateTime, ZonedDateTime, OffsetDateTime, Instant, or
23 | %       arrays of same.
24 | %
25 | % zonePolicy is a string indicating how the TimeZone on the constructed
26 | % datetime array should be set. It may be one of:
27 | %   - 'passthrough' (default) - Keep whatever zone was set on the input, if any
28 | %   - 'unzoned' - no TimeZone is set
29 | %   - 'utc' - TimeZone is set to UTC
30 | %   - 'local' - TimeZone is set to the system's local time zone
31 | %   - anything else - the name of a specific time zone to set on the output
32 | %
33 | % The way the zone policy is applied is:
34 | %   * Inputs that do not have zone information are interpreted as being in the
35 | %     zone specified by zonePolicy.
36 | %   * Inputs that do have zone information are converted to the zone specified
37 | %     by zonePolicy.
38 | %
39 | % If the input x is a datetime or zoned Java date and a zonePolicy is provided, 
40 | % but the zonePolicy conflicts with the actual TimeZone set on x, an error is 
41 | % raised.
42 | %
43 | % Returns a datetime array.
44 | arguments
45 |   x
46 |   zonePolicy (1,1) string = 'passthrough'
47 | end
48 | 
49 | out = mypackage.internal.util.todatetime(x, zonePolicy);
50 | 
51 | end
52 | 


--------------------------------------------------------------------------------
/Mcode/+mypackage/+logger/private/mustBeA.m:
--------------------------------------------------------------------------------
 1 | function mustBeA(value, type)
 2 | %MUSTBETYPE Validate that an input is of a particular data type
 3 | %
 4 | % MUSTBETYPE(value, type)
 5 | %
 6 | % Validates that the input Value is of the specified Type or a
 7 | % subtype. If Value is not of Type, an error is raised. If Value is
 8 | % of Type, does nothing and returns.
 9 | %
10 | % Value is the value to validates the type of. It may be anything. If
11 | % you call it using a variable (as opposed to a longer expression),
12 | % the variable name is included in any error messages.
13 | %
14 | % Type (char) is the name of the type that Value must be. A type
15 | % name may be one of:
16 | %   * A class, such as 'double', 'cell', or 'containers.Map'
17 | %   * One of the special SLF4M pseudotypes:
18 | %       cellstr
19 | %       numeric
20 | %       object
21 | %       any
22 | %
23 | % Note: The cellstr pseudotype is nontrivial to check for, as it
24 | % must call iscellstr() and check all cell contents.
25 | 
26 | % Avoid infinite recursion
27 | assert(ischar(type), 'mypackage:InvalidInput',...
28 |     'type must be a char, but got a %s', class(type));
29 | 
30 | 
31 | % Special pseudotype cases
32 | switch type
33 |     case 'cellstr'
34 |         if iscellstr(value)
35 |             return
36 |         else
37 |             if iscell(value)
38 |                 elementTypes = unique(cellfun(@class, value, 'UniformOutput',false));
39 |                 typeDescription = sprintf('cell containing %s', strjoin(elementTypes, ' and '));
40 |             else
41 |                 typeDescription = class(value);
42 |             end
43 |             reportBadValue(inputname(1), type, typeDescription);
44 |         end
45 |     case 'numeric'
46 |         if isnumeric(value)
47 |             return
48 |         else
49 |             reportBadValue(inputname(1), 'numeric', class(value));
50 |         end
51 |     case 'object'
52 |         % 'object' means user-defined Matlab objects
53 |         if isobject(value)
54 |             return
55 |         else
56 |             reportBadValue(inputname(1), 'object', class(value));
57 |         end
58 |     case 'any'
59 |         % Always passes: any type is an 'any'
60 |         return
61 | end
62 | 
63 | % General case
64 | if ~isa(value, type)
65 |     reportBadValue(inputname(1), type, class(value));
66 | end
67 | 
68 | end
69 | 
70 | 
71 | 


--------------------------------------------------------------------------------
/Mcode/+mypackage/+internal/+util/mustBeA.m:
--------------------------------------------------------------------------------
 1 | function mustBeA(value, type)
 2 | %MUSTBEA Validate that an input is of a particular data type
 3 | %
 4 | % MUSTBEA(value, type)
 5 | %
 6 | % Validates that the input Value is of the specified Type or a
 7 | % subtype. If Value is not of Type, an error is raised. If Value is
 8 | % of Type, does nothing and returns.
 9 | %
10 | % Value is the value to validates the type of. It may be anything. If
11 | % you call it using a variable (as opposed to a longer expression),
12 | % the variable name is included in any error messages.
13 | %
14 | % Type (char) is the name of the type that Value must be. A type
15 | % name may be one of:
16 | %   * A class, such as 'double', 'cell', or 'containers.Map'
17 | %   * A Janklab pseudotype, such as 'cellstr' or 'numeric'
18 | %
19 | % Note: The cellstr pseudotype is nontrivial to check for, as it
20 | % must call iscellstr() and check all cell contents. Avoid calling it in
21 | % performance-critical code.
22 | 
23 | % Avoid infinite recursion
24 | assert(ischar(type), 'type must be a char, but got a %s', class(type));
25 | 
26 | % Special pseudotype cases
27 | % TODO: These can probably go away now that we're using isa2()
28 | switch type
29 |     case 'cellstr'
30 |         if iscellstr(value) %#ok
31 |             return
32 |         else
33 |             if iscell(value)
34 |                 elementTypes = unique(cellfun(@class, value, 'UniformOutput',false));
35 |                 typeDescription = sprintf('cell containing %s', strjoin(elementTypes, ' and '));
36 |             else
37 |                 typeDescription = class(value);
38 |             end
39 |             reportBadValue(inputname(1), type, typeDescription);
40 |         end
41 |     case 'numeric'
42 |         if isnumeric(value)
43 |             return
44 |         else
45 |             reportBadValue(inputname(1), 'numeric', class(value));
46 |         end
47 |     case 'object'
48 |         % 'object' means user-defined Matlab objects
49 |         if isobject(value)
50 |             return
51 |         else
52 |             reportBadValue(inputname(1), 'object', class(value));
53 |         end
54 |     case 'nil'
55 |         % nil uses a special test
56 |         if isnil(value)
57 |             return
58 |         else
59 |             reportBadValue(inputname(1), 'nil', class(value));
60 |         end
61 |     case 'any'
62 |         % Always passes: any type is an 'any'
63 |         return
64 | end
65 | 
66 | % General case
67 | if ~isa(value, type)
68 |     reportBadValue(inputname(1), type, class(value));
69 | end
70 | 
71 | end
72 | 
73 | 
74 | 


--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
 1 | # This Makefile lets you build the project and its documentation, run tests,
 2 | # package it for distribution, and so on.
 3 | #
 4 | # This is mostly provided just as a convenience for developers who like using 'make'.
 5 | # All the actual build logic is in the dev-kit/*.m files, which can be run
 6 | # directly, without using 'make'. The exception is 'make java', which must be
 7 | # run without Matlab running, because Matlab locks the JAR files it has loaded.
 8 | #
 9 | # Targets provided:
10 | #
11 | #   make docs     - Build the Markdown-stage doco in docs/ (from docs-src/)
12 | #   make doc      - Build the final static doco into doc/ (from docs/)
13 | #
14 | #   make test     - Run the project Matlab unit tests
15 | #
16 | #   make dist     - Build all the project distribution files
17 | #   make toolbox  - Build the project distribution Matlab Toolbox .mltbx file
18 | #   make zips     - Build the project distribution zip files
19 | #
20 | #   make clean    - Remove derived files
21 | #
22 | #   make java     - Build the project's custom Java code
23 | #   make build    - "Build" (p-code & munge) Mcode source files for distribution
24 | #   make m-doc    - Copy the static documentation into mltbx staging area
25 | 
26 | .PHONY: test
27 | test:
28 | 	./dev-kit/run_matlab "mypackage_make test"
29 | 
30 | .PHONY: build
31 | build:
32 | 	./dev-kit/run_matlab "mypackage_make build"
33 | 
34 | # Build the programmatically-generated parts of the _source_ files for the doco
35 | .PHONY: docs
36 | docs:
37 | 	./dev-kit/run_matlab "mypackage_make docs"
38 | 
39 | # Build the actual output documents
40 | .PHONY: doc
41 | doc:
42 | 	./dev-kit/run_matlab "mypackage_make doc"
43 | 
44 | .PHONY: m-doc
45 | m-doc:
46 | 	./dev-kit/run_matlab "mypackage_make m-doc"
47 | 
48 | .PHONY: toolbox
49 | toolbox: m-doc
50 | 	./dev-kit/run_matlab "mypackage_make toolbox"
51 | 
52 | .PHONY: zips
53 | zips:
54 | 	./dev-kit/run_matlab "mypackage_make zips"
55 | 
56 | .PHONY: dist
57 | dist:
58 | 	./dev-kit/run_matlab "mypackage_make dist"
59 | 
60 | # TODO: Port this to M-code. This is hard because the .jar cannot be copied in to place
61 | # in lib while Matlab is running, because it locks loaded .jar files (at least on Windows).
62 | .PHONY: java
63 | java:
64 | 	cd src/java/myproject-java; mvn package
65 | 	mkdir -p lib/java/myproject-java
66 | 	cp src/java/myproject-java/target/*.jar lib/java/myproject-java
67 | 
68 | .PHONY: clean
69 | clean:
70 | 	./dev-kit/run_matlab "mypackage_make clean"
71 | 
72 | # Run this _after_ initialization if you want to throw away some nonessential
73 | # features to make your repo layout simpler.
74 | .PHONY: simplify
75 | simplify:
76 | 	./dev-kit/run_matlab "mypackage_make simplify"
77 | 


--------------------------------------------------------------------------------
/docs-src-jekyll/_layouts/default.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |   
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 | {% seo %}
11 |   
12 | 
13 |   
14 | 
15 |     
16 |     
17 |
18 | {% if site.github.is_project_page %} 19 | View on GitHub 20 | {% endif %} 21 | 22 |

{{ site.title | default: site.github.repository_name }}

23 |

{{ site.description | default: site.github.project_tagline }}

24 | 25 | {% if site.show_downloads %} 26 |
27 | Download this project as a .zip file 28 | Download this project as a tar.gz file 29 |
30 | {% endif %} 31 |
32 |
33 | 34 | 35 |
36 |
37 | {{ content }} 38 |
39 |
40 | 41 | 42 | 50 | 51 | {% if site.google_analytics %} 52 | 60 | {% endif %} 61 | 62 | {% include mathjax.html %} 63 | 64 | -------------------------------------------------------------------------------- /dev-kit/mypackage_make_release.m: -------------------------------------------------------------------------------- 1 | function mypackage_make_release(newVersion) 2 | % Cut a release of myproject 3 | % 4 | % mypackage_make_release newVersion 5 | % 6 | % This does most of the steps of performing a release for you. It will: 7 | % - run the tests 8 | % - build the project 9 | % - build the distribution artifacts 10 | % - update the VERSION file 11 | % - tag a new release in git 12 | % 13 | % NewVersion (string) is the version number of the release you want to make, like 14 | % "1.2.3". 15 | 16 | %#ok<*STRNU> 17 | 18 | arguments 19 | newVersion (1,1) string 20 | end 21 | 22 | % Enforce SemVer version formatting 23 | % TODO: Fix this to prohibit leading zeros, but still allow "0" as a version 24 | % number component. 25 | validVersionPat = '^[0-9]+\.[0-9]+\.[0-9]+[+a-zA-Z-]*$'; 26 | if isempty(regexp(newVersion, validVersionPat, 'once')) 27 | error('Invalid version number: "%s". Please use a valid SemVer version number.', newVersion); 28 | end 29 | echo('Cutting release %s', newVersion) 30 | 31 | RAII.cd = withcd(reporoot); 32 | 33 | echo("Checking prerequisites...") 34 | rslt = system2('git status --porcelain'); 35 | if ~isempty(rslt) 36 | error("Error: Your repo is not clean! There are local changes. No release for you!"); 37 | end 38 | 39 | % TODO: Figure out how to check for pending remote changes on the tracked branch 40 | % in git. Diffing origin/main is incorrect, because we may be releasing a patch 41 | % release from a different branch. 42 | %must git remote update 43 | %if [[ ! -z "$(git diff origin/main)" ]]; then 44 | % echo >&2 "Error: There are pending remote changes in git. No release for you!" 45 | % exit 1 46 | %fi 47 | 48 | % First run the tests before we make any changes 49 | 50 | echo("Running tests...") 51 | % TODO 52 | 53 | % Okay, let's do the release 54 | 55 | markVersion(newVersion); 56 | 57 | echo("Regenerating doco..."); 58 | mypackage_make doc 59 | 60 | echo("Building dist...") 61 | mypackage_make dist 62 | echo("Building toolbox...") 63 | mypackage_package_toolbox 64 | 65 | echo("Tagging release...") 66 | system2(sprintf('git commit -a -m "Version %s"', newVersion)); 67 | system2(sprintf('git tag "v%s"', newVersion)); 68 | markVersion(newVersion+"+") 69 | system2(sprintf('git commit -a -m "Open development for next release"')) 70 | system2(sprintf('git push')) 71 | system2(sprintf('git push --tags')) 72 | 73 | echo("Release is pushed! Now go to GitHub and draft the actual Release:") 74 | echo(" https://github.com/myghuser/myproject/releases") 75 | if ismac 76 | % TODO: Figure out how to do this on Windows and Linux 77 | system('open "https://github.com/myghuser/myproject/releases"') 78 | end 79 | 80 | end 81 | 82 | function markVersion(version) 83 | writetext(sprintf('%s\n', version), 'VERSION'); 84 | end 85 | 86 | function echo(fmt, varargin) 87 | if nargin == 0 88 | fprintf('\n'); 89 | return 90 | end 91 | fprintf([char(fmt) '\n'], varargin{:}); 92 | end 93 | 94 | -------------------------------------------------------------------------------- /project_settings.m: -------------------------------------------------------------------------------- 1 | function out = project_settings 2 | % Edit these variables to for your project 3 | % 4 | % This file is only used during project initialization. You can throw it away after 5 | % that, but I'd keep it around anyway, just in case. 6 | 7 | % The name of your project and its GitHub repo. Capitalization should match what 8 | % your public "branding" is; this will show up in human-readable documentation. 9 | % No spaces, /, or & allowed! 10 | out.PROJECT = "MyCoolProject"; 11 | 12 | % The version of Matlab you're developing against. This will be the version of 13 | % Matlab that the project builds against (on Mac and Windows anyway), and the minimum 14 | % required version declared in the project Toolbox file. 15 | out.PROJECT_MATLAB_VERSION = "R2019b"; 16 | 17 | % The name of the top-level Matlab package that your project defines and keeps 18 | % its code in. This is the "+" directory that'll be directly under Mcode, 19 | % and is the "namespace" that your project lives in. 20 | % It is conventional for package names to be in all lower case. 21 | % Nothing but letters allowed! 22 | out.PACKAGE = "mycoolpackage"; 23 | 24 | % Your GitHub user name or organization name that's hosting the project 25 | out.GHUSER = "mygithubusername"; 26 | 27 | % If you want to provide a contact email for your project, put it here. Optional. 28 | out.PROJECT_EMAIL = ""; 29 | 30 | % The site generator tool you want to use for the project documentation. 31 | % 32 | % READ THIS PART! 33 | % 34 | % The default is "gh-pages-raw", even though that's an inferior option, because 35 | % that's the only one that works on Windows. If you're on Mac or Linux (or have 36 | % WSL on Windows), pick one of the other options! 37 | % 38 | % Valid choices are: 39 | % 40 | % "jekyll" - Regular (full-power) Jeyll for building local docs 41 | % "mkdocs" - mkdocs for building local docs 42 | % "gh-pages" - GitHub-Pages-compatible (limited) Jekyll 43 | % "gh-pages-raw" - GitHub Pages raw Markdown files 44 | % 45 | % If you have a large project, you should stick with "jekyll" or "mkdocs" and put your 46 | % main GitHub Pages website in a separate repo. "gh-pages" is more appropriate 47 | % for small projects. 48 | out.DOCTOOL = "gh-pages-raw"; 49 | 50 | % Human-readable name of the project's primary author or maintainer 51 | out.PROJECT_AUTHOR = "Your Name Here"; 52 | 53 | % Everything below here is optional! If you omit it, you'll end up with placeholder text 54 | % in some of your documentation, but the project will still work 55 | 56 | % One-sentence summary of the project. No <, >, /, or & characters allowed! 57 | out.PROJECT_SUMMARY = "Short summary of project goes here"; 58 | 59 | % Multi-sentence project description. No <, >, /, or & characters allowed! 60 | out.PROJECT_DESCRIPTION = "Longer description of project goes here."; 61 | 62 | % Home page web site for the project's author 63 | out.AUTHOR_HOMEPAGE = "https://github.com/" + out.GHUSER; 64 | 65 | end -------------------------------------------------------------------------------- /docs-src-jekyll/css/asciidoc-pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos pre { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; } 4 | td.linenos pre.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | pre.pygments .hll { background-color: #ffffcc } 7 | pre.pygments, pre.pygments code { background: #ffffff; } 8 | pre.pygments .tok-c { color: #008000 } /* Comment */ 9 | pre.pygments .tok-err { border: 1px solid #FF0000 } /* Error */ 10 | pre.pygments .tok-k { color: #0000ff } /* Keyword */ 11 | pre.pygments .tok-ch { color: #008000 } /* Comment.Hashbang */ 12 | pre.pygments .tok-cm { color: #008000 } /* Comment.Multiline */ 13 | pre.pygments .tok-cp { color: #0000ff } /* Comment.Preproc */ 14 | pre.pygments .tok-cpf { color: #008000 } /* Comment.PreprocFile */ 15 | pre.pygments .tok-c1 { color: #008000 } /* Comment.Single */ 16 | pre.pygments .tok-cs { color: #008000 } /* Comment.Special */ 17 | pre.pygments .tok-ge { font-style: italic } /* Generic.Emph */ 18 | pre.pygments .tok-gh { font-weight: bold } /* Generic.Heading */ 19 | pre.pygments .tok-gp { font-weight: bold } /* Generic.Prompt */ 20 | pre.pygments .tok-gs { font-weight: bold } /* Generic.Strong */ 21 | pre.pygments .tok-gu { font-weight: bold } /* Generic.Subheading */ 22 | pre.pygments .tok-kc { color: #0000ff } /* Keyword.Constant */ 23 | pre.pygments .tok-kd { color: #0000ff } /* Keyword.Declaration */ 24 | pre.pygments .tok-kn { color: #0000ff } /* Keyword.Namespace */ 25 | pre.pygments .tok-kp { color: #0000ff } /* Keyword.Pseudo */ 26 | pre.pygments .tok-kr { color: #0000ff } /* Keyword.Reserved */ 27 | pre.pygments .tok-kt { color: #2b91af } /* Keyword.Type */ 28 | pre.pygments .tok-s { color: #a31515 } /* Literal.String */ 29 | pre.pygments .tok-nc { color: #2b91af } /* Name.Class */ 30 | pre.pygments .tok-ow { color: #0000ff } /* Operator.Word */ 31 | pre.pygments .tok-sa { color: #a31515 } /* Literal.String.Affix */ 32 | pre.pygments .tok-sb { color: #a31515 } /* Literal.String.Backtick */ 33 | pre.pygments .tok-sc { color: #a31515 } /* Literal.String.Char */ 34 | pre.pygments .tok-dl { color: #a31515 } /* Literal.String.Delimiter */ 35 | pre.pygments .tok-sd { color: #a31515 } /* Literal.String.Doc */ 36 | pre.pygments .tok-s2 { color: #a31515 } /* Literal.String.Double */ 37 | pre.pygments .tok-se { color: #a31515 } /* Literal.String.Escape */ 38 | pre.pygments .tok-sh { color: #a31515 } /* Literal.String.Heredoc */ 39 | pre.pygments .tok-si { color: #a31515 } /* Literal.String.Interpol */ 40 | pre.pygments .tok-sx { color: #a31515 } /* Literal.String.Other */ 41 | pre.pygments .tok-sr { color: #a31515 } /* Literal.String.Regex */ 42 | pre.pygments .tok-s1 { color: #a31515 } /* Literal.String.Single */ 43 | pre.pygments .tok-ss { color: #a31515 } /* Literal.String.Symbol */ -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | MatlabProjectTemplate Changelog 2 | =============================== 3 | 4 | Version 0.5.0 (in progress) 5 | --------------------------- 6 | 7 | * Improve organization of `make` targets, making it clearer how the doco generation sequence works. 8 | * Gracefully handle case where dirs like `lib/` are removed. 9 | * Update Travis CI config for new Matlab version, and reduce covered versions to conserved Travis credits (now that Travis doesn't have unlimited free plans). 10 | * Rename internal.utils class to internal.misc, to avoid confusion with the internal.util package, and make tab completion work better. 11 | * Rename doc-src/ to docs-src/, since it is the source for docs/, not doc/. (docs/ is the source for doc/.) 12 | 13 | Version 0.4.2 (2021-09-11) 14 | --------------------------- 15 | 16 | * Clean up `VERSION` formatting 17 | * Use `CHANGES.md` instead of `CHANGES.txt` (works better with GitHub Releases) 18 | * Fix busted naming in `make java` target 19 | * Fix `.travis.yml` munging 20 | * Various small fixes in how the build tools work 21 | * `make dist` now makes both zips and mltbx; there are separate targets for zips or mltbx 22 | 23 | Version 0.4.1 (2021-01-24) 24 | --------------------------- 25 | 26 | * Add util functions: todatetime, mustBeA, size2str 27 | * Better error messages when Ruby is not installed and you need it 28 | * Add `make doc-preview` target 29 | * Fix initialization of project `README.md` file 30 | * Fix some bugs in `make release` target 31 | 32 | Version 0.4.0 (2021-01-22) 33 | --------------------------- 34 | 35 | * Convert dev-kit and init_project_from_template to pure-Matlab implementations 36 | * Utility functions in +mypackage/+internal/+util 37 | 38 | Version 0.3.5 (2021-01-21) 39 | --------------------------- 40 | 41 | * Remove the `.sh` suffix from `init_project_from_template`; its language is an internal implementation detail 42 | * Clean up internal MPT dev targets from Makefile 43 | 44 | Version 0.3.4 (2021-01-20) 45 | -------------------------- 46 | 47 | * Fix 'make java' 48 | 49 | Version 0.3.3 (2021-01-20) 50 | -------------------------- 51 | 52 | * Fix a few missed files for munging 53 | 54 | Version 0.3.2 (2021-01-20) 55 | -------------------------- 56 | 57 | * Fix make_release's release creation 58 | 59 | Version 0.3.0 (2021-01-20) 60 | -------------------------- 61 | 62 | * Move more build tools into dev-kit/ to clean up the root directory 63 | * More project metadata options 64 | * Refine how settings are initialized 65 | * Fix toolbox build failure due to missing M-doc dependency 66 | * Add a gh-pages doco building option 67 | 68 | Version 0.2.2 (2021-01-19) 69 | -------------------------- 70 | 71 | * Fix class name munging for Mypackage* classes 72 | 73 | Version 0.2.1 (2021-01-19) 74 | -------------------------- 75 | 76 | * Fix project name in info.xml for Toolbox generation 77 | * Automatic munging of Matlab version in metadata files 78 | 79 | Version 0.2.0 (2021-01-17) 80 | -------------------------- 81 | 82 | * P-coding support 83 | * Scoped dev-kit names, to allow for compatibility with other packages 84 | 85 | Version 0.1.0 (2021-01-17) 86 | -------------------------- 87 | 88 | * Initial project release 89 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/private/loggerCallImpl.m: -------------------------------------------------------------------------------- 1 | function loggerCallImpl(logLevel, msg, args, form) 2 | %LOGGERCALLIMPL Implementation for the top-level logger functions 3 | % 4 | if nargin < 3 || isempty(args); args = {}; end 5 | if nargin < 4 || isempty(form); form = 'm'; end 6 | 7 | % Can't use a regular dbstack call here because the stack info doesn't include 8 | % package names. Resort to parsing the human-readable formatted stack trace. 9 | strStack = evalc('dbstack(2)'); 10 | stackLine = regexprep(strStack, '\n.+', ''); 11 | % BUG: Caller name detection doesn't work right for anonymous functions. 12 | % This pattern isn't quite right. But truly parsing the HTML would be expensive. 13 | caller = regexp(stackLine, '>([^<>]+)<', 'once', 'tokens'); 14 | if isempty(caller) 15 | % This will happen if you call the log functions interactively 16 | % Use a generic logger name 17 | callerId = 'base'; 18 | else 19 | callerEl = caller{1}; 20 | % Could be 'package.class/method', 'package.function', or 21 | % 'package.class.staticmethod'. (I think, based on R2016b output.) 22 | if contains(callerEl, '/') 23 | ixSlash = find(callerEl == '/', 1, 'last'); 24 | callerId = callerEl(1:ixSlash-1); 25 | else 26 | % Static method or function 27 | ixDot = find(callerEl == '.', 1, 'last'); 28 | if isempty(ixDot) 29 | callerId = callerEl; 30 | else 31 | prefix = callerEl(1:ixDot-1); 32 | maybeClass = meta.class.fromName(prefix); 33 | if isempty(maybeClass) 34 | callerId = callerEl; 35 | else 36 | callerId = prefix; 37 | end 38 | end 39 | end 40 | end 41 | 42 | loggerObj = mypackage.logger.Logger.getLogger(callerId); 43 | 44 | switch form 45 | case 'm' 46 | switch logLevel 47 | case 'error' 48 | loggerObj.error(msg, args{:}); 49 | return 50 | case 'warn' 51 | loggerObj.warn(msg, args{:}); 52 | return 53 | case 'info' 54 | loggerObj.info(msg, args{:}); 55 | return 56 | case 'debug' 57 | loggerObj.debug(msg, args{:}); 58 | return 59 | case 'trace' 60 | loggerObj.trace(msg, args{:}); 61 | return 62 | otherwise 63 | error('logger:InvalidInput', 'Invalid logLevel: %s', logLevel); 64 | end 65 | case 'j' 66 | switch logLevel 67 | case 'error' 68 | loggerObj.errorj(msg, args{:}); 69 | return 70 | case 'warn' 71 | loggerObj.warnj(msg, args{:}); 72 | return 73 | case 'info' 74 | loggerObj.infoj(msg, args{:}); 75 | return 76 | case 'debug' 77 | loggerObj.debugj(msg, args{:}); 78 | return 79 | case 'trace' 80 | loggerObj.tracej(msg, args{:}); 81 | return 82 | otherwise 83 | error('logger:InvalidInput', 'Invalid logLevel: %s', logLevel); 84 | end 85 | end 86 | 87 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/private/dispstrs.m: -------------------------------------------------------------------------------- 1 | function out = dispstrs(x) 2 | %DISPSTRS Display strings for array elements 3 | % 4 | % out = dispstrs(x, options) 5 | % 6 | % DISPSTRS returns a cellstr array containing display strings that represent the 7 | % values in the elements of x. These strings are concise, single-line strings 8 | % suitable for incorporation into multi-element output. If x is a cell, each 9 | % element cell's contents are displayed, instead of each cell. 10 | % 11 | % Unlike DISPSTR, DISPSTRS returns output describing each element of the input 12 | % array individually. 13 | % 14 | % This is used for constructing display output for functions like DISP. 15 | % User-defined objects are expected to override DISPSTRS to produce suitable, 16 | % readable output. 17 | % 18 | % The output is human-consumable text. It does not have to be fully precise, and 19 | % does not have to be parseable back to the original input. Full type 20 | % information will not be inferrable from DISPSTRS output. The primary audience 21 | % for DISPSTRS output is Matlab programmers and advanced users. 22 | % 23 | % The intention is for user-defined classes to override this method, providing 24 | % customized display of their values. 25 | % 26 | % The input x may be a value of any type. The main DISPSTRS implementation has 27 | % support for Matlab built-ins and common types. Other user-defined objects are 28 | % displayed in a generic "m-by-n array" format. 29 | % 30 | % Returns a cellstr the same size as x. 31 | % 32 | % Options: 33 | % None are currently defined. This argument is reserved for future use. 34 | % 35 | % Examples: 36 | % dispstrs(magic(3)) 37 | % 38 | % See also: DISPSTR 39 | 40 | if isempty(x) 41 | out = reshape({}, size(x)); 42 | elseif isnumeric(x) 43 | out = dispstrsNumeric(x); 44 | elseif iscellstr(x) 45 | out = x; 46 | elseif isstring(x) 47 | out = cellstr(x); 48 | elseif iscell(x) 49 | out = dispstrsGenericDisp(x); 50 | elseif ischar(x) 51 | % An unfortunate consequence of the typical use of char and dispstrs' contract 52 | out = num2cell(x); 53 | elseif isa(x, 'tabular') 54 | out = dispstrsTabular(x); 55 | elseif isa(x, 'datetime') 56 | out = dispstrsDatetime(x); 57 | elseif isa(x, 'struct') 58 | out = repmat({'1-by-1 struct'}, size(x)); 59 | else 60 | out = dispstrsGenericDisp(x); 61 | end 62 | 63 | out = string(out); 64 | 65 | end 66 | 67 | function out = dispstrsDatetime(x) 68 | out = cell(size(x)); 69 | tfFinite = isfinite(x); 70 | out(tfFinite) = cellstr(datestr(x(tfFinite))); 71 | out(isnat(x)) = {'NaT'}; 72 | dnum = datenum(x); 73 | out(isinf(dnum) & dnum > 0) = {'Inf'}; 74 | out(isinf(dnum) & dnum < 0) = {'-Inf'}; 75 | end 76 | 77 | function out = dispstrsNumeric(x) 78 | out = reshape(strtrim(cellstr(num2str(x(:)))), size(x)); 79 | end 80 | 81 | function out = dispstrsTabular(x) 82 | out = cell(size(x)); 83 | for iRow = 1:size(x, 1) 84 | for iCol = 1:size(x, 2) 85 | val = x{iRow,iCol}; 86 | if iscell(val) 87 | val = val{1}; 88 | end 89 | out{iRow,iCol} = dispstr(val); 90 | end 91 | end 92 | end 93 | 94 | function out = dispstrsGenericDisp(x) 95 | out = cell(size(x)); 96 | for i = 1:numel(x) 97 | if iscell(x) 98 | xi = x{i}; %#ok 99 | else 100 | xi = x(i); %#ok 101 | end 102 | str = evalc('disp(xi)'); 103 | str(end) = []; % chomp newline 104 | out{i} = str; 105 | end 106 | end 107 | 108 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/+internal/private/dispstrs.m: -------------------------------------------------------------------------------- 1 | function out = dispstrs(x) 2 | %DISPSTRS Display strings for array elements 3 | % 4 | % out = dispstrs(x, options) 5 | % 6 | % DISPSTRS returns a cellstr array containing display strings that represent the 7 | % values in the elements of x. These strings are concise, single-line strings 8 | % suitable for incorporation into multi-element output. If x is a cell, each 9 | % element cell's contents are displayed, instead of each cell. 10 | % 11 | % Unlike DISPSTR, DISPSTRS returns output describing each element of the input 12 | % array individually. 13 | % 14 | % This is used for constructing display output for functions like DISP. 15 | % User-defined objects are expected to override DISPSTRS to produce suitable, 16 | % readable output. 17 | % 18 | % The output is human-consumable text. It does not have to be fully precise, and 19 | % does not have to be parseable back to the original input. Full type 20 | % information will not be inferrable from DISPSTRS output. The primary audience 21 | % for DISPSTRS output is Matlab programmers and advanced users. 22 | % 23 | % The intention is for user-defined classes to override this method, providing 24 | % customized display of their values. 25 | % 26 | % The input x may be a value of any type. The main DISPSTRS implementation has 27 | % support for Matlab built-ins and common types. Other user-defined objects are 28 | % displayed in a generic "m-by-n array" format. 29 | % 30 | % Returns a cellstr the same size as x. 31 | % 32 | % Options: 33 | % None are currently defined. This argument is reserved for future use. 34 | % 35 | % Examples: 36 | % dispstrs(magic(3)) 37 | % 38 | % See also: DISPSTR 39 | 40 | if isempty(x) 41 | out = reshape({}, size(x)); 42 | elseif isnumeric(x) 43 | out = dispstrsNumeric(x); 44 | elseif iscellstr(x) 45 | out = x; 46 | elseif isstring(x) 47 | out = cellstr(x); 48 | elseif iscell(x) 49 | out = dispstrsGenericDisp(x); 50 | elseif ischar(x) 51 | % An unfortunate consequence of the typical use of char and dispstrs' contract 52 | out = num2cell(x); 53 | elseif isa(x, 'tabular') 54 | out = dispstrsTabular(x); 55 | elseif isa(x, 'datetime') 56 | out = dispstrsDatetime(x); 57 | elseif isa(x, 'struct') 58 | out = repmat({'1-by-1 struct'}, size(x)); 59 | else 60 | out = dispstrsGenericDisp(x); 61 | end 62 | 63 | out = string(out); 64 | 65 | end 66 | 67 | function out = dispstrsDatetime(x) 68 | out = cell(size(x)); 69 | tfFinite = isfinite(x); 70 | out(tfFinite) = cellstr(datestr(x(tfFinite))); 71 | out(isnat(x)) = {'NaT'}; 72 | dnum = datenum(x); 73 | out(isinf(dnum) & dnum > 0) = {'Inf'}; 74 | out(isinf(dnum) & dnum < 0) = {'-Inf'}; 75 | end 76 | 77 | function out = dispstrsNumeric(x) 78 | out = reshape(strtrim(cellstr(num2str(x(:)))), size(x)); 79 | end 80 | 81 | function out = dispstrsTabular(x) 82 | out = cell(size(x)); 83 | for iRow = 1:size(x, 1) 84 | for iCol = 1:size(x, 2) 85 | val = x{iRow,iCol}; 86 | if iscell(val) 87 | val = val{1}; 88 | end 89 | out{iRow,iCol} = dispstr(val); 90 | end 91 | end 92 | end 93 | 94 | function out = dispstrsGenericDisp(x) 95 | out = cell(size(x)); 96 | for i = 1:numel(x) 97 | if iscell(x) 98 | xi = x{i}; %#ok 99 | else 100 | xi = x(i); %#ok 101 | end 102 | str = evalc('disp(xi)'); 103 | str(end) = []; % chomp newline 104 | out{i} = str; 105 | end 106 | end 107 | 108 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/private/dispstr.m: -------------------------------------------------------------------------------- 1 | function out = dispstr(x, options) 2 | %DISPSTR Display string for arrays 3 | % 4 | % out = dispstr(x, options) 5 | % 6 | % This returns a one-line string representing the input value, in a format 7 | % suitable for inclusion into multi-element output. The output describes the 8 | % entire input array in a single string (as opposed to dumping all its 9 | % elements.) 10 | % 11 | % The intention is for user-defined classes to override this method, providing 12 | % customized display of their values. 13 | % 14 | % The input x may be a value of any type. The main DISPSTR implementation has 15 | % support for Matlab built-ins and common types. Other user-defined objects are 16 | % displayed in a generic "m-by-n array" format. 17 | % 18 | % Options may be a struct or an n-by-2 cell array of name/value pairs (names in 19 | % column 1; values in column 2). 20 | % 21 | % Returns a single string as char. 22 | % 23 | % Options: 24 | % QuoteStrings - Put scalar strings in quotes. 25 | % 26 | % Examples: 27 | % dispstr(magic(3)) 28 | % 29 | % See also: 30 | % DISPSTRS, SPRINTFD 31 | 32 | if nargin < 2; options = []; end 33 | options = parseOpts(options, {'QuoteStrings',false}); 34 | 35 | if ~ismatrix(x) 36 | out = sprintf('%s %s', mypackage.logger.internal.Dispstr.size2str(size(x)), class(x)); 37 | elseif isempty(x) 38 | if ischar(x) && isequal(size(x), [0 0]) 39 | out = ''''''; 40 | elseif isnumeric(x) && isequal(size(x), [0 0]) 41 | out = '[]'; 42 | else 43 | out = sprintf('Empty %s %s', mypackage.logger.internal.Dispstr.size2str(size(x)), class(x)); 44 | end 45 | elseif isnumeric(x) 46 | if isscalar(x) 47 | out = num2str(x); 48 | else 49 | strs = strtrim(cellstr(num2str(x(:)))); 50 | strs = reshape(strs, size(x)); 51 | out = formatArrayOfStrings(strs); 52 | end 53 | elseif ischar(x) 54 | if isrow(x) 55 | if options.QuoteStrings 56 | out = ['''' x '''']; 57 | else 58 | out = x; 59 | end 60 | else 61 | strs = strcat({''''}, num2cell(x,2), {''''}); 62 | out = formatArrayOfStrings(strs); 63 | end 64 | elseif iscell(x) 65 | if iscellstr(x) 66 | strs = strcat('''', x, ''''); 67 | else 68 | strs = cellfun(@dispstr, x, 'UniformOutput',false); 69 | end 70 | out = formatArrayOfStrings(strs, {'{','}'}); 71 | elseif isstring(x) 72 | if options.QuoteStrings 73 | strs = strcat('"', cellstr(x), '"'); 74 | else 75 | strs = cellstr(x); 76 | end 77 | out = formatArrayOfStrings(strs, {'[',']'}); 78 | elseif isa(x, 'datetime') && isscalar(x) 79 | if isnat(x) 80 | out = 'NaT'; 81 | else 82 | out = char(x); 83 | end 84 | elseif isscalar(x) && (isa(x, 'duration') || isa(x, 'calendarDuration')) 85 | out = char(x); 86 | elseif isscalar(x) && iscategorical(x) 87 | out = char(x); 88 | else 89 | out = sprintf('%s %s', mypackage.logger.internal.Dispstr.size2str(size(x)), class(x)); 90 | end 91 | 92 | out = string(out); 93 | 94 | end 95 | 96 | function out = formatArrayOfStrings(strs, brackets) 97 | if nargin < 2 || isempty(brackets); brackets = { '[' ']' }; end 98 | rowStrs = cell(size(strs,1), 1); 99 | for iRow = 1:size(strs,1) 100 | rowStrs{iRow} = strjoin(strs(iRow,:), ' '); 101 | end 102 | out = [brackets{1} strjoin(rowStrs, '; ') brackets{2}]; 103 | end 104 | 105 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/+internal/private/dispstr.m: -------------------------------------------------------------------------------- 1 | function out = dispstr(x, options) 2 | %DISPSTR Display string for arrays 3 | % 4 | % out = dispstr(x, options) 5 | % 6 | % This returns a one-line string representing the input value, in a format 7 | % suitable for inclusion into multi-element output. The output describes the 8 | % entire input array in a single string (as opposed to dumping all its 9 | % elements.) 10 | % 11 | % The intention is for user-defined classes to override this method, providing 12 | % customized display of their values. 13 | % 14 | % The input x may be a value of any type. The main DISPSTR implementation has 15 | % support for Matlab built-ins and common types. Other user-defined objects are 16 | % displayed in a generic "m-by-n array" format. 17 | % 18 | % Options may be a struct or an n-by-2 cell array of name/value pairs (names in 19 | % column 1; values in column 2). 20 | % 21 | % Returns a single string as char. 22 | % 23 | % Options: 24 | % QuoteStrings - Put scalar strings in quotes. 25 | % 26 | % Examples: 27 | % dispstr(magic(3)) 28 | % 29 | % See also: 30 | % DISPSTRS, SPRINTFD 31 | 32 | if nargin < 2; options = []; end 33 | options = parseOpts(options, {'QuoteStrings',false}); 34 | 35 | if ~ismatrix(x) 36 | out = sprintf('%s %s', mypackage.logger.internal.Dispstr.size2str(size(x)), class(x)); 37 | elseif isempty(x) 38 | if ischar(x) && isequal(size(x), [0 0]) 39 | out = ''''''; 40 | elseif isnumeric(x) && isequal(size(x), [0 0]) 41 | out = '[]'; 42 | else 43 | out = sprintf('Empty %s %s', mypackage.logger.internal.Dispstr.size2str(size(x)), class(x)); 44 | end 45 | elseif isnumeric(x) 46 | if isscalar(x) 47 | out = num2str(x); 48 | else 49 | strs = strtrim(cellstr(num2str(x(:)))); 50 | strs = reshape(strs, size(x)); 51 | out = formatArrayOfStrings(strs); 52 | end 53 | elseif ischar(x) 54 | if isrow(x) 55 | if options.QuoteStrings 56 | out = ['''' x '''']; 57 | else 58 | out = x; 59 | end 60 | else 61 | strs = strcat({''''}, num2cell(x,2), {''''}); 62 | out = formatArrayOfStrings(strs); 63 | end 64 | elseif iscell(x) 65 | if iscellstr(x) 66 | strs = strcat('''', x, ''''); 67 | else 68 | strs = cellfun(@dispstr, x, 'UniformOutput',false); 69 | end 70 | out = formatArrayOfStrings(strs, {'{','}'}); 71 | elseif isstring(x) 72 | if options.QuoteStrings 73 | strs = strcat('"', cellstr(x), '"'); 74 | else 75 | strs = cellstr(x); 76 | end 77 | out = formatArrayOfStrings(strs, {'[',']'}); 78 | elseif isa(x, 'datetime') && isscalar(x) 79 | if isnat(x) 80 | out = 'NaT'; 81 | else 82 | out = char(x); 83 | end 84 | elseif isscalar(x) && (isa(x, 'duration') || isa(x, 'calendarDuration')) 85 | out = char(x); 86 | elseif isscalar(x) && iscategorical(x) 87 | out = char(x); 88 | else 89 | out = sprintf('%s %s', mypackage.logger.internal.Dispstr.size2str(size(x)), class(x)); 90 | end 91 | 92 | out = string(out); 93 | 94 | end 95 | 96 | function out = formatArrayOfStrings(strs, brackets) 97 | if nargin < 2 || isempty(brackets); brackets = { '[' ']' }; end 98 | rowStrs = cell(size(strs,1), 1); 99 | for iRow = 1:size(strs,1) 100 | rowStrs{iRow} = strjoin(strs(iRow,:), ' '); 101 | end 102 | out = [brackets{1} strjoin(rowStrs, '; ') brackets{2}]; 103 | end 104 | 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MatlabProjectTemplate 2 | 3 | [![Travis Build Status](https://travis-ci.com/janklab/MatlabProjectTemplate.svg?branch=main)](https://travis-ci.com/github/janklab/MatlabProjectTemplate) [![CircleCI Build Status](https://circleci.com/gh/janklab/MatlabProjectTemplate.svg?style=shield)](https://circleci.com/gh/janklab/MatlabProjectTemplate) [![Azure Build Status](https://dev.azure.com/janklab/MatlabProjectTemplate/_apis/build/status/janklab.MatlabProjectTemplate?branchName=main)](https://dev.azure.com/janklab/MatlabProjectTemplate/_build/latest?definitionId=1&branchName=main) [![View MatlabProjectTemplate on File Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://www.mathworks.com/matlabcentral/fileexchange/85840-matlabprojecttemplate) 4 | 5 | MatlabProjectTemplate ("MPT") is a template repo for creating Matlab library and application projects. It defines a "standard" project structure that should be suitable for many projects, including those intended for redistribution / open source. 6 | 7 | It is suitable for both libraries and applications, and includes coding and organizational conventions that make it safe to use this project's code in a Matlab environment that uses code from other projects, too. 8 | 9 | ## Features 10 | 11 | MatlabProjectTemplate supports the following features. You don't _have_ to use any of them; you can just ignore the ones you don't care about. But they're there if you need them! MPT's philosophy is "support but do not require". 12 | 13 | * Collaboration between multiple developers 14 | * Building [Matlab Toolboxes](https://www.mathworks.com/help/matlab/matlab_prog/create-and-share-custom-matlab-toolboxes.html) 15 | * Matlab [Continuous Integration](https://www.mathworks.com/solutions/continuous-integration.html) and unit tests 16 | * Distribution as both plain zip files and Matlab Toolbox `.mltbx` files 17 | * Using ("vendoring") third-party Java JAR and Matlab libraries 18 | * Custom Java code 19 | * Library initialization 20 | * [_Automatic_](https://matlabprojecttemplate.janklab.net/AutoInitialization.html) library initialization 21 | * Logging, in an [SLF4M](https://github.com/janklab/SLF4M)/SLF4J/Log4j-compatible manner 22 | 23 | ## Requirements 24 | 25 | Just Matlab, for most things. 26 | 27 | Building custom Java code requires Mac or Linux plus a Java JDK and Apache Maven. If you're a Java developer, you know how to set theseup. 28 | 29 | ## Usage 30 | 31 | To create a new project from this template, go to [its repo on GitHub](https://github.com/janklab/MatlabProjectTemplate) and create a new repo by clicking the green "Use this template" button. 32 | 33 | NOTE: Don't "fork" or "clone" the MatlabProjectTemplate repo. That will leave your project's Git repo set up wrong! Do the "Use this template" thing. 34 | 35 | Then: 36 | 37 | * Add a license file! 38 | * Edit the variables `project_settings.m`. 39 | * Open Matlab and run `init_project_from_template.m`. 40 | * Edit `.editorconfig` to reflect your preferred code style. 41 | * Edit `.prj.in` and put in all your contact and descriptive info and other stuff. 42 | 43 | And then: 44 | 45 | * Put your main Matlab source code in `Mcode/`. 46 | * Put your example scripts in `examples/`. 47 | * Edit the files in `doc-project` to reflect your plans. 48 | * Hack away! 49 | 50 | When you're developing code for your project, you should add the `dev-kit/` directory to your Matlab path. 51 | 52 | See the [User Guide](https://matlabprojecttemplate.janklab.net/UserGuide.html) for more information. 53 | 54 | ## Unit tests 55 | 56 | You should write unit tests for your project! Use the [Matlab Unit Test Framework](https://www.mathworks.com/help/matlab/matlab-unit-test-framework.html) and put your tests in `Mcode/+/+test`. Run them with `make test` in the shell or with `dev-kit/launchtests.m` in Matlab. 57 | 58 | ## License 59 | 60 | MatlabProjectTemplate is multi-licensed under all of: BSD 3-Clause License, BSD 2-Clause License, Apache License, MIT License, and GPLv3. You can use it in any project with a license compatible with any of those licenses. This includes commercial and proprietary software, and contemporary postings to MathWorks File Exchange. 61 | 62 | If you have a licensing scenario which is not covered by the above (and jeez, what are you doing that would require _that_?), just contact me, and I'll probably add support for it. My intention is that _everybody_ can use MatlabProjectTemplate. 63 | 64 | ## About MatlabProjectTemplate 65 | 66 | MatlabProjectTemplate was written by [Andrew Janke](https://apjanke.net). 67 | 68 | You can read the online documentation on the [project website](https://matlabprojecttemplate.janklab.net) and or find the code and get support at the [repo on GitHub](https://github.com/janklab/MatlabProjectTemplate). Bug reports, feature requests, and other feedback are welcome. 69 | 70 | See the [FAQ](https://matlabprojecttemplate.janklab.net/FAQ.md) or the stuff in the `MatlabProjectTemplate/doc` directory for more info. 71 | -------------------------------------------------------------------------------- /dev-kit/mypackage_make.m: -------------------------------------------------------------------------------- 1 | function mypackage_make(target, varargin) 2 | % Build tool for mypackage 3 | % 4 | % mypackage_make [...options...] 5 | % 6 | % This is the main build tool for doing all the build and packaging operations 7 | % for mypackage. It's intended to be called as a command. This is what you will 8 | % use to build & package the distribution files for a release of the package. 9 | % 10 | % Targets: 11 | % 12 | % test - run the tests 13 | % 14 | % dist - build the full dist files (both zips and toolbox) 15 | % zips - build the dist archive files (zips) 16 | % 17 | % toolbox - build the Matlab Toolbox .mltbx installer file 18 | % clean - delete all the derived artifacts 19 | % 20 | % docs - build docs/ etc. (the GH Pages stuff) from docs-src and examples (merge) 21 | % doc - build doc/, the final static (local) doco files (from docs/, replace) 22 | % m-doc - build build/M-doc/ MLTBX format docs (from doc/) 23 | % docview - live-preview the project doco (from docs/) (requires Jekyll) 24 | % 25 | % build - "build" (transform and pcode) the source code 26 | % buildmex - build all the MEX files in the source tree 27 | % 28 | % simplify - remove some optional MatlabProjectTemplate features from this repo 29 | % util-shim - "shim" utility functions into a package 30 | 31 | %#ok<*STRNU> 32 | 33 | arguments 34 | target (1,1) string 35 | end 36 | arguments (Repeating) 37 | varargin 38 | end 39 | 40 | 41 | if target == "test" 42 | mypackage_launchtests 43 | elseif target == "docs" 44 | build_docs 45 | elseif target == "doc" 46 | build_doc 47 | elseif target == "doc-preview" 48 | preview_docs 49 | elseif target == "m-doc" 50 | make_mdoc 51 | elseif target == "toolbox" 52 | mypackage_make m-doc 53 | mypackage_package_toolbox 54 | elseif target == "zips" 55 | mypackage_make build 56 | mypackage_make m-doc 57 | make_zips 58 | elseif target == "dist" 59 | mypackage_make zips 60 | mypackage_make toolbox 61 | fprintf('Made dist.\n') 62 | elseif target == "clean" 63 | make_clean 64 | elseif target == "build" 65 | mypackage_build 66 | elseif target == "buildmex" 67 | mypackage_build_all_mex 68 | elseif target == "simplify" 69 | make_simplify 70 | elseif target == "util-shim" 71 | pkg = varargin{1}; 72 | make_util_shim(pkg); 73 | else 74 | error("Unknown target: %s", target); 75 | end 76 | 77 | end 78 | 79 | function make_mdoc 80 | rmrf('build/M-doc') 81 | mkdir2('build/M-doc') 82 | copyfile2('doc/*', 'build/M-doc') 83 | if isfile('build/M-doc/feed.xml') 84 | delete('build/M-doc/feed.xml') 85 | end 86 | end 87 | 88 | function preview_docs 89 | import mypackage.internal.util.*; 90 | RAII.cd = withcd('docs'); 91 | make_doc --preview 92 | end 93 | 94 | function make_zips 95 | program = "myproject"; 96 | distName = program + "-" + mypackage.globals.version; 97 | verDistDir = fullfile("dist", distName); 98 | distfiles = ["build/Mcode" "doc" "lib" "examples" "README.md" "LICENSE" ... 99 | "CHANGES.md" "VERSION"]; 100 | rmrf([verDistDir, distName+".tar.gz", distName+".zip"]) 101 | if ~isfolder('dist') 102 | mkdir2('dist') 103 | end 104 | mkdir2(verDistDir) 105 | for distFile = distfiles 106 | if isfile(distFile) || isfolder(distFile) 107 | system2(sprintf("cp -R '%s' '%s'", distFile, verDistDir)); 108 | end 109 | end 110 | RAII.cd = withcd('dist'); 111 | tar(distName+".tar.gz", distName) 112 | zip(distName+".zip", distName) 113 | end 114 | 115 | function make_clean 116 | rmrf(strsplit("dist/* build docs/site docs/_site test-output", " ")); 117 | end 118 | 119 | function make_simplify 120 | rmrf(strsplit(".circleci .travis.yml azure-pipelines.yml src lib/java/MyCoolProject-java", " ")); 121 | end 122 | 123 | function make_package_docs(varargin) 124 | doOnlySrc = ismember('--src', varargin); 125 | build_docs; 126 | if ~doOnlySrc 127 | build_doc; 128 | end 129 | end 130 | 131 | function build_docs 132 | % Build the generated (Markdown) parts of the doc sources in docs/ 133 | RAII.cd = withcd(reporoot); 134 | docsDir = fullfile(reporoot, 'docs'); 135 | % Copy over examples 136 | docsExsDir = fullfile(docsDir, 'examples'); 137 | if isfolder(docsExsDir) 138 | rmdir2(docsExsDir, 's'); 139 | end 140 | copyfile('examples', fullfile('docs', 'examples')); 141 | % TODO: Generate API Reference 142 | end 143 | 144 | function build_doc 145 | % Build the final static local doc files in doc/ 146 | RAII.cd = withcd(fullfile(reporoot, 'docs')); 147 | make_doc; 148 | delete('../doc/make_doc*'); 149 | end 150 | 151 | function make_util_shim(pkg) 152 | shimsDir = fullfile(reporoot, 'dev-kit', 'util-shims'); 153 | relpkgpath = strjoin(strcat("+", strsplit(pkg, ".")), "/"); 154 | pkgdir = fullfile(fullfile(reporoot, 'Mcode'), relpkgpath); 155 | if ~isfolder(pkgdir) 156 | error('Package folder does not exist: %s', pkgdir); 157 | end 158 | privateDir = fullfile(pkgdir, 'private'); 159 | if ~isfolder(privateDir) 160 | mkdir(privateDir); 161 | end 162 | copyfile2(fullfile(shimsDir, '*.m'), privateDir); 163 | fprintf('Util-shimmed package: %s', pkg); 164 | end 165 | -------------------------------------------------------------------------------- /myproject.prj.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | myproject 4 | __YOUR_NAME_HERE__ 5 | __myprojectemail__ 6 | 7 | __myproject_summary__ 8 | __myproject_description__ 9 | 10 | ${PROJECT_VERSION} 11 | ${PROJECT_ROOT}/myproject.mltbx 12 | 13 | 14 | 15 | 16 | __myprojectguid__ 17 | .git 18 | .github 19 | scratch 20 | .DS_Store 21 | .gitignore 22 | .gitmodules 23 | true 24 | 25 | 26 | 27 | 28 | ${PROJECT_ROOT}/info.xml 29 | ${PROJECT_ROOT}/doc/GettingStarted.mlx 30 | 31 | 32 | 33 | 34 | false 35 | 36 | 37 | 38 | __myproject_matlab_version__ 39 | latest 40 | false 41 | true 42 | true 43 | true 44 | true 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | ${PROJECT_ROOT} 84 | 85 | 86 | ${PROJECT_ROOT}/.editorconfig 87 | ${PROJECT_ROOT}/LICENSE 88 | ${PROJECT_ROOT}/VERSION 89 | ${PROJECT_ROOT}/build/M-doc 90 | ${PROJECT_ROOT}/Makefile 91 | ${PROJECT_ROOT}/Mcode 92 | ${PROJECT_ROOT}/README.md 93 | ${PROJECT_ROOT}/VERSION 94 | ${PROJECT_ROOT}/doc 95 | ${PROJECT_ROOT}/info.xml 96 | ${PROJECT_ROOT}/lib 97 | ${PROJECT_ROOT}/src 98 | ${PROJECT_ROOT}/examples 99 | 100 | 101 | 102 | 103 | 104 | ./__myproject__.mltbx 105 | 106 | 107 | 108 | /Applications/MATLAB___myproject_matlab_version__.app 109 | 110 | 111 | 112 | true 113 | true 114 | false 115 | false 116 | false 117 | false 118 | false 119 | false 120 | 10.14.6 121 | false 122 | true 123 | maci64 124 | true 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/java/myproject-java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.example.myproject 5 | mypackage-java 6 | 0.1.0-SNAPSHOT 7 | jar 8 | 9 | 10 | 11 | 12 | 13 | commons-io 14 | commons-io 15 | 2.4 16 | 17 | 18 | commons-cli 19 | commons-cli 20 | 1.1 21 | 22 | 23 | commons-codec 24 | commons-codec 25 | 1.3 26 | 27 | 28 | commons-collections 29 | commons-collections 30 | 3.2.2 31 | 32 | 33 | 34 | net.sourceforge.collections 35 | collections-generic 36 | 4.01 37 | 38 | 39 | org.apache.commons 40 | commons-compress 41 | 1.8.1 42 | 43 | 44 | commons-lang 45 | commons-lang 46 | 2.3 47 | 48 | 49 | commons-logging 50 | commons-logging 51 | 1.1.1 52 | 53 | 54 | com.google.guava 55 | guava 56 | 15.0 57 | 58 | 59 | com.google.inject 60 | guice 61 | 4.0 62 | 63 | 64 | commons-httpclient 65 | commons-httpclient 66 | 3.1 67 | 68 | 69 | dom4j 70 | dom4j 71 | 1.6.1 72 | 73 | 74 | org.apache.felix 75 | org.apache.felix.main 76 | 1.8.0 77 | 78 | 79 | org.apache.pdfbox 80 | pdfbox 81 | 2.0.7 82 | 83 | 84 | org.apache.pdfbox 85 | pdfbox-tools 86 | 2.0.7 87 | 88 | 89 | org.apache.pdfbox 90 | fontbox 91 | 2.0.7 92 | 93 | 94 | com.google.code.gson 95 | gson 96 | 2.2.4 97 | 98 | 99 | org.slf4j 100 | slf4j-api 101 | 1.5.8 102 | 103 | 104 | log4j 105 | log4j 106 | 1.2.15 107 | 108 | 109 | com.sun.jmx 110 | jmxri 111 | 112 | 113 | javax.jms 114 | jms 115 | 116 | 117 | com.sun.jdmk 118 | jmxtools 119 | 120 | 121 | 122 | 123 | wsdl4j 124 | wsdl4j 125 | 1.6.2 126 | 127 | 128 | 129 | 130 | UTF-8 131 | 1.8 132 | 1.8 133 | 134 | 135 | -------------------------------------------------------------------------------- /Mcode/+mypackage/+internal/+util/todatetime.m: -------------------------------------------------------------------------------- 1 | function out = todatetime(x, zonePolicy) 2 | % Convert values to datetime array 3 | % 4 | % out = todatetime(x) 5 | % out = todatetime(x, zonePolicy) 6 | % 7 | % TODATETIME converts an input array to a datetime array. It is an alternative 8 | % to the datetime constructor, and is provided because MPT thinks Matlab got the 9 | % datetime constructor behavior wrong: in the one-arg constructor, numeric inputs are 10 | % interpreted as datevecs instead of datetimes; but we think that datenums are 11 | % in more common use and should have been the default interpretation of numeric 12 | % inputs. In addition, DATETIME does not support Java date/time types as inputs. 13 | % 14 | % TODATETIME also provides a zonePolicy argument for convenient setting 15 | % of the TimeZone on the constructed datetimes. 16 | % 17 | % x may be any of: 18 | % - numeric, which are interpreted as datenums 19 | % - string, char or cellstr, which are parsed as datestrs 20 | % - datetime, which are left as is 21 | % - java.util.Date or java.util.Date[] 22 | % - java.time LocalDate, LocalDateTime, ZonedDateTime, OffsetDateTime, Instant, or 23 | % arrays of same. 24 | % 25 | % zonePolicy is a string indicating how the TimeZone on the constructed 26 | % datetime array should be set. It may be one of: 27 | % - 'passthrough' (default) - Keep whatever zone was set on the input, if any 28 | % - 'unzoned' - no TimeZone is set 29 | % - 'utc' - TimeZone is set to UTC 30 | % - 'local' - TimeZone is set to the system's local time zone 31 | % - anything else - the name of a specific time zone to set on the output 32 | % 33 | % The way the zone policy is applied is: 34 | % * Inputs that do not have zone information are interpreted as being in the 35 | % zone specified by zonePolicy. 36 | % * Inputs that do have zone information are converted to the zone specified 37 | % by zonePolicy. 38 | % 39 | % If the input x is a datetime or zoned Java date and a zonePolicy is provided, 40 | % but the zonePolicy conflicts with the actual TimeZone set on x, an error is 41 | % raised. 42 | % 43 | % Returns a datetime array. 44 | arguments 45 | x 46 | zonePolicy (1,1) string = 'passthrough' 47 | end 48 | if lower(zonePolicy) == "UTC" 49 | zonePolicy = "utc"; 50 | end 51 | 52 | % TODO: Add support for Joda Time for legacy code? 53 | 54 | % Convert values 55 | 56 | if isa(x, 'datetime') 57 | out = x; 58 | elseif isnumeric(x) 59 | out = datetime(x, 'ConvertFrom','datenum'); 60 | elseif isstring(x) || ischar(x) || iscellstr(x) 61 | % TODO: Replace this with our own try/catch-less parsing logic 62 | out = datetime(x); 63 | elseif isjava(x) 64 | out = convertJavaDateToDatetime(x, zonePolicy); 65 | else 66 | % Anything else, let method overrides or datetime's own behavior sort it out 67 | out = datetime(x); 68 | end 69 | 70 | % Apply TimeZone policy 71 | 72 | if zonePolicy == "passthrough" 73 | % NOP; leave whatever zone was set on input 74 | elseif zonePolicy == "unzoned" 75 | out.TimeZone = []; 76 | elseif zonePolicy == "utc" 77 | out.TimeZone = 'UTC'; 78 | elseif zonePolicy == "local" 79 | out.TimeZone = datetime.SystemTimeZone; 80 | else 81 | % Looks like a specific TimeZone was requested 82 | out.TimeZone = zonePolicy; 83 | end 84 | 85 | end 86 | 87 | function out = convertJavaDateToDatetime(j, zonePolicy) 88 | % TODO: Once this code has been validated, optimize it by converting it to 89 | % use toInstant() and toEpochSecond() on java.time datetime values. 90 | nanoScale = 10^9; 91 | if isa(j, 'java.util.Date') 92 | out = datetime(j.getTime / 1000, 'ConvertFrom','posixtime'); 93 | tzoff = j.getTimezoneOffset; 94 | if tzoff ~= 0 95 | if tzoff > 0 96 | sign = '-'; 97 | else 98 | sign = '+'; 99 | end 100 | absoff = abs(tzoff); 101 | offhours = floor(absoff / 60); 102 | offminutes = rem(absoff, 60); 103 | tzoffStr = sprintf('%s%02d:%02d', sign, offhours, offminutes); 104 | out.TimeZone = 'UTC'; 105 | if zonePolicy == "utc" 106 | % Leave as is (optimization) 107 | else 108 | % Make the zone on the input date visible 109 | out.TimeZone = tzoffStr; 110 | end 111 | end 112 | elseif isa(j, 'java.time.Instant') 113 | sec = j.getEpochSecond; 114 | plusNanos = j.getNano; 115 | posixTime = double(sec) + (double(plusNanos) / nanoScale); 116 | out = datetime(posixTime, 'ConvertFrom','posixtime'); 117 | % Instants are in UTC by definition 118 | out.TimeZone = 'UTC'; 119 | elseif isa(j, 'java.time.LocalDate') 120 | out = convertJavaDateToDatetime(j.atStartOfDay); 121 | elseif isa(j, 'java.time.LocalDateTime') 122 | secondsWithNanos = double(j.getSecond) + (double(j.getNano) / nanoScale); 123 | out = datetime(j.getYear, j.getMonthValue, j.getDayOfMonth, j.getHour, j.getMinute, secondsWithNanos); 124 | elseif isa(j, 'java.time.ZonedDateTime') 125 | secondsWithNanos = double(j.getSecond) + (double(j.getNano) / nanoScale); 126 | out = datetime(j.getYear, j.getMonthValue, j.getDayOfMonth, j.getHour, j.getMinute, secondsWithNanos); 127 | out.TimeZone = char(j.getZone.getId); 128 | elseif isa(j, 'java.time.OffsetDateTime') 129 | secondsWithNanos = double(j.getSecond) + (double(j.getNano) / nanoScale); 130 | out = datetime(j.getYear, j.getMonthValue, j.getDayOfMonth, j.getHour, j.getMinute, secondsWithNanos); 131 | out.TimeZone = char(j.getOffset.getId); 132 | elseif isa(j, 'java.util.Date[]') || isa(j, 'java.time.Instant[]') || isa(j, 'java.time.LocalDate[]') ... 133 | || isa(j, 'java.time.LocalDateTime[]') || isa(j, 'java.time.ZonedDateTime[]') ... 134 | || isa(j, 'java.time.OffsetDateTime[]') 135 | % TODO: Push this loop down in to Java for efficiency? 136 | out = repmat(NaT, [1 numel(j)]); 137 | zone = []; 138 | for i = 1:numel(j) 139 | jEl = j(i); 140 | if isempty(jEl) 141 | % It's a Java null; leave as NaT 142 | else 143 | dt = convertJavaDateToDatetime(jEl); 144 | if isempty(zone) 145 | zone = dt.TimeZone; 146 | out.TimeZone = dt.TimeZone; 147 | else 148 | if ~isequal(dt.TimeZone, zone) 149 | error(['Input java.util.Date[] array has inconsistent time zones on its ' ... 150 | 'elements; cannot convert to datetime']); 151 | end 152 | end 153 | out(i) = dt; 154 | end 155 | end 156 | elseif isa(j, 'java.util.Collection') 157 | out = repmat(NaT, [1 j.size]); 158 | zone = []; 159 | it = j.iterator; 160 | i = 1; 161 | while it.hasNext 162 | jEl = it.next; 163 | if isempty(jEl) 164 | % It's a Java null; leave as NaT 165 | else 166 | dt = convertJavaDateToDatetime(jEl); 167 | if isempty(zone) 168 | zone = dt.TimeZone; 169 | out.TimeZone = dt.TimeZone; 170 | else 171 | if ~isequal(dt.TimeZone, zone) 172 | error(['Input Collection has inconsistent time zones on its ' ... 173 | 'elements; cannot convert to datetime']); 174 | end 175 | end 176 | out(i) = dt; 177 | end 178 | i = i + 1; 179 | end 180 | else 181 | error('Conversion to datetime from Java type %s is not supported', class(j)); 182 | end 183 | 184 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/Log4jConfigurator.m: -------------------------------------------------------------------------------- 1 | classdef Log4jConfigurator 2 | % A configurator for log4j 3 | % 4 | % This class configures the logging setup for Matlab/SLF4M logging. It 5 | % configures the log4j library that SLF4M logging sits on top of. (We use log4j 6 | % because it ships with Matlab.) 7 | % 8 | % This class is provided as a convenience. You can also configure SLF4M logging 9 | % by directly configuring log4j using its normal Java interface. 10 | % 11 | % SLF4M does not automatically configure log4j. You must either call a 12 | % configureXxx method on this class or configure log4j directly yourself to get 13 | % logging to work. Otherwise, you may get warnings like this at the console: 14 | % 15 | % log4j:WARN No appenders could be found for logger (unknown). 16 | % log4j:WARN Please initialize the log4j system properly. 17 | % 18 | % If that happens, it means you need to call 19 | % logger.Log4jConfigurator.configureBasicConsoleLogging. 20 | % 21 | % This also provides a log4j configuration GUI that you can launch with 22 | % `logger.Log4jConfigurator.showGui`. 23 | % 24 | % Examples: 25 | % 26 | % mypackage.logger.Log4jConfigurator.configureBasicConsoleLogging 27 | % 28 | % mypackage.logger.Log4jConfigurator.setLevels({'root','DEBUG'}); 29 | % 30 | % mypackage.logger.Log4jConfigurator.setLevels({ 31 | % 'root' 'INFO' 32 | % 'net.apjanke.logger.swing' 'DEBUG' 33 | % }); 34 | % 35 | % mypackage.logger.Log4jConfigurator.prettyPrintLogConfiguration 36 | % 37 | % % Display fully-qualified class/category names in the log output: 38 | % mypackage.logger.Log4jConfigurator.setRootAppenderPattern(... 39 | % ['%d{HH:mm:ss.SSS} %p %c - %m' sprintf('\n')]); 40 | % 41 | % % Bring up the configuration GUI 42 | % mypackage.logger.Log4jConfigurator.showGui 43 | 44 | % This class does *not* use the implicit initializer trick, because the 45 | % implicit library initializer may depend on this class! 46 | 47 | methods (Static) 48 | function configureBasicConsoleLogging 49 | % Configures log4j to do basic logging to the console 50 | % 51 | % This sets up a basic log4j configuration, with log output going to the 52 | % console, and the root logger set to the INFO level. 53 | % 54 | % This method can safely be called multiple times. If there's already an 55 | % appender on the root logger (indicating logging has already been 56 | % configured), it silently does nothing. 57 | 58 | rootLogger = org.apache.log4j.Logger.getRootLogger(); 59 | rootAppenders = rootLogger.getAllAppenders(); 60 | isConfiguredAlready = rootAppenders.hasMoreElements; 61 | if ~isConfiguredAlready 62 | org.apache.log4j.BasicConfigurator.configure(); 63 | rootLogger = org.apache.log4j.Logger.getRootLogger(); 64 | rootLogger.setLevel(org.apache.log4j.Level.INFO); 65 | rootAppender = rootLogger.getAllAppenders().nextElement(); 66 | % Use \n instead of %n because the Matlab console wants Unix-style line 67 | % endings, even on Windows. 68 | pattern = ['%d{HH:mm:ss.SSS} %-5p %c{1} %x - %m' sprintf('\n')]; 69 | myLayout = org.apache.log4j.PatternLayout(pattern); 70 | rootAppender.setLayout(myLayout); 71 | end 72 | 73 | end 74 | 75 | function setRootAppenderPattern(pattern) 76 | % Sets the pattern on the root appender 77 | % 78 | % This is just a convenience method. Assumes there is a single 79 | % appender on the root logger. 80 | rootLogger = org.apache.log4j.Logger.getRootLogger(); 81 | rootAppender = rootLogger.getAllAppenders().nextElement(); 82 | myLayout = org.apache.log4j.PatternLayout(pattern); 83 | rootAppender.setLayout(myLayout); 84 | end 85 | 86 | function out = getLog4jLevel(levelName) 87 | % Gets the log4j Level enum for a named level 88 | validLevels = {'OFF' 'FATAL' 'ERROR' 'WARN' 'INFO' 'DEBUG' 'TRACE' 'ALL'}; 89 | levelName = upper(levelName); 90 | if ~ismember(levelName, validLevels) 91 | error('Invalid levelName: ''%s''', levelName); 92 | end 93 | out = eval(['org.apache.log4j.Level.' levelName]); 94 | end 95 | 96 | function setLevels(levels) 97 | % Set the logging levels for multiple loggers 98 | % 99 | % mypackage.logger.Log4jConfigurator.setLevels(levels) 100 | % 101 | % This is a convenience method for setting the logging levels for multiple 102 | % loggers. 103 | % 104 | % The levels input is an n-by-2 cellstr with logger names in column 1 and 105 | % level names in column 2. 106 | % 107 | % Examples: 108 | % 109 | % mypackage.logger.Log4jConfigurator.setLevels({'root','DEBUG'}); 110 | % 111 | % mypackage.logger.Log4jConfigurator.setLevels({ 112 | % 'root' 'INFO' 113 | % 'net.apjanke.logger.swing' 'DEBUG' 114 | % }); 115 | for i = 1:size(levels, 1) 116 | [logName,levelName] = levels{i,:}; 117 | logger = org.apache.log4j.LogManager.getLogger(logName); 118 | level = mypackage.logger.Log4jConfigurator.getLog4jLevel(levelName); 119 | logger.setLevel(level); 120 | end 121 | end 122 | 123 | function prettyPrintLogConfiguration(verbose) 124 | % Displays the current log configuration to the console 125 | % 126 | % mypackage.logger.Log4jConfigurator.prettyPrintLogConfiguration() 127 | 128 | if nargin < 1 || isempty(verbose); verbose = false; end 129 | 130 | function out = getLevelName(lgr) 131 | level = lgr.getLevel(); 132 | if isempty(level) 133 | out = ''; 134 | else 135 | out = char(level.toString()); 136 | end 137 | end 138 | 139 | % Get all names first so we can display in sorted order 140 | loggers = org.apache.log4j.LogManager.getCurrentLoggers(); 141 | loggerNames = {}; 142 | while loggers.hasMoreElements() 143 | logger = loggers.nextElement(); 144 | loggerNames{end+1} = char(logger.getName()); %#ok 145 | end 146 | loggerNames = sort(loggerNames); 147 | 148 | % Display the hierarchy 149 | rootLogger = org.apache.log4j.LogManager.getRootLogger(); 150 | fprintf('Root (%s): %s\n', char(rootLogger.getName()), getLevelName(rootLogger)); 151 | for i = 1:numel(loggerNames) 152 | logger = org.apache.log4j.LogManager.getLogger(loggerNames{i}); 153 | appenders = logger.getAllAppenders(); 154 | appenderStrs = {}; 155 | while appenders.hasMoreElements 156 | appender = appenders.nextElement(); 157 | if isa(appender, 'org.apache.log4j.varia.NullAppender') 158 | appenderStr = 'NullAppender'; 159 | else 160 | appenderStr = sprintf('%s (%s)', char(appender.toString()), ... 161 | char(appender.getName())); 162 | end 163 | appenderStrs{end+1} = ['appender: ' appenderStr]; %#ok 164 | end 165 | appenderList = strjoin(appenderStrs, ' '); 166 | if ~verbose 167 | if isempty(logger.getLevel()) && isempty(appenderList) ... 168 | && logger.getAdditivity() 169 | continue 170 | end 171 | end 172 | items = {}; 173 | if ~isempty(getLevelName(logger)) 174 | items{end+1} = getLevelName(logger); %#ok 175 | end 176 | if ~isempty(appenderStr) 177 | items{end+1} = appenderList; %#ok 178 | end 179 | if ~logger.getAdditivity() 180 | items{end+1} = sprintf('additivity=%d', logger.getAdditivity()); %#ok 181 | end 182 | str = strjoin(items, ' '); 183 | fprintf('%s: %s\n',... 184 | loggerNames{i}, str); 185 | end 186 | end 187 | 188 | end 189 | 190 | end -------------------------------------------------------------------------------- /Mcode/+mypackage/+logger/Logger.m: -------------------------------------------------------------------------------- 1 | classdef Logger 2 | %LOGGER Main entry point through which logging happens 3 | % 4 | % The Logger class provides method calls for performing logging, and the ability 5 | % to look up loggers by name. This is the main entry point through which all 6 | % mypackage.logger logging happens. 7 | % 8 | % Usually you don't need to interact with this class directly, but can just call 9 | % one of the error(), warn(), info(), debug(), or trace() functions in the logger 10 | % namespace. Those will log messages using the calling class's name as the name 11 | % of the logger. Also, don't call the constructor for this class. Use the static 12 | % getLogger() method instead. 13 | % 14 | % Use this class directly if you want to customize the names of the loggers to 15 | % which logging is directed. 16 | % 17 | % Each of the logging methods - error(), warn(), info(), debug(), and 18 | % trace() - takes a sprintf()-style signature, with a format string as 19 | % the first argument, and substitution values as the remaining 20 | % arguments. 21 | % mypackage.logger.info(format, varargin) 22 | % You can also insert an MException object at the beginning of the 23 | % argument list to have its message and stack trace included in the log 24 | % message. 25 | % mypackage.logger.warn(exception, format, varargin) 26 | % 27 | % See also: 28 | % mypackage.logger.error 29 | % mypackage.logger.warn 30 | % mypackage.logger.info 31 | % mypackage.logger.debug 32 | % mypackage.logger.trace 33 | % 34 | % Examples: 35 | % 36 | % log = mypackage.logger.Logger.getLogger('foo.bar.FooBar'); 37 | % log.info('Hello, world! Running on Matlab %s', version); 38 | % 39 | % try 40 | % some_operation_that_could_go_wrong(); 41 | % catch err 42 | % log.warn(err, 'Caught exception during processing') 43 | % end 44 | 45 | properties (SetAccess = private) 46 | % The underlying SLF4J Logger object 47 | jLogger 48 | end 49 | 50 | properties (Dependent = true) 51 | % The name of this logger 52 | name 53 | % A list of the levels enabled on this logger 54 | enabledLevels 55 | end 56 | 57 | 58 | methods (Static) 59 | function out = getLogger(identifier) 60 | % Gets the named Logger 61 | jLogger = org.slf4j.LoggerFactory.getLogger(identifier); 62 | out = mypackage.logger.Logger(jLogger); 63 | end 64 | end 65 | 66 | methods 67 | function this = Logger(jLogger) 68 | %LOGGER Build a new logger object around an SLF4J Logger object. 69 | % 70 | % Generally, you shouldn't call this. Use logger.Logger.getLogger() instead. 71 | mustBeA(jLogger, 'org.slf4j.Logger'); 72 | this.jLogger = jLogger; 73 | end 74 | 75 | function disp(this) 76 | %DISP Custom object display. 77 | disp(dispstr(this)); 78 | end 79 | 80 | function out = dispstr(this) 81 | %DISPSTR Custom object display string. 82 | if isscalar(this) 83 | strs = dispstrs(this); 84 | out = strs{1}; 85 | else 86 | out = sprintf('%s %s', size2str(size(this)), class(this)); 87 | end 88 | end 89 | 90 | function out = dispstrs(this) 91 | %DISPSTRS Custom object display strings. 92 | out = cell(size(this)); 93 | for i = 1:numel(this) 94 | out{i} = sprintf('Logger: %s (%s)', this(i).name, ... 95 | strjoin(this(i).enabledLevels, ', ')); 96 | end 97 | end 98 | 99 | function error(this, msg, varargin) 100 | % Log a message at the ERROR level. 101 | if ~this.jLogger.isErrorEnabled() 102 | return 103 | end 104 | msgStr = formatMessage(msg, varargin{:}); 105 | this.jLogger.error(msgStr); 106 | end 107 | 108 | function warn(this, msg, varargin) 109 | % Log a message at the WARN level. 110 | if ~this.jLogger.isWarnEnabled() 111 | return 112 | end 113 | msgStr = formatMessage(msg, varargin{:}); 114 | this.jLogger.warn(msgStr); 115 | end 116 | 117 | function info(this, msg, varargin) 118 | % Log a message at the INFO level. 119 | if ~this.jLogger.isInfoEnabled() 120 | return 121 | end 122 | msgStr = formatMessage(msg, varargin{:}); 123 | this.jLogger.info(msgStr); 124 | end 125 | 126 | function debug(this, msg, varargin) 127 | % Log a message at the DEBUG level. 128 | if ~this.jLogger.isDebugEnabled() 129 | return 130 | end 131 | msgStr = formatMessage(msg, varargin{:}); 132 | this.jLogger.debug(msgStr); 133 | end 134 | 135 | function trace(this, msg, varargin) 136 | % Log a message at the TRACE level. 137 | if ~this.jLogger.isTraceEnabled() 138 | return 139 | end 140 | msgStr = formatMessage(msg, varargin{:}); 141 | this.jLogger.trace(msgStr); 142 | end 143 | 144 | function errorj(this, msg, varargin) 145 | % Log a message at the ERROR level, using SLFJ formatting. 146 | if ~this.jLogger.isErrorEnabled() 147 | return 148 | end 149 | this.jLogger.error(msg, varargin{:}); 150 | end 151 | 152 | function warnj(this, msg, varargin) 153 | % Log a message at the WARN level, using SLFJ formatting. 154 | if ~this.jLogger.isWarnEnabled() 155 | return 156 | end 157 | this.jLogger.warn(msg, varargin{:}); 158 | end 159 | 160 | function infoj(this, msg, varargin) 161 | % Log a message at the INFO level, using SLFJ formatting. 162 | if ~this.jLogger.isInfoEnabled() 163 | return 164 | end 165 | this.jLogger.info(msg, varargin{:}); 166 | end 167 | 168 | function debugj(this, msg, varargin) 169 | % Log a message at the DEBUG level, using SLFJ formatting. 170 | if ~this.jLogger.isDebugEnabled() 171 | return 172 | end 173 | this.jLogger.debug(msg, varargin{:}); 174 | end 175 | 176 | function tracej(this, msg, varargin) 177 | % Log a message at the TRACE level, using SLFJ formatting. 178 | if ~this.jLogger.isTraceEnabled() 179 | return 180 | end 181 | this.jLogger.trace(msg, varargin{:}); 182 | end 183 | 184 | function out = isErrorEnabled(this) 185 | % True if ERROR level logging is enabled for this logger. 186 | out = this.jLogger.isErrorEnabled; 187 | end 188 | 189 | function out = isWarnEnabled(this) 190 | % True if WARN level logging is enabled for this logger. 191 | out = this.jLogger.isWarnEnabled; 192 | end 193 | 194 | function out = isInfoEnabled(this) 195 | % True if INFO level logging is enabled for this logger. 196 | out = this.jLogger.isInfoEnabled; 197 | end 198 | 199 | function out = isDebugEnabled(this) 200 | % True if DEBUG level logging is enabled for this logger. 201 | out = this.jLogger.isDebugEnabled; 202 | end 203 | 204 | function out = isTraceEnabled(this) 205 | % True if TRACE level logging is enabled for this logger. 206 | out = this.jLogger.isTraceEnabled; 207 | end 208 | 209 | function out = listEnabledLevels(this) 210 | % List the levels that are enabled for this logger. 211 | out = {}; 212 | if this.isErrorEnabled 213 | out{end+1} = 'error'; 214 | end 215 | if this.isWarnEnabled 216 | out{end+1} = 'warn'; 217 | end 218 | if this.isInfoEnabled 219 | out{end+1} = 'info'; 220 | end 221 | if this.isDebugEnabled 222 | out{end+1} = 'debug'; 223 | end 224 | if this.isTraceEnabled 225 | out{end+1} = 'trace'; 226 | end 227 | end 228 | 229 | function out = get.enabledLevels(this) 230 | out = this.listEnabledLevels; 231 | end 232 | 233 | function out = get.name(this) 234 | out = char(this.jLogger.getName()); 235 | end 236 | end 237 | 238 | end 239 | 240 | function out = formatMessage(format, varargin) 241 | args = varargin; 242 | exceptionStr = []; 243 | if isa(format, 'MException') 244 | exception = format; 245 | if isempty(args) 246 | format = ''; 247 | else 248 | format = args{1}; 249 | args = args(2:end); 250 | end 251 | exceptionStr = getReport(exception, 'extended', 'hyperlinks','off'); 252 | % Remove blank lines for more compact, readable (imho) logs 253 | exceptionStr = strrep(exceptionStr, sprintf('\n\n'), newline); 254 | end 255 | for i = 1:numel(args) 256 | if isobject(args{i}) 257 | if isa(args{i}, 'string') && isscalar(args{i}) 258 | if ismissing(args{i}) 259 | args{i} = ''; 260 | else 261 | args{i} = char(args{i}); 262 | end 263 | else 264 | % General case 265 | args{i} = dispstr(args{i}); 266 | end 267 | end 268 | end 269 | out = sprintf(format, args{:}); 270 | if ~isempty(exceptionStr) 271 | out = sprintf('%s\n%s', out, exceptionStr); 272 | end 273 | end 274 | -------------------------------------------------------------------------------- /init_project_from_template.m: -------------------------------------------------------------------------------- 1 | function init_project_from_template(varargin) 2 | % Initialize this project from the MatlabProjectTemplate template 3 | 4 | %#ok<*STRNU> 5 | %#ok<*AGROW> 6 | %#ok<*DEFNU> 7 | 8 | % Parse options 9 | 10 | doDev = false; 11 | 12 | for i = 1:numel(varargin) 13 | switch varargin{i} 14 | case {'-d', '--dev'} 15 | doDev = true; 16 | otherwise 17 | error('Invalid option: %s', varargin{i}); 18 | end 19 | end 20 | 21 | % Init empty variables just so we can use tab-completion while working on this 22 | % script 23 | 24 | PROJECT = []; 25 | PROJECT_MATLAB_VERSION = []; 26 | PACKAGE = []; 27 | GHUSER = []; 28 | PROJECT_EMAIL = []; 29 | DOCTOOL = []; 30 | PROJECT_AUTHOR = []; 31 | PROJECT_SUMMARY = []; 32 | PROJECT_DESCRIPTION = []; 33 | AUTHOR_HOMEPAGE = []; 34 | 35 | % Get going 36 | 37 | reporoot = string(fileparts(mfilename('fullpath'))); 38 | RAII.cd = withcd(reporoot); 39 | 40 | info = project_settings; 41 | f = fieldnames(info); 42 | for i = 1:numel(f) 43 | if isstring(info.(f{i})) || ischar(info.(f{i})) 44 | info.(f{i}) = char(info.(f{i})); 45 | else 46 | error('Bad value for %s: Must be a string', f{i}); 47 | end 48 | end 49 | 50 | % Validate project options 51 | 52 | validFields = {'PROJECT', 'PROJECT_MATLAB_VERSION', 'PACKAGE', 'GHUSER', ... 53 | 'PROJECT_EMAIL', 'DOCTOOL', 'PROJECT_AUTHOR', 'PROJECT_SUMMARY', ... 54 | 'PROJECT_DESCRIPTION', 'AUTHOR_HOMEPAGE'}; 55 | missingFields = setdiff(validFields, fieldnames(info)); 56 | if ~isempty(missingFields) 57 | error(['Missing fields in project_settings.m: %s.\n' ... 58 | 'You didn''t delete stuff from that file, did you?'], strjoin(missingFields, ', ')); 59 | end 60 | badFields = setdiff(fieldnames(info), validFields); 61 | if ~isempty(badFields) 62 | fprintf('WARNING: Ignoring unrecognized fields in project_settings.m: %s\n', ... 63 | strjoin(badFields, ', ')); 64 | end 65 | 66 | copyStructFieldsIntoWorkspace(info); 67 | 68 | docSiteDir = "docs-src-" + DOCTOOL; 69 | if ~isfolder(docSiteDir) 70 | error(['Invalid choice for DOCTOOL: %s\nValid choices are: ' ... 71 | 'jekyll, mkdocs, gh-pages, gh-pages-raw'], DOCTOOL); 72 | end 73 | 74 | badHtmlChars = '<>&'; 75 | htmlFields = ["PROJECT_SUMMARY", "PROJECT_DESCRIPTION", "AUTHOR_HOMEPAGE"]; 76 | for i = 1:numel(htmlFields) 77 | if any(ismember(info.(htmlFields{i}), badHtmlChars)) 78 | error("%s may not contain %s", htmlFields{i}, badHtmlChars); 79 | end 80 | end 81 | 82 | PACKAGE_CAP = [upper(PACKAGE(1)) PACKAGE(2:end)]; 83 | PACKAGE_UPPER = upper(PACKAGE); 84 | 85 | % Do work! 86 | 87 | echo 88 | echo("Initializing project " + PROJECT); 89 | 90 | echo 91 | echo("Generating a GUID for your project's Toolbox...") 92 | PROJECT_GUID = char(toString(java.util.UUID.randomUUID)); 93 | 94 | % Munge the source code and documentation 95 | 96 | mv("Mcode/+mypackage/+internal/MypackageBase.m", "Mcode/+mypackage/+internal/"+PACKAGE_CAP+"Base.m"); 97 | mv("Mcode/+mypackage/+internal/MypackageBaseHandle.m", "Mcode/+mypackage/+internal/"+PACKAGE_CAP+"BaseHandle.m"); 98 | mv("Mcode/+mypackage", "Mcode/+"+PACKAGE) 99 | mv("src/java/myproject-java/src/main/java/com/example/mypackage", "src/java/myproject-java/src/main/java/com/example/"+PACKAGE) 100 | mv("src/java/myproject-java", "src/java/"+PROJECT+"-java") 101 | 102 | fileGlobsToMunge = regexp(".gitignore Makefile *.md */*.md */*.adoc */*.yml " ... 103 | + "myproject.prj.in */*.m */*/*.m */*/*/*.m */*/*/*/*.m src/java/*/*.xml " ... 104 | + "src/java/*/*/*/*/*/*/*/*.java azure-pipelines.yml .travis.yml " ... 105 | + ".circleci/config.yml dev-kit/*.m dev-kit/*.sh dev-kit/run_matlab " ... 106 | + "dev-kit/private/*.m mypackage_toolbox_info.m " ... 107 | + "dev-kit/make_release dev-kit/*.m CHANGES.md info.xml doc-project/*.txt doc-project/*.md", ' +', 'split'); 108 | filesToMunge = unique(fileglob2abspath(fileGlobsToMunge)); 109 | replacements = { 110 | "__myproject__" PROJECT 111 | "__myprojectemail__" PROJECT_EMAIL 112 | "__myprojectguid__" PROJECT_GUID 113 | "__myproject_matlab_version__" PROJECT_MATLAB_VERSION 114 | "__myghuser__" GHUSER 115 | "__YOUR_NAME_HERE__" PROJECT_AUTHOR 116 | "__myproject_summary__" PROJECT_SUMMARY 117 | "__myproject_description__" PROJECT_DESCRIPTION 118 | "__author_homepage__" AUTHOR_HOMEPAGE 119 | "myproject" PROJECT 120 | "mypackage" PACKAGE 121 | "MYPACKAGE" PACKAGE_UPPER 122 | "myghuser" GHUSER 123 | "Mypackage" PACKAGE_CAP 124 | "R2019b" PROJECT_MATLAB_VERSION 125 | }; 126 | mungefiles(filesToMunge, replacements); 127 | copyfile2('MatlabProjectTemplate/project-README.md', 'README.md') 128 | if ~doDev 129 | replacements= { 130 | "# start_MPT_targets.*# end_MPT_targets" "" 131 | }; 132 | ciConfigFiles = [".travis.yml", ".circleci/config.yml", "azure-pipelines.yml"]; 133 | mungefiles(ciConfigFiles, replacements, "regex"); 134 | end 135 | 136 | for f = fileglob2abspath({'*mypackage*', 'dev-kit/*mypackage*', 'dev-kit/private/*mypackage*'}) 137 | relFile = strrep(f, reporoot+filesep, ''); 138 | newName = strrep(relFile, 'mypackage', PACKAGE); 139 | mv(relFile, newName); 140 | end 141 | delete('LICENSE-MatlabProjectTemplate.md') 142 | delete('LICENSE') 143 | mv('myproject-LICENSE', 'LICENSE') 144 | delete('CHANGES.md') 145 | mv('myproject-CHANGES.md', 'CHANGES.md') 146 | 147 | if ~doDev 148 | delete('rollback_init') 149 | end 150 | 151 | rmdir2('docs', 's'); 152 | copyfile2("docs-src-"+DOCTOOL, 'docs'); 153 | for f = ["docs-src-jekyll" "docs-src-gh-pages" "docs-src-gh-pages-raw" "docs-src-mkdocs"] 154 | rmdir2(f, 's'); 155 | end 156 | 157 | rmdir2('doc', 's') 158 | mkdir('doc') 159 | 160 | writetext(PROJECT_MATLAB_VERSION, '.matlab_version') 161 | writetext("0.1.0", "VERSION") 162 | movefile('myproject.prj.in', PROJECT+".prj.in") 163 | 164 | echo 165 | echo("Project "+PROJECT+" is initialized.") 166 | echo("See MatlabProjectTemplate/README.md for more info.") 167 | echo 168 | echo("Happy hacking!") 169 | echo 170 | 171 | % This message will self-destruct in 5 seconds. 5... 4... 3... 2... 1... 172 | delete([mfilename('fullpath') '.m']) 173 | 174 | end 175 | 176 | function mungefiles(files, replacements, replacementType) 177 | arguments 178 | files (1,:) string 179 | replacements cell 180 | replacementType (1,1) string = "string" 181 | end 182 | for file = files 183 | origTxt = readtext(file); 184 | txt = origTxt; 185 | for i = 1:size(replacements, 1) 186 | [old, new] = replacements{i,:}; 187 | if replacementType == "string" 188 | txt = strrep(txt, old, new); 189 | elseif replacementType == "regex" 190 | txt = regexprep(txt, old, new); 191 | else 192 | error('Invalid replacementType: %s', replacementType); 193 | end 194 | end 195 | writetext(txt, file); 196 | end 197 | end 198 | 199 | function out = fileglob2abspath(pats) 200 | arguments 201 | pats string 202 | end 203 | names = string([]); 204 | abspaths = string([]); 205 | for i = 1:numel(pats) 206 | d = dir(pats(i)); 207 | name = string({d.name}); 208 | abspath = fullfile(string({d.folder}), name); 209 | names = [names name]; 210 | abspaths = [abspath abspaths]; 211 | end 212 | out = abspaths; 213 | end 214 | 215 | function echo(fmt, varargin) 216 | if nargin == 0 217 | fprintf('\n'); 218 | return 219 | end 220 | fprintf([char(fmt) '\n'], varargin{:}); 221 | end 222 | 223 | function copyStructFieldsIntoWorkspace(s) 224 | fields = fieldnames(s); 225 | for i = 1:numel(fields) 226 | assignin('caller', fields{i}, s.(fields{i})); 227 | end 228 | end 229 | 230 | function copyfile2(src, dest, varargin) 231 | % A version of copyfile that raises an error on failure 232 | [ok,msg] = copyfile(src, dest, varargin{:}); 233 | if ~ok 234 | error('Failed copying file "%s" to "%s": %s', src, dest, msg); 235 | end 236 | end 237 | 238 | function mkdir2(varargin) 239 | % A version of mkdir that raises error on failure 240 | [ok,msg] = mkdir(varargin{:}); 241 | if ~ok 242 | if nargin == 1 243 | target = varargin{1}; 244 | else 245 | target = fullfile(varargin{:}); 246 | end 247 | error('Failed creating directory "%s": %s', target, msg); 248 | end 249 | end 250 | 251 | function rmdir2(dir, varargin) 252 | % A version of rmdir that raises errors on failure 253 | [ok,msg] = rmdir(dir, varargin{:}); 254 | if ~ok 255 | error('rmdir of "%s" failed: %s', dir, msg); 256 | end 257 | end 258 | 259 | function out = system2(cmd) 260 | % A version of system that raises an error on failure 261 | if nargout == 0 262 | ok = system(cmd); 263 | else 264 | [ok,out] = system(cmd); 265 | end 266 | if ~ok 267 | error('Command failed. Command: %s', cmd); 268 | end 269 | end 270 | 271 | function out = withcd(dir) 272 | % Temporarily change to a new directory 273 | origDir = pwd; 274 | cd(dir); 275 | out.RAII = onCleanup(@() cd(origDir)); 276 | end 277 | 278 | function mv(source, dest) 279 | % A version of movefile that raises an error on failure 280 | [ok,msg] = movefile(source, dest); 281 | if ~ok 282 | error('Failed moving "%s" to "%s": %s', source, dest, msg); 283 | end 284 | end 285 | 286 | function out = readtext(file, encoding) 287 | % Read the contents of a text file as a string 288 | % 289 | % This is analagous to Matlab's readcsv and readtable, and exists because Matlab 290 | % doesn't provide a basic file-slurping mechanism. 291 | 292 | arguments 293 | file (1,1) string 294 | encoding (1,1) string = 'UTF-8' % TODO: auto-detect file encoding via sniffing 295 | end 296 | [fid,msg] = fopen(file, 'r', 'n', encoding); 297 | if fid < 1 298 | error('Failed opening file %s: %s', file, msg); 299 | end 300 | RAII.fh = onCleanup(@() fclose(fid)); 301 | c = fread(fid, Inf, 'uint8=>char'); 302 | out = string(c'); 303 | end 304 | 305 | function writetext(text, file, encoding) 306 | arguments 307 | text (1,1) string 308 | file (1,1) string 309 | encoding (1,1) string = 'UTF-8' 310 | end 311 | [fid,msg] = fopen(file, 'w', 'n', encoding); 312 | if fid < 1 313 | error('Failed opening file %s: %s', file, msg); 314 | end 315 | RAII.fh = onCleanup(@() fclose(fid)); 316 | fprintf(fid, '%s', text); 317 | end 318 | --------------------------------------------------------------------------------