diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index e27cea2..a610a9a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -5,7 +5,7 @@ on: types: [published] # Trigger the workflow when a new release is created jobs: - build-and-release: + build-and-release-win: runs-on: windows-latest steps: @@ -35,6 +35,49 @@ jobs: run: | pipenv run pyinstaller --noconsole --windowed --onefile main.py + # Step 6: Upload executable to release assets + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} # This is provided by the release event + asset_path: ./dist/main.exe + asset_name: TaskNoteManager-${{ github.event.release.tag_name }}.exe + asset_content_type: application/octet-stream + + build-and-release-linux: + runs-on: ubuntu-latest + + steps: + # Step 1: Check out the repository + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: linux + + # Step 2: Set up Python + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11.4' + + # Step 3: Install Pipenv + - name: Install Pipenv + run: | + python -m pip install --upgrade pip + pip install pipenv + + # Step 4: Install dependencies using Pipenv + - name: Install dependencies with Pipenv + run: | + pipenv install --dev + + # Step 5: Build the application using PyInstaller + - name: Build with PyInstaller + run: | + pipenv run pyinstaller --noconsole --windowed --onefile main.py + # Step 6: Upload executable to release assets - name: Upload Release Asset uses: actions/upload-release-asset@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b6a4176 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,115 @@ +name: Run tests when PR is created + +on: + issue_comment: + types: [created] + +jobs: + run-tests: + name: Run tests on PR when new comment is added + # Only run on PR comments (not issue comments) containing "!test" + if: github.event.issue.pull_request && contains(github.event.comment.body, '!test') + runs-on: ubuntu-latest + permissions: + pull-requests: write + statuses: write # needed to post commit status to the PR + steps: + - name: Get PR HEAD sha + id: pr-sha + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.issue.number, + }); + core.setOutput('sha', pr.data.head.sha); + + - name: Set commit status to pending + uses: actions/github-script@v7 + with: + script: | + await github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: '${{ steps.pr-sha.outputs.sha }}', + state: 'pending', + context: 'Run tests', + description: 'Tests are running...', + }); + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Checkout branch + run: gh pr checkout ${{ github.event.issue.number }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11.4' + + - name: Install dependencies with apt get + run: sudo apt-get install -y libdbus-1-dev libdbus-glib-1-dev + + - name: Install GUI dependencies and Xvfb + run: | + sudo apt-get update + sudo apt-get install -y xvfb python3-tk libx11-dev libxext-dev libxrender-dev libxinerama-dev libxi-dev libxrandr-dev libxcursor-dev libglx-mesa0 mesa-utils + + - name: Install Pipenv + run: | + python -m pip install --upgrade pip + pip install pipenv + + - name: Install dependencies with Pipenv + run: pipenv install --dev --deploy + + - name: Run tests with xvfb-run + run: | + # Ukaz xvfb-run bo samodejno ustvaril zaslon in varno zagnal vaša orodja v Pipenv + xvfb-run --server-args="-screen 0 800x600x24" pipenv run coverage run --source=./src -m unittest discover -s test/ + + - name: Post coverage comment + uses: actions/github-script@v7 + with: + script: | + const { execSync } = require('child_process'); + const total = execSync('pipenv run coverage report -m --format=markdown', { cwd: process.env.GITHUB_WORKSPACE }).toString().trim(); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: `## Coverage Report\n\n ${total}%` + }); + + - name: Set commit status to success + if: success() + uses: actions/github-script@v7 + with: + script: | + await github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: '${{ steps.pr-sha.outputs.sha }}', + state: 'success', + context: 'Run tests', + description: 'Tests passed!', + }); + + - name: Set commit status to failure + if: failure() + uses: actions/github-script@v7 + with: + script: | + await github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: '${{ steps.pr-sha.outputs.sha }}', + state: 'failure', + context: 'Run tests', + description: 'Tests failed!', + }); \ No newline at end of file diff --git a/Pipfile b/Pipfile index 1778f21..86125cf 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ notify2 = "*" pyexcel-ods3 = "*" typing-extensions = "*" customtkinter = "*" +dbus-python = "*" [dev-packages] pyinstaller = "*" diff --git a/Pipfile.lock b/Pipfile.lock index aefc0b6..10c5d24 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8c4365c116c1c4a58c85e8fe9b264167106bbef8dddbed0e1cb02c08d38b7b14" + "sha256": "7dbe098ff63f3d749d65a54cee0b0a26e13c6b322baf3e8d633716d05ebf549e" }, "pipfile-spec": 6, "requires": { @@ -42,6 +42,14 @@ "markers": "python_version >= '3.6'", "version": "==0.8.0" }, + "dbus-python": { + "hashes": [ + "sha256:991666e498f60dbf3e49b8b7678f5559b8a65034fdf61aae62cdecdb7d89c770" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==1.4.0" + }, "lml": { "hashes": [ "sha256:20c80728189e46e8d986f5d0cdf6d83c493471fc25b1c31a8cb3fa96e80b58f8", @@ -51,143 +59,143 @@ }, "lxml": { "hashes": [ - "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", - "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", - "sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7", - "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", - "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635", - "sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286", - "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9", - "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", - "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", - "sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618", - "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7", - "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39", - "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", - "sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551", - "sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc", - "sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd", - "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", - "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", - "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289", - "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", - "sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a", - "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", - "sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd", - "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", - "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", - "sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad", - "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", - "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", - "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c", - "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", - "sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544", - "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", - "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", - "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512", - "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", - "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", - "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", - "sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec", - "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", - "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", - "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037", - "sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13", - "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", - "sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64", - "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", - "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", - "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", - "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", - "sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8", - "sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39", - "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842", - "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", - "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", - "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c", - "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", - "sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6", - "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", - "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", - "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", - "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", - "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", - "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", - "sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755", - "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", - "sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280", - "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace", - "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", - "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", - "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", - "sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2", - "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3", - "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", - "sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce", - "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", - "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", - "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", - "sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7", - "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5", - "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", - "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4", - "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", - "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50", - "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", - "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", - "sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16", - "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", - "sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919", - "sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4", - "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", - "sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec", - "sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd", - "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", - "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", - "sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940", - "sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72", - "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", - "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", - "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", - "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", - "sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a", - "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", - "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", - "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", - "sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317", - "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", - "sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383", - "sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee", - "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", - "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", - "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9", - "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e", - "sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd", - "sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3", - "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", - "sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad", - "sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea", - "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", - "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", - "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de", - "sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1", - "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", - "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11", - "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", - "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", - "sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776", - "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", - "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", - "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a", - "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5", - "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", - "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", - "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3", - "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", - "sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215" + "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", + "sha256:07a4a68e286ee7a1ed7dfb8af83e615757c0ccfe9f18c6b4ea6771388d9ba8c9", + "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60", + "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", + "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", + "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", + "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", + "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", + "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", + "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", + "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", + "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", + "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", + "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30", + "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", + "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", + "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", + "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", + "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", + "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", + "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", + "sha256:32ab449a5486f6c758e849bb86710d0e45edc24a04e250c01555f8f5653958f8", + "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", + "sha256:34c2d737beabfe35baada43941ed519251e9a12e779031496bcd5d539fcfd730", + "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", + "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1", + "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", + "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", + "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", + "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5", + "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", + "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", + "sha256:441dd227fa0690eb9fc81edabc63cdcefc212bba99b906dcf6e32cc1a9d3e533", + "sha256:469e3618338bd7ab5beb412d2439825479fcf0dab99e394ca563dbc4eaf6c834", + "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", + "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", + "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", + "sha256:53c909b62a0532183542fed00c5a7218258c56292d409bc789886fe1cb04c438", + "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", + "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea", + "sha256:55b03549819867ea141c0202242c4816c82e52ec36e7e648db9d8da5a3dc3ed6", + "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", + "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", + "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383", + "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", + "sha256:5bec7d03d78d853597d6107854c2310ce3f761fd218fe9fe91d5101fcf6c2efe", + "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074", + "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", + "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", + "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", + "sha256:639f6c857d91d9be29bd7502348d6736dab168b54b5158cd899abf11684dc186", + "sha256:640f97d43d867bcb9c75b3af013b64850756b746cb6bce8ace83b70da3abba9d", + "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", + "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", + "sha256:6689e828a94eee4f139408c337bb198e014724bb8a8c26d3cfac49d119ed69a6", + "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", + "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", + "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", + "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", + "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", + "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", + "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", + "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", + "sha256:76447f65250ed2501ead1a1552f5ce8edff159a86f308348e6a9c4acb5e1f1b4", + "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", + "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", + "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", + "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", + "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", + "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", + "sha256:83b6b30eb131da7a75b601f28c5d6971e6ed3e887919bf6b6a1ad3c2df289080", + "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", + "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", + "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", + "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067", + "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", + "sha256:8be8ad51249698103d24b0571df35a10990fbe93dd043b6c024172189485f5e3", + "sha256:8d43ca737b20e106e4aebc42b2f3ae19f00ba63d7eb731698ee083d72d15646f", + "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485", + "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", + "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", + "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315", + "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", + "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", + "sha256:9f76acfb5f68ba982635a53fd985a8044be98a35b43232c2a1ee235ffab3e1dd", + "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", + "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", + "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", + "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", + "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", + "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", + "sha256:aae97dfdb60715c164419ac2532a76d013c3918a665eb6cb7288098b5f349aaf", + "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", + "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", + "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", + "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1", + "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", + "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", + "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", + "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", + "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", + "sha256:bdebcc8a75d38c7598dfb2c9ed852d7a9eb4a10d6e2d0764b919b802bf32ac88", + "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", + "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", + "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", + "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", + "sha256:c674693f055fa2495de12292cb45e9944199d8eaef5a2dec45175c7c61cb73e3", + "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", + "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", + "sha256:c9a4b821dc7055bf9e05ff5719e18ec501f75c0f0bbfabd573b277559780833d", + "sha256:c9f79d5325907f13e1be0b3e4dacc1049d1dffc4aeee3c995284bea5fe0fab7d", + "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa", + "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a", + "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a", + "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206", + "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", + "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", + "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", + "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", + "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", + "sha256:e07c65f443c887bbcf31cc1771d932ecc192a5273943589b3c7572b749f1ffb2", + "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", + "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", + "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", + "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", + "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", + "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", + "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", + "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d", + "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", + "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", + "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", + "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", + "sha256:ffecec8eb889b58ba9be5b95fb1cc78e22ea8eedea38e8736a1568fe1979250e" ], "markers": "python_version >= '3.8'", - "version": "==6.1.0" + "version": "==6.1.1" }, "notify2": { "hashes": [ @@ -337,116 +345,116 @@ }, "coverage": { "hashes": [ - "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", - "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e", - "sha256:0a951308cde22cf77f953955a754d04dccb57fe3bb8e345d685778ed9fc1632a", - "sha256:0c451757d3fa2603354fdc789b5e58a0e327a117c370a40e3476ba4eabab228c", - "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", - "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", - "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1", - "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", - "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9", - "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e", - "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", - "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", - "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", - "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", - "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", - "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", - "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", - "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", - "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", - "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917", - "sha256:3fd43f0616e765ab78d069cf8358def7363957a45cee446d65c502dcfeea7893", - "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", - "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90", - "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", - "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef", - "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", - "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087", - "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5", - "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27", - "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", - "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", - "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47", - "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", - "sha256:65f267ca1370726ec2c1aa38bbe4df9a71a740f22878d2d4bf59d71a4cd8d323", - "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d", - "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd", - "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426", - "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", - "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480", - "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", - "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8", - "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899", - "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2", - "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5", - "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3", - "sha256:731e535b1498b27d13594a0527a79b0510867b0ad891532be41cb883f2128e20", - "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", - "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327", - "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", - "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb", - "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe", - "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab", - "sha256:7c843572c605ab51cfdb5c6b5f2586e2a8467c0d28eca4bdef4ec70c5fecbd82", - "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627", - "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", - "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3", - "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477", - "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", - "sha256:84c32d90bf4537f0e7b4dec9aaa9a938fb8205136b9d2ecf4d7629d5262dc075", - "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f", - "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", - "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7", - "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52", - "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d", - "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9", - "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed", - "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90", - "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1", - "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", - "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980", - "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca", - "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", - "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", - "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc", - "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4", - "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", - "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d", - "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", - "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2", - "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742", - "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", - "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", - "sha256:b34ece8065914f938ed7f2c5872bb865336977a52919149846eac3744327267a", - "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", - "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96", - "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", - "sha256:b812eb847b19876ebf33fb6c4f11819af05ab6050b0bfa1bc53412ae81779adb", - "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", - "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c", - "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1", - "sha256:c7492f2d493b976941c7ca050f273cbda2f43c381124f7586a3e3c16d1804fec", - "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367", - "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20", - "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67", - "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b", - "sha256:d128b1bba9361fbaaf6a19e179e6cfd6a9103ce0c0555876f72780acc93efd85", - "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0", - "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4", - "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7", - "sha256:d9c8ef6ed820c433de075657d72dda1f89a2984955e58b8a75feb3f184250218", - "sha256:dc38367eaa2abb1b766ac333142bce7655335a73537f5c8b75aaa89c2b987757", - "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", - "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", - "sha256:fab3877e4ebb06bd9d4d4d00ee53309ee5478e66873c66a382272e3ee33eb7ea", - "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae", - "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595" + "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", + "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", + "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", + "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", + "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", + "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", + "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", + "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", + "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", + "sha256:1238cb94638e610e972c60dac68e813f868dc7d6e982535270558443058d9d59", + "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", + "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", + "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", + "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", + "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", + "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", + "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", + "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", + "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", + "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", + "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", + "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", + "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", + "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", + "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", + "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", + "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", + "sha256:3c18ebc343e15be53049b3a2dce38fe82d58f37e20ab9094b3a39c0aa4f6bb47", + "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", + "sha256:3e3680291c4a1d0dadfa84a2c459576a4af5133abb617905714339a0c73138cf", + "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", + "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", + "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", + "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", + "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", + "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", + "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", + "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", + "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", + "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", + "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", + "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", + "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", + "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", + "sha256:62a9f70b52e0b5a95cfef4a5c5641b06983cadc5e538a3feeb5c00211f523ac2", + "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", + "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", + "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", + "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", + "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", + "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", + "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", + "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", + "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", + "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", + "sha256:7f02d09f70776579b926d889a4c9c235070a1f47c40458aeaca563fae5acfdb5", + "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", + "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", + "sha256:84ac9499e48700399a5dd0ea7085b5091961fec52c68d66b4ec0d3cf7f4441b1", + "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", + "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", + "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", + "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", + "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", + "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", + "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", + "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", + "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", + "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", + "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", + "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", + "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", + "sha256:a5274669f37f2343635a347b91a60777621341ab3378e9c6ac9335eee704bddf", + "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", + "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", + "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", + "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", + "sha256:b84ffdf877644e7096aa936991efeed873f7f3df57b9cd001312b7668ab08550", + "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", + "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", + "sha256:c643734307300234fafa36bf2a040a7235f8f177ea1fd6ec1423aea6fb7b929f", + "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", + "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", + "sha256:c912c259304cfb5ee584481cfb7ce1ff932b4d61e6c9140b8f19cb7b5ed82332", + "sha256:ce66d8e46da2bb5ee313a745cbd2e391d319176c1f7a9451bfcd3a2fb920859b", + "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", + "sha256:cfe5a5fec635799ef33428f1e5e61bafa45a92a96190ba731561ba558ccc214d", + "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", + "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", + "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", + "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", + "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", + "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", + "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", + "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", + "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", + "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", + "sha256:e854312c4103f2ad4c0dc023b69b77ebfd2c89db5f86c4c94dc2353f9a92167e", + "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", + "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", + "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", + "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", + "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", + "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", + "sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253", + "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==7.14.0" + "version": "==7.14.1" }, "packaging": { "hashes": [ @@ -477,11 +485,11 @@ }, "pyinstaller-hooks-contrib": { "hashes": [ - "sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9", - "sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08" + "sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725", + "sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3" ], "markers": "python_version >= '3.8'", - "version": "==2026.5" + "version": "==2026.6" }, "setuptools": { "hashes": [ diff --git a/README.md b/README.md index 384ae27..89db00d 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ Whether you're managing personal goals or team projects, Task Note Manager is he # Local run instructions -Make sure you have Python 3.11 or higher installed. You would also need pipenv. +Make sure you have Python 3.11.7 or higher installed. You would also need pipenv. -1. `sudo apt-get install libdbus-1-dev libdbus-glib-1-dev` +1. `sudo apt-get install -y libdbus-1-dev libdbus-glib-1-dev` 1. `pipenv install` 2. `pipenv run python main.py` @@ -51,4 +51,14 @@ When building this application locally, note that antivirus may flag it as poten 2. Run `pipenv run pyinstaller --noconsole --onefile --windowed --name "Task Note Manager" main.py`. - `--noconsole`: Hides the console window when running the executable. - `--onefile`: Bundles all files into a single executable. - - `--windowed`: Ensures the app runs as a GUI application. \ No newline at end of file + - `--windowed`: Ensures the app runs as a GUI application. + +# Tests and coverage + +Run the tests: + + python -m unittest discover + +In order to get test coverage, run the following command and then open `htmlcov/index.html` + + coverage run -m unittest discover && coverage html \ No newline at end of file diff --git a/src/UI.py b/src/UI.py index 445d8df..66b8d34 100644 --- a/src/UI.py +++ b/src/UI.py @@ -2,7 +2,7 @@ from tkinter import BaseWidget from customtkinter import LEFT, CTkFrame as Frame, CTk as Tk, CTkButton as Button, CTkLabel as Label, \ - CTkToplevel as Toplevel + CTkToplevel as Toplevel, CTkTextbox as Text from src.manager.ConfigManager import ConfigManager from src.manager.UILayoutHelper import UILayoutHelper @@ -39,7 +39,7 @@ def __init__(self, config: ConfigManager): # Task list self.task_list_container = self.ui_helper.display_frame(self.root, 0, 1) self.task_list = None - self.task_name_list = Label(None) + self.task_name_list = None # Input fields self.input_container = self.ui_helper.display_frame(self.root, 0, 0) @@ -60,9 +60,9 @@ def __init__(self, config: ConfigManager): def __configure_button_widgets(self): self.submit_button = self.ui_helper.display_button("Submit",self.input_container,6,0, True) - self.open_file = self.ui_helper.display_button("Open YAML File", self.task_list_container, 3, 0) - self.export_button = self.ui_helper.display_button("Export data to ODS", self.task_list_container, 3, 1) - self.instructions_button = self.ui_helper.display_button("About", self.task_list_container, 3, 2) + self.open_file = self.ui_helper.display_button("Open YAML File", self.task_list_container, 4, 0) + self.export_button = self.ui_helper.display_button("Export data to ODS", self.task_list_container, 4, 1) + self.instructions_button = self.ui_helper.display_button("About", self.task_list_container, 4, 2) self.instructions_button.configure(command=self.__open_information_popup) @@ -121,13 +121,14 @@ def __configure_status_widgets(self): 0 ) - self.task_name_list = self.ui_helper.display_new_note_label( - "", + self.ui_helper.display_todays_notes_label("Task list: ", self.task_list_container, 2, 0) + + self.task_name_list = self.ui_helper.display_status_input( self.task_list_container, - 2, + 3, 0 ) - + self.task_name_list.configure(height=100, width=400) def __configure_scheduler_widgets(self): self.scheduler_container = self.ui_helper.display_frame(self.root, 2, 0, columnspan=2) @@ -168,4 +169,4 @@ def __open_information_popup(self): about_frame = self.ui_helper.display_frame(top, 0, 0, "About") - self.ui_helper.display_new_note_label("Task Note Manager is a streamlined tool designed to help you effortlessly track and log your daily work activities.\n\nKey Features & Benefits:\n• Automated Reminders: The app automatically prompts you for input after 6 hours of runtime.\n• Structured Insights: Answer 3 simple questions to capture crucial details about your workday.\n• Meeting & Report Ready: Generated notes are perfect for daily standups, status updates, or personal tracking.\n• Local Storage: All data is saved safely in a clean, human-readable YAML format.\n• Full Control: Click the 'Open file' button to view, edit, or copy entries directly. Every log is marked with '!NoteEntry' for easy customization.", about_frame, 0, 0) + self.ui_helper.display_new_note_label(f"Task Note Manager is a streamlined tool designed to help you effortlessly track and log your daily work activities.\n\nKey Features & Benefits:\n• Automated Reminders: The app automatically prompts you for input after 6 hours of runtime.\n• Structured Insights: Answer 3 simple questions to capture crucial details about your workday.\n• Meeting & Report Ready: Generated notes are perfect for daily standups, status updates, or personal tracking.\n• Local Storage: All data is saved safely in a clean, human-readable YAML format.\n• Full Control: Click the 'Open file' button to view, edit, or copy entries directly. Every log is marked with '!NoteEntry' for easy customization.\n\nData is saved in YAML file at {self.config_manager.FULL_PATH}", about_frame, 0, 0) diff --git a/src/main.py b/src/main.py index 9bb4bfc..49f7f14 100644 --- a/src/main.py +++ b/src/main.py @@ -5,13 +5,13 @@ import notify2 +from src.Scheduler import Scheduler +from src.UI import UI +from src.factory.NoteEntryFactory import NoteEntryFactory from src.manager.ConfigManager import ConfigManager from src.manager.DataManager import DataManager from src.manager.ExportManager import ExportManager from src.manager.FileManager import FileManager -from Scheduler import Scheduler -from UI import UI -from factory.NoteEntryFactory import NoteEntryFactory from src.manager.TaskManager import TaskManager from src.models.NoteEntry import NoteEntry from src.models.NoteList import NoteList @@ -47,8 +47,8 @@ def __setup(self): self.note_factory, self.current_data ) - self.task_manager = TaskManager(self.config_manager, self.data_manager) - self.export_manager = ExportManager(self.config_manager, self.data_manager, self.task_manager, self.current_data) + self.task_manager = TaskManager(self.config_manager) + self.export_manager = ExportManager(self.config_manager, self.data_manager, self.task_manager) self._update_time_until_next_run(datetime.now(timezone.utc) + timedelta(hours=self.config_manager.frequency_hours)) self.scheduler = Scheduler(self.__trigger_notification, self._update_time_until_next_run, self.config_manager) @@ -84,7 +84,8 @@ def __after_submit(self, note_list: NoteList): self.task_list.see(END) all_task_names = [str(s.name)+", " for s in self.task_manager.get_tasks_from_note_list(note_list)] task_names = "".join(list(OrderedDict.fromkeys(all_task_names))) - self.task_name_list.configure(text=f"Tasks:\n\n{task_names}") + + Application.__set_text_and_disable(self.task_name_list, f"{task_names}") def __on_click_submit_button(self, *args): asyncio.run(self.save_data()) @@ -93,7 +94,7 @@ def __on_click_submit_button(self, *args): async def save_data(self): self.current_data = await self.data_manager.process_save_input(self.current_data) await self.__update_data() - Notification.send_info_notification("Data saved") + Notification.send_info_notification(f"Data saved to file {self.config_manager.FULL_PATH}") def __set_note_input_text(self, entry: NoteEntry): self.__set_text(self.done_field, entry.done) diff --git a/src/manager/ConfigManager.py b/src/manager/ConfigManager.py index 149df2c..7f7d9b3 100644 --- a/src/manager/ConfigManager.py +++ b/src/manager/ConfigManager.py @@ -1,5 +1,6 @@ import logging import os +import platform import sys from tkinter.font import BOLD @@ -22,7 +23,7 @@ def __init__(self): # Excel export self.EXPORT_EMPTY_ROW = ["No data", ] - self.EXPORT_FILE_NAME = os.path.join(ConfigManager._get_full_curr_dir_path(), "../../task_notes.ods") + self.EXPORT_FILE_NAME = os.path.join(ConfigManager.get_full_curr_dir_path(), "../../task_notes.ods") self.EXPORT_TAB_NAME_DEFAULT = "Default" self.EXPORT_TAB_NAME_TASK_NAMES = "Task names" @@ -51,7 +52,7 @@ def __init__(self): self.PRIMARY_BUTTON_COLOR_HOVER = "#2E7D32" # Yaml - self.FULL_PATH = os.path.join(ConfigManager._get_full_curr_dir_path(), "../../task_notes.yaml") + self.FULL_PATH = os.path.join(ConfigManager.get_full_curr_dir_path(), "task_notes.yaml") # Logging logging.basicConfig( @@ -60,12 +61,13 @@ def __init__(self): datefmt="%d-%b-%y %H:%M:%S") @staticmethod - def _get_full_curr_dir_path(): - if getattr(sys, 'frozen', False): - application_path = os.path.dirname(sys.executable) + def get_full_curr_dir_path(): + if platform.system() == "Windows": + os.makedirs(os.path.join(os.environ["APPDATA"], "TaskNoteManager"), exist_ok=True) + return os.path.join(os.environ["APPDATA"], "TaskNoteManager") else: - application_path = os.path.dirname(os.path.abspath(__file__)) - return application_path + os.makedirs(os.path.join(os.path.expanduser("~"), ".TaskNoteManager"), exist_ok=True) + return os.path.join(os.path.expanduser("~"), ".TaskNoteManager") def __str__(self): return """ diff --git a/src/manager/DataManager.py b/src/manager/DataManager.py index 331423c..a500355 100644 --- a/src/manager/DataManager.py +++ b/src/manager/DataManager.py @@ -1,5 +1,7 @@ import copy import logging +from types import NoneType + from customtkinter import CTkTextbox as Text, CTkLabel as Label import yaml @@ -40,44 +42,45 @@ def __init__(self, yaml.add_representer(NoteList, YamlUtils.note_list_representer) yaml.add_representer(NoteEntry, YamlUtils.note_entry_representer) + yaml.add_representer(str, YamlUtils.string_representer, Dumper=yaml.Dumper) + yaml.add_representer(NoneType, YamlUtils.none_representer, Dumper=yaml.Dumper) + # Use self.current:data def extract_today_notes(self, note_list : NoteList) -> NoteEntry: result = NoteEntry() if note_list is None: return result - note_list_iter = NoteListHelper.get_note_list_iter(note_list) - for note in note_list_iter: + for note in note_list.notes: if note.date == str(LocalizedDate(self.config_manager.DATE_FORMAT)): result = note - break + break return result @staticmethod def __override_existing_data_with_new_note(note_list: NoteList, entry: NoteEntry) -> None: - is_found = False note_list_copy = copy.deepcopy(note_list) + for i, n in enumerate(note_list_copy.notes): if n.date == entry.date: note_list.notes[i] = entry - is_found = True + return - if not is_found: - note_list.notes.append(entry) + note_list.notes.append(entry) @staticmethod - def __get_text_from_input(input_text: Text): + def __get_text_from_input(input_text: Text) -> str: return input_text.get("1.0", "end-1c") async def process_save_input(self, current_data: NoteList) -> NoteList: - done = DataManager.__get_text_from_input(self.done) - to_be_done = DataManager.__get_text_from_input(self.in_progress) - problems = DataManager.__get_text_from_input(self.problems) + done = self.__get_text_from_input(self.done) + to_be_done = self.__get_text_from_input(self.in_progress) + problems = self.__get_text_from_input(self.problems) new_note = self.note_factory.create_note(done, to_be_done, problems) - DataManager.__override_existing_data_with_new_note(current_data, new_note) + self.__override_existing_data_with_new_note(current_data, new_note) await self._write_data_to_file_direct(current_data) self.logger.debug(f"Wrote data to file, input: {done} {to_be_done} {problems}") return current_data diff --git a/src/manager/ExportManager.py b/src/manager/ExportManager.py index 88a5b74..fb1034e 100644 --- a/src/manager/ExportManager.py +++ b/src/manager/ExportManager.py @@ -11,8 +11,7 @@ class ExportManager: def __init__(self, config_manager: ConfigManager, data_manager: DataManager, - task_manager: TaskManager, - current_data: NoteList) -> None: + task_manager: TaskManager) -> None: self.config_manager = config_manager self.data_manager = data_manager self.task_manager = task_manager diff --git a/src/manager/FileManager.py b/src/manager/FileManager.py index 7aec23c..e30e399 100644 --- a/src/manager/FileManager.py +++ b/src/manager/FileManager.py @@ -1,3 +1,6 @@ +import os +import platform +import subprocess import webbrowser from io import TextIOWrapper from typing import Any, IO @@ -21,5 +24,10 @@ def get_write_wrapper(self) -> TextIOWrapper | IO[Any]: return open(self.full_path, "w") def open_file_in_ext_app(self): - webbrowser.open(self.full_path) + if platform.system() == 'Darwin': # macOS + subprocess.call(('open', self.full_path)) + elif platform.system() == 'Windows': # Windows + os.startfile(self.full_path) + else: # linux variants + subprocess.call(('xdg-open', self.full_path)) diff --git a/src/manager/TaskManager.py b/src/manager/TaskManager.py index 7c84409..49d5020 100644 --- a/src/manager/TaskManager.py +++ b/src/manager/TaskManager.py @@ -1,16 +1,14 @@ import re from src.manager.ConfigManager import ConfigManager -from src.manager.DataManager import DataManager from src.models.NoteEntry import NoteEntry from src.models.NoteList import NoteList from src.models.Task import Task class TaskManager: - def __init__(self, config_manager: ConfigManager, data_manager: DataManager): + def __init__(self, config_manager: ConfigManager): self.config_manager = config_manager - self.data_manager = data_manager def get_tasks_from_note_list(self, note_list: NoteList) -> list[Task]: result = list() diff --git a/src/models/NoteEntry.py b/src/models/NoteEntry.py index 9c74348..7529b39 100644 --- a/src/models/NoteEntry.py +++ b/src/models/NoteEntry.py @@ -4,9 +4,9 @@ class NoteEntry: def __init__(self, date: str = "", done: str = "", in_progress: str = "", problems: str = ""): self.date = date - self.done = done - self.in_progress = in_progress - self.problems = problems + self.done = done or "" + self.in_progress = in_progress or "" + self.problems = problems or "" def is_empty(self) -> bool: return self.done == "" and self.in_progress == "" and self.problems == "" diff --git a/src/utils/NoteListTestUtils.py b/src/utils/NoteListTestUtils.py deleted file mode 100644 index a46a9a3..0000000 --- a/src/utils/NoteListTestUtils.py +++ /dev/null @@ -1,14 +0,0 @@ -from src.models.NoteEntry import NoteEntry -from src.models.NoteList import NoteList - - -class NoteListTestUtils: - @staticmethod - def get_test_note_list(): - return NoteList( - notes=[ - NoteEntry( - "12. Jan. 2024", - "Created draft") - ] - ) \ No newline at end of file diff --git a/src/utils/YamlUtils.py b/src/utils/YamlUtils.py index aecef27..9eeb30d 100644 --- a/src/utils/YamlUtils.py +++ b/src/utils/YamlUtils.py @@ -8,20 +8,20 @@ class YamlUtils: - _allowed_nodes = [ + __allowed_nodes = { "date", "problems", "done", "in_progress" - ] + } @staticmethod - def remove_unknown_keys(value: dict) -> dict: - result = {} - for key, value in value.items(): - if key in YamlUtils._allowed_nodes: - result[key] = value - return result + def remove_unknown_keys(data: dict) -> dict: + return { + key: val + for key, val in data.items() + if key in YamlUtils.__allowed_nodes + } @staticmethod def note_entry_constructor(loader: yaml.SafeLoader, node: yaml.nodes.MappingNode) -> NoteEntry: @@ -41,9 +41,18 @@ def get_loader() -> Type[SafeLoader]: return loader @staticmethod - def note_list_representer(dumper, data: NoteList): return dumper.represent_mapping("!NoteList", data.__dict__) + def note_list_representer(dumper, data: NoteList): + return dumper.represent_mapping("!NoteList", data.__dict__) @staticmethod - def note_entry_representer(dumper, data: NoteEntry): return dumper.represent_mapping("!NoteEntry", - data.__dict__) + def note_entry_representer(dumper, data: NoteEntry): + return dumper.represent_mapping("!NoteEntry", + {k: (None if v == '' else v) for k, v in data.__dict__.items()}) + @staticmethod + def string_representer(dumper, data): + return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='"') + + @staticmethod + def none_representer(dumper, data): + return dumper.represent_scalar('tag:yaml.org,2002:str', '', style='"') \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/foo b/test/foo new file mode 100644 index 0000000..e69de29 diff --git a/test/test_DataManager.py b/test/test_DataManager.py index 6eb40d7..fa8b3c3 100644 --- a/test/test_DataManager.py +++ b/test/test_DataManager.py @@ -1,48 +1,84 @@ -import unittest -from unittest.mock import patch, Mock, mock_open +from unittest import IsolatedAsyncioTestCase +from unittest.mock import Mock, patch, AsyncMock -from src.manager.ConfigManager import ConfigManager from src.manager.DataManager import DataManager -from src.manager.FileManager import FileManager -from src.utils.NoteListTestUtils import NoteListTestUtils +from src.models.NoteEntry import NoteEntry +from src.models.NoteList import NoteList +from test.util.TestUtils import get_note_list, get_empty_note_list -class DataManagerTest(unittest.TestCase): +class TestDataManager(IsolatedAsyncioTestCase): def setUp(self): - config_manager_mock = ConfigManager() - config_manager_mock.DATE_FORMAT = "%d. %b. %Y" + done = Mock() + done.get.return_value = "Finished with tests UPDATED" - file_manager_mock = FileManager(config_manager_mock) - file_content = "key: value" - m = mock_open(read_data=file_content) - self.patch_open = patch("builtins.open", m) - self.patch_open.__enter__() + in_progress = Mock() + in_progress.get.return_value = "Refactoring UPDATED" - file_manager_mock.get_read_wrapper = open("dummy") + problems = Mock() + problems.get.return_value = "Need to prepare docs UPDATED" + + status = Mock() + + file_manager = Mock() + + config_manager = Mock() + config_manager.DATE_FORMAT = "%d. %b. %Y" + + note_factory = Mock() + note_factory.create_note.return_value = get_note_list().notes[1] + + current_data = get_note_list() self.data_manager = DataManager( - Mock(), - Mock(), - file_manager_mock, - config_manager_mock, - Mock() + done, + in_progress, + problems, + status, + file_manager, + config_manager, + note_factory, + current_data ) - @unittest.skip("reason for skipping") - @patch("models.helpers.NoteListHelper") - def test_extract_todays_notes_for_correct_input(self, note_list_iter): - note_list = NoteListTestUtils.get_test_note_list() - note_list_iter.return_value = note_list + def test_extract_today_notes(self): + # Input is None + result = self.data_manager.extract_today_notes(None) + assert result == NoteEntry() + + # Input is defined - todays note is included + note_list = get_note_list() + result = self.data_manager.extract_today_notes(note_list) + assert result is not None + assert result.done == "Finished with tests" + assert result.in_progress == "Refactoring" + assert result.problems == "Need to prepare docs" + + # Input is defined - todays note is not included + note_list = get_empty_note_list() + result = self.data_manager.extract_today_notes(note_list) + assert result is not None + assert result.done == "" + assert result.in_progress == "" + assert result.problems == "" - note_entry = self.data_manager.get_data_for_current_day(lambda x: x) + async def test_process_save_input_with_defined_note_list(self): + note_list = get_note_list() - assert note_entry is None + with patch.object(self.data_manager, "_write_data_to_file_direct", new_callable=AsyncMock) as write_data_mock: + await self.data_manager.process_save_input(note_list) - def test_get_data_for_current_day(self): - callable_mock = Mock() + assert write_data_mock.called + args, kwargs = write_data_mock.call_args + result_note_list:NoteList = args[0] + assert len(result_note_list.notes) == 2 - self.data_manager.get_data_for_current_day(callable_mock) + async def test_process_save_input_with_empty_note_list(self): + with patch.object(self.data_manager, "_write_data_to_file_direct", new_callable=AsyncMock) as write_data_mock: + await self.data_manager.process_save_input(NoteList(notes=list())) - def tearDown(self): - self.patch_open.__exit__() + assert write_data_mock.called + args, kwargs = write_data_mock.call_args + result_note_list: NoteList = args[0] + assert len(result_note_list.notes) == 1 diff --git a/test/test_DataManagerBase.py b/test/test_DataManagerBase.py index d824217..bcfd141 100644 --- a/test/test_DataManagerBase.py +++ b/test/test_DataManagerBase.py @@ -1,113 +1,42 @@ -import unittest -from unittest.mock import patch, MagicMock, Mock, mock_open +from typing import Any +from unittest import IsolatedAsyncioTestCase +from unittest.mock import Mock, mock_open, patch, MagicMock, ANY from src.manager.DataManagerBase import DataManagerBase -from src.models.NoteEntry import NoteEntry -from src.models.NoteList import NoteList +from test.util.TestUtils import get_note_list -class TestDataManagerBase(unittest.TestCase): - def setUp(self): - mock_instance = MagicMock() - self.data_manager_base = DataManagerBase(mock_instance) +class TestDataManagerBase(IsolatedAsyncioTestCase): + def setUp(self) -> None: - @patch('threading.Thread') - def test_read_data_from_file_async(self, mock_thread): - mock_instance = MagicMock() - mock_thread.return_value = mock_instance + file_manager_mock = Mock() + file_manager_mock.get_read_wrapper = mock_open(read_data="foo") + file_manager_mock.get_write_wrapper = mock_open() - self.data_manager_base.read_data_from_file_async(lambda x: x, {}) + self.data_manager_base = DataManagerBase(file_manager_mock) - mock_thread.assert_called_once() + async def test_read_data_from_file_async_direct(self): + # Empty file + with patch("yaml.load") as mock_empty: + mock_empty.read.return_value = None - kwargs = mock_thread.call_args.kwargs - self.assertIsNotNone(kwargs.get("target")) - kwargs_for_callable = kwargs.get("kwargs") - self.assertIsNotNone(kwargs_for_callable.get("read_instance")) - self.assertIsNotNone(kwargs_for_callable.get("first_callback")) - self.assertIsNotNone(kwargs_for_callable.get("args")) + result = await self.data_manager_base.read_data_from_file_async_direct() - mock_instance.start.assert_called_once() + assert result is not None + assert len(result.notes) == 1 - @patch('threading.Thread') - def test_write_data_from_file_async(self, mock_thread): - mock_instance = MagicMock() - mock_thread.return_value = mock_instance - mock_callback = MagicMock() + # Non-empty file + with patch("yaml.load") as mock_non_empty: + mock_non_empty.return_value = get_note_list() #Should return an NoteList, but the test gets MagicMock - self.data_manager_base._write_data_to_file_async(NoteList(Mock()), mock_callback) + result = await self.data_manager_base.read_data_from_file_async_direct() - mock_thread.assert_called_once() + assert result is not None + assert len(result.notes) == 2 # This is empty - kwargs = mock_thread.call_args.kwargs - self.assertIsNotNone(kwargs.get("target")) - kwargs_for_callable = kwargs.get("kwargs") - self.assertIsNotNone(kwargs_for_callable.get("write_instance")) - self.assertIsNotNone(kwargs_for_callable.get("note_list")) - self.assertIsNotNone(kwargs_for_callable.get("callback")) - - mock_instance.start.assert_called_once() - - def test_read_data_from_file_with_undefined_arguments(self): - file_content = "key: value" - m = mock_open(read_data=file_content) - - with patch("builtins.open", m): - first_callback = Mock() - args = {} - - read_instance = open("dummy") # will use mocked open - - self.data_manager_base._read_data_from_file( - read_instance, - first_callback, - args - ) - - first_callback.assert_called_once() - args = first_callback.call_args.args - self.assertEquals(args[0], {'key': 'value'}) - - def test_read_data_from_file_with_defined_arguments(self): - file_content = "key: value" - m = mock_open(read_data=file_content) - - with patch("builtins.open", m): - first_callback = Mock() - input_args = {"test": "hello"} - - read_instance = open("dummy") # will use mocked open - - self.data_manager_base._read_data_from_file( - read_instance, - first_callback, - input_args - ) - - first_callback.assert_called_once() - args = first_callback.call_args.args - self.assertEquals(args[0], {'key': 'value'}) - self.assertEquals(args[1], {'test': 'hello'}) - - - def test_write_data_to_file_with_undefined_arguments(self): - m = mock_open() - note_list = NoteList(notes=[NoteEntry()]) - - with patch("builtins.open", m): - first_callback = Mock() - - write_instance = open("dummy") # will use mocked open - - self.data_manager_base._write_data_to_file( - write_instance, - note_list, - first_callback - ) - - first_callback.assert_called_once() - args = first_callback.call_args.args - self.assertEquals(args[0], note_list) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file + async def test_write_data_to_file_direct(self): + note_list = get_note_list() + with patch("yaml.dump") as dump_mock: + await self.data_manager_base._write_data_to_file_direct(note_list) + dump_mock.assert_called_once() + dump_mock.assert_called_with(note_list, ANY, allow_unicode=True) \ No newline at end of file diff --git a/test/test_ExportManager.py b/test/test_ExportManager.py new file mode 100644 index 0000000..5155f05 --- /dev/null +++ b/test/test_ExportManager.py @@ -0,0 +1,42 @@ +from unittest import IsolatedAsyncioTestCase +from unittest.mock import Mock, patch, MagicMock, AsyncMock + +from src.manager.ExportManager import ExportManager +from test.util.TestUtils import get_note_list + + +class TestExportManager(IsolatedAsyncioTestCase): + @patch("src.manager.ExportManager.NoteByTaskNameByDate") + @patch("src.manager.ExportManager.NoteByStatusByDate") + @patch("src.manager.ExportManager.OdsTabExportBase") + async def test_export_data(self, export_base_mock, by_status_mock, by_task_mock): + export_base_mock.init.return_value = None + export_base_mock.delete_data.return_value = None + + status_instance = MagicMock() + status_instance.run_export = AsyncMock() + + task_instance = MagicMock() + task_instance.run_export = AsyncMock() + + by_status_mock.return_value = status_instance + by_task_mock.return_value = task_instance + + export_manager = ExportManager( + config_manager=MagicMock(), + data_manager=MagicMock(), + task_manager=MagicMock(), + ) + + input_note_list = get_note_list() + + await export_manager.export_data(input_note_list) + + by_status_mock.assert_called_once() + by_task_mock.assert_called_once() + + export_base_mock.init.assert_called_once() + export_base_mock.delete_data.assert_called_once() + + status_instance.run_export.assert_called_once_with(input_note_list) + task_instance.run_export.assert_called_once_with(input_note_list) diff --git a/test/test_FileManager.py b/test/test_FileManager.py new file mode 100644 index 0000000..f253f97 --- /dev/null +++ b/test/test_FileManager.py @@ -0,0 +1,43 @@ +import unittest +from unittest.mock import Mock, patch, mock_open + +from src.manager.FileManager import FileManager + + +class TestFileManager(unittest.TestCase): + def setUp(self): + self.config = Mock() + self.config.FULL_PATH = "foo" + + self.file_manager = FileManager(self.config) + + def test_init(self): + m = mock_open() + with patch("builtins.open", m): + FileManager(self.config) + + m.assert_called_with("foo", "a") + handle = m() + handle.write.assert_called_once_with("") + + def test_get_read_wrapper(self): + m = mock_open() + with patch("builtins.open", m): + self.file_manager.get_read_wrapper() + + m.assert_called_with("foo", "r") + + def test_get_write_wrapper(self): + m = mock_open() + with patch("builtins.open", m): + self.file_manager.get_write_wrapper() + + m.assert_called_with("foo", "w") + + def test_open_file_in_ext_app(self): + with patch("webbrowser.open") as mock: + self.file_manager.open_file_in_ext_app() + + assert mock.called + + diff --git a/test/test_TaskManager.py b/test/test_TaskManager.py new file mode 100644 index 0000000..4331d60 --- /dev/null +++ b/test/test_TaskManager.py @@ -0,0 +1,44 @@ +import unittest +from unittest.mock import Mock + +from src.manager.TaskManager import TaskManager +from src.models.LocalizedDate import LocalizedDate +from src.models.NoteEntry import NoteEntry +from src.models.NoteList import NoteList +from src.models.Task import Task + + +class TestTaskManager(unittest.TestCase): + def setUp(self): + config_manager_mock = Mock() + config_manager_mock.TASK_NAME_REGEX = r"\s*([^:;]*)\s*:\s*([^:;]*)" + self.task_manager = TaskManager(config_manager_mock) + + self.note_entry = NoteEntry( + str(LocalizedDate("%d. %b. %Y")), + "TASK1: Writing documentation; TASK2: Defining roadmap", + ) + + self.note_list = NoteList([self.note_entry,]) + + def test_get_tasks_from_note_entry_with_defined_input(self): + result = self.task_manager.get_tasks_from_note_entry(self.note_entry) + + assert len(result) == 2 + + assert result[0].name == "TASK1" + assert result[0].description == "Writing documentation" + assert result[1].name == "TASK2" + assert result[1].description == "Defining roadmap" + + def test_get_tasks_from_note_list(self): + get_tasks_from_note_entry_mock = Mock() + get_tasks_from_note_entry_mock.return_value = [Task("TASK1", "Started with project")] + + self.task_manager.get_tasks_from_note_entry = get_tasks_from_note_entry_mock + + result = self.task_manager.get_tasks_from_note_list(self.note_list) + + assert len(result) == 1 + assert result[0].name == "TASK1" + assert result[0].description == "Started with project" diff --git a/test/test_UILayoutHelper.py b/test/test_UILayoutHelper.py new file mode 100644 index 0000000..54f351f --- /dev/null +++ b/test/test_UILayoutHelper.py @@ -0,0 +1,61 @@ +import unittest +from unittest.mock import Mock, patch + +from customtkinter import CTkFrame, CTk, CTkFont, CTkBaseClass, CTkLabel +from pyvirtualdisplay import Display + +from src.manager.UILayoutHelper import UILayoutHelper + + +class TestUILayoutHelper(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.disp = Display(visible=0, size=(800, 600)) + cls.disp.start() + + cls.root = CTk() + + def setUp(self): + config_manager = Mock() + config_manager.get_regular_font.return_value = CTkFont( + family="Arial", + size=4 + ) + config_manager.NEW_NOTE_INPUT_PADY = 30 + config_manager.NEW_NOTE_INPUT_PADX = 30 + + self.parent = CTkFrame(master=None) + + self.layout_helper = UILayoutHelper(config_manager) + + @staticmethod + def __check_position(result: CTkBaseClass): + info = result.grid_info() + assert info["row"] == 0 + assert info["column"] == 0 + + def test_display_new_note_input(self): + + result = self.layout_helper.display_new_note_input(self.parent, 0, 0) + + assert result is not None + assert result.master is not None + self.__check_position(result) + + + def test_display_ftodays_notes_label(self): + with patch.object(UILayoutHelper, "display_new_note_label") as new_note_mock: + new_note_mock.return_value = CTkLabel(self.parent) + + result = self.layout_helper.display_todays_notes_label("Todays notes", self.parent, 0, 0) + + assert result.master is not None + self.__check_position(result) + + @classmethod + def tearDownClass(cls): + if hasattr(cls, "root"): + cls.root.destroy() + if hasattr(cls, "disp"): + cls.disp.stop() + diff --git a/test/test_YamlUtils.py b/test/test_YamlUtils.py new file mode 100644 index 0000000..18fc25e --- /dev/null +++ b/test/test_YamlUtils.py @@ -0,0 +1,57 @@ +import unittest +from unittest.mock import patch, Mock, MagicMock + +from yaml import SafeLoader + +from src.utils.YamlUtils import YamlUtils +from test.util.TestUtils import get_note_list + + +class TestYamlUtils(unittest.TestCase): + def setUp(self): + self.yaml_utils = YamlUtils() + + @patch("src.utils.YamlUtils") + def test_remove_unknown_keys(self, yaml_utils_mock): + yaml_utils = YamlUtils() + + result = yaml_utils.remove_unknown_keys({"done": "test", "notes": "test"}) + + + assert len(result.keys()) == 1 + + @patch("src.utils.YamlUtils.YamlUtils.remove_unknown_keys") + def test_note_entry_constructor(self, remove_keys_mock): + loader_mock = Mock() + node_mock = Mock() + remove_keys_mock.return_value = {"done": "implementation", "in_progress": "test"} + + note_entry = self.yaml_utils.note_entry_constructor(loader_mock, node_mock) + + assert note_entry.done == "implementation" + assert note_entry.in_progress == "test" + + def test_note_entry_list_constructor(self): + loader_mock = Mock() + note_entry = get_note_list().notes[0] + loader_mock.construct_mapping.return_value = {"notes": [note_entry]} + + note_list = self.yaml_utils.note_entry_list_constructor(loader_mock, Mock()) + + assert note_list.notes[0].done == "Lored ipsum" + assert note_list.notes[0].in_progress == "Something" + assert note_list.notes[0].problems == "None" + + @patch("src.utils.YamlUtils.YamlUtils.note_entry_constructor") + @patch("src.utils.YamlUtils.YamlUtils.note_entry_list_constructor") + @patch("yaml.SafeLoader", autospec=True) + def test_get_loader(self, safe_loader_mock, list_constructor_mock, entry_constructor_mock): + loader_mock = MagicMock() + loader_mock.add_constructor.return_value = None + + self.yaml_utils.get_loader() + + safe_loader_mock.add_constructor.assert_any_call("!NoteList", list_constructor_mock) + safe_loader_mock.add_constructor.assert_any_call("!NoteEntry", entry_constructor_mock) + + diff --git a/test/test_main.py b/test/test_main.py deleted file mode 100644 index 1c284f7..0000000 --- a/test/test_main.py +++ /dev/null @@ -1,22 +0,0 @@ -import unittest - -from src.main import Application - - -class TestMain(unittest.TestCase): - - def test_init(self): - application_under_test = Application() - - self.assertIsNotNone(application_under_test.config_manager) - - self.assertIsNotNone(application_under_test.file_manager) - self.assertIsNotNone(application_under_test.note_factory) - self.assertIsNotNone(application_under_test.notification_manager) - - self.assertIsNotNone(application_under_test.data_manager) - self.assertIsNotNone(application_under_test.export_manager) - - - - diff --git a/test/util/TestUtils.py b/test/util/TestUtils.py new file mode 100644 index 0000000..f46a18a --- /dev/null +++ b/test/util/TestUtils.py @@ -0,0 +1,35 @@ +from datetime import datetime, timedelta + +from src.models.LocalizedDate import LocalizedDate +from src.models.NoteEntry import NoteEntry +from src.models.NoteList import NoteList + + +def get_empty_note_list(): + yesterday = datetime.now() - timedelta(days=1) + + return NoteList([NoteEntry( + date=str(LocalizedDate("%d. %b. %Y", yesterday)) + ), ]) + +def get_note_list(): + yesterday = datetime.now() - timedelta(days=1) + return NoteList([ + NoteEntry( + date=str(LocalizedDate("%d. %b. %Y", yesterday)), + done="Lored ipsum", + in_progress="Something", + problems="None"), + NoteEntry( + date=str(LocalizedDate("%d. %b. %Y")), + done="Finished with tests", + in_progress="Refactoring", + problems="Need to prepare docs" + ) ]) + +def get_current_date(): + return datetime( + datetime.now().year, + datetime.now().month, + datetime.now().day + ) \ No newline at end of file