diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml
new file mode 100644
index 0000000..2ddee4a
--- /dev/null
+++ b/.github/workflows/black.yml
@@ -0,0 +1,19 @@
+name: Code style
+
+on:
+ push:
+ branches: [main, features]
+ pull_request:
+ branches: [main, features]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ name: Code Style
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ with:
+ python-version: 3.9
+ - uses: psf/black@20.8b1
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index 6c147c1..0000000
--- a/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-name: "CodeQL"
-
-on:
- push:
- branches: [master, features]
- pull_request:
- branches: [master, features]
- schedule:
- - cron: '0 8 * * 0'
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: false
- matrix:
- language: ['python']
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v2
- with:
- # We must fetch at least the immediate parents so that if this is
- # a pull request then we can checkout the head.
- fetch-depth: 2
-
- # If this run was triggered by a pull request event, then checkout
- # the head of the pull request instead of the merge commit.
- - run: git checkout HEAD^2
- if: ${{ github.event_name == 'pull_request' }}
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v1
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
- # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v1
-
- # ℹ️ Command-line programs to run using the OS shell.
- # 📚 https://git.io/JvXDl
-
- # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
- # and modify them (or add more) to build your code if your project
- # uses a compiled language
-
- #- run: |
- # make bootstrap
- # make release
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml
index 2ddd167..95d77e9 100644
--- a/.github/workflows/i18n.yml
+++ b/.github/workflows/i18n.yml
@@ -23,7 +23,7 @@ jobs:
- name: Check i18n
run: |
pipenv run python setup.py extract_messages --output-file=/tmp/kosmorro-messages.pot > /dev/null
- diff=$(diff kosmorrolib/locales/messages.pot /tmp/kosmorro-messages.pot | grep '^>')
+ diff=$(diff _kosmorro/locales/messages.pot /tmp/kosmorro-messages.pot | grep '^>')
n=$(echo "$diff" | grep -v '> "POT-Creation-Date: ' | wc -l)
if [ "$(echo "$diff" | grep -E '^"Generated-By: Babel' | wc -l)" -eq "1" ]; then
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
deleted file mode 100644
index 7151b2b..0000000
--- a/.github/workflows/pylint.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Python Lint
-
-on:
- push:
- branches: [master, features]
- pull_request:
- branches: [master, features]
-
-jobs:
- pylint:
- runs-on: ubuntu-latest
-
- name: PyLint
- steps:
- - uses: actions/checkout@v1
- - name: Set up Python
- uses: actions/setup-python@v1
- with:
- python-version: 3.9
- - name: Install dependencies
- run: |
- pip install --upgrade pip pipenv
- pipenv sync -d
- - name: Lint
- run: |
- pipenv run pylint kosmorro *.py kosmorrolib/*.py
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
deleted file mode 100644
index e57034b..0000000
--- a/.github/workflows/unit-tests.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Unit tests
-
-on:
- push:
- branches: [master, features]
- pull_request:
- branches: [master, features]
-
-jobs:
- unit-tests:
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os:
- - ubuntu-18.04
- - ubuntu-20.04
- - macos-10.15
- - macos-11.0
- python-version:
- - '3.7'
- - '3.8'
- - '3.9'
-
- name: Unit tests (Python ${{ matrix.python-version }} on ${{ matrix.os }})
- steps:
- - uses: actions/checkout@v1
- - name: Set up Python
- uses: actions/setup-python@v1
- with:
- python-version: ${{ matrix.python-version }}
- - name: Install dependencies
- run: |
- pip install --upgrade pip pipenv
- pipenv sync -d
- - name: Unit tests
- env:
- COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
- run: |
- make test
- COVERALLS_REPO_TOKEN=$COVERALLS_TOKEN pipenv run coveralls
diff --git a/.scripts/tests-e2e.sh b/.scripts/tests-e2e.sh
index 35363ac..6b823ff 100755
--- a/.scripts/tests-e2e.sh
+++ b/.scripts/tests-e2e.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-VERSION=$(grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' kosmorrolib/version.py)
+VERSION=$(grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' _kosmorro/__version__.py)
PYTHON_BIN=$(command -v python)
PIP_BIN=$(command -v pip)
@@ -68,6 +68,8 @@ function assertFailure() {
echo -n '.'
}
+mkdir -p $HOME/kosmorro/export
+
echo
echo "==== RUNNING E2E TESTS ===="
echo
@@ -76,36 +78,42 @@ echo
assertSuccess "make build"
assertSuccess "$PIP_BIN install dist/kosmorro-$VERSION.tar.gz" "CI"
-assertSuccess kosmorro
-assertSuccess "kosmorro -h"
-assertSuccess "kosmorro -d 2020-01-27"
-assertFailure "kosmorro -d yolo-yo-lo"
-assertFailure "kosmorro -d 2020-13-32"
-assertFailure "kosmorro --date=1789-05-05"
-assertFailure "kosmorro --date=3000-01-01"
-assertSuccess "kosmorro --date='+3y 5m3d'"
-assertSuccess "kosmorro --date='-1y3d'"
-assertFailure "kosmorro --date='+3d4m"
-assertFailure "kosmorro -date='3y'"
-assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624"
-assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27"
-assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --timezone=1"
-assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --timezone=-1"
-assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=json"
-assertFailure "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf"
+KOSMORRO_COMMAND="kosmorro --debug"
+
+assertSuccess "$KOSMORRO_COMMAND"
+assertSuccess "$KOSMORRO_COMMAND -h"
+assertSuccess "$KOSMORRO_COMMAND -d 2020-01-27"
+assertFailure "$KOSMORRO_COMMAND -d yolo-yo-lo"
+assertFailure "$KOSMORRO_COMMAND -d 2020-13-32"
+assertFailure "$KOSMORRO_COMMAND --date=1789-05-05"
+assertFailure "$KOSMORRO_COMMAND --date=3000-01-01"
+assertSuccess "$KOSMORRO_COMMAND --date='+3y 5m3d'"
+assertSuccess "$KOSMORRO_COMMAND --date='-1y3d'"
+assertFailure "$KOSMORRO_COMMAND --date='+3d4m"
+assertFailure "$KOSMORRO_COMMAND -date='3y'"
+assertSuccess "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624"
+assertSuccess "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27"
+assertSuccess "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --timezone=1"
+assertSuccess "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --timezone=-1"
+assertSuccess "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=json"
+assertFailure "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf"
# Environment variables
assertSuccess "LATITUDE=50.5876 LONGITUDE=3.0624 TIMEZONE=1 kosmorro -d 2020-01-27"
assertSuccess "LATITUDE=50.5876 LONGITUDE=3.0624 TIMEZONE=-1 kosmorro -d 2020-01-27"
# Missing dependencies, should fail
-assertFailure "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf -o /tmp/document.pdf"
+assertFailure "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf -o $HOME/kosmorro/export/document.pdf"
+assertFailure "ls $HOME/kosmorro/export/document.pdf"
assertSuccess "sudo apt-get install -y texlive texlive-latex-extra" "CI"
# Dependencies installed, should not fail
-assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf -o /tmp/document.pdf"
-assertSuccess "kosmorro --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf -o /tmp/document.pdf --no-graph"
+assertSuccess "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf -o $HOME/kosmorro/export/document.pdf"
+assertSuccess "ls $HOME/kosmorro/export/document.pdf"
+
+assertSuccess "$KOSMORRO_COMMAND --latitude=50.5876 --longitude=3.0624 -d 2020-01-27 --format=pdf -o $HOME/kosmorro/export/document-no-graph.pdf --no-graph"
+assertSuccess "ls $HOME/kosmorro/export/document-no-graph.pdf"
# man page
assertSuccess "man --pager=cat kosmorro"
diff --git a/MANIFEST.in b/MANIFEST.in
index 462d2e1..52a10d1 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
-recursive-include kosmorrolib/locales *
-recursive-include kosmorrolib/assets *
+recursive-include _kosmorro/locales *
+recursive-include _kosmorro/assets *
diff --git a/Makefile b/Makefile
index 4e1892f..9662751 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,7 @@
-.PHONY: test
+black:
+ pipenv run black kosmorro _kosmorro setup.py
+.PHONY: test
test:
export LC_ALL=C.UTF-8; \
export LANG=C.UTF-8; \
@@ -12,7 +14,7 @@ build: i18n manpages
python3 setup.py sdist bdist_wheel
messages:
- pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot
+ pipenv run python setup.py extract_messages --output-file=_kosmorro/locales/messages.pot
manpages:
ronn --roff manpage/kosmorro.1.md
@@ -35,10 +37,10 @@ release: env
@echo -e "\e[1mCreating release with version number \e[36m$$RELEASE_NUMBER\e[0m"
@echo
- sed "s/^VERSION =.*/VERSION = '$$RELEASE_NUMBER'/g" kosmorrolib/version.py > version.py
- mv version.py kosmorrolib/version.py
+ sed "s/^VERSION =.*/VERSION = '$$RELEASE_NUMBER'/g" _kosmorro/version.py > version.py
+ mv version.py _kosmorro/version.py
- pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot > /dev/null
+ pipenv run python setup.py extract_messages --output-file=_kosmorro/locales/messages.pot > /dev/null
conventional-changelog -p angular -i CHANGELOG.md -s
sed "0,/\\[\\]/s/\\[\\]/[v$$RELEASE_NUMBER]/g" CHANGELOG.md > /tmp/CHANGELOG.md
@@ -50,7 +52,7 @@ release: env
@echo -e "Please review the changes, then invoke \e[33mmake finish-release\e[39m."
finish-release: env
- git add CHANGELOG.md kosmorrolib/version.py kosmorrolib/locales/messages.pot
+ git add CHANGELOG.md _kosmorro/version.py _kosmorro/locales/messages.pot
git commit -m "build: bump version $$RELEASE_NUMBER"
git tag "v$$RELEASE_NUMBER"
git checkout features
@@ -60,3 +62,6 @@ finish-release: env
@echo
@echo -e "\e[1mVersion \e[36m$$RELEASE_NUMBER\e[39m successfully tagged!"
@echo -e "Invoke \e[33mgit push origin master features v$$RELEASE_NUMBER\e[39m to finish."
+
+clean:
+ rm -rf build dist kosmorro.egg-info manpage/kosmorro.{1,7}{,.html}
diff --git a/Pipfile b/Pipfile
index 2808ca8..6d9d4d7 100644
--- a/Pipfile
+++ b/Pipfile
@@ -4,18 +4,17 @@ url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
-pylintfileheader = "*"
-pylint = "*"
babel = "*"
-unittest-data-provider = "*"
-coveralls = "*"
+black = "*"
[packages]
-skyfield = ">=1.32.0,<2.0.0"
tabulate = "*"
-numpy = ">=1.17.0,<2.0.0"
termcolor = "*"
+kosmorrolib = ">=0.11.0,<0.12.0"
python-dateutil = "*"
[requires]
python_version = "3"
+
+[pipenv]
+allow_prereleases = true
diff --git a/Pipfile.lock b/Pipfile.lock
index 3d009ba..2cea5e2 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "ae6348a3fa83012a1686db0dbef545714fa96ada808e642b9f1db071ab5112a7"
+ "sha256": "7ca2ce1c14eec2a7542613e3f502550e9facb550e09cd2430e2d73ac4fe2fa10"
},
"pipfile-spec": 6,
"requires": {
@@ -29,45 +29,43 @@
],
"version": "==2.15"
},
- "numpy": {
+ "kosmorrolib": {
"hashes": [
- "sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94",
- "sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080",
- "sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e",
- "sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c",
- "sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76",
- "sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371",
- "sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c",
- "sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2",
- "sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a",
- "sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb",
- "sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140",
- "sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28",
- "sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f",
- "sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d",
- "sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff",
- "sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8",
- "sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa",
- "sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea",
- "sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc",
- "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73",
- "sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d",
- "sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d",
- "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4",
- "sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c",
- "sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e",
- "sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea",
- "sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd",
- "sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f",
- "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff",
- "sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e",
- "sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7",
- "sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa",
- "sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827",
- "sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60"
+ "sha256:9a3059a8df2ec4ec83005be0cba3e4620cb005e37893a274f27e4739fa251091",
+ "sha256:ec1d252fc9ec5ab387f0b49d94a5e4dcb4f99cfb8f0661330c1ae23f2f2b1144"
],
"index": "pypi",
- "version": "==1.19.5"
+ "version": "==0.11.2"
+ },
+ "numpy": {
+ "hashes": [
+ "sha256:2428b109306075d89d21135bdd6b785f132a1f5a3260c371cee1fae427e12727",
+ "sha256:377751954da04d4a6950191b20539066b4e19e3b559d4695399c5e8e3e683bf6",
+ "sha256:4703b9e937df83f5b6b7447ca5912b5f5f297aba45f91dbbbc63ff9278c7aa98",
+ "sha256:471c0571d0895c68da309dacee4e95a0811d0a9f9f532a48dc1bea5f3b7ad2b7",
+ "sha256:61d5b4cf73622e4d0c6b83408a16631b670fc045afd6540679aa35591a17fe6d",
+ "sha256:6c915ee7dba1071554e70a3664a839fbc033e1d6528199d4621eeaaa5487ccd2",
+ "sha256:6e51e417d9ae2e7848314994e6fc3832c9d426abce9328cf7571eefceb43e6c9",
+ "sha256:719656636c48be22c23641859ff2419b27b6bdf844b36a2447cb39caceb00935",
+ "sha256:780ae5284cb770ade51d4b4a7dce4faa554eb1d88a56d0e8b9f35fca9b0270ff",
+ "sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee",
+ "sha256:924dc3f83de20437de95a73516f36e09918e9c9c18d5eac520062c49191025fb",
+ "sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042",
+ "sha256:9c0fab855ae790ca74b27e55240fe4f2a36a364a3f1ebcfd1fb5ac4088f1cec3",
+ "sha256:9cab23439eb1ebfed1aaec9cd42b7dc50fc96d5cd3147da348d9161f0501ada5",
+ "sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6",
+ "sha256:aa046527c04688af680217fffac61eec2350ef3f3d7320c07fd33f5c6e7b4d5f",
+ "sha256:abc81829c4039e7e4c30f7897938fa5d4916a09c2c7eb9b244b7a35ddc9656f4",
+ "sha256:bad70051de2c50b1a6259a6df1daaafe8c480ca98132da98976d8591c412e737",
+ "sha256:c73a7975d77f15f7f68dacfb2bca3d3f479f158313642e8ea9058eea06637931",
+ "sha256:d15007f857d6995db15195217afdbddfcd203dfaa0ba6878a2f580eaf810ecd6",
+ "sha256:d76061ae5cab49b83a8cf3feacefc2053fac672728802ac137dd8c4123397677",
+ "sha256:e8e4fbbb7e7634f263c5b0150a629342cc19b47c5eba8d1cd4363ab3455ab576",
+ "sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935",
+ "sha256:edb1f041a9146dcf02cd7df7187db46ab524b9af2515f392f337c7cbbf5b52cd"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==1.20.2"
},
"python-dateutil": {
"hashes": [
@@ -79,71 +77,58 @@
},
"sgp4": {
"hashes": [
- "sha256:1073b970fcbb32bcb4052f27a8a913815c3a50743a1eb6136f82772b18c4c915",
- "sha256:1cdc963fb3deebecdbe37df7cd9d59cdf8f1ce14741896ae33ed122b743e805c",
- "sha256:21eb83c2b302d539d4925900ce22da22400bea3c795951f97fb67d52c948e900",
- "sha256:255174b1a25891e783842935f7e4483698afe0865fc0419d0e21c9a20899166d",
- "sha256:29c96d0727edfa05201fd97f402b1c1b2b33dd8a63b8c49b8f824b278553ce30",
- "sha256:29d09d996cb2144714a74fb21eca13022b42877a5d3e5983dd6b6b48454e2ede",
- "sha256:2a7febfd91f0fd64bbbfcc6d9c220690f3410597d487d1de3e6a9f7ef45468d8",
- "sha256:351312a810aa28a9e371acef563ecf72ecc1a386508d6f9ab4ba696cf69eb5e3",
- "sha256:3522a4f7396c47a81f344166bf1a615f0382c9d3948d061b5f5e71d1ca4123ad",
- "sha256:3f884a4f24f5ff61f9053deb627c95db5faf64ec654e4d4d54986fe23860bc32",
- "sha256:449ef73a46e05f2d0e6d12c48b62c5789b30ed85ce62965c8d60ee9d3f5d2c5a",
- "sha256:52a6ee66cc64792a4767c4ef5a4c47b6bd064e36d42edac77721dd5cdb5f947d",
- "sha256:538fd4cd6947764b0fbc302c1a1a6d9080007c72d17fdf1cca182da5445e08e0",
- "sha256:5cdbb88137f34a61d8a0a233ca2b357cbe13d9834b209569e03f9faab695b54d",
- "sha256:632f87fd7d5f2220fcd2202a5da49a1ad05cb1829e48855137b5cd97665ec346",
- "sha256:6b6359f08bf5ad423a023d04fe5f0052b569f70f04928e6a97bd6d3b5c68f86b",
- "sha256:6d08eb24195b2cf82930e746192a03d41212619a262764a681ec45efc185ddbe",
- "sha256:762b9f8c4e202c55f236a492ccbf89d681510902b00ac464a2a6098847d68137",
- "sha256:7745c7c7a62385c27854d7cffe00441da373f9cf2b6db07b2b92bd2324a71018",
- "sha256:7dd8e209c3cfb3b2c538f032caa7c573a28ce0a371e55582086a6a2d2d45a357",
- "sha256:8b50de6e938d49a1044e712670fb07e36d152e7abcf1da6e593caf11caed4137",
- "sha256:8bbacfb654c7351a32e0d4f01375c4faa2d8579589c27333fe89d24f8d098d7a",
- "sha256:8f87ca1f0d615b35d6c4bb1ebcf29aeeb44d69280b3a8e147fd92c76e6722e0f",
- "sha256:918cf45a7e88124dbed55ce902c14fa12203e8c3c3c8d0ec5da9ae8e3d2cba81",
- "sha256:958ff08b21e62694c8f6ee873cdc76494cb7126e80db395ffbfcfccc63e324d6",
- "sha256:9bd185aee53decfdccd5d9bc92a25d1f4582b6cafb0f76fbdf7f5251e08b6e2c",
- "sha256:a76ddf3034747b2128844bb2bb757e435a770a495015aa3400113a2bc5d34af8",
- "sha256:bdb8e31cb2f5cf8a3e0d8ebf9ccd380c64fe872c4f9b9ea0cdc5e3bf729df43e",
- "sha256:be22bafcae01fc379b1823a3526096edf5c5c0c64b3c0f28ef91eddf08468fbc",
- "sha256:c18fd089a5e7660b60cad9effe4d654931c8023e9c469a43416ff20c6ef5af58",
- "sha256:c58155c687c4b20b37ba0dc68c6ee5a87f9e17d9f036b3daae8c86cf3356fb06",
- "sha256:c6dcdd53752f8048195a0b3d5902dc13c11694dd129c73f9e4d3de6b20a2a0e5",
- "sha256:c98205bfe99802f18d6917112fce1e4dbb9505f9ebcb6c23582d6465bd686db4",
- "sha256:ca224d6567f0d0f59a1bcc1fa8b5dd765a401e046c416b5804173668fb53c2fd",
- "sha256:cce8142be05ad2a5706a7f297d673f6f0017c41bfae6d1158c2ff2ee0772a2f0",
- "sha256:db36f2b7177520a263b238f2b88d8a3eb1f6a5e00b7bb6fb34bee65857431d61",
- "sha256:e095fbfacc49c56ef847baaef1ce8756661ff5f05d863cb57d1361bf6744c03c",
- "sha256:edbac400aa5aeb15dec15c43fa92417351ec3df56b86ec961dbaf48828ae30a6",
- "sha256:f4251912b603bb4b5d0d8cc8f1495d69688577ae23fb9493e7aab8d63aeae6cc",
- "sha256:f70894042b0bf9e2efc4ec733a0b161d8ef2d9b130ea89946364a6085d29400a",
- "sha256:fdfff689ae7c0d230e5db5e00c2b258dcce326341ccefa7ec5b3a8458f537398"
- ],
- "version": "==2.15"
+ "sha256:0e9d5764a60fa92a1f58fd5d5bc24f44a36e48e34723967951f6ca15330bc984",
+ "sha256:11a1d84c940462127a651368aced98e25f8cddf3ee6f3fff086c88549e3afb50",
+ "sha256:12bf193480bb734233b67c32dbead214a292cd451062eeb48cda5fe3a6b1bc44",
+ "sha256:15aa5c2fc22fa8995e3ddaaae3da65283c8e6d7c3c2c784a0b2ed2cb60ba0efe",
+ "sha256:16ed70a83dc5249b41d1dc2a5d129216fb8ccbda67ce4ea6d8aea47b1cae46cf",
+ "sha256:2274a7a2b88905c0de12fcff0ffc51a24a8e2e936d4f02313f9c2816273ebbb9",
+ "sha256:2a045b230f7b7afa095b2df434f3d84cf23235134a31e183c2a94ab6cf6bf644",
+ "sha256:2f2f54948b04b6bd472926b7fcb64f07c535a85bd7395006e766528bd47dc077",
+ "sha256:3755a35cefb6d9884223ebcab8453264f03d9cde5287a72d7109c9e832e13095",
+ "sha256:50e89196e610022f9ff2766a3cb31aae3bb7c98e79db0bf3352376ff2e2e3ea8",
+ "sha256:549ceaba69e8426a072d95498c60d81b5803c962ff78e35a7696ab1634d9b55d",
+ "sha256:5774221bcf1cbb21bee9bbaede05d4696651e1050e050b6ca5fb46a3b7cda83a",
+ "sha256:584f0b6acd53f523340af98b746c595befbb939426ddc6d604c2ea2b006822a5",
+ "sha256:9166d85f34e26ae66e828ca6748dbfc28ef45cd2be6478e418ccf24e6cdbd333",
+ "sha256:972322e6ef456ca15ae857339585612b27843ca3afc25cc8ee93ee97efce1e94",
+ "sha256:981b6977ec0136eb733ffe0fb852ce34dd1994dbae01f53f7d9b8fba13b18e85",
+ "sha256:a172ca0d65439ff9ca2824d6e4e12d9587b0226e3511204298b11cdbe280b72a",
+ "sha256:ae35b79bb3837044e81af6ab6cfa74e2e0293d1a471d60c295fac6747c5b2ac9",
+ "sha256:b2dcfcc2e41c229669caa101740bd7f37f8539b16a138f9683593e7de0dee3d6",
+ "sha256:b68ffc1a08bc4b7270e0d34c40efd35010cc49a5c1cf0904097e17591ee4e922",
+ "sha256:c9b7a85e3ce83908247ca6e5633d37543973562cbed2f076d667874f0c7396d9",
+ "sha256:cfa86f71cc394ad6b555edaee9cccb16e48a253c99038a63124b86476645f491",
+ "sha256:d4d95f789785319771782d06aa6fcdef0cf4fb749995b50c82156ea7a9713ed6",
+ "sha256:de8f77ddc146a0b6bc6ccf7b0355990b3c0928fc1a16a63fd8121d410d717a42",
+ "sha256:e64f985c99286467a2f84de8cc6e99832af6819eb52ad4e6106a4df5af7bc02d",
+ "sha256:eab7a12baa13057db6456ce031d98a58ca62daa59b5988612ad70c33309f35c0",
+ "sha256:f31ed36be5d8950f759b344a52d1ed6926cc1e93061cb2a9c4096ed34c74fa94",
+ "sha256:ffad58b31bf1316829fa432f6f63fbdff80c1b2d9012926ae4e1f294a8772087"
+ ],
+ "version": "==2.19"
},
"six": {
"hashes": [
- "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
- "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
+ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+ "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
- "version": "==1.15.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.16.0"
},
"skyfield": {
"hashes": [
- "sha256:06727f67db494a8cea4cd58bf2b10b5b1c0a576b3bd395249d6363a28e18a1b6"
+ "sha256:5a8da9720b49981bc6fc60ab8d52ef42a8d46fe7161e34ef6dd1d496de9903e2"
],
- "index": "pypi",
- "version": "==1.36"
+ "version": "==1.39"
},
"tabulate": {
"hashes": [
- "sha256:ac64cb76d53b1231d364babcd72abbb16855adac7de6665122f97b593f1eb2ba",
- "sha256:db2723a20d04bcda8522165c73eea7c300eda74e0ce852d9022e0159d7895007"
+ "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4",
+ "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7"
],
"index": "pypi",
- "version": "==0.8.7"
+ "version": "==0.8.9"
},
"termcolor": {
"hashes": [
@@ -154,213 +139,111 @@
}
},
"develop": {
- "astroid": {
+ "appdirs": {
"hashes": [
- "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
- "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
+ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
+ "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
- "version": "==2.4.2"
+ "version": "==1.4.4"
},
"babel": {
"hashes": [
- "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
- "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
+ "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9",
+ "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"
],
"index": "pypi",
- "version": "==2.9.0"
- },
- "certifi": {
- "hashes": [
- "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
- "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
- ],
- "version": "==2020.12.5"
- },
- "chardet": {
- "hashes": [
- "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
- "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
- ],
- "version": "==4.0.0"
- },
- "coverage": {
- "hashes": [
- "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7",
- "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5",
- "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f",
- "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde",
- "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f",
- "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f",
- "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c",
- "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66",
- "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90",
- "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337",
- "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d",
- "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4",
- "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409",
- "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37",
- "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1",
- "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247",
- "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39",
- "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c",
- "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994",
- "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c",
- "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb",
- "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc",
- "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f",
- "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca",
- "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135",
- "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3",
- "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339",
- "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9",
- "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9",
- "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af",
- "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370",
- "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19",
- "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3",
- "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44",
- "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3",
- "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a",
- "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c",
- "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b",
- "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9",
- "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8",
- "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22",
- "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f",
- "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345",
- "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880",
- "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0",
- "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b",
- "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec",
- "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3",
- "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"
- ],
- "version": "==5.4"
+ "version": "==2.9.1"
},
- "coveralls": {
+ "black": {
"hashes": [
- "sha256:5399c0565ab822a70a477f7031f6c88a9dd196b3de2877b3facb43b51bd13434",
- "sha256:f8384968c57dee4b7133ae701ecdad88e85e30597d496dcba0d7fbb470dca41f"
+ "sha256:0e80435b8a88f383c9149ae89d671eb2095b72344b0fe8a1d61d2ff5110ed173",
+ "sha256:9dc2042018ca10735366d944c2c12d9cad6dec74a3d5f679d09384ea185d9943"
],
"index": "pypi",
- "version": "==3.0.0"
- },
- "docopt": {
- "hashes": [
- "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
- ],
- "version": "==0.6.2"
- },
- "idna": {
- "hashes": [
- "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
- "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
- ],
- "version": "==2.10"
+ "version": "==21.5b0"
},
- "isort": {
+ "click": {
"hashes": [
- "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e",
- "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"
+ "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
+ "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
- "version": "==5.7.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==7.1.2"
},
- "lazy-object-proxy": {
+ "mypy-extensions": {
"hashes": [
- "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
- "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
- "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
- "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
- "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
- "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
- "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
- "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
- "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
- "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
- "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
- "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
- "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
- "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
- "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
- "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
- "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
- "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
- "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
- "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
- "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
+ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+ "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
- "version": "==1.4.3"
+ "version": "==0.4.3"
},
- "mccabe": {
+ "pathspec": {
"hashes": [
- "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
- "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
+ "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
],
- "version": "==0.6.1"
- },
- "pylint": {
- "hashes": [
- "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210",
- "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"
- ],
- "index": "pypi",
- "version": "==2.6.0"
- },
- "pylintfileheader": {
- "hashes": [
- "sha256:7871193691484210268d467dc12d88ac5b3ba7eb7dec6239e24075797185a3b2",
- "sha256:a23f143b0fb4d65f984ffd824731d6e41f2840e26a5752a90df93f4454b5ccd1"
- ],
- "index": "pypi",
- "version": "==0.3.0"
+ "version": "==0.8.1"
},
"pytz": {
"hashes": [
- "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4",
- "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"
- ],
- "version": "==2020.5"
- },
- "requests": {
- "hashes": [
- "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
- "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
- ],
- "version": "==2.25.1"
- },
- "six": {
- "hashes": [
- "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
- "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
- ],
- "version": "==1.15.0"
+ "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
+ "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
+ ],
+ "version": "==2021.1"
+ },
+ "regex": {
+ "hashes": [
+ "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5",
+ "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79",
+ "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31",
+ "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500",
+ "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11",
+ "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14",
+ "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3",
+ "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439",
+ "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c",
+ "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82",
+ "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711",
+ "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093",
+ "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a",
+ "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb",
+ "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8",
+ "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17",
+ "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000",
+ "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d",
+ "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480",
+ "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc",
+ "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0",
+ "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9",
+ "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765",
+ "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e",
+ "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a",
+ "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07",
+ "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f",
+ "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac",
+ "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7",
+ "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed",
+ "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968",
+ "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7",
+ "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2",
+ "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4",
+ "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87",
+ "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8",
+ "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10",
+ "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29",
+ "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605",
+ "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6",
+ "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"
+ ],
+ "version": "==2021.4.4"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
- },
- "unittest-data-provider": {
- "hashes": [
- "sha256:86bc7fb6608c2570aeedadea346fe3034afc940807dd7519e95e5dbc899ac2be"
- ],
- "index": "pypi",
- "version": "==1.0.1"
- },
- "urllib3": {
- "hashes": [
- "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
- "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
- ],
- "version": "==1.26.3"
- },
- "wrapt": {
- "hashes": [
- "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
- ],
- "version": "==1.12.1"
}
}
}
diff --git a/kosmorrolib/__init__.py b/_kosmorro/__init__.py
similarity index 100%
rename from kosmorrolib/__init__.py
rename to _kosmorro/__init__.py
diff --git a/kosmorrolib/version.py b/_kosmorro/__version__.py
similarity index 73%
rename from kosmorrolib/version.py
rename to _kosmorro/__version__.py
index 0d09c55..6afb826 100644
--- a/kosmorrolib/version.py
+++ b/_kosmorro/__version__.py
@@ -16,4 +16,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-VERSION = '0.9.0'
+__title__ = "kosmorrolib"
+__description__ = "A program that computes your ephemerides"
+__url__ = "http://kosmorro.space"
+__version__ = "0.9.0"
+__author__ = "Jérôme Deuchnord"
+__author_email__ = "jerome@deuchnord.fr"
+__license__ = "AGPL"
+__copyright__ = "Copyright 2021 Jérôme Deuchnord"
diff --git a/kosmorrolib/assets/moonphases/png/first-quarter.png b/_kosmorro/assets/moonphases/png/first-quarter.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/first-quarter.png
rename to _kosmorro/assets/moonphases/png/first-quarter.png
diff --git a/kosmorrolib/assets/moonphases/png/full-moon.png b/_kosmorro/assets/moonphases/png/full-moon.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/full-moon.png
rename to _kosmorro/assets/moonphases/png/full-moon.png
diff --git a/kosmorrolib/assets/moonphases/png/last-quarter.png b/_kosmorro/assets/moonphases/png/last-quarter.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/last-quarter.png
rename to _kosmorro/assets/moonphases/png/last-quarter.png
diff --git a/kosmorrolib/assets/moonphases/png/new-moon.png b/_kosmorro/assets/moonphases/png/new-moon.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/new-moon.png
rename to _kosmorro/assets/moonphases/png/new-moon.png
diff --git a/kosmorrolib/assets/moonphases/png/unknown.png b/_kosmorro/assets/moonphases/png/unknown.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/unknown.png
rename to _kosmorro/assets/moonphases/png/unknown.png
diff --git a/kosmorrolib/assets/moonphases/png/waning-crescent.png b/_kosmorro/assets/moonphases/png/waning-crescent.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/waning-crescent.png
rename to _kosmorro/assets/moonphases/png/waning-crescent.png
diff --git a/kosmorrolib/assets/moonphases/png/waning-gibbous.png b/_kosmorro/assets/moonphases/png/waning-gibbous.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/waning-gibbous.png
rename to _kosmorro/assets/moonphases/png/waning-gibbous.png
diff --git a/kosmorrolib/assets/moonphases/png/waxing-crescent.png b/_kosmorro/assets/moonphases/png/waxing-crescent.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/waxing-crescent.png
rename to _kosmorro/assets/moonphases/png/waxing-crescent.png
diff --git a/kosmorrolib/assets/moonphases/png/waxing-gibbous.png b/_kosmorro/assets/moonphases/png/waxing-gibbous.png
similarity index 100%
rename from kosmorrolib/assets/moonphases/png/waxing-gibbous.png
rename to _kosmorro/assets/moonphases/png/waxing-gibbous.png
diff --git a/kosmorrolib/assets/moonphases/svg/first-quarter.svg b/_kosmorro/assets/moonphases/svg/first-quarter.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/first-quarter.svg
rename to _kosmorro/assets/moonphases/svg/first-quarter.svg
diff --git a/kosmorrolib/assets/moonphases/svg/full-moon.svg b/_kosmorro/assets/moonphases/svg/full-moon.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/full-moon.svg
rename to _kosmorro/assets/moonphases/svg/full-moon.svg
diff --git a/kosmorrolib/assets/moonphases/svg/last-quarter.svg b/_kosmorro/assets/moonphases/svg/last-quarter.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/last-quarter.svg
rename to _kosmorro/assets/moonphases/svg/last-quarter.svg
diff --git a/kosmorrolib/assets/moonphases/svg/new-moon.svg b/_kosmorro/assets/moonphases/svg/new-moon.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/new-moon.svg
rename to _kosmorro/assets/moonphases/svg/new-moon.svg
diff --git a/kosmorrolib/assets/moonphases/svg/unknown.svg b/_kosmorro/assets/moonphases/svg/unknown.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/unknown.svg
rename to _kosmorro/assets/moonphases/svg/unknown.svg
diff --git a/kosmorrolib/assets/moonphases/svg/waning-crescent.svg b/_kosmorro/assets/moonphases/svg/waning-crescent.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/waning-crescent.svg
rename to _kosmorro/assets/moonphases/svg/waning-crescent.svg
diff --git a/kosmorrolib/assets/moonphases/svg/waning-gibbous.svg b/_kosmorro/assets/moonphases/svg/waning-gibbous.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/waning-gibbous.svg
rename to _kosmorro/assets/moonphases/svg/waning-gibbous.svg
diff --git a/kosmorrolib/assets/moonphases/svg/waxing-crescent.svg b/_kosmorro/assets/moonphases/svg/waxing-crescent.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/waxing-crescent.svg
rename to _kosmorro/assets/moonphases/svg/waxing-crescent.svg
diff --git a/kosmorrolib/assets/moonphases/svg/waxing-gibbous.svg b/_kosmorro/assets/moonphases/svg/waxing-gibbous.svg
similarity index 100%
rename from kosmorrolib/assets/moonphases/svg/waxing-gibbous.svg
rename to _kosmorro/assets/moonphases/svg/waxing-gibbous.svg
diff --git a/kosmorrolib/assets/pdf/kosmorro.sty b/_kosmorro/assets/pdf/kosmorro.sty
similarity index 100%
rename from kosmorrolib/assets/pdf/kosmorro.sty
rename to _kosmorro/assets/pdf/kosmorro.sty
diff --git a/kosmorrolib/assets/pdf/template.tex b/_kosmorro/assets/pdf/template.tex
similarity index 100%
rename from kosmorrolib/assets/pdf/template.tex
rename to _kosmorro/assets/pdf/template.tex
diff --git a/kosmorrolib/assets/png/kosmorro-icon-white.png b/_kosmorro/assets/png/kosmorro-icon-white.png
similarity index 100%
rename from kosmorrolib/assets/png/kosmorro-icon-white.png
rename to _kosmorro/assets/png/kosmorro-icon-white.png
diff --git a/kosmorrolib/assets/png/kosmorro-icon.png b/_kosmorro/assets/png/kosmorro-icon.png
similarity index 100%
rename from kosmorrolib/assets/png/kosmorro-icon.png
rename to _kosmorro/assets/png/kosmorro-icon.png
diff --git a/kosmorrolib/assets/png/kosmorro-logo-white.png b/_kosmorro/assets/png/kosmorro-logo-white.png
similarity index 100%
rename from kosmorrolib/assets/png/kosmorro-logo-white.png
rename to _kosmorro/assets/png/kosmorro-logo-white.png
diff --git a/kosmorrolib/assets/png/kosmorro-logo.png b/_kosmorro/assets/png/kosmorro-logo.png
similarity index 100%
rename from kosmorrolib/assets/png/kosmorro-logo.png
rename to _kosmorro/assets/png/kosmorro-logo.png
diff --git a/kosmorrolib/assets/svg/kosmorro-icon-white.svg b/_kosmorro/assets/svg/kosmorro-icon-white.svg
similarity index 100%
rename from kosmorrolib/assets/svg/kosmorro-icon-white.svg
rename to _kosmorro/assets/svg/kosmorro-icon-white.svg
diff --git a/kosmorrolib/assets/svg/kosmorro-icon.svg b/_kosmorro/assets/svg/kosmorro-icon.svg
similarity index 100%
rename from kosmorrolib/assets/svg/kosmorro-icon.svg
rename to _kosmorro/assets/svg/kosmorro-icon.svg
diff --git a/kosmorrolib/assets/svg/kosmorro-logo-white.svg b/_kosmorro/assets/svg/kosmorro-logo-white.svg
similarity index 100%
rename from kosmorrolib/assets/svg/kosmorro-logo-white.svg
rename to _kosmorro/assets/svg/kosmorro-logo-white.svg
diff --git a/kosmorrolib/assets/svg/kosmorro-logo.svg b/_kosmorro/assets/svg/kosmorro-logo.svg
similarity index 100%
rename from kosmorrolib/assets/svg/kosmorro-logo.svg
rename to _kosmorro/assets/svg/kosmorro-logo.svg
diff --git a/_kosmorro/date.py b/_kosmorro/date.py
new file mode 100644
index 0000000..5d25a71
--- /dev/null
+++ b/_kosmorro/date.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+import re
+
+from dateutil.relativedelta import relativedelta
+from datetime import date
+
+from _kosmorro.i18n.utils import _
+
+
+def parse_date(date_arg: str) -> date:
+ if re.match(r"^\d{4}-\d{2}-\d{2}$", date_arg):
+ try:
+ return date.fromisoformat(date_arg)
+ except ValueError as error:
+ raise ValueError(
+ _("The date {date} is not valid: {error}").format(
+ date=date_arg, error=error.args[0]
+ )
+ ) from error
+ elif re.match(r"^([+-])(([0-9]+)y)?[ ]?(([0-9]+)m)?[ ]?(([0-9]+)d)?$", date_arg):
+
+ def get_offset(date_arg: str, signifier: str):
+ if re.search(r"([0-9]+)" + signifier, date_arg):
+ return abs(
+ int(re.search(r"[+-]?([0-9]+)" + signifier, date_arg).group(0)[:-1])
+ )
+ return 0
+
+ days = get_offset(date_arg, "d")
+ months = get_offset(date_arg, "m")
+ years = get_offset(date_arg, "y")
+
+ if date_arg[0] == "+":
+ return date.today() + relativedelta(days=days, months=months, years=years)
+ return date.today() - relativedelta(days=days, months=months, years=years)
+
+ else:
+ error_msg = _(
+ "The date {date} does not match the required YYYY-MM-DD format or the offset format."
+ )
+ raise ValueError(error_msg.format(date=date_arg))
diff --git a/_kosmorro/debug.py b/_kosmorro/debug.py
new file mode 100644
index 0000000..72b04c1
--- /dev/null
+++ b/_kosmorro/debug.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python3
+
+from traceback import print_exc
+
+show_debug_messages = False
+
+
+def debug_print(what):
+ if not show_debug_messages:
+ return
+
+ if isinstance(what, Exception):
+ print_exc(what)
+ else:
+ print("[DEBUG] %s" % what)
diff --git a/_kosmorro/dumper.py b/_kosmorro/dumper.py
new file mode 100644
index 0000000..50d5c0e
--- /dev/null
+++ b/_kosmorro/dumper.py
@@ -0,0 +1,527 @@
+#!/usr/bin/env python3
+
+# Kosmorro - Compute The Next Ephemerides
+# Copyright (C) 2019 Jérôme Deuchnord
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from abc import ABC, abstractmethod
+import datetime
+import json
+import os
+import tempfile
+import subprocess
+import shutil
+from pathlib import Path
+from tabulate import tabulate
+from termcolor import colored
+
+from kosmorrolib import AsterEphemerides, Event
+from kosmorrolib.model import ASTERS, MoonPhase
+
+from .i18n.utils import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT
+from .i18n import strings
+from .__version__ import __version__ as version
+from .exceptions import CompileError
+from .debug import debug_print
+
+
+class Dumper(ABC):
+ def __init__(
+ self,
+ ephemerides: [AsterEphemerides] = None,
+ moon_phase: MoonPhase = None,
+ events: [Event] = None,
+ date: datetime.date = datetime.date.today(),
+ timezone: int = 0,
+ with_colors: bool = True,
+ show_graph: bool = False,
+ ):
+ self.ephemerides = ephemerides
+ self.moon_phase = moon_phase
+ self.events = events
+ self.date = date
+ self.timezone = timezone
+ self.with_colors = with_colors
+ self.show_graph = show_graph
+
+ def get_date_as_string(self, capitalized: bool = False) -> str:
+ date = self.date.strftime(FULL_DATE_FORMAT)
+
+ if capitalized:
+ return "".join([date[0].upper(), date[1:]])
+
+ return date
+
+ def __str__(self):
+ return self.to_string()
+
+ @abstractmethod
+ def to_string(self):
+ pass
+
+ @staticmethod
+ def is_file_output_needed() -> bool:
+ return False
+
+
+class JsonDumper(Dumper):
+ def to_string(self):
+ return json.dumps(
+ {
+ "ephemerides": [
+ ephemeris.serialize() for ephemeris in self.ephemerides
+ ],
+ "moon_phase": self.moon_phase.serialize(),
+ "events": [event.serialize() for event in self.events],
+ },
+ indent=4,
+ )
+
+
+class TextDumper(Dumper):
+ def to_string(self):
+ text = [self.style(self.get_date_as_string(capitalized=True), "h1")]
+
+ if self.ephemerides is not None:
+ text.append(self.stringify_ephemerides())
+
+ text.append(self.get_moon(self.moon_phase))
+
+ if len(self.events) > 0:
+ text.append(
+ "\n".join(
+ [
+ self.style(_("Expected events:"), "h2"),
+ self.get_events(self.events),
+ ]
+ )
+ )
+
+ if self.timezone == 0:
+ text.append(self.style(_("Note: All the hours are given in UTC."), "em"))
+ else:
+ tz_offset = str(self.timezone)
+ if self.timezone > 0:
+ tz_offset = "".join(["+", tz_offset])
+ text.append(
+ self.style(
+ _(
+ "Note: All the hours are given in the UTC{offset} timezone."
+ ).format(offset=tz_offset),
+ "em",
+ )
+ )
+
+ return "\n\n".join(text)
+
+ def style(self, text: str, tag: str) -> str:
+ if not self.with_colors:
+ return text
+
+ styles = {
+ "h1": lambda t: colored(t, "yellow", attrs=["bold"]),
+ "h2": lambda t: colored(t, "magenta", attrs=["bold"]),
+ "th": lambda t: colored(t, "white", attrs=["bold"]),
+ "strong": lambda t: colored(t, attrs=["bold"]),
+ "em": lambda t: colored(t, attrs=["dark"]),
+ }
+
+ return styles[tag](text)
+
+ def stringify_ephemerides(self) -> str:
+ data = []
+
+ for ephemeris in self.ephemerides:
+ name = self.style(strings.from_object(ephemeris.object.identifier), "th")
+
+ if ephemeris.rise_time is not None:
+ time_fmt = (
+ TIME_FORMAT
+ if ephemeris.rise_time.day == self.date.day
+ else SHORT_DATETIME_FORMAT
+ )
+ planet_rise = ephemeris.rise_time.strftime(time_fmt)
+ else:
+ planet_rise = "-"
+
+ if ephemeris.culmination_time is not None:
+ time_fmt = (
+ TIME_FORMAT
+ if ephemeris.culmination_time.day == self.date.day
+ else SHORT_DATETIME_FORMAT
+ )
+ planet_culmination = ephemeris.culmination_time.strftime(time_fmt)
+ else:
+ planet_culmination = "-"
+
+ if ephemeris.set_time is not None:
+ time_fmt = (
+ TIME_FORMAT
+ if ephemeris.set_time.day == self.date.day
+ else SHORT_DATETIME_FORMAT
+ )
+ planet_set = ephemeris.set_time.strftime(time_fmt)
+ else:
+ planet_set = "-"
+
+ data.append([name, planet_rise, planet_culmination, planet_set])
+
+ return tabulate(
+ data,
+ headers=[
+ self.style(_("Object"), "th"),
+ self.style(_("Rise time"), "th"),
+ self.style(_("Culmination time"), "th"),
+ self.style(_("Set time"), "th"),
+ ],
+ tablefmt="simple",
+ stralign="center",
+ colalign=("left",),
+ )
+
+ def get_events(self, events: [Event]) -> str:
+ def get_event_description(ev: Event):
+ description = strings.from_event(ev)
+
+ if ev.details is not None:
+ description += " ({:s})".format(ev.details)
+ return description
+
+ data = []
+
+ for event in events:
+ time_fmt = (
+ TIME_FORMAT
+ if event.start_time.day == self.date.day
+ else SHORT_DATETIME_FORMAT
+ )
+ data.append(
+ [
+ self.style(event.start_time.strftime(time_fmt), "th"),
+ get_event_description(event),
+ ]
+ )
+
+ return tabulate(data, tablefmt="plain", stralign="left")
+
+ def get_moon(self, moon_phase: MoonPhase) -> str:
+ if moon_phase is None:
+ return _("Moon phase is unavailable for this date.")
+
+ current_moon_phase = " ".join(
+ [
+ self.style(_("Moon phase:"), "strong"),
+ strings.from_moon_phase(moon_phase.phase_type),
+ ]
+ )
+ new_moon_phase = _(
+ "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}"
+ ).format(
+ next_moon_phase=_(strings.from_moon_phase(moon_phase.get_next_phase())),
+ next_moon_phase_date=moon_phase.next_phase_date.strftime(FULL_DATE_FORMAT),
+ next_moon_phase_time=moon_phase.next_phase_date.strftime(TIME_FORMAT),
+ )
+
+ return "\n".join([current_moon_phase, new_moon_phase])
+
+
+class _LatexDumper(Dumper):
+ def to_string(self):
+ template_path = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)), "assets", "pdf", "template.tex"
+ )
+
+ with open(template_path, mode="r") as file:
+ template = file.read()
+
+ return self._make_document(template)
+
+ def _make_document(self, template: str) -> str:
+ kosmorro_logo_path = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)),
+ "assets",
+ "png",
+ "kosmorro-logo.png",
+ )
+
+ moon_phase_graphics = os.path.join(
+ os.path.abspath(os.path.dirname(__file__)),
+ "assets",
+ "moonphases",
+ "png",
+ ".".join(
+ [self.moon_phase.phase_type.name.lower().replace("_", "-"), "png"]
+ ),
+ )
+
+ document = template
+
+ if self.ephemerides is None:
+ document = self._remove_section(document, "ephemerides")
+
+ if len(self.events) == 0:
+ document = self._remove_section(document, "events")
+
+ document = self.add_strings(document, kosmorro_logo_path, moon_phase_graphics)
+
+ if self.show_graph:
+ # The graphephemerides environment beginning tag must end with a percent symbol to ensure
+ # that no extra space will interfere with the graph.
+ document = document.replace(
+ r"\begin{ephemerides}", r"\begin{graphephemerides}%"
+ ).replace(r"\end{ephemerides}", r"\end{graphephemerides}")
+
+ return document
+
+ def add_strings(
+ self, document: str, kosmorro_logo_path: str, moon_phase_graphics: str
+ ) -> str:
+ document = document.replace("+++KOSMORRO-VERSION+++", version)
+ document = document.replace("+++KOSMORRO-LOGO+++", kosmorro_logo_path)
+ document = document.replace("+++DOCUMENT-TITLE+++", _("A Summary of your Sky"))
+ document = document.replace(
+ "+++DOCUMENT-DATE+++", self.get_date_as_string(capitalized=True)
+ )
+ document = document.replace(
+ "+++INTRODUCTION+++",
+ "\n\n".join(
+ [
+ _(
+ "This document summarizes the ephemerides and the events of {date}. "
+ "It aims to help you to prepare your observation session. "
+ "All the hours are given in {timezone}."
+ ).format(
+ date=self.get_date_as_string(),
+ timezone="UTC+%d" % self.timezone
+ if self.timezone != 0
+ else "UTC",
+ ),
+ _(
+ "Don't forget to check the weather forecast before you go out with your equipment."
+ ),
+ ]
+ ),
+ )
+ document = document.replace(
+ "+++SECTION-EPHEMERIDES+++", _("Ephemerides of the day")
+ )
+ document = document.replace("+++EPHEMERIDES-OBJECT+++", _("Object"))
+ document = document.replace("+++EPHEMERIDES-RISE-TIME+++", _("Rise time"))
+ document = document.replace(
+ "+++EPHEMERIDES-CULMINATION-TIME+++", _("Culmination time")
+ )
+ document = document.replace("+++EPHEMERIDES-SET-TIME+++", _("Set time"))
+ document = document.replace("+++EPHEMERIDES+++", self._make_ephemerides())
+ document = document.replace("+++GRAPH_LABEL_HOURS+++", _("hours"))
+ document = document.replace("+++MOON-PHASE-GRAPHICS+++", moon_phase_graphics)
+ document = document.replace("+++CURRENT-MOON-PHASE-TITLE+++", _("Moon phase:"))
+ document = document.replace(
+ "+++CURRENT-MOON-PHASE+++",
+ strings.from_moon_phase(self.moon_phase.phase_type),
+ )
+ document = document.replace("+++SECTION-EVENTS+++", _("Expected events"))
+ document = document.replace("+++EVENTS+++", self._make_events())
+
+ for aster in ASTERS:
+ document = document.replace(
+ "+++ASTER_%s+++" % aster.skyfield_name.upper().split(" ")[0],
+ strings.from_object(aster.identifier),
+ )
+
+ return document
+
+ def _make_ephemerides(self) -> str:
+ latex = []
+ graph_y_component = 18
+
+ if self.ephemerides is not None:
+ for ephemeris in self.ephemerides:
+ if ephemeris.rise_time is not None:
+ time_fmt = (
+ TIME_FORMAT
+ if ephemeris.rise_time.day == self.date.day
+ else SHORT_DATETIME_FORMAT
+ )
+ aster_rise = ephemeris.rise_time.strftime(time_fmt)
+ else:
+ aster_rise = "-"
+
+ if ephemeris.culmination_time is not None:
+ time_fmt = (
+ TIME_FORMAT
+ if ephemeris.culmination_time.day == self.date.day
+ else SHORT_DATETIME_FORMAT
+ )
+ aster_culmination = ephemeris.culmination_time.strftime(time_fmt)
+ else:
+ aster_culmination = "-"
+
+ if ephemeris.set_time is not None:
+ time_fmt = (
+ TIME_FORMAT
+ if ephemeris.set_time.day == self.date.day
+ else SHORT_DATETIME_FORMAT
+ )
+ aster_set = ephemeris.set_time.strftime(time_fmt)
+ else:
+ aster_set = "-"
+
+ if not self.show_graph:
+ latex.append(
+ r"\object{%s}{%s}{%s}{%s}"
+ % (
+ strings.from_object(ephemeris.object.identifier),
+ aster_rise,
+ aster_culmination,
+ aster_set,
+ )
+ )
+ else:
+ if ephemeris.rise_time is not None:
+ raise_hour = ephemeris.rise_time.hour
+ raise_minute = ephemeris.rise_time.minute
+ else:
+ raise_hour = raise_minute = 0
+ aster_rise = ""
+
+ if ephemeris.set_time is not None:
+ set_hour = ephemeris.set_time.hour
+ set_minute = ephemeris.set_time.minute
+ else:
+ set_hour = 24
+ set_minute = 0
+ aster_set = ""
+ sets_after_end = set_hour > raise_hour
+
+ if not sets_after_end:
+ latex.append(
+ r"\graphobject{%d}{gray}{0}{0}{%d}{%d}{}{%s}"
+ % (graph_y_component, set_hour, set_minute, aster_set)
+ )
+ set_hour = 24
+ set_minute = 0
+
+ latex.append(
+ r"\graphobject{%d}{gray}{%d}{%d}{%d}{%d}{%s}{%s}"
+ % (
+ graph_y_component,
+ raise_hour,
+ raise_minute,
+ set_hour,
+ set_minute,
+ aster_rise,
+ aster_set if sets_after_end else "",
+ )
+ )
+ graph_y_component -= 2
+
+ return "".join(latex)
+
+ def _make_events(self) -> str:
+ latex = []
+
+ for event in self.events:
+ print(event)
+ latex.append(
+ r"\event{%s}{%s}"
+ % (event.start_time.strftime(TIME_FORMAT), strings.from_event(event))
+ )
+
+ return "".join(latex)
+
+ @staticmethod
+ def _remove_section(document: str, section: str):
+ begin_section_tag = "%%%%%% BEGIN-%s-SECTION" % section.upper()
+ end_section_tag = "%%%%%% END-%s-SECTION" % section.upper()
+
+ document = document.split("\n")
+ new_document = []
+
+ ignore_line = False
+ for line in document:
+ if begin_section_tag in line or end_section_tag in line:
+ ignore_line = not ignore_line
+ continue
+ if ignore_line:
+ continue
+ new_document.append(line)
+
+ return "\n".join(new_document)
+
+
+class PdfDumper(Dumper):
+ def to_string(self):
+ try:
+ latex_dumper = _LatexDumper(
+ self.ephemerides,
+ self.moon_phase,
+ self.events,
+ date=self.date,
+ timezone=self.timezone,
+ with_colors=self.with_colors,
+ show_graph=self.show_graph,
+ )
+ return self._compile(latex_dumper.to_string())
+ except RuntimeError as error:
+ raise error
+ # raise UnavailableFeatureError(
+ # _(
+ # "Building PDFs was not possible, because some dependencies are not"
+ # " installed.\nPlease look at the documentation at http://kosmorro.space "
+ # "for more information."
+ # )
+ # ) from error
+
+ @staticmethod
+ def is_file_output_needed() -> bool:
+ return True
+
+ @staticmethod
+ def _compile(latex_input) -> bytes:
+ package = str(Path(__file__).parent.absolute()) + "/assets/pdf/kosmorro.sty"
+
+ with tempfile.TemporaryDirectory() as tempdir:
+ timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
+ shutil.copy(package, tempdir)
+
+ with open("%s/%s.tex" % (tempdir, timestamp), "w") as texfile:
+ texfile.write(latex_input)
+
+ os.chdir(tempdir)
+ debug_print("LaTeX content:\n%s" % latex_input)
+
+ try:
+ subprocess.run(
+ ["pdflatex", "-interaction", "nonstopmode", "%s.tex" % timestamp],
+ capture_output=True,
+ check=True,
+ )
+ except FileNotFoundError as error:
+ raise RuntimeError("pdflatex is not installed.") from error
+ except subprocess.CalledProcessError as error:
+ with open("/tmp/kosmorro-%s.log" % timestamp, "wb") as file:
+ file.write(error.stdout)
+
+ raise CompileError(
+ _(
+ "An error occured during the compilation of the PDF.\n"
+ "Please open an issue at https://github.com/Kosmorro/kosmorro/issues and share "
+ "the content of the log file at /tmp/kosmorro-%s.log"
+ % timestamp
+ )
+ ) from error
+
+ with open("%s.pdf" % timestamp, "rb") as pdffile:
+ return bytes(pdffile.read())
diff --git a/kosmorrolib/enum.py b/_kosmorro/environment.py
similarity index 53%
rename from kosmorrolib/enum.py
rename to _kosmorro/environment.py
index 718e8cf..18542c3 100644
--- a/kosmorrolib/enum.py
+++ b/_kosmorro/environment.py
@@ -16,25 +16,38 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from enum import Enum
-from .i18n import _
-
-
-class MoonPhaseType(Enum):
- NEW_MOON = _('New Moon')
- WAXING_CRESCENT = _('Waxing crescent')
- FIRST_QUARTER = _('First Quarter')
- WAXING_GIBBOUS = _('Waxing gibbous')
- FULL_MOON = _('Full Moon')
- WANING_GIBBOUS = _('Waning gibbous')
- LAST_QUARTER = _('Last Quarter')
- WANING_CRESCENT = _('Waning crescent')
-
-
-class EventType(Enum):
- OPPOSITION = _('%s is in opposition')
- CONJUNCTION = _('%s and %s are in conjunction')
- OCCULTATION = _('%s occults %s')
- MAXIMAL_ELONGATION = _("%s's largest elongation")
- MOON_PERIGEE = _("%s is at its perigee")
- MOON_APOGEE = _("%s is at its apogee")
+import os
+import re
+from pathlib import Path
+
+CACHE_FOLDER = str(Path.home()) + "/.kosmorro-cache"
+
+
+class Environment:
+ def __init__(self):
+ self._vars = {}
+
+ def __set__(self, key, value):
+ self._vars[key] = value
+
+ def __getattr__(self, key):
+ return self._vars[key] if key in self._vars else None
+
+ def __str__(self):
+ return self._vars.__str__()
+
+ def __len__(self):
+ return len(self._vars)
+
+
+def get_env_vars() -> Environment:
+ environment = Environment()
+
+ for var in os.environ:
+ if not re.search("^KOSMORRO_", var):
+ continue
+
+ [_, env] = var.split("_", 1)
+ environment.__set__(env.lower(), os.getenv(var))
+
+ return environment
diff --git a/kosmorrolib/exceptions.py b/_kosmorro/exceptions.py
similarity index 80%
rename from kosmorrolib/exceptions.py
rename to _kosmorro/exceptions.py
index de64af2..46cc3e2 100644
--- a/kosmorrolib/exceptions.py
+++ b/_kosmorro/exceptions.py
@@ -17,7 +17,7 @@
# along with this program. If not, see .
from datetime import date
-from .i18n import _, SHORT_DATE_FORMAT
+from _kosmorro.i18n.utils import _, SHORT_DATE_FORMAT
class UnavailableFeatureError(RuntimeError):
@@ -31,9 +31,12 @@ class OutOfRangeDateError(RuntimeError):
super().__init__()
self.min_date = min_date
self.max_date = max_date
- self.msg = _('The date must be between {minimum_date}'
- ' and {maximum_date}').format(minimum_date=min_date.strftime(SHORT_DATE_FORMAT),
- maximum_date=max_date.strftime(SHORT_DATE_FORMAT))
+ self.msg = _(
+ "The date must be between {minimum_date}" " and {maximum_date}"
+ ).format(
+ minimum_date=min_date.strftime(SHORT_DATE_FORMAT),
+ maximum_date=max_date.strftime(SHORT_DATE_FORMAT),
+ )
class CompileError(RuntimeError):
diff --git a/_kosmorro/i18n/__init__.py b/_kosmorro/i18n/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/_kosmorro/i18n/strings.py b/_kosmorro/i18n/strings.py
new file mode 100644
index 0000000..798dbf0
--- /dev/null
+++ b/_kosmorro/i18n/strings.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+from .utils import _
+
+from kosmorrolib import EventType, MoonPhaseType, ObjectIdentifier, Event
+
+
+def from_event(event: Event) -> str:
+ return {
+ EventType.OPPOSITION: _("%s is in opposition")
+ % (from_object(event.objects[0].identifier)),
+ EventType.CONJUNCTION: _("%s and %s are in conjunction")
+ % (
+ from_object(event.objects[0].identifier),
+ from_object(event.objects[1].identifier),
+ ),
+ EventType.OCCULTATION: _("%s occults %s")
+ % (
+ from_object(event.objects[0].identifier),
+ from_object(event.objects[1].identifier),
+ ),
+ EventType.MAXIMAL_ELONGATION: _("%s's largest elongation")
+ % (from_object(event.objects[0].identifier)),
+ EventType.MOON_PERIGEE: _("%s is at its perigee")
+ % (from_object(event.objects[0].identifier)),
+ EventType.MOON_APOGEE: _("%s is at its apogee")
+ % (from_object(event.objects[0].identifier)),
+ }.get(event.event_type)
+
+
+def from_moon_phase(moon_phase: MoonPhaseType) -> str:
+ return {
+ MoonPhaseType.NEW_MOON: _("New Moon"),
+ MoonPhaseType.WAXING_CRESCENT: _("Waxing Crescent"),
+ MoonPhaseType.FIRST_QUARTER: _("First Quarter"),
+ MoonPhaseType.WAXING_GIBBOUS: _("Waxing Gibbous"),
+ MoonPhaseType.FULL_MOON: _("Full Moon"),
+ MoonPhaseType.WANING_GIBBOUS: _("Waning Gibbous"),
+ MoonPhaseType.LAST_QUARTER: _("Last Quarter"),
+ MoonPhaseType.WANING_CRESCENT: _("Waning Crescent"),
+ }.get(moon_phase, _("Unknown phase"))
+
+
+def from_object(identifier: ObjectIdentifier) -> str:
+ return {
+ ObjectIdentifier.SUN: _("Sun"),
+ ObjectIdentifier.MOON: _("Moon"),
+ ObjectIdentifier.MERCURY: _("Mercury"),
+ ObjectIdentifier.VENUS: _("Venus"),
+ ObjectIdentifier.MARS: _("Mars"),
+ ObjectIdentifier.JUPITER: _("Jupiter"),
+ ObjectIdentifier.SATURN: _("Saturn"),
+ ObjectIdentifier.URANUS: _("Uranus"),
+ ObjectIdentifier.NEPTUNE: _("Neptune"),
+ ObjectIdentifier.PLUTO: _("Pluto"),
+ }.get(identifier, _("Unknown object"))
diff --git a/kosmorrolib/i18n.py b/_kosmorro/i18n/utils.py
similarity index 63%
rename from kosmorrolib/i18n.py
rename to _kosmorro/i18n/utils.py
index d0257b1..b65d525 100644
--- a/kosmorrolib/i18n.py
+++ b/_kosmorro/i18n/utils.py
@@ -19,17 +19,21 @@
import gettext
import os
-_LOCALE_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locales')
-_TRANSLATION = gettext.translation('messages', localedir=_LOCALE_DIR, fallback=True)
+_LOCALE_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "../locales")
+_TRANSLATION = gettext.translation("messages", localedir=_LOCALE_DIR, fallback=True)
_ = _TRANSLATION.gettext
-FULL_DATE_FORMAT = _('{day_of_week} {month} {day_number}, {year}').format(day_of_week='%A', month='%B',
- day_number='%d', year='%Y')
-SHORT_DATETIME_FORMAT = _('{month} {day_number}, {hours}:{minutes}').format(month='%b', day_number='%d',
- hours='%H', minutes='%M')
-SHORT_DATE_FORMAT = _('{month} {day_number}, {year}').format(month='%b', day_number='%d', year='%Y')
-TIME_FORMAT = _('{hours}:{minutes}').format(hours='%H', minutes='%M')
+FULL_DATE_FORMAT = _("{day_of_week} {month} {day_number}, {year}").format(
+ day_of_week="%A", month="%B", day_number="%d", year="%Y"
+)
+SHORT_DATETIME_FORMAT = _("{month} {day_number}, {hours}:{minutes}").format(
+ month="%b", day_number="%d", hours="%H", minutes="%M"
+)
+SHORT_DATE_FORMAT = _("{month} {day_number}, {year}").format(
+ month="%b", day_number="%d", year="%Y"
+)
+TIME_FORMAT = _("{hours}:{minutes}").format(hours="%H", minutes="%M")
def ngettext(msgid1, msgid2, number):
diff --git a/kosmorrolib/locales/messages.pot b/_kosmorro/locales/messages.pot
similarity index 66%
rename from kosmorrolib/locales/messages.pot
rename to _kosmorro/locales/messages.pot
index 5a7136c..79902c3 100644
--- a/kosmorrolib/locales/messages.pot
+++ b/_kosmorro/locales/messages.pot
@@ -8,143 +8,95 @@ msgid ""
msgstr ""
"Project-Id-Version: kosmorro 0.9.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2021-01-31 10:15+0100\n"
+"POT-Creation-Date: 2021-05-08 17:53+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.9.0\n"
+"Generated-By: Babel 2.9.1\n"
-#: kosmorrolib/core.py:101
+#: _kosmorro/date.py:17
msgid "The date {date} is not valid: {error}"
msgstr ""
-#: kosmorrolib/core.py:118
+#: _kosmorro/date.py:39
msgid ""
"The date {date} does not match the required YYYY-MM-DD format or the "
"offset format."
msgstr ""
-#: kosmorrolib/data.py:197
-msgid "Sun"
-msgstr ""
-
-#: kosmorrolib/data.py:198
-msgid "Moon"
-msgstr ""
-
-#: kosmorrolib/data.py:199
-msgid "Mercury"
-msgstr ""
-
-#: kosmorrolib/data.py:200
-msgid "Venus"
-msgstr ""
-
-#: kosmorrolib/data.py:201
-msgid "Mars"
-msgstr ""
-
-#: kosmorrolib/data.py:202
-msgid "Jupiter"
-msgstr ""
-
-#: kosmorrolib/data.py:203
-msgid "Saturn"
-msgstr ""
-
-#: kosmorrolib/data.py:204
-msgid "Uranus"
-msgstr ""
-
-#: kosmorrolib/data.py:205
-msgid "Neptune"
-msgstr ""
-
-#: kosmorrolib/data.py:206
-msgid "Pluto"
-msgstr ""
-
-#: kosmorrolib/dumper.py:87
+#: _kosmorro/dumper.py:106
msgid "Expected events:"
msgstr ""
-#: kosmorrolib/dumper.py:91
+#: _kosmorro/dumper.py:113
msgid "Note: All the hours are given in UTC."
msgstr ""
-#: kosmorrolib/dumper.py:96
+#: _kosmorro/dumper.py:120
msgid "Note: All the hours are given in the UTC{offset} timezone."
msgstr ""
-#: kosmorrolib/dumper.py:142 kosmorrolib/dumper.py:226
+#: _kosmorro/dumper.py:184 _kosmorro/dumper.py:320
msgid "Object"
msgstr ""
-#: kosmorrolib/dumper.py:143 kosmorrolib/dumper.py:227
+#: _kosmorro/dumper.py:185 _kosmorro/dumper.py:321
msgid "Rise time"
msgstr ""
-#: kosmorrolib/dumper.py:144 kosmorrolib/dumper.py:228
+#: _kosmorro/dumper.py:186 _kosmorro/dumper.py:323
msgid "Culmination time"
msgstr ""
-#: kosmorrolib/dumper.py:145 kosmorrolib/dumper.py:229
+#: _kosmorro/dumper.py:187 _kosmorro/dumper.py:325
msgid "Set time"
msgstr ""
-#: kosmorrolib/dumper.py:160
+#: _kosmorro/dumper.py:221
msgid "Moon phase is unavailable for this date."
msgstr ""
-#: kosmorrolib/dumper.py:162 kosmorrolib/dumper.py:233
+#: _kosmorro/dumper.py:225 _kosmorro/dumper.py:329
msgid "Moon phase:"
msgstr ""
-#: kosmorrolib/dumper.py:163
+#: _kosmorro/dumper.py:229
msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}"
msgstr ""
-#: kosmorrolib/dumper.py:213
+#: _kosmorro/dumper.py:293
msgid "A Summary of your Sky"
msgstr ""
-#: kosmorrolib/dumper.py:217
+#: _kosmorro/dumper.py:301
msgid ""
"This document summarizes the ephemerides and the events of {date}. It "
"aims to help you to prepare your observation session. All the hours are "
"given in {timezone}."
msgstr ""
-#: kosmorrolib/dumper.py:223
+#: _kosmorro/dumper.py:311
msgid ""
"Don't forget to check the weather forecast before you go out with your "
"equipment."
msgstr ""
-#: kosmorrolib/dumper.py:225
+#: _kosmorro/dumper.py:318
msgid "Ephemerides of the day"
msgstr ""
-#: kosmorrolib/dumper.py:231
+#: _kosmorro/dumper.py:327
msgid "hours"
msgstr ""
-#: kosmorrolib/dumper.py:235
+#: _kosmorro/dumper.py:334
msgid "Expected events"
msgstr ""
-#: kosmorrolib/dumper.py:351
-msgid ""
-"Building PDFs was not possible, because some dependencies are not "
-"installed.\n"
-"Please look at the documentation at http://kosmorro.space for more "
-"information."
-msgstr ""
-
-#: kosmorrolib/dumper.py:380
+#: _kosmorro/dumper.py:518
#, python-format
msgid ""
"An error occured during the compilation of the PDF.\n"
@@ -152,188 +104,240 @@ msgid ""
"share the content of the log file at /tmp/kosmorro-%s.log"
msgstr ""
-#: kosmorrolib/enum.py:24
-msgid "New Moon"
+#: _kosmorro/exceptions.py:34
+msgid "The date must be between {minimum_date} and {maximum_date}"
msgstr ""
-#: kosmorrolib/enum.py:25
-msgid "Waxing crescent"
+#: _kosmorro/main.py:61
+msgid ""
+"Save the planet and paper!\n"
+"Consider printing you PDF document only if really necessary, and use the "
+"other side of the sheet."
msgstr ""
-#: kosmorrolib/enum.py:26
-msgid "First Quarter"
+#: _kosmorro/main.py:70
+msgid ""
+"PDF output will not contain the ephemerides, because you didn't provide "
+"the observation coordinate."
+msgstr ""
+
+#: _kosmorro/main.py:115
+msgid "Could not save the output in \"{path}\": {error}"
msgstr ""
-#: kosmorrolib/enum.py:27
-msgid "Waxing gibbous"
+#: _kosmorro/main.py:129
+msgid "Selected output format needs an output file (--output)."
msgstr ""
-#: kosmorrolib/enum.py:28
-msgid "Full Moon"
+#: _kosmorro/main.py:157
+msgid "Moon phase can only be displayed between {min_date} and {max_date}"
msgstr ""
-#: kosmorrolib/enum.py:29
-msgid "Waning gibbous"
+#: _kosmorro/main.py:194
+msgid "Running on Python {python_version} with Kosmorrolib v{kosmorrolib_version}"
msgstr ""
-#: kosmorrolib/enum.py:30
-msgid "Last Quarter"
+#: _kosmorro/main.py:204
+msgid "Do you really want to clear Kosmorro's cache? [yN] "
+msgstr ""
+
+#: _kosmorro/main.py:212
+msgid "Answer did not match expected options, cache not cleared."
+msgstr ""
+
+#: _kosmorro/main.py:222
+msgid ""
+"Compute the ephemerides and the events for a given date, at a given "
+"position on Earth."
+msgstr ""
+
+#: _kosmorro/main.py:226
+msgid ""
+"By default, only the events will be computed for today ({date}).\n"
+"To compute also the ephemerides, latitude and longitude arguments are "
+"needed."
+msgstr ""
+
+#: _kosmorro/main.py:240
+msgid "Show the program version"
+msgstr ""
+
+#: _kosmorro/main.py:248
+msgid "Delete all the files Kosmorro stored in the cache."
+msgstr ""
+
+#: _kosmorro/main.py:256
+msgid "The format under which the information have to be output"
+msgstr ""
+
+#: _kosmorro/main.py:263
+msgid ""
+"The observer's latitude on Earth. Can also be set in the "
+"KOSMORRO_LATITUDE environment variable."
msgstr ""
-#: kosmorrolib/enum.py:31
-msgid "Waning crescent"
+#: _kosmorro/main.py:273
+msgid ""
+"The observer's longitude on Earth. Can also be set in the "
+"KOSMORRO_LONGITUDE environment variable."
+msgstr ""
+
+#: _kosmorro/main.py:283
+msgid ""
+"The date for which the ephemerides must be computed (in the YYYY-MM-DD "
+"format), or as an interval in the \"[+-]YyMmDd\" format (with Y, M, and D"
+" numbers). Defaults to the current date ({default_date})"
msgstr ""
-#: kosmorrolib/enum.py:35
+#: _kosmorro/main.py:294
+msgid ""
+"The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). "
+"Can also be set in the KOSMORRO_TIMEZONE environment variable."
+msgstr ""
+
+#: _kosmorro/main.py:303
+msgid "Disable the colors in the console."
+msgstr ""
+
+#: _kosmorro/main.py:310
+msgid ""
+"A file to export the output to. If not given, the standard output is "
+"used. This argument is needed for PDF format."
+msgstr ""
+
+#: _kosmorro/main.py:319
+msgid ""
+"Do not generate a graph to represent the rise and set times in the PDF "
+"format."
+msgstr ""
+
+#: _kosmorro/main.py:327
+msgid "Show debugging messages"
+msgstr ""
+
+#: _kosmorro/i18n/strings.py:10
#, python-format
msgid "%s is in opposition"
msgstr ""
-#: kosmorrolib/enum.py:36
+#: _kosmorro/i18n/strings.py:12
#, python-format
msgid "%s and %s are in conjunction"
msgstr ""
-#: kosmorrolib/enum.py:37
+#: _kosmorro/i18n/strings.py:17
#, python-format
msgid "%s occults %s"
msgstr ""
-#: kosmorrolib/enum.py:38
+#: _kosmorro/i18n/strings.py:22
#, python-format
msgid "%s's largest elongation"
msgstr ""
-#: kosmorrolib/enum.py:39
+#: _kosmorro/i18n/strings.py:24
#, python-format
msgid "%s is at its perigee"
msgstr ""
-#: kosmorrolib/enum.py:40
+#: _kosmorro/i18n/strings.py:26
#, python-format
msgid "%s is at its apogee"
msgstr ""
-#: kosmorrolib/exceptions.py:34
-msgid "The date must be between {minimum_date} and {maximum_date}"
-msgstr ""
-
-#: kosmorrolib/i18n.py:27
-msgid "{day_of_week} {month} {day_number}, {year}"
+#: _kosmorro/i18n/strings.py:33
+msgid "New Moon"
msgstr ""
-#: kosmorrolib/i18n.py:29
-msgid "{month} {day_number}, {hours}:{minutes}"
+#: _kosmorro/i18n/strings.py:34
+msgid "Waxing Crescent"
msgstr ""
-#: kosmorrolib/i18n.py:31
-msgid "{month} {day_number}, {year}"
+#: _kosmorro/i18n/strings.py:35
+msgid "First Quarter"
msgstr ""
-#: kosmorrolib/i18n.py:32
-msgid "{hours}:{minutes}"
+#: _kosmorro/i18n/strings.py:36
+msgid "Waxing Gibbous"
msgstr ""
-#: kosmorrolib/main.py:61
-msgid ""
-"Save the planet and paper!\n"
-"Consider printing you PDF document only if really necessary, and use the "
-"other side of the sheet."
+#: _kosmorro/i18n/strings.py:37
+msgid "Full Moon"
msgstr ""
-#: kosmorrolib/main.py:65
-msgid ""
-"PDF output will not contain the ephemerides, because you didn't provide "
-"the observation coordinate."
+#: _kosmorro/i18n/strings.py:38
+msgid "Waning Gibbous"
msgstr ""
-#: kosmorrolib/main.py:93
-msgid "Could not save the output in \"{path}\": {error}"
+#: _kosmorro/i18n/strings.py:39
+msgid "Last Quarter"
msgstr ""
-#: kosmorrolib/main.py:100
-msgid "Selected output format needs an output file (--output)."
+#: _kosmorro/i18n/strings.py:40
+msgid "Waning Crescent"
msgstr ""
-#: kosmorrolib/main.py:117
-msgid "Moon phase can only be displayed between {min_date} and {max_date}"
+#: _kosmorro/i18n/strings.py:41
+msgid "Unknown phase"
msgstr ""
-#: kosmorrolib/main.py:139
-msgid "Running on Python {python_version}"
+#: _kosmorro/i18n/strings.py:46
+msgid "Sun"
msgstr ""
-#: kosmorrolib/main.py:145
-msgid "Do you really want to clear Kosmorro's cache? [yN] "
+#: _kosmorro/i18n/strings.py:47
+msgid "Moon"
msgstr ""
-#: kosmorrolib/main.py:152
-msgid "Answer did not match expected options, cache not cleared."
+#: _kosmorro/i18n/strings.py:48
+msgid "Mercury"
msgstr ""
-#: kosmorrolib/main.py:161
-msgid ""
-"Compute the ephemerides and the events for a given date, at a given "
-"position on Earth."
+#: _kosmorro/i18n/strings.py:49
+msgid "Venus"
msgstr ""
-#: kosmorrolib/main.py:163
-msgid ""
-"By default, only the events will be computed for today ({date}).\n"
-"To compute also the ephemerides, latitude and longitude arguments are "
-"needed."
+#: _kosmorro/i18n/strings.py:50
+msgid "Mars"
msgstr ""
-#: kosmorrolib/main.py:168
-msgid "Show the program version"
+#: _kosmorro/i18n/strings.py:51
+msgid "Jupiter"
msgstr ""
-#: kosmorrolib/main.py:170
-msgid "Delete all the files Kosmorro stored in the cache."
+#: _kosmorro/i18n/strings.py:52
+msgid "Saturn"
msgstr ""
-#: kosmorrolib/main.py:172
-msgid "The format under which the information have to be output"
+#: _kosmorro/i18n/strings.py:53
+msgid "Uranus"
msgstr ""
-#: kosmorrolib/main.py:174
-msgid ""
-"The observer's latitude on Earth. Can also be set in the "
-"KOSMORRO_LATITUDE environment variable."
+#: _kosmorro/i18n/strings.py:54
+msgid "Neptune"
msgstr ""
-#: kosmorrolib/main.py:177
-msgid ""
-"The observer's longitude on Earth. Can also be set in the "
-"KOSMORRO_LONGITUDE environment variable."
+#: _kosmorro/i18n/strings.py:55
+msgid "Pluto"
msgstr ""
-#: kosmorrolib/main.py:180
-msgid ""
-"The date for which the ephemerides must be computed (in the YYYY-MM-DD "
-"format), or as an interval in the \"[+-]YyMmDd\" format (with Y, M, and D"
-" numbers). Defaults to the current date ({default_date})"
+#: _kosmorro/i18n/strings.py:56
+msgid "Unknown object"
msgstr ""
-#: kosmorrolib/main.py:185
-msgid ""
-"The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). "
-"Can also be set in the KOSMORRO_TIMEZONE environment variable."
+#: _kosmorro/i18n/utils.py:27
+msgid "{day_of_week} {month} {day_number}, {year}"
msgstr ""
-#: kosmorrolib/main.py:188
-msgid "Disable the colors in the console."
+#: _kosmorro/i18n/utils.py:30
+msgid "{month} {day_number}, {hours}:{minutes}"
msgstr ""
-#: kosmorrolib/main.py:190
-msgid ""
-"A file to export the output to. If not given, the standard output is "
-"used. This argument is needed for PDF format."
+#: _kosmorro/i18n/utils.py:33
+msgid "{month} {day_number}, {year}"
msgstr ""
-#: kosmorrolib/main.py:193
-msgid ""
-"Do not generate a graph to represent the rise and set times in the PDF "
-"format."
+#: _kosmorro/i18n/utils.py:36
+msgid "{hours}:{minutes}"
msgstr ""
diff --git a/_kosmorro/main.py b/_kosmorro/main.py
new file mode 100644
index 0000000..e60ebf0
--- /dev/null
+++ b/_kosmorro/main.py
@@ -0,0 +1,330 @@
+#!/usr/bin/env python3
+
+# Kosmorro - Compute The Next Ephemerides
+# Copyright (C) 2019 Jérôme Deuchnord
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import argparse
+import locale
+import re
+import sys
+
+from kosmorrolib import Position, get_ephemerides, get_events, get_moon_phase
+from kosmorrolib.__version__ import __version__ as kosmorrolib_version
+from datetime import date
+from termcolor import colored
+
+from . import dumper, environment, debug
+from .date import parse_date
+from .__version__ import __version__ as kosmorro_version
+from .exceptions import UnavailableFeatureError, OutOfRangeDateError
+from _kosmorro.i18n.utils import _
+
+
+def main():
+ env_vars = environment.get_env_vars()
+ output_formats = get_dumpers()
+ args = get_args(list(output_formats.keys()))
+ debug.show_debug_messages = args.show_debug_messages
+ output_format = args.format
+
+ if args.special_action is not None:
+ return 0 if args.special_action() else 1
+
+ try:
+ compute_date = parse_date(args.date)
+ except ValueError as error:
+ print(colored(error.args[0], color="red", attrs=["bold"]))
+ return -1
+
+ position = None
+
+ if args.latitude is not None or args.longitude is not None:
+ position = Position(args.latitude, args.longitude)
+ elif env_vars.latitude is not None and env_vars.longitude is not None:
+ position = Position(float(env_vars.latitude), float(env_vars.longitude))
+
+ if output_format == "pdf":
+ print(
+ _(
+ "Save the planet and paper!\n"
+ "Consider printing you PDF document only if really necessary, and use the other side of the sheet."
+ )
+ )
+ if position is None:
+ print()
+ print(
+ colored(
+ _(
+ "PDF output will not contain the ephemerides, because you didn't provide the observation "
+ "coordinate."
+ ),
+ "yellow",
+ )
+ )
+
+ timezone = args.timezone
+
+ if timezone is None and env_vars.timezone is not None:
+ timezone = int(env_vars.timezone)
+ elif timezone is None:
+ timezone = 0
+
+ try:
+ output = get_information(
+ compute_date,
+ position,
+ timezone,
+ output_format,
+ args.colors,
+ args.show_graph,
+ )
+ except UnavailableFeatureError as error:
+ print(colored(error.msg, "red"))
+ debug.debug_print(error)
+ return 2
+ except OutOfRangeDateError as error:
+ print(colored(error.msg, "red"))
+ debug.debug_print(error)
+ return 1
+
+ if args.output is not None:
+ try:
+ pdf_content = output.to_string()
+ with open(args.output, "wb") as output_file:
+ output_file.write(pdf_content)
+ except UnavailableFeatureError as error:
+ print(colored(error.msg, "red"))
+ debug.debug_print(error)
+ return 2
+ except OSError as error:
+ print(
+ colored(
+ _('Could not save the output in "{path}": {error}').format(
+ path=args.output, error=error.strerror
+ ),
+ "red",
+ )
+ )
+ debug.debug_print(error)
+
+ return 3
+ elif not output.is_file_output_needed():
+ print(output)
+ else:
+ print(
+ colored(
+ _("Selected output format needs an output file (--output)."),
+ color="red",
+ )
+ )
+ return 1
+
+ return 0
+
+
+def get_information(
+ compute_date: date,
+ position: Position,
+ timezone: int,
+ output_format: str,
+ colors: bool,
+ show_graph: bool,
+) -> dumper.Dumper:
+ if position is not None:
+ eph = get_ephemerides(date=compute_date, position=position, timezone=timezone)
+ else:
+ eph = None
+
+ try:
+ moon_phase = get_moon_phase(compute_date)
+ except OutOfRangeDateError as error:
+ moon_phase = None
+ print(
+ colored(
+ _(
+ "Moon phase can only be displayed"
+ " between {min_date} and {max_date}"
+ ).format(min_date=error.min_date, max_date=error.max_date),
+ "yellow",
+ )
+ )
+
+ events_list = get_events(compute_date, timezone)
+
+ return get_dumpers()[output_format](
+ ephemerides=eph,
+ moon_phase=moon_phase,
+ events=events_list,
+ date=compute_date,
+ timezone=timezone,
+ with_colors=colors,
+ show_graph=show_graph,
+ )
+
+
+def get_dumpers() -> {str: dumper.Dumper}:
+ return {
+ "text": dumper.TextDumper,
+ "json": dumper.JsonDumper,
+ "pdf": dumper.PdfDumper,
+ }
+
+
+def output_version() -> bool:
+ python_version = "%d.%d.%d" % (
+ sys.version_info[0],
+ sys.version_info[1],
+ sys.version_info[2],
+ )
+ print("Kosmorro %s" % kosmorro_version)
+ print(
+ _(
+ "Running on Python {python_version} "
+ "with Kosmorrolib v{kosmorrolib_version}"
+ ).format(python_version=python_version, kosmorrolib_version=kosmorrolib_version)
+ )
+
+ return True
+
+
+def clear_cache() -> bool:
+ confirm = input(_("Do you really want to clear Kosmorro's cache? [yN] ")).upper()
+ if re.match(locale.nl_langinfo(locale.YESEXPR), confirm) is not None:
+ try:
+ environment.clear_cache()
+ except FileNotFoundError:
+ debug.debug_print("No cache found, nothing done.")
+ pass
+ elif confirm != "" and re.match(locale.nl_langinfo(locale.NOEXPR), confirm) is None:
+ print(_("Answer did not match expected options, cache not cleared."))
+ return False
+
+ return True
+
+
+def get_args(output_formats: [str]):
+ today = date.today()
+
+ parser = argparse.ArgumentParser(
+ description=_(
+ "Compute the ephemerides and the events for a given date,"
+ " at a given position on Earth."
+ ),
+ epilog=_(
+ "By default, only the events will be computed for today ({date}).\n"
+ "To compute also the ephemerides, latitude and longitude arguments"
+ " are needed."
+ ).format(date=today.strftime(dumper.FULL_DATE_FORMAT)),
+ )
+
+ parser.add_argument(
+ "--version",
+ "-v",
+ dest="special_action",
+ action="store_const",
+ const=output_version,
+ default=None,
+ help=_("Show the program version"),
+ )
+ parser.add_argument(
+ "--clear-cache",
+ dest="special_action",
+ action="store_const",
+ const=clear_cache,
+ default=None,
+ help=_("Delete all the files Kosmorro stored in the cache."),
+ )
+ parser.add_argument(
+ "--format",
+ "-f",
+ type=str,
+ default=output_formats[0],
+ choices=output_formats,
+ help=_("The format under which the information have to be output"),
+ )
+ parser.add_argument(
+ "--latitude",
+ "-lat",
+ type=float,
+ default=None,
+ help=_(
+ "The observer's latitude on Earth. Can also be set in the KOSMORRO_LATITUDE environment "
+ "variable."
+ ),
+ )
+ parser.add_argument(
+ "--longitude",
+ "-lon",
+ type=float,
+ default=None,
+ help=_(
+ "The observer's longitude on Earth. Can also be set in the KOSMORRO_LONGITUDE "
+ "environment variable."
+ ),
+ )
+ parser.add_argument(
+ "--date",
+ "-d",
+ type=str,
+ default=today.strftime("%Y-%m-%d"),
+ help=_(
+ "The date for which the ephemerides must be computed (in the YYYY-MM-DD format), "
+ 'or as an interval in the "[+-]YyMmDd" format (with Y, M, and D numbers). '
+ "Defaults to the current date ({default_date})"
+ ).format(default_date=today.strftime("%Y-%m-%d")),
+ )
+ parser.add_argument(
+ "--timezone",
+ "-t",
+ type=int,
+ default=None,
+ help=_(
+ "The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). "
+ "Can also be set in the KOSMORRO_TIMEZONE environment variable."
+ ),
+ )
+ parser.add_argument(
+ "--no-colors",
+ dest="colors",
+ action="store_false",
+ help=_("Disable the colors in the console."),
+ )
+ parser.add_argument(
+ "--output",
+ "-o",
+ type=str,
+ default=None,
+ help=_(
+ "A file to export the output to. If not given, the standard output is used. "
+ "This argument is needed for PDF format."
+ ),
+ )
+ parser.add_argument(
+ "--no-graph",
+ dest="show_graph",
+ action="store_false",
+ help=_(
+ "Do not generate a graph to represent the rise and set times in the PDF format."
+ ),
+ )
+ parser.add_argument(
+ "--debug",
+ dest="show_debug_messages",
+ action="store_true",
+ help=_("Show debugging messages"),
+ )
+
+ return parser.parse_args()
diff --git a/kosmorro b/kosmorro
index f0b6e77..7b10fe5 100755
--- a/kosmorro
+++ b/kosmorro
@@ -18,11 +18,11 @@
import sys
import locale
-from kosmorrolib.main import main
+from _kosmorro.main import main
-locale.setlocale(locale.LC_ALL, '')
+locale.setlocale(locale.LC_ALL, "")
-if __name__ == '__main__':
+if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
diff --git a/kosmorrolib/core.py b/kosmorrolib/core.py
deleted file mode 100644
index 36989b5..0000000
--- a/kosmorrolib/core.py
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/usr/bin/env python3
-
-# Kosmorro - Compute The Next Ephemerides
-# Copyright (C) 2019 Jérôme Deuchnord
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import os
-import re
-from shutil import rmtree
-from pathlib import Path
-
-from datetime import date
-from dateutil.relativedelta import relativedelta
-
-from skyfield.api import Loader
-from skyfield.timelib import Time
-from skyfield.nutationlib import iau2000b
-
-from kosmorrolib.i18n import _
-
-CACHE_FOLDER = str(Path.home()) + '/.kosmorro-cache'
-
-class Environment:
- def __init__(self):
- self._vars = {}
-
- def __set__(self, key, value):
- self._vars[key] = value
-
- def __getattr__(self, key):
- return self._vars[key] if key in self._vars else None
-
- def __str__(self):
- return self._vars.__str__()
-
- def __len__(self):
- return len(self._vars)
-
-def get_env() -> Environment:
- environment = Environment()
-
- for var in os.environ:
- if not re.search('^KOSMORRO_', var):
- continue
-
- [_, env] = var.split('_', 1)
- environment.__set__(env.lower(), os.getenv(var))
-
- return environment
-
-def get_loader():
- return Loader(CACHE_FOLDER)
-
-
-def get_timescale():
- return get_loader().timescale()
-
-
-def get_skf_objects():
- return get_loader()('de421.bsp')
-
-
-def get_iau2000b(time: Time):
- return iau2000b(time.tt)
-
-
-def clear_cache():
- rmtree(CACHE_FOLDER)
-
-
-def flatten_list(the_list: list):
- new_list = []
- for item in the_list:
- if isinstance(item, list):
- for item2 in flatten_list(item):
- new_list.append(item2)
- continue
-
- new_list.append(item)
-
- return new_list
-
-
-def get_date(date_arg: str) -> date:
- if re.match(r'^\d{4}-\d{2}-\d{2}$', date_arg):
- try:
- return date.fromisoformat(date_arg)
- except ValueError as error:
- raise ValueError(_('The date {date} is not valid: {error}').format(date=date_arg,
- error=error.args[0])) from error
- elif re.match(r'^([+-])(([0-9]+)y)?[ ]?(([0-9]+)m)?[ ]?(([0-9]+)d)?$', date_arg):
- def get_offset(date_arg: str, signifier: str):
- if re.search(r'([0-9]+)' + signifier, date_arg):
- return abs(int(re.search(r'[+-]?([0-9]+)' + signifier, date_arg).group(0)[:-1]))
- return 0
-
- days = get_offset(date_arg, 'd')
- months = get_offset(date_arg, 'm')
- years = get_offset(date_arg, 'y')
-
- if date_arg[0] == '+':
- return date.today() + relativedelta(days=days, months=months, years=years)
- return date.today() - relativedelta(days=days, months=months, years=years)
-
- else:
- error_msg = _('The date {date} does not match the required YYYY-MM-DD format or the offset format.')
- raise ValueError(error_msg.format(date=date_arg))
diff --git a/kosmorrolib/data.py b/kosmorrolib/data.py
deleted file mode 100644
index ea30eb4..0000000
--- a/kosmorrolib/data.py
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/usr/bin/env python3
-
-# Kosmorro - Compute The Next Ephemerides
-# Copyright (C) 2019 Jérôme Deuchnord
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from abc import ABC, abstractmethod
-from typing import Union
-from datetime import datetime
-
-from numpy import pi, arcsin
-
-from skyfield.api import Topos, Time
-from skyfield.vectorlib import VectorSum as SkfPlanet
-
-from .core import get_skf_objects
-from .enum import MoonPhaseType, EventType
-from .i18n import _
-
-
-class Serializable(ABC):
- @abstractmethod
- def serialize(self) -> dict:
- pass
-
-
-class MoonPhase(Serializable):
- def __init__(self, phase_type: MoonPhaseType, time: datetime = None, next_phase_date: datetime = None):
- self.phase_type = phase_type
- self.time = time
- self.next_phase_date = next_phase_date
-
- def get_next_phase(self):
- if self.phase_type in [MoonPhaseType.NEW_MOON, MoonPhaseType.WAXING_CRESCENT]:
- return MoonPhaseType.FIRST_QUARTER
- if self.phase_type in [MoonPhaseType.FIRST_QUARTER, MoonPhaseType.WAXING_GIBBOUS]:
- return MoonPhaseType.FULL_MOON
- if self.phase_type in [MoonPhaseType.FULL_MOON, MoonPhaseType.WANING_GIBBOUS]:
- return MoonPhaseType.LAST_QUARTER
-
- return MoonPhaseType.NEW_MOON
-
- def serialize(self) -> dict:
- return {
- 'phase': self.phase_type.name,
- 'time': self.time.isoformat() if self.time is not None else None,
- 'next': {
- 'phase': self.get_next_phase().name,
- 'time': self.next_phase_date.isoformat()
- }
- }
-
-
-class Object(Serializable):
- """
- An astronomical object.
- """
-
- def __init__(self,
- name: str,
- skyfield_name: str,
- radius: float = None):
- """
- Initialize an astronomical object
-
- :param str name: the official name of the object (may be internationalized)
- :param str skyfield_name: the internal name of the object in Skyfield library
- :param float radius: the radius (in km) of the object
- :param AsterEphemerides ephemerides: the ephemerides associated to the object
- """
- self.name = name
- self.skyfield_name = skyfield_name
- self.radius = radius
-
- def __repr__(self):
- return '' % (self.get_type(), self.name)
-
- def get_skyfield_object(self) -> SkfPlanet:
- return get_skf_objects()[self.skyfield_name]
-
- @abstractmethod
- def get_type(self) -> str:
- pass
-
- def get_apparent_radius(self, time: Time, from_place) -> float:
- """
- Calculate the apparent radius, in degrees, of the object from the given place at a given time.
- :param time:
- :param from_place:
- :return:
- """
- if self.radius is None:
- raise ValueError('Missing radius for %s object' % self.name)
-
- return 360 / pi * arcsin(self.radius / from_place.at(time).observe(self.get_skyfield_object()).distance().km)
-
- def serialize(self) -> dict:
- return {
- 'name': self.name,
- 'type': self.get_type(),
- 'radius': self.radius,
- }
-
-
-class Star(Object):
- def get_type(self) -> str:
- return 'star'
-
-
-class Planet(Object):
- def get_type(self) -> str:
- return 'planet'
-
-
-class DwarfPlanet(Planet):
- def get_type(self) -> str:
- return 'dwarf_planet'
-
-
-class Satellite(Object):
- def get_type(self) -> str:
- return 'satellite'
-
-
-class Event(Serializable):
- def __init__(self, event_type: EventType, objects: [Object], start_time: datetime,
- end_time: Union[datetime, None] = None, details: str = None):
- self.event_type = event_type
- self.objects = objects
- self.start_time = start_time
- self.end_time = end_time
- self.details = details
-
- def __repr__(self):
- return '' % (self.event_type,
- self.objects,
- self.start_time,
- self.end_time,
- self.details)
-
- def get_description(self, show_details: bool = True) -> str:
- description = self.event_type.value % self._get_objects_name()
- if show_details and self.details is not None:
- description += ' ({:s})'.format(self.details)
- return description
-
- def _get_objects_name(self):
- if len(self.objects) == 1:
- return self.objects[0].name
-
- return tuple(object.name for object in self.objects)
-
- def serialize(self) -> dict:
- return {
- 'objects': [object.serialize() for object in self.objects],
- 'event': self.event_type.name,
- 'starts_at': self.start_time.isoformat(),
- 'ends_at': self.end_time.isoformat() if self.end_time is not None else None,
- 'details': self.details
- }
-
-
-class AsterEphemerides(Serializable):
- def __init__(self,
- rise_time: Union[datetime, None],
- culmination_time: Union[datetime, None],
- set_time: Union[datetime, None],
- aster: Object):
- self.rise_time = rise_time
- self.culmination_time = culmination_time
- self.set_time = set_time
- self.object = aster
-
- def serialize(self) -> dict:
- return {
- 'object': self.object.serialize(),
- 'rise_time': self.rise_time.isoformat() if self.rise_time is not None else None,
- 'culmination_time': self.culmination_time.isoformat() if self.culmination_time is not None else None,
- 'set_time': self.set_time.isoformat() if self.set_time is not None else None
- }
-
-
-EARTH = Planet('Earth', 'EARTH')
-
-ASTERS = [Star(_('Sun'), 'SUN', radius=696342),
- Satellite(_('Moon'), 'MOON', radius=1737.4),
- Planet(_('Mercury'), 'MERCURY', radius=2439.7),
- Planet(_('Venus'), 'VENUS', radius=6051.8),
- Planet(_('Mars'), 'MARS', radius=3396.2),
- Planet(_('Jupiter'), 'JUPITER BARYCENTER', radius=71492),
- Planet(_('Saturn'), 'SATURN BARYCENTER', radius=60268),
- Planet(_('Uranus'), 'URANUS BARYCENTER', radius=25559),
- Planet(_('Neptune'), 'NEPTUNE BARYCENTER', radius=24764),
- Planet(_('Pluto'), 'PLUTO BARYCENTER', radius=1185)]
-
-
-class Position:
- def __init__(self, latitude: float, longitude: float, aster: Object):
- self.latitude = latitude
- self.longitude = longitude
- self.aster = aster
- self._topos = None
-
- def get_planet_topos(self) -> Topos:
- if self.aster is None:
- raise TypeError('Observation planet must be set.')
-
- if self._topos is None:
- self._topos = self.aster.get_skyfield_object() + Topos(latitude_degrees=self.latitude,
- longitude_degrees=self.longitude)
-
- return self._topos
diff --git a/kosmorrolib/dateutil.py b/kosmorrolib/dateutil.py
deleted file mode 100644
index 6ff64d4..0000000
--- a/kosmorrolib/dateutil.py
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env python3
-
-# Kosmorro - Compute The Next Ephemerides
-# Copyright (C) 2019 Jérôme Deuchnord
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from datetime import datetime, timezone, timedelta
-
-
-def translate_to_timezone(date: datetime, to_tz: int, from_tz: int = None):
- if from_tz is not None:
- source_tz = timezone(timedelta(hours=from_tz))
- else:
- source_tz = timezone.utc
-
- return date.replace(tzinfo=source_tz).astimezone(tz=timezone(timedelta(hours=to_tz)))
diff --git a/kosmorrolib/dumper.py b/kosmorrolib/dumper.py
deleted file mode 100644
index e12d558..0000000
--- a/kosmorrolib/dumper.py
+++ /dev/null
@@ -1,385 +0,0 @@
-#!/usr/bin/env python3
-
-# Kosmorro - Compute The Next Ephemerides
-# Copyright (C) 2019 Jérôme Deuchnord
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from abc import ABC, abstractmethod
-import datetime
-import json
-import os
-import tempfile
-import subprocess
-import shutil
-from pathlib import Path
-from tabulate import tabulate
-from termcolor import colored
-
-from .data import ASTERS, AsterEphemerides, MoonPhase, Event
-from .i18n import _, FULL_DATE_FORMAT, SHORT_DATETIME_FORMAT, TIME_FORMAT
-from .version import VERSION
-from .exceptions import UnavailableFeatureError, CompileError
-
-
-class Dumper(ABC):
- def __init__(self, ephemerides: [AsterEphemerides] = None, moon_phase: MoonPhase = None, events: [Event] = None,
- date: datetime.date = datetime.date.today(), timezone: int = 0, with_colors: bool = True,
- show_graph: bool = False):
- self.ephemerides = ephemerides
- self.moon_phase = moon_phase
- self.events = events
- self.date = date
- self.timezone = timezone
- self.with_colors = with_colors
- self.show_graph = show_graph
-
- def get_date_as_string(self, capitalized: bool = False) -> str:
- date = self.date.strftime(FULL_DATE_FORMAT)
-
- if capitalized:
- return ''.join([date[0].upper(), date[1:]])
-
- return date
-
- def __str__(self):
- return self.to_string()
-
- @abstractmethod
- def to_string(self):
- pass
-
- @staticmethod
- def is_file_output_needed() -> bool:
- return False
-
-
-class JsonDumper(Dumper):
- def to_string(self):
- return json.dumps({
- 'ephemerides': [ephemeris.serialize() for ephemeris in self.ephemerides],
- 'moon_phase': self.moon_phase.serialize(),
- 'events': [event.serialize() for event in self.events]
- }, indent=4)
-
-
-class TextDumper(Dumper):
- def to_string(self):
- text = [self.style(self.get_date_as_string(capitalized=True), 'h1')]
-
- if self.ephemerides is not None:
- text.append(self.stringify_ephemerides())
-
- text.append(self.get_moon(self.moon_phase))
-
- if len(self.events) > 0:
- text.append('\n'.join([self.style(_('Expected events:'), 'h2'),
- self.get_events(self.events)]))
-
- if self.timezone == 0:
- text.append(self.style(_('Note: All the hours are given in UTC.'), 'em'))
- else:
- tz_offset = str(self.timezone)
- if self.timezone > 0:
- tz_offset = ''.join(['+', tz_offset])
- text.append(self.style(_('Note: All the hours are given in the UTC{offset} timezone.').format(
- offset=tz_offset), 'em'))
-
- return '\n\n'.join(text)
-
- def style(self, text: str, tag: str) -> str:
- if not self.with_colors:
- return text
-
- styles = {
- 'h1': lambda t: colored(t, 'yellow', attrs=['bold']),
- 'h2': lambda t: colored(t, 'magenta', attrs=['bold']),
- 'th': lambda t: colored(t, 'white', attrs=['bold']),
- 'strong': lambda t: colored(t, attrs=['bold']),
- 'em': lambda t: colored(t, attrs=['dark'])
- }
-
- return styles[tag](text)
-
- def stringify_ephemerides(self) -> str:
- data = []
-
- for ephemeris in self.ephemerides:
- name = self.style(ephemeris.object.name, 'th')
-
- if ephemeris.rise_time is not None:
- time_fmt = TIME_FORMAT if ephemeris.rise_time.day == self.date.day else SHORT_DATETIME_FORMAT
- planet_rise = ephemeris.rise_time.strftime(time_fmt)
- else:
- planet_rise = '-'
-
- if ephemeris.culmination_time is not None:
- time_fmt = TIME_FORMAT if ephemeris.culmination_time.day == self.date.day \
- else SHORT_DATETIME_FORMAT
- planet_culmination = ephemeris.culmination_time.strftime(time_fmt)
- else:
- planet_culmination = '-'
-
- if ephemeris.set_time is not None:
- time_fmt = TIME_FORMAT if ephemeris.set_time.day == self.date.day else SHORT_DATETIME_FORMAT
- planet_set = ephemeris.set_time.strftime(time_fmt)
- else:
- planet_set = '-'
-
- data.append([name, planet_rise, planet_culmination, planet_set])
-
- return tabulate(data, headers=[self.style(_('Object'), 'th'),
- self.style(_('Rise time'), 'th'),
- self.style(_('Culmination time'), 'th'),
- self.style(_('Set time'), 'th')],
- tablefmt='simple', stralign='center', colalign=('left',))
-
- def get_events(self, events: [Event]) -> str:
- data = []
-
- for event in events:
- time_fmt = TIME_FORMAT if event.start_time.day == self.date.day else SHORT_DATETIME_FORMAT
- data.append([self.style(event.start_time.strftime(time_fmt), 'th'),
- event.get_description()])
-
- return tabulate(data, tablefmt='plain', stralign='left')
-
- def get_moon(self, moon_phase: MoonPhase) -> str:
- if moon_phase is None:
- return _('Moon phase is unavailable for this date.')
-
- current_moon_phase = ' '.join([self.style(_('Moon phase:'), 'strong'), moon_phase.phase_type.value])
- new_moon_phase = _('{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}').format(
- next_moon_phase=moon_phase.get_next_phase().value,
- next_moon_phase_date=moon_phase.next_phase_date.strftime(FULL_DATE_FORMAT),
- next_moon_phase_time=moon_phase.next_phase_date.strftime(TIME_FORMAT)
- )
-
- return '\n'.join([current_moon_phase, new_moon_phase])
-
-
-class _LatexDumper(Dumper):
- def to_string(self):
- template_path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
- 'assets', 'pdf', 'template.tex')
-
- with open(template_path, mode='r') as file:
- template = file.read()
-
- return self._make_document(template)
-
- def _make_document(self, template: str) -> str:
- kosmorro_logo_path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
- 'assets', 'png', 'kosmorro-logo.png')
-
- moon_phase_graphics = os.path.join(os.path.abspath(os.path.dirname(__file__)),
- 'assets', 'moonphases', 'png',
- '.'.join([self.moon_phase.phase_type.name.lower().replace('_', '-'),
- 'png']))
-
- document = template
-
- if self.ephemerides is None:
- document = self._remove_section(document, 'ephemerides')
-
- if len(self.events) == 0:
- document = self._remove_section(document, 'events')
-
- document = self.add_strings(document, kosmorro_logo_path, moon_phase_graphics)
-
- if self.show_graph:
- # The graphephemerides environment beginning tag must end with a percent symbol to ensure
- # that no extra space will interfere with the graph.
- document = document.replace(r'\begin{ephemerides}', r'\begin{graphephemerides}%')\
- .replace(r'\end{ephemerides}', r'\end{graphephemerides}')
-
- return document
-
- def add_strings(self, document, kosmorro_logo_path, moon_phase_graphics) -> str:
- document = document \
- .replace('+++KOSMORRO-VERSION+++', VERSION) \
- .replace('+++KOSMORRO-LOGO+++', kosmorro_logo_path) \
- .replace('+++DOCUMENT-TITLE+++', _('A Summary of your Sky')) \
- .replace('+++DOCUMENT-DATE+++', self.get_date_as_string(capitalized=True)) \
- .replace('+++INTRODUCTION+++',
- '\n\n'.join([
- _("This document summarizes the ephemerides and the events of {date}. "
- "It aims to help you to prepare your observation session. "
- "All the hours are given in {timezone}.").format(
- date=self.get_date_as_string(),
- timezone='UTC+%d' % self.timezone if self.timezone != 0 else 'UTC'
- ),
- _("Don't forget to check the weather forecast before you go out with your equipment.")
- ])) \
- .replace('+++SECTION-EPHEMERIDES+++', _('Ephemerides of the day')) \
- .replace('+++EPHEMERIDES-OBJECT+++', _('Object')) \
- .replace('+++EPHEMERIDES-RISE-TIME+++', _('Rise time')) \
- .replace('+++EPHEMERIDES-CULMINATION-TIME+++', _('Culmination time')) \
- .replace('+++EPHEMERIDES-SET-TIME+++', _('Set time')) \
- .replace('+++EPHEMERIDES+++', self._make_ephemerides()) \
- .replace('+++GRAPH_LABEL_HOURS+++', _('hours')) \
- .replace('+++MOON-PHASE-GRAPHICS+++', moon_phase_graphics) \
- .replace('+++CURRENT-MOON-PHASE-TITLE+++', _('Moon phase:')) \
- .replace('+++CURRENT-MOON-PHASE+++', self.moon_phase.phase_type.value) \
- .replace('+++SECTION-EVENTS+++', _('Expected events')) \
- .replace('+++EVENTS+++', self._make_events())
-
- for aster in ASTERS:
- document = document.replace('+++ASTER_%s+++' % aster.skyfield_name.upper().split(' ')[0],
- aster.name)
-
- return document
-
- def _make_ephemerides(self) -> str:
- latex = []
- graph_y_component = 18
-
- if self.ephemerides is not None:
- for ephemeris in self.ephemerides:
- if ephemeris.rise_time is not None:
- time_fmt = TIME_FORMAT if ephemeris.rise_time.day == self.date.day else SHORT_DATETIME_FORMAT
- aster_rise = ephemeris.rise_time.strftime(time_fmt)
- else:
- aster_rise = '-'
-
- if ephemeris.culmination_time is not None:
- time_fmt = TIME_FORMAT if ephemeris.culmination_time.day == self.date.day\
- else SHORT_DATETIME_FORMAT
- aster_culmination = ephemeris.culmination_time.strftime(time_fmt)
- else:
- aster_culmination = '-'
-
- if ephemeris.set_time is not None:
- time_fmt = TIME_FORMAT if ephemeris.set_time.day == self.date.day else SHORT_DATETIME_FORMAT
- aster_set = ephemeris.set_time.strftime(time_fmt)
- else:
- aster_set = '-'
-
- if not self.show_graph:
- latex.append(r'\object{%s}{%s}{%s}{%s}' % (ephemeris.object.name,
- aster_rise,
- aster_culmination,
- aster_set))
- else:
- if ephemeris.rise_time is not None:
- raise_hour = ephemeris.rise_time.hour
- raise_minute = ephemeris.rise_time.minute
- else:
- raise_hour = raise_minute = 0
- aster_rise = ''
-
- if ephemeris.set_time is not None:
- set_hour = ephemeris.set_time.hour
- set_minute = ephemeris.set_time.minute
- else:
- set_hour = 24
- set_minute = 0
- aster_set = ''
- sets_after_end = set_hour > raise_hour
-
- if not sets_after_end:
- latex.append(r'\graphobject{%d}{gray}{0}{0}{%d}{%d}{}{%s}' % (graph_y_component,
- set_hour,
- set_minute,
- aster_set))
- set_hour = 24
- set_minute = 0
-
- latex.append(r'\graphobject{%d}{gray}{%d}{%d}{%d}{%d}{%s}{%s}' % (
- graph_y_component,
- raise_hour,
- raise_minute,
- set_hour,
- set_minute,
- aster_rise,
- aster_set if sets_after_end else ''
- ))
- graph_y_component -= 2
-
- return ''.join(latex)
-
- def _make_events(self) -> str:
- latex = []
-
- for event in self.events:
- latex.append(r'\event{%s}{%s}' % (event.start_time.strftime(TIME_FORMAT),
- event.get_description()))
-
- return ''.join(latex)
-
- @staticmethod
- def _remove_section(document: str, section: str):
- begin_section_tag = '%%%%%% BEGIN-%s-SECTION' % section.upper()
- end_section_tag = '%%%%%% END-%s-SECTION' % section.upper()
-
- document = document.split('\n')
- new_document = []
-
- ignore_line = False
- for line in document:
- if begin_section_tag in line or end_section_tag in line:
- ignore_line = not ignore_line
- continue
- if ignore_line:
- continue
- new_document.append(line)
-
- return '\n'.join(new_document)
-
-
-class PdfDumper(Dumper):
- def to_string(self):
- try:
- latex_dumper = _LatexDumper(self.ephemerides, self.moon_phase, self.events,
- date=self.date, timezone=self.timezone, with_colors=self.with_colors,
- show_graph=self.show_graph)
- return self._compile(latex_dumper.to_string())
- except CompileError as error:
- raise UnavailableFeatureError from error
- except RuntimeError as error:
- raise UnavailableFeatureError(_("Building PDFs was not possible, because some dependencies are not"
- " installed.\nPlease look at the documentation at http://kosmorro.space "
- "for more information.")) from error
-
- @staticmethod
- def is_file_output_needed() -> bool:
- return True
-
- @staticmethod
- def _compile(latex_input) -> bytes:
- package = str(Path(__file__).parent.absolute()) + '/assets/pdf/kosmorro.sty'
-
- with tempfile.TemporaryDirectory() as tempdir:
- timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
- shutil.copy(package, tempdir)
-
- with open('%s/%s.tex' % (tempdir, timestamp), 'w') as texfile:
- texfile.write(latex_input)
-
- os.chdir(tempdir)
- try:
- subprocess.run(['pdflatex', '-interaction', 'nonstopmode', '%s.tex' % timestamp],
- capture_output=True, check=True)
- except FileNotFoundError as error:
- raise RuntimeError('pdflatex is not installed.') from error
- except subprocess.CalledProcessError as error:
- with open('/tmp/kosmorro-%s.log' % timestamp, 'wb') as file:
- file.write(error.stdout)
-
- raise CompileError(_('An error occured during the compilation of the PDF.\n'
- 'Please open an issue at https://github.com/Kosmorro/kosmorro/issues and share '
- 'the content of the log file at /tmp/kosmorro-%s.log' % timestamp)) from error
-
- with open('%s.pdf' % timestamp, 'rb') as pdffile:
- return bytes(pdffile.read())
diff --git a/kosmorrolib/ephemerides.py b/kosmorrolib/ephemerides.py
deleted file mode 100644
index 2ddc578..0000000
--- a/kosmorrolib/ephemerides.py
+++ /dev/null
@@ -1,163 +0,0 @@
-#!/usr/bin/env python3
-
-# Kosmorro - Compute The Next Ephemerides
-# Copyright (C) 2019 Jérôme Deuchnord
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import datetime
-from typing import Union
-
-from skyfield.searchlib import find_discrete, find_maxima
-from skyfield.timelib import Time
-from skyfield.constants import tau
-from skyfield.errors import EphemerisRangeError
-
-from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS
-from .dateutil import translate_to_timezone
-from .core import get_skf_objects, get_timescale, get_iau2000b
-from .enum import MoonPhaseType
-from .exceptions import OutOfRangeDateError
-
-RISEN_ANGLE = -0.8333
-
-
-def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]:
- tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1)
-
- phases = list(MoonPhaseType)
- current_phase = None
- current_phase_time = None
- next_phase_time = None
- i = 0
-
- if len(times) == 0:
- return None
-
- for i, time in enumerate(times):
- if now.utc_iso() <= time.utc_iso():
- if vals[i] in [0, 2, 4, 6]:
- if time.utc_datetime() < tomorrow.utc_datetime():
- current_phase_time = time
- current_phase = phases[vals[i]]
- else:
- i -= 1
- current_phase_time = None
- current_phase = phases[vals[i]]
- else:
- current_phase = phases[vals[i]]
-
- break
-
- for j in range(i + 1, len(times)):
- if vals[j] in [0, 2, 4, 6]:
- next_phase_time = times[j]
- break
-
- return MoonPhase(current_phase,
- current_phase_time.utc_datetime() if current_phase_time is not None else None,
- next_phase_time.utc_datetime() if next_phase_time is not None else None)
-
-
-def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:
- earth = get_skf_objects()['earth']
- moon = get_skf_objects()['moon']
- sun = get_skf_objects()['sun']
-
- def moon_phase_at(time: Time):
- time._nutation_angles = get_iau2000b(time)
- current_earth = earth.at(time)
- _, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon('date')
- _, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon('date')
- return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int)
-
- moon_phase_at.rough_period = 7.0 # one lunar phase per week
-
- today = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day)
- time1 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day - 10)
- time2 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day + 10)
-
- try:
- times, phase = find_discrete(time1, time2, moon_phase_at)
- except EphemerisRangeError as error:
- start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
- end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
-
- start = datetime.date(start.year, start.month, start.day) + datetime.timedelta(days=12)
- end = datetime.date(end.year, end.month, end.day) - datetime.timedelta(days=12)
-
- raise OutOfRangeDateError(start, end) from error
-
- return _get_skyfield_to_moon_phase(times, phase, today)
-
-
-def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]:
- ephemerides = []
-
- def get_angle(for_aster: Object):
- def fun(time: Time) -> float:
- return position.get_planet_topos().at(time).observe(for_aster.get_skyfield_object()).apparent().altaz()[0]\
- .degrees
- fun.rough_period = 1.0
- return fun
-
- def is_risen(for_aster: Object):
- def fun(time: Time) -> bool:
- return get_angle(for_aster)(time) > RISEN_ANGLE
- fun.rough_period = 0.5
- return fun
-
- start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
- end_time = get_timescale().utc(date.year, date.month, date.day, 23 - timezone, 59, 59)
-
- try:
- for aster in ASTERS:
- rise_times, arr = find_discrete(start_time, end_time, is_risen(aster))
- try:
- culmination_time, _ = find_maxima(start_time, end_time, f=get_angle(aster), epsilon=1./3600/24, num=12)
- culmination_time = culmination_time[0] if len(culmination_time) > 0 else None
- except ValueError:
- culmination_time = None
-
- if len(rise_times) == 2:
- rise_time = rise_times[0 if arr[0] else 1]
- set_time = rise_times[1 if not arr[1] else 0]
- else:
- rise_time = rise_times[0] if arr[0] else None
- set_time = rise_times[0] if not arr[0] else None
-
- # Convert the Time instances to Python datetime objects
- if rise_time is not None:
- rise_time = translate_to_timezone(rise_time.utc_datetime().replace(microsecond=0),
- to_tz=timezone)
-
- if culmination_time is not None:
- culmination_time = translate_to_timezone(culmination_time.utc_datetime().replace(microsecond=0),
- to_tz=timezone)
-
- if set_time is not None:
- set_time = translate_to_timezone(set_time.utc_datetime().replace(microsecond=0),
- to_tz=timezone)
-
- ephemerides.append(AsterEphemerides(rise_time, culmination_time, set_time, aster=aster))
- except EphemerisRangeError as error:
- start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
- end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
-
- start = datetime.date(start.year, start.month, start.day + 1)
- end = datetime.date(end.year, end.month, end.day - 1)
-
- raise OutOfRangeDateError(start, end) from error
-
- return ephemerides
diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py
deleted file mode 100644
index 661246f..0000000
--- a/kosmorrolib/events.py
+++ /dev/null
@@ -1,199 +0,0 @@
-#!/usr/bin/env python3
-
-# Kosmorro - Compute The Next Ephemerides
-# Copyright (C) 2019 Jérôme Deuchnord
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-from datetime import date as date_type
-
-from skyfield.errors import EphemerisRangeError
-from skyfield.timelib import Time
-from skyfield.searchlib import find_discrete, find_maxima, find_minima
-from numpy import pi
-
-from .data import Event, Star, Planet, ASTERS
-from .dateutil import translate_to_timezone
-from .enum import EventType
-from .exceptions import OutOfRangeDateError
-from .core import get_timescale, get_skf_objects, flatten_list
-
-
-def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Event]:
- earth = get_skf_objects()['earth']
- aster1 = None
- aster2 = None
-
- def is_in_conjunction(time: Time):
- earth_pos = earth.at(time)
- _, aster1_lon, _ = earth_pos.observe(aster1.get_skyfield_object()).apparent().ecliptic_latlon()
- _, aster2_lon, _ = earth_pos.observe(aster2.get_skyfield_object()).apparent().ecliptic_latlon()
-
- return ((aster1_lon.radians - aster2_lon.radians) / pi % 2.0).astype('int8') == 0
-
- is_in_conjunction.rough_period = 60.0
-
- computed = []
- conjunctions = []
-
- for aster1 in ASTERS:
- # Ignore the Sun
- if isinstance(aster1, Star):
- continue
-
- for aster2 in ASTERS:
- if isinstance(aster2, Star) or aster2 == aster1 or aster2 in computed:
- continue
-
- times, is_conjs = find_discrete(start_time, end_time, is_in_conjunction)
-
- for i, time in enumerate(times):
- if is_conjs[i]:
- aster1_pos = (aster1.get_skyfield_object() - earth).at(time)
- aster2_pos = (aster2.get_skyfield_object() - earth).at(time)
- distance = aster1_pos.separation_from(aster2_pos).degrees
-
- if distance - aster2.get_apparent_radius(time, earth) < aster1.get_apparent_radius(time, earth):
- occulting_aster = [aster1,
- aster2] if aster1_pos.distance().km < aster2_pos.distance().km else [aster2,
- aster1]
-
- conjunctions.append(Event(EventType.OCCULTATION, occulting_aster,
- translate_to_timezone(time.utc_datetime(), timezone)))
- else:
- conjunctions.append(Event(EventType.CONJUNCTION, [aster1, aster2],
- translate_to_timezone(time.utc_datetime(), timezone)))
-
- computed.append(aster1)
-
- return conjunctions
-
-
-def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Event]:
- earth = get_skf_objects()['earth']
- sun = get_skf_objects()['sun']
- aster = None
-
- def is_oppositing(time: Time) -> [bool]:
- earth_pos = earth.at(time)
- sun_pos = earth_pos.observe(sun).apparent() # Never do this without eyes protection!
- aster_pos = earth_pos.observe(get_skf_objects()[aster.skyfield_name]).apparent()
- _, lon1, _ = sun_pos.ecliptic_latlon()
- _, lon2, _ = aster_pos.ecliptic_latlon()
- return (lon1.degrees - lon2.degrees) > 180
-
- is_oppositing.rough_period = 1.0
- events = []
-
- for aster in ASTERS:
- if not isinstance(aster, Planet) or aster.skyfield_name in ['MERCURY', 'VENUS']:
- continue
-
- times, _ = find_discrete(start_time, end_time, is_oppositing)
- for time in times:
- events.append(Event(EventType.OPPOSITION, [aster], translate_to_timezone(time.utc_datetime(), timezone)))
-
- return events
-
-
-def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int) -> [Event]:
- earth = get_skf_objects()['earth']
- sun = get_skf_objects()['sun']
- aster = None
-
- def get_elongation(time: Time):
- sun_pos = (sun - earth).at(time)
- aster_pos = (aster.get_skyfield_object() - earth).at(time)
- separation = sun_pos.separation_from(aster_pos)
- return separation.degrees
-
- get_elongation.rough_period = 1.0
-
- events = []
-
- for aster in ASTERS:
- if aster.skyfield_name not in ['MERCURY', 'VENUS']:
- continue
-
- times, elongations = find_maxima(start_time, end_time, f=get_elongation, epsilon=1./24/3600, num=12)
-
- for i, time in enumerate(times):
- elongation = elongations[i]
- events.append(Event(EventType.MAXIMAL_ELONGATION,
- [aster],
- translate_to_timezone(time.utc_datetime(), timezone),
- details='{:.3n}°'.format(elongation)))
-
- return events
-
-
-def _get_moon_distance():
- earth = get_skf_objects()['earth']
- moon = get_skf_objects()['moon']
-
- def get_distance(time: Time):
- earth_pos = earth.at(time)
- moon_pos = earth_pos.observe(moon).apparent()
-
- return moon_pos.distance().au
-
- get_distance.rough_period = 1.0
-
- return get_distance
-
-
-def _search_moon_apogee(start_time: Time, end_time: Time, timezone: int) -> [Event]:
- moon = ASTERS[1]
- events = []
-
- times, _ = find_maxima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60)
-
- for time in times:
- events.append(Event(EventType.MOON_APOGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone)))
-
- return events
-
-
-def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Event]:
- moon = ASTERS[1]
- events = []
-
- times, _ = find_minima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60)
-
- for time in times:
- events.append(Event(EventType.MOON_PERIGEE, [moon], translate_to_timezone(time.utc_datetime(), timezone)))
-
- return events
-
-
-def search_events(date: date_type, timezone: int = 0) -> [Event]:
- start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
- end_time = get_timescale().utc(date.year, date.month, date.day + 1, -timezone)
-
- try:
- return sorted(flatten_list([
- _search_oppositions(start_time, end_time, timezone),
- _search_conjunction(start_time, end_time, timezone),
- _search_maximal_elongations(start_time, end_time, timezone),
- _search_moon_apogee(start_time, end_time, timezone),
- _search_moon_perigee(start_time, end_time, timezone),
- ]), key=lambda event: event.start_time)
- except EphemerisRangeError as error:
- start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone)
- end_date = translate_to_timezone(error.end_time.utc_datetime(), timezone)
-
- start_date = date_type(start_date.year, start_date.month, start_date.day)
- end_date = date_type(end_date.year, end_date.month, end_date.day)
-
- raise OutOfRangeDateError(start_date, end_date) from error
diff --git a/kosmorrolib/main.py b/kosmorrolib/main.py
deleted file mode 100644
index c971ffe..0000000
--- a/kosmorrolib/main.py
+++ /dev/null
@@ -1,195 +0,0 @@
-#!/usr/bin/env python3
-
-# Kosmorro - Compute The Next Ephemerides
-# Copyright (C) 2019 Jérôme Deuchnord
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import argparse
-import locale
-import re
-import sys
-
-from datetime import date
-from termcolor import colored
-
-from . import dumper
-from . import core
-from . import events
-
-from .data import Position, EARTH
-from .exceptions import UnavailableFeatureError, OutOfRangeDateError
-from .i18n import _
-from . import ephemerides
-from .version import VERSION
-
-
-def main():
- environment = core.get_env()
- output_formats = get_dumpers()
- args = get_args(list(output_formats.keys()))
- output_format = args.format
-
- if args.special_action is not None:
- return 0 if args.special_action() else 1
-
- try:
- compute_date = core.get_date(args.date)
- except ValueError as error:
- print(colored(error.args[0], color='red', attrs=['bold']))
- return -1
-
- position = None
-
- if args.latitude is not None or args.longitude is not None:
- position = Position(args.latitude, args.longitude, EARTH)
- elif environment.latitude is not None and environment.longitude is not None:
- position = Position(float(environment.latitude), float(environment.longitude), EARTH)
-
- if output_format == 'pdf':
- print(_('Save the planet and paper!\n'
- 'Consider printing you PDF document only if really necessary, and use the other side of the sheet.'))
- if position is None:
- print()
- print(colored(_("PDF output will not contain the ephemerides, because you didn't provide the observation "
- "coordinate."), 'yellow'))
-
- timezone = args.timezone
-
- if timezone is None and environment.timezone is not None:
- timezone = int(environment.timezone)
- elif timezone is None:
- timezone = 0
-
- try:
- output = get_information(compute_date, position, timezone, output_format,
- args.colors, args.show_graph)
- except UnavailableFeatureError as error:
- print(colored(error.msg, 'red'))
- return 2
- except OutOfRangeDateError as error:
- print(colored(error.msg, 'red'))
- return 1
-
- if args.output is not None:
- try:
- with open(args.output, 'wb') as output_file:
- output_file.write(output.to_string())
- except UnavailableFeatureError as error:
- print(colored(error.msg, 'red'))
- return 2
- except OSError as error:
- print(colored(_('Could not save the output in "{path}": {error}').format(path=args.output,
- error=error.strerror),
- 'red'))
- return 3
- elif not output.is_file_output_needed():
- print(output)
- else:
- print(colored(_('Selected output format needs an output file (--output).'), color='red'))
- return 1
-
- return 0
-
-
-def get_information(compute_date: date, position: Position, timezone: int,
- output_format: str, colors: bool, show_graph: bool) -> dumper.Dumper:
- if position is not None:
- eph = ephemerides.get_ephemerides(date=compute_date, position=position, timezone=timezone)
- else:
- eph = None
-
- try:
- moon_phase = ephemerides.get_moon_phase(compute_date)
- except OutOfRangeDateError as error:
- moon_phase = None
- print(colored(_('Moon phase can only be displayed'
- ' between {min_date} and {max_date}').format(min_date=error.min_date,
- max_date=error.max_date), 'yellow'))
-
- events_list = events.search_events(compute_date, timezone)
-
- return get_dumpers()[output_format](ephemerides=eph, moon_phase=moon_phase, events=events_list,
- date=compute_date, timezone=timezone, with_colors=colors,
- show_graph=show_graph)
-
-
-def get_dumpers() -> {str: dumper.Dumper}:
- return {
- 'text': dumper.TextDumper,
- 'json': dumper.JsonDumper,
- 'pdf': dumper.PdfDumper,
- }
-
-
-def output_version() -> bool:
- python_version = '%d.%d.%d' % (sys.version_info[0], sys.version_info[1], sys.version_info[2])
- print('Kosmorro %s' % VERSION)
- print(_('Running on Python {python_version}').format(python_version=python_version))
-
- return True
-
-
-def clear_cache() -> bool:
- confirm = input(_("Do you really want to clear Kosmorro's cache? [yN] ")).upper()
- if re.match(locale.nl_langinfo(locale.YESEXPR), confirm) is not None:
- try:
- core.clear_cache()
- except FileNotFoundError:
- pass
- elif confirm != '' and re.match(locale.nl_langinfo(locale.NOEXPR), confirm) is None:
- print(_('Answer did not match expected options, cache not cleared.'))
- return False
-
- return True
-
-
-def get_args(output_formats: [str]):
- today = date.today()
-
- parser = argparse.ArgumentParser(description=_('Compute the ephemerides and the events for a given date,'
- ' at a given position on Earth.'),
- epilog=_('By default, only the events will be computed for today ({date}).\n'
- 'To compute also the ephemerides, latitude and longitude arguments'
- ' are needed.').format(date=today.strftime(dumper.FULL_DATE_FORMAT)))
-
- parser.add_argument('--version', '-v', dest='special_action', action='store_const', const=output_version,
- default=None, help=_('Show the program version'))
- parser.add_argument('--clear-cache', dest='special_action', action='store_const', const=clear_cache, default=None,
- help=_('Delete all the files Kosmorro stored in the cache.'))
- parser.add_argument('--format', '-f', type=str, default=output_formats[0], choices=output_formats,
- help=_('The format under which the information have to be output'))
- parser.add_argument('--latitude', '-lat', type=float, default=None,
- help=_("The observer's latitude on Earth. Can also be set in the KOSMORRO_LATITUDE environment "
- "variable."))
- parser.add_argument('--longitude', '-lon', type=float, default=None,
- help=_("The observer's longitude on Earth. Can also be set in the KOSMORRO_LONGITUDE "
- "environment variable."))
- parser.add_argument('--date', '-d', type=str, default=today.strftime('%Y-%m-%d'),
- help=_('The date for which the ephemerides must be computed (in the YYYY-MM-DD format), '
- 'or as an interval in the "[+-]YyMmDd" format (with Y, M, and D numbers). '
- 'Defaults to the current date ({default_date})').format(
- default_date=today.strftime('%Y-%m-%d')))
- parser.add_argument('--timezone', '-t', type=int, default=None,
- help=_('The timezone to display the hours in (e.g. 2 for UTC+2 or -3 for UTC-3). '
- 'Can also be set in the KOSMORRO_TIMEZONE environment variable.'))
- parser.add_argument('--no-colors', dest='colors', action='store_false',
- help=_('Disable the colors in the console.'))
- parser.add_argument('--output', '-o', type=str, default=None,
- help=_('A file to export the output to. If not given, the standard output is used. '
- 'This argument is needed for PDF format.'))
- parser.add_argument('--no-graph', dest='show_graph', action='store_false',
- help=_('Do not generate a graph to represent the rise and set times in the PDF format.'))
-
- return parser.parse_args()
diff --git a/setup.py b/setup.py
index badbe2f..ee01c8f 100644
--- a/setup.py
+++ b/setup.py
@@ -19,36 +19,41 @@
import pathlib
from setuptools import setup, find_packages
-from kosmorrolib.version import VERSION
+from _kosmorro.__version__ import __version__
HERE = pathlib.Path(__file__).parent
-README = (HERE / 'README.md').read_text()
+README = (HERE / "README.md").read_text()
setup(
- name='kosmorro',
- version=VERSION,
- author='Jérôme Deuchnord',
- author_email='jerome@deuchnord.fr',
- url='http://kosmorro.space',
- license='AGPL-3.0',
- description='A program that computes the ephemerides.',
+ name="kosmorro",
+ version=__version__,
+ author="Jérôme Deuchnord",
+ author_email="jerome@deuchnord.fr",
+ url="http://kosmorro.space",
+ license="AGPL-3.0",
+ description="A program that computes the ephemerides.",
long_description=README,
- long_description_content_type='text/markdown',
- keywords='kosmorro astronomy ephemerides ephemeris',
+ long_description_content_type="text/markdown",
+ keywords="kosmorro astronomy ephemerides ephemeris",
packages=find_packages(),
- scripts=['kosmorro'],
+ scripts=["kosmorro"],
include_package_data=True,
data_files=[
- ('man/man1', ['manpage/kosmorro.1']),
- ('man/man7', ['manpage/kosmorro.7'])
+ ("man/man1", ["manpage/kosmorro.1"]),
+ ("man/man7", ["manpage/kosmorro.7"]),
+ ],
+ install_requires=[
+ "kosmorrolib",
+ "tabulate",
+ "termcolor",
+ "python-dateutil",
],
- install_requires=['skyfield>=1.21.0,<2.0.0', 'tabulate', 'numpy>=1.17.0,<2.0.0', 'termcolor', 'python-dateutil'],
classifiers=[
- 'Development Status :: 3 - Alpha',
- 'Operating System :: POSIX :: Linux',
- 'Operating System :: MacOS :: MacOS X',
- 'Environment :: Console',
- 'Topic :: Scientific/Engineering :: Astronomy'
+ "Development Status :: 3 - Alpha",
+ "Operating System :: POSIX :: Linux",
+ "Operating System :: MacOS :: MacOS X",
+ "Environment :: Console",
+ "Topic :: Scientific/Engineering :: Astronomy",
],
- python_requires='>=3.7'
+ python_requires=">=3.7",
)
diff --git a/test/__init__.py b/test/__init__.py
deleted file mode 100644
index bdb6885..0000000
--- a/test/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from .core import *
-from .data import *
-from .dumper import *
-from .ephemerides import *
-from .events import *
-from .testutils import *
-from .dateutil import *
diff --git a/test/core.py b/test/core.py
deleted file mode 100644
index 34cf8e5..0000000
--- a/test/core.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import unittest
-
-import os
-import kosmorrolib.core as core
-
-from datetime import date
-from dateutil.relativedelta import relativedelta
-
-
-class CoreTestCase(unittest.TestCase):
- def test_flatten_list(self):
- self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], core.flatten_list([0, 1, 2, [3, 4, [5, 6], 7], 8, [9]]))
-
- def test_get_env(self):
- self.assertEqual(0, len(core.get_env()))
-
- os.environ['SOME_RANDOM_VAR'] = 'an awesome value'
- self.assertEqual(0, len(core.get_env()))
-
- os.environ['KOSMORRO_GREAT_VARIABLE'] = 'value'
- env = core.get_env()
- self.assertEqual(1, len(env))
- self.assertEqual('value', env.great_variable)
-
- os.environ['KOSMORRO_ANOTHER_VARIABLE'] = 'another value'
- env = core.get_env()
- self.assertEqual(2, len(env))
- self.assertEqual('value', env.great_variable)
- self.assertEqual('another value', env.another_variable)
-
- self.assertEqual("{'great_variable': 'value', 'another_variable': 'another value'}", str(env))
-
- def test_date_arg_parsing(self):
- self.assertEqual(core.get_date("+1y 2m3d"), date.today() + relativedelta(years=1, months=2, days=3))
- self.assertEqual(core.get_date("-1y2d"), date.today() - relativedelta(years=1, days=2))
- self.assertEqual(core.get_date("1111-11-13"), date(1111, 11, 13))
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/test/data.py b/test/data.py
deleted file mode 100644
index d1e2103..0000000
--- a/test/data.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import unittest
-
-from kosmorrolib import data, core
-
-
-class DataTestCase(unittest.TestCase):
- def test_object_radius_must_be_set_to_get_apparent_radius(self):
- o = data.Planet('Saturn', 'SATURN')
-
- with self.assertRaises(ValueError) as context:
- o.get_apparent_radius(core.get_timescale().now(), core.get_skf_objects()['earth'])
-
- self.assertEqual(('Missing radius for Saturn object',), context.exception.args)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/test/dateutil.py b/test/dateutil.py
deleted file mode 100644
index 984add3..0000000
--- a/test/dateutil.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import unittest
-
-from kosmorrolib import dateutil
-from datetime import datetime
-
-
-class DateUtilTestCase(unittest.TestCase):
- def test_translate_to_timezone(self):
- date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 4), to_tz=-2)
- self.assertEqual(2, date.hour)
-
- date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 0), to_tz=2)
- self.assertEqual(2, date.hour)
-
- date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 8), to_tz=2, from_tz=6)
- self.assertEqual(4, date.hour)
-
- date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 1), to_tz=0, from_tz=2)
- self.assertEqual(8, date.day)
- self.assertEqual(23, date.hour)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/test/dumper.py b/test/dumper.py
deleted file mode 100644
index e37b572..0000000
--- a/test/dumper.py
+++ /dev/null
@@ -1,301 +0,0 @@
-import unittest
-from datetime import date, datetime
-
-from kosmorrolib.data import AsterEphemerides, Planet, MoonPhase, Event
-from kosmorrolib.dumper import JsonDumper, TextDumper, _LatexDumper
-from kosmorrolib.enum import MoonPhaseType, EventType
-
-
-class DumperTestCase(unittest.TestCase):
- def setUp(self) -> None:
- self.maxDiff = None
-
- def test_json_dumper_returns_correct_json(self):
- self.assertEqual('{\n'
- ' "ephemerides": [\n'
- ' {\n'
- ' "object": {\n'
- ' "name": "Mars",\n'
- ' "type": "planet",\n'
- ' "radius": null\n'
- ' },\n'
- ' "rise_time": null,\n'
- ' "culmination_time": null,\n'
- ' "set_time": null\n'
- ' }\n'
- ' ],\n'
- ' "moon_phase": {\n'
- ' "phase": "FULL_MOON",\n'
- ' "time": "2019-10-14T00:00:00",\n'
- ' "next": {\n'
- ' "phase": "LAST_QUARTER",\n'
- ' "time": "2019-10-21T00:00:00"\n'
- ' }\n'
- ' },\n'
- ' "events": [\n'
- ' {\n'
- ' "objects": [\n'
- ' {\n'
- ' "name": "Mars",\n'
- ' "type": "planet",\n'
- ' "radius": null\n'
- ' }\n'
- ' ],\n'
- ' "event": "OPPOSITION",\n'
- ' "starts_at": "2019-10-14T23:00:00",\n'
- ' "ends_at": null,\n'
- ' "details": null\n'
- ' },\n'
- ' {\n'
- ' "objects": [\n'
- ' {\n'
- ' "name": "Venus",\n'
- ' "type": "planet",\n'
- ' "radius": null\n'
- ' }\n'
- ' ],\n'
- ' "event": "MAXIMAL_ELONGATION",\n'
- ' "starts_at": "2019-10-14T12:00:00",\n'
- ' "ends_at": null,\n'
- ' "details": "42.0\\u00b0"\n'
- ' }\n'
- ' ]\n'
- '}', JsonDumper(self._get_ephemerides(), self._get_moon_phase(), self._get_events()).to_string())
-
- self.assertEqual('{\n'
- ' "ephemerides": [\n'
- ' {\n'
- ' "object": {\n'
- ' "name": "Mars",\n'
- ' "type": "planet",\n'
- ' "radius": null\n'
- ' },\n'
- ' "rise_time": "2019-10-14T08:00:00",\n'
- ' "culmination_time": "2019-10-14T13:00:00",\n'
- ' "set_time": "2019-10-14T23:00:00"\n'
- ' }\n'
- ' ],\n'
- ' "moon_phase": {\n'
- ' "phase": "FULL_MOON",\n'
- ' "time": "2019-10-14T00:00:00",\n'
- ' "next": {\n'
- ' "phase": "LAST_QUARTER",\n'
- ' "time": "2019-10-21T00:00:00"\n'
- ' }\n'
- ' },\n'
- ' "events": [\n'
- ' {\n'
- ' "objects": [\n'
- ' {\n'
- ' "name": "Mars",\n'
- ' "type": "planet",\n'
- ' "radius": null\n'
- ' }\n'
- ' ],\n'
- ' "event": "OPPOSITION",\n'
- ' "starts_at": "2019-10-14T23:00:00",\n'
- ' "ends_at": null,\n'
- ' "details": null\n'
- ' },\n'
- ' {\n'
- ' "objects": [\n'
- ' {\n'
- ' "name": "Venus",\n'
- ' "type": "planet",\n'
- ' "radius": null\n'
- ' }\n'
- ' ],\n'
- ' "event": "MAXIMAL_ELONGATION",\n'
- ' "starts_at": "2019-10-14T12:00:00",\n'
- ' "ends_at": null,\n'
- ' "details": "42.0\\u00b0"\n'
- ' }\n'
- ' ]\n'
- '}', JsonDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(),
- self._get_events()).to_string())
-
- def test_text_dumper_without_events(self):
- ephemerides = self._get_ephemerides()
- self.assertEqual('Monday October 14, 2019\n\n'
- 'Object Rise time Culmination time Set time\n'
- '-------- ----------- ------------------ ----------\n'
- 'Mars - - -\n\n'
- 'Moon phase: Full Moon\n'
- 'Last Quarter on Monday October 21, 2019 at 00:00\n\n'
- 'Note: All the hours are given in UTC.',
- TextDumper(ephemerides, self._get_moon_phase(), [], date=date(2019, 10, 14), with_colors=False).to_string())
-
- ephemerides = self._get_ephemerides(aster_rise_set=True)
- self.assertEqual('Monday October 14, 2019\n\n'
- 'Object Rise time Culmination time Set time\n'
- '-------- ----------- ------------------ ----------\n'
- 'Mars 08:00 13:00 23:00\n\n'
- 'Moon phase: Full Moon\n'
- 'Last Quarter on Monday October 21, 2019 at 00:00\n\n'
- 'Note: All the hours are given in UTC.',
- TextDumper(ephemerides, self._get_moon_phase(), [], date=date(2019, 10, 14), with_colors=False).to_string())
-
- def test_text_dumper_with_events(self):
- ephemerides = self._get_ephemerides()
- self.assertEqual("Monday October 14, 2019\n\n"
- "Object Rise time Culmination time Set time\n"
- "-------- ----------- ------------------ ----------\n"
- "Mars - - -\n\n"
- "Moon phase: Full Moon\n"
- "Last Quarter on Monday October 21, 2019 at 00:00\n\n"
- "Expected events:\n"
- "23:00 Mars is in opposition\n"
- "12:00 Venus's largest elongation (42.0°)\n\n"
- "Note: All the hours are given in UTC.",
- TextDumper(ephemerides, self._get_moon_phase(), self._get_events(), date=date(2019, 10, 14), with_colors=False).to_string())
-
- def test_text_dumper_without_ephemerides_and_with_events(self):
- self.assertEqual('Monday October 14, 2019\n\n'
- 'Moon phase: Full Moon\n'
- 'Last Quarter on Monday October 21, 2019 at 00:00\n\n'
- 'Expected events:\n'
- '23:00 Mars is in opposition\n'
- "12:00 Venus's largest elongation (42.0°)\n\n"
- 'Note: All the hours are given in UTC.',
- TextDumper(None, self._get_moon_phase(), self._get_events(),
- date=date(2019, 10, 14), with_colors=False).to_string())
-
- def test_latex_dumper(self):
- latex = _LatexDumper(self._get_ephemerides(), self._get_moon_phase(), self._get_events(),
- date=date(2019, 10, 14)).to_string()
-
- self.assertRegex(latex, 'Monday October 14, 2019')
- self.assertRegex(latex, 'Full Moon')
- self.assertRegex(latex, r'\\section{\\sffamily Expected events}')
- self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}')
- self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}')
- self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}')
- self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}")
-
- latex = _LatexDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(),
- self._get_events(), date=date(2019, 10, 14)).to_string()
- self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}')
-
- def test_latex_dumper_without_ephemerides(self):
- latex = _LatexDumper(None, self._get_moon_phase(), self._get_events(),
- date=date(2019, 10, 14)).to_string()
-
- self.assertRegex(latex, 'Monday October 14, 2019')
- self.assertRegex(latex, 'Full Moon')
- self.assertRegex(latex, r'\\section{\\sffamily Expected events}')
- self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}')
- self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}")
-
- self.assertNotRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}')
- self.assertNotRegex(latex, r'\\section{\\sffamily Ephemerides of the day}')
-
- def test_latex_dumper_without_events(self):
- latex = _LatexDumper(self._get_ephemerides(), self._get_moon_phase(), [], date=date(2019, 10, 14)).to_string()
-
- self.assertRegex(latex, 'Monday October 14, 2019')
- self.assertRegex(latex, 'Full Moon')
- self.assertRegex(latex, r'\\object\{Mars\}\{-\}\{-\}\{-\}')
- self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}')
-
- self.assertNotRegex(latex, r'\\section{\\sffamily Expected events}')
-
- def test_latex_dumper_with_graph(self):
- latex = _LatexDumper(self._get_ephemerides(True), self._get_moon_phase(), self._get_events(),
- date=date(2019, 10, 14), show_graph=True).to_string()
-
- self.assertRegex(latex, 'Monday October 14, 2019')
- self.assertRegex(latex, 'Full Moon')
- self.assertRegex(latex, r'\\section{\\sffamily Expected events}')
- self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}')
- self.assertRegex(latex, r'\\graphobject\{18\}\{gray\}\{8\}\{0\}\{23\}\{0\}\{08:00\}\{23:00\}')
- self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}')
- self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}")
-
- latex = _LatexDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(),
- self._get_events(), date=date(2019, 10, 14)).to_string()
- self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}')
-
- def test_latex_dumper_with_graph_but_without_rise_time(self):
- ephemerides = self._get_ephemerides(True)
- ephemerides[0].rise_time = None
- latex = _LatexDumper(ephemerides, self._get_moon_phase(), self._get_events(),
- date=date(2019, 10, 14), show_graph=True).to_string()
-
- self.assertRegex(latex, 'Monday October 14, 2019')
- self.assertRegex(latex, 'Full Moon')
- self.assertRegex(latex, r'\\section{\\sffamily Expected events}')
- self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}')
- self.assertRegex(latex, r'\\graphobject\{18\}\{gray\}\{0\}\{0\}\{23\}\{0\}\{\}\{23:00\}')
- self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}')
- self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}")
-
- latex = _LatexDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(),
- self._get_events(), date=date(2019, 10, 14)).to_string()
- self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}')
-
- def test_latex_dumper_with_graph_but_without_set_time(self):
- ephemerides = self._get_ephemerides(True)
- ephemerides[0].set_time = None
- latex = _LatexDumper(ephemerides, self._get_moon_phase(), self._get_events(),
- date=date(2019, 10, 14), show_graph=True).to_string()
-
- self.assertRegex(latex, 'Monday October 14, 2019')
- self.assertRegex(latex, 'Full Moon')
- self.assertRegex(latex, r'\\section{\\sffamily Expected events}')
- self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}')
- self.assertRegex(latex, r'\\graphobject\{18\}\{gray\}\{8\}\{0\}\{24\}\{0\}\{08:00\}\{\}')
- self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}')
- self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}")
-
- latex = _LatexDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(),
- self._get_events(), date=date(2019, 10, 14)).to_string()
- self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}')
-
- def test_latex_dumper_with_graph_but_mars_sets_tomorrow(self):
- ephemerides = self._get_ephemerides(True)
- ephemerides[0].set_time = datetime(2019, 10, 15, 1)
- latex = _LatexDumper(ephemerides, self._get_moon_phase(), self._get_events(),
- date=date(2019, 10, 14), show_graph=True).to_string()
-
- self.assertRegex(latex, 'Monday October 14, 2019')
- self.assertRegex(latex, 'Full Moon')
- self.assertRegex(latex, r'\\section{\\sffamily Expected events}')
- self.assertRegex(latex, r'\\section{\\sffamily Ephemerides of the day}')
- self.assertRegex(latex, r'\\graphobject\{18\}\{gray\}\{8\}\{0\}\{24\}\{0\}\{08:00\}\{\}')
- self.assertRegex(latex, r'\\graphobject\{18\}\{gray\}\{0\}\{0\}\{1\}\{0\}\{\}\{Oct 15, 01:00\}')
- self.assertRegex(latex, r'\\event\{23:00\}\{Mars is in opposition\}')
- self.assertRegex(latex, r"\\event\{12:00\}\{Venus's largest elongation \(42.0°\)\}")
-
- latex = _LatexDumper(self._get_ephemerides(aster_rise_set=True), self._get_moon_phase(),
- self._get_events(), date=date(2019, 10, 14)).to_string()
- self.assertRegex(latex, r'\\object\{Mars\}\{08:00\}\{13:00\}\{23:00\}')
-
- def test_get_moon_with_moon_phase_none(self):
- dumper = TextDumper()
- self.assertEqual('Moon phase is unavailable for this date.', dumper.get_moon(None))
-
- @staticmethod
- def _get_ephemerides(aster_rise_set=False) -> [AsterEphemerides]:
- rise_time = datetime(2019, 10, 14, 8) if aster_rise_set else None
- culmination_time = datetime(2019, 10, 14, 13) if aster_rise_set else None
- set_time = datetime(2019, 10, 14, 23) if aster_rise_set else None
-
- return [AsterEphemerides(rise_time, culmination_time, set_time, Planet('Mars', 'MARS'))]
-
- @staticmethod
- def _get_moon_phase():
- return MoonPhase(MoonPhaseType.FULL_MOON, datetime(2019, 10, 14), datetime(2019, 10, 21))
-
- @staticmethod
- def _get_events():
- return [Event(EventType.OPPOSITION,
- [Planet('Mars', 'MARS')],
- datetime(2019, 10, 14, 23, 00)),
- Event(EventType.MAXIMAL_ELONGATION,
- [Planet('Venus', 'VENUS')],
- datetime(2019, 10, 14, 12, 00), details='42.0°'),
- ]
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/test/ephemerides.py b/test/ephemerides.py
deleted file mode 100644
index a854ef7..0000000
--- a/test/ephemerides.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import unittest
-
-from .testutils import expect_assertions
-from kosmorrolib import ephemerides
-from kosmorrolib.data import EARTH, Position, MoonPhase
-from kosmorrolib.enum import MoonPhaseType
-
-from datetime import date
-from kosmorrolib.exceptions import OutOfRangeDateError
-
-
-class EphemeridesTestCase(unittest.TestCase):
- def test_get_ephemerides_for_aster_returns_correct_hours(self):
- position = Position(0, 0, EARTH)
- eph = ephemerides.get_ephemerides(date=date(2019, 11, 18),
- position=position)
-
- @expect_assertions(self.assertRegex, num=3)
- def do_assertions(assert_regex):
- for ephemeris in eph:
- if ephemeris.object.skyfield_name == 'SUN':
- assert_regex(ephemeris.rise_time.isoformat(), '^2019-11-18T05:41:')
- assert_regex(ephemeris.culmination_time.isoformat(), '^2019-11-18T11:45:')
- assert_regex(ephemeris.set_time.isoformat(), '^2019-11-18T17:48:')
- break
-
- do_assertions()
-
- ###################################################################################################################
- ### MOON PHASE TESTS ###
- ###################################################################################################################
-
- def test_moon_phase_new_moon(self):
- phase = ephemerides.get_moon_phase(date(2019, 11, 25))
- self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 26))
- self.assertEqual(MoonPhaseType.NEW_MOON, phase.phase_type)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 27))
- self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T')
-
- def test_moon_phase_first_crescent(self):
- phase = ephemerides.get_moon_phase(date(2019, 11, 3))
- self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 4))
- self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.phase_type)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 5))
- self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T')
-
- def test_moon_phase_full_moon(self):
- phase = ephemerides.get_moon_phase(date(2019, 11, 11))
- self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 12))
- self.assertEqual(MoonPhaseType.FULL_MOON, phase.phase_type)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 13))
- self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T')
-
- def test_moon_phase_last_quarter(self):
- phase = ephemerides.get_moon_phase(date(2019, 11, 18))
- self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 19))
- self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.phase_type)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T')
-
- phase = ephemerides.get_moon_phase(date(2019, 11, 20))
- self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type)
- self.assertIsNone(phase.time)
- self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T')
-
- def test_moon_phase_prediction(self):
- phase = MoonPhase(MoonPhaseType.NEW_MOON, None, None)
- self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase())
- phase = MoonPhase(MoonPhaseType.WAXING_CRESCENT, None, None)
- self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase())
-
- phase = MoonPhase(MoonPhaseType.FIRST_QUARTER, None, None)
- self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase())
- phase = MoonPhase(MoonPhaseType.WAXING_GIBBOUS, None, None)
- self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase())
-
- phase = MoonPhase(MoonPhaseType.FULL_MOON, None, None)
- self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase())
- phase = MoonPhase(MoonPhaseType.WANING_GIBBOUS, None, None)
- self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase())
-
- phase = MoonPhase(MoonPhaseType.LAST_QUARTER, None, None)
- self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase())
- phase = MoonPhase(MoonPhaseType.WANING_CRESCENT, None, None)
- self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase())
-
- def test_get_ephemerides_raises_exception_on_out_of_date_range(self):
- with self.assertRaises(OutOfRangeDateError):
- ephemerides.get_ephemerides(date(1789, 5, 5), Position(0, 0, EARTH))
-
- def test_get_moon_phase_raises_exception_on_out_of_date_range(self):
- with self.assertRaises(OutOfRangeDateError):
- ephemerides.get_moon_phase(date(1789, 5, 5))
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/test/events.py b/test/events.py
deleted file mode 100644
index de397bd..0000000
--- a/test/events.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import unittest
-
-from datetime import date, datetime
-from unittest_data_provider import data_provider
-
-from kosmorrolib import events
-from kosmorrolib.data import Event, ASTERS
-from kosmorrolib.enum import EventType
-from kosmorrolib.exceptions import OutOfRangeDateError
-
-
-class EventTestCase(unittest.TestCase):
- def setUp(self) -> None:
- self.maxDiff = None
-
- expected_events_provider = lambda: (
- (date(2020, 2, 7), []),
-
- (date(2020, 10, 13), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2020, 10, 13, 23, 25))]),
-
- (date(2022, 12, 8), [Event(EventType.CONJUNCTION, [ASTERS[1], ASTERS[4]], datetime(2022, 12, 8, 4, 18)),
- Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2022, 12, 8, 5, 41))]),
-
- (date(2025, 1, 16), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]),
-
- (date(2027, 2, 19), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2027, 2, 19, 7, 38)),
- Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]),
-
- (date(2020, 1, 2), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 2, 1, 32)),
- Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[5]], datetime(2020, 1, 2, 16, 41))]),
-
- (date(2020, 1, 12), [Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[6]], datetime(2020, 1, 12, 9, 51)),
- Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)),
- Event(EventType.CONJUNCTION, [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]),
-
- (date(2020, 2, 10), [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°'),
- Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 2, 10, 20, 34))]),
-
- (date(2020, 3, 24), [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'),
- Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 3, 24, 15, 39)),
- Event(EventType.MAXIMAL_ELONGATION, [ASTERS[3]], datetime(2020, 3, 24, 21, 58), details='46.1°')]),
-
- (date(2005, 6, 16), [Event(EventType.OCCULTATION, [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]),
-
- (date(2020, 4, 7), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]),
-
- (date(2020, 1, 29), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 29, 21, 32))])
- )
-
- @data_provider(expected_events_provider)
- def test_search_events(self, d: date, expected_events: [Event]):
- actual_events = events.search_events(d)
- self.assertEqual(len(expected_events), len(actual_events),
- 'Expected %d elements, got %d for date %s.\n%s' % (len(expected_events),
- len(actual_events),
- d.isoformat(),
- actual_events))
-
- for i, expected_event in enumerate(expected_events):
- actual_event = actual_events[i]
- # Remove unnecessary precision (seconds and microseconds)
- actual_event.start_time = datetime(actual_event.start_time.year,
- actual_event.start_time.month,
- actual_event.start_time.day,
- actual_event.start_time.hour,
- actual_event.start_time.minute)
-
- self.assertEqual(expected_event.__dict__, actual_event.__dict__)
-
- def test_get_events_raises_exception_on_out_of_date_range(self):
- with self.assertRaises(OutOfRangeDateError):
- events.search_events(date(1789, 5, 5))
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/test/testutils.py b/test/testutils.py
deleted file mode 100644
index a1828eb..0000000
--- a/test/testutils.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import functools
-from unittest import mock
-
-
-def expect_assertions(assert_fun, num=1):
- """Asserts that an assertion function is called as expected.
-
- This is very useful when the assertions are in loops.
- To use it, create a nested function in the the test function.
- The nested function will receive as parameter the mocked assertion function to use in place of the original one.
- Finally, run the nested function.
-
- Example of use:
-
- >>> # the function we test:
- >>> def my_sum_function(n, m):
- >>> # some code here
- >>> pass
-
- >>> # The unit test:
- >>> def test_sum(self):
- >>> @expect_assertions(self.assertEqual, num=10):
- >>> def make_assertions(assert_equal):
- >>> for i in range (-5, 5):
- >>> for j in range(-5, 5):
- >>> assert_equal(i + j, my_sum_function(i, j)
- >>>
- >>> make_assertions() # You don't need to give any parameter, the decorator does it for you.
-
- :param assert_fun: the assertion function to test
- :param num: the number of times the assertion function is expected to be called
- """
- assert_fun_mock = mock.Mock(side_effect=assert_fun)
-
- def fun_decorator(fun):
- @functools.wraps(fun)
- def sniff_function():
- fun(assert_fun_mock)
-
- count = assert_fun_mock.call_count
- if count != num:
- raise AssertionError('Expected %d call(s) to function %s but called %d time(s).' % (num,
- assert_fun.__name__,
- count))
-
- return sniff_function
-
- return fun_decorator