Compare commits
21 Commits
buster
...
v1.0.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ffc4f8aa2 | ||
|
|
87bd420d31 | ||
|
|
3e1a43c10f | ||
|
|
ed0b21a496 | ||
|
|
b93358dc32 | ||
|
|
7f8e69fcf3 | ||
|
|
751095a5a1 | ||
|
|
665cf6050b | ||
|
|
8e7435ff8f | ||
|
|
08310e074d | ||
|
|
598352f526 | ||
|
|
e88171b31c | ||
|
|
a5b393a879 | ||
|
|
9b4e733209 | ||
|
|
4cf102946e | ||
|
|
c72c0800fc | ||
|
|
095a95c1eb | ||
|
|
fd5e482de9 | ||
|
|
ccbeb20f70 | ||
|
|
a4dca60892 | ||
|
|
95e22b5d1f |
133
.gitignore
vendored
133
.gitignore
vendored
@@ -1 +1,132 @@
|
||||
site/
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# Include frontend libraries though
|
||||
!/frontend/lib
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/main.pyc
BIN
backend/main.pyc
Binary file not shown.
2
backend/run.sh
Normal file → Executable file
2
backend/run.sh
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
# run.sh - run the backend server
|
||||
cd `dirname $0`
|
||||
sudo gunicorn app:app -b 0.0.0.0:5000 &
|
||||
disown
|
||||
|
||||
188
docs/build.md
188
docs/build.md
@@ -1,169 +1,39 @@
|
||||
# TeraHz build guide
|
||||
This document describes the process of building/installing TeraHz from the Git
|
||||
repository.
|
||||
In its early development phase, TeraHz was hard and time-consuming to compile and install.
|
||||
This is not case now, as the more optimized DietPi Linux distribution allows
|
||||
better performance and simpler configuration than formerly used Raspbian.
|
||||
|
||||
## Warning
|
||||
The recommended way of getting TeraHz is the official Raspberry Pi SD card image
|
||||
provided under the releases tab in the GitHub repository. Installing TeraHz from
|
||||
source is a time consuming and painful process, even more so if you don't know
|
||||
what you're doing, and whatever you end up building __will not be officially
|
||||
supported__ (unless you're a core developer).
|
||||
## Downloading the complete image
|
||||
The easiest way to get TeraHz is to download the premade complete image and
|
||||
write it to an SD card. It already has TeraHz installed and **will work out of
|
||||
the box** with the correct hardware. The image is designed to run from a 16 GB
|
||||
micro SD card, class 10 or higher is recommended for snappy performance. The
|
||||
recommended image writer is Etcher
|
||||
|
||||
With this warning out of the way, let's begin.
|
||||
Please note that while this installation process is the easiest to perform, it
|
||||
does not guarantee the most recent TeraHz software. Complete images take time to
|
||||
prepare and despite the developer's best efforts aren't guaranteed to be always
|
||||
up-to-date.
|
||||
|
||||
## Getting the latest sources
|
||||
The most reliable way to get working source code is by cloning the official
|
||||
GitHub repository and checking out the `development-stable` tag. This tag marks
|
||||
the latest confirmed working commit. Building from the master branch is somewhat
|
||||
risky, and building from development branches is straight up stupid if you're
|
||||
not a developer.
|
||||
## Downloading the clean DietPi image and installing TeraHz manually
|
||||
This process is a bit more involved, but the version of TeraHz installed is
|
||||
guaranteed to be the latest one available from the repository.
|
||||
|
||||
Make sure that the repository is cloned into `/home/pi/TeraHz`, as Lighttpd
|
||||
expects to find frontend files inside this directory.
|
||||
The SD card image used in this case also contains some pre-configuration, but no
|
||||
TeraHz code is installed. To install TeraHz, you'll need a console access to the
|
||||
Raspberry Pi, preferably an SSH console over a wired network. DietPi is configured
|
||||
to get an IP over DHCP. A tool such as arp-scan on linux is very helpful at determining
|
||||
the device's IP address.
|
||||
|
||||
After cloning and checking out, check the documentation for module dependencies
|
||||
and the required version of python in the `docs/dependencies.md` file.
|
||||
|
||||
## Installing Python
|
||||
This step depends a lot on the platform you're using. TeraHz was developed with
|
||||
Raspberry Pi and Raspbian in mind. If you're familiar with Raspbian enough,
|
||||
you'll know that the latest version of Python available is `3.5`, which is too
|
||||
obsolete to run TeraHz and the required modules consistently. This leaves us
|
||||
with compiling Python from source. __This step is guaranteed to be slow,
|
||||
overnight compiling with something like tmux is recommended.__
|
||||
|
||||
### Pre-requirements
|
||||
Installing python without most C libraries will lead to a rather minimalistic
|
||||
Python install, missing a lot of important modules. To prevent this, update
|
||||
the system packages. After that, reboot.
|
||||
After connecting to the Raspberry Pi with username `root` and password `terahz`,
|
||||
TeraHz can be installed by cloning the Git repository and running the `etcs/install.sh`
|
||||
script.
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt full-upgrade
|
||||
sudo reboot
|
||||
git clone https://github.com/cls-02/TeraHz.git
|
||||
cd TeraHz/etcs
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Install the required build tools, libraries and their headers.
|
||||
|
||||
```
|
||||
sudo apt-get install build-essential tk-dev libncurses5-dev libncursesw5-dev \
|
||||
libreadline6-dev libdb5.3-dev libgdbm-dev libsqlite3-dev libssl-dev libbz2-dev \
|
||||
libexpat1-dev liblzma-dev zlib1g-dev
|
||||
```
|
||||
|
||||
### Compiling
|
||||
Compiling Python from source is, in fact pretty easy, just time-consuming. To combat
|
||||
that, using tmux to detach and later reattach the session is advised.
|
||||
|
||||
Python is packaged in many forms, but you'll be using the most basic
|
||||
of them all: a gzipped tarball. Download and decompress it, then cd into its
|
||||
directory.
|
||||
|
||||
```
|
||||
wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tgz
|
||||
tar -xzf Python-3.6.8.tgz
|
||||
cd Python-3.6.8
|
||||
```
|
||||
|
||||
Python's build process is pretty classic, a `.configure` script and a Makefile.
|
||||
Using the `-j` option with Make can reduce the compile time significantly. Go
|
||||
with as many threads as you have cores: `-j 4` works great on the Pi 3 B/B+.
|
||||
|
||||
```
|
||||
./configure
|
||||
make -j4
|
||||
```
|
||||
|
||||
When the compilation ends, install your freshly built version of python.
|
||||
|
||||
```
|
||||
sudo make altinstall
|
||||
```
|
||||
|
||||
"Altinstall" means that the new version of Python will be installed beside the
|
||||
existing version, and all related commands will use the full naming scheme:
|
||||
think `python3.6` or `pip3.6` instead of the shorter `python3` or `pip3`.
|
||||
|
||||
### Modules
|
||||
Another painfully slow part is the installation of all the required modules
|
||||
needed by TeraHz. Luckily, `pip3.6` takes care of the entire installation
|
||||
process. As before, using tmux is advised.
|
||||
|
||||
```
|
||||
pip3.6 install smbus pyserial flask pandas
|
||||
```
|
||||
|
||||
## Raspi-config
|
||||
For some law-obeying reason, Raspbian locks down the Wi-Fi interface card until
|
||||
the Wi-Fi country is set. This means that we will need to set it manually through
|
||||
the `raspi-config` program. It requires superuser privileges.
|
||||
|
||||
```
|
||||
sudo raspi-config
|
||||
```
|
||||

|
||||
|
||||
Configure the network hostname to something specific. If setting up multiple
|
||||
TeraHz machines, make their hostnames unique so you can tell them apart.
|
||||
|
||||

|
||||

|
||||
|
||||
Enable SSH and I2C interfaces.
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Expand the root filesystem along the entire SD card.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Set the Wi-Fi country to the country you'll be using TeraHz in.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Save and reboot to enable Wi-Fi
|
||||
|
||||
## Installing packages
|
||||
In addition to what's already installed, TeraHz requires the following daemons
|
||||
to run:
|
||||
- Lighttpd - Frontend HTTP server
|
||||
- Dnsmasq - DNS and DHCP server, used to redirect the `terahz.site` domain
|
||||
- Hostapd - Wi-Fi access point
|
||||
|
||||
They are available from the Raspbian repository. Install it via `apt`.
|
||||
|
||||
```
|
||||
apt install hostapd dnsmasq hostapd
|
||||
```
|
||||
|
||||
## Configuring daemons
|
||||
By default, the daemons we installed are disabled and start only manually. To
|
||||
change that, enable them through systemctl. Hostapd conflicts with
|
||||
wpa_supplicant, the solution is to disable wpa_supplicant (this will break your
|
||||
wireless connections, so use wired ethernet).
|
||||
|
||||
```
|
||||
sudo systemctl unmask hostapd
|
||||
sudo systemctl stop wpa_supplicant
|
||||
sudo systemctl disable wpa_supplicant
|
||||
sudo systemctl enable dnsmasq hostapd lighttpd
|
||||
```
|
||||
|
||||
## Copying configuration files
|
||||
To simplify the process of configuring Raspbian to run TeraHz, sample
|
||||
configuration file are provided in the `etcs` subdirectory of the Git
|
||||
repository.
|
||||
|
||||
These files have been verified to work, but it's not a brilliant idea to just
|
||||
copy them into your `/etc` directory. Use them carefully and more as a template
|
||||
for your own configuration rather than as a _de facto_ way of configuring
|
||||
TeraHz.
|
||||
When the installation completes, reboot the Raspberry Pi and it will
|
||||
automatically boot into TeraHz.
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
# Development-stable dependencies
|
||||
The current development version of TeraHz has been verified to work with:
|
||||
|
||||
- Raspbian Stretch (9)
|
||||
- Python 3.6.8 (built from source code and altinstall'd)
|
||||
- Module versions (direct `pip3.6 list` output):
|
||||
|
||||
# 1.0.0-rc1 Dependencies
|
||||
The 1.0.0-rc1 version of TeraHz is confirmed to work with:
|
||||
- DietPi 6.26.3
|
||||
- Python 3.7.3
|
||||
- Pip 18.1
|
||||
- Following versions of pip3 packages:
|
||||
```
|
||||
Package Version
|
||||
--------------- ---------
|
||||
Click 7.0
|
||||
Flask 1.0.3
|
||||
itsdangerous 1.1.0
|
||||
Jinja2 2.10.1
|
||||
MarkupSafe 1.1.1
|
||||
numpy 1.16.4
|
||||
pandas 0.24.2
|
||||
pip 18.1
|
||||
pyserial 3.4
|
||||
python-dateutil 2.8.0
|
||||
pytz 2019.1
|
||||
setuptools 40.6.2
|
||||
six 1.12.0
|
||||
smbus 1.1.post2
|
||||
Werkzeug 0.15.4
|
||||
Click 7.0
|
||||
Flask 1.1.1
|
||||
gunicorn 20.0.0
|
||||
itsdangerous 1.1.0
|
||||
Jinja2 2.10.3
|
||||
MarkupSafe 1.1.1
|
||||
numpy 1.17.4
|
||||
pandas 0.25.3
|
||||
pip 18.1
|
||||
pyserial 3.4
|
||||
python-dateutil 2.8.1
|
||||
pytz 2019.3
|
||||
setuptools 41.6.0
|
||||
six 1.13.0
|
||||
smbus2 0.3.0
|
||||
Werkzeug 0.16.0
|
||||
```
|
||||
|
||||
@@ -12,9 +12,9 @@ compiling Python on the first generation of Raspberry Pis is also very
|
||||
questionable.
|
||||
|
||||
Sensors required for operation are:
|
||||
+ AS7265x
|
||||
+ VEML6075
|
||||
+ APDS-9301
|
||||
- AS7265x
|
||||
- VEML6075
|
||||
- APDS-9301
|
||||
|
||||
They provide the spectrometry data, UV data and illuminance data, respectively.
|
||||
They all support I2C, AS7265x supports UART in addition.
|
||||
|
||||
@@ -21,13 +21,9 @@ project when basic testing will be done.
|
||||
|
||||
GPIO can be routed to the PCB with a standard old IDE disk cable, and terminated
|
||||
with another 40-pin connector at the PCB. Sensor breakouts should be mounted
|
||||
<<<<<<< HEAD
|
||||
through standard 0.1" connectors, male on the sensor breakout and female on the
|
||||
PCB. A shitty add-on header and a shitty add-on header v1.69bis can't hurt, either.
|
||||
=======
|
||||
through standard 0.1" connectors, male on the sensor brakout and female on the
|
||||
PCB. A shitty addon header and a shitty addon header v1.69bis can't hurt, either.
|
||||
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
|
||||
|
||||
|
||||
## SMBus sensors
|
||||
SMBus is a well-defined version of the well-known I2C bus, widely used
|
||||
@@ -39,11 +35,8 @@ Pins are familiarly marked as SDA and SCL, the same as with classic I2C. They
|
||||
connect to the SDA and SCL pins on the VEML6075 and APDS-9301 sensor.
|
||||
|
||||
## UART sensor
|
||||
<<<<<<< HEAD
|
||||
Spectral sensor attaches through the UART port on the Raspberry pi (see picture).
|
||||
=======
|
||||
Spectrometry sensor attaches through the UART port on the Raspberry pi (see picture).
|
||||
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
|
||||
|
||||
|
||||
The Tx and Rx lines must cross over, connecting the sensor's Tx line to the
|
||||
computer's Rx line and vice versa.
|
||||
|
||||
1
etcs/hostapd/edit_ssid.sh
Normal file → Executable file
1
etcs/hostapd/edit_ssid.sh
Normal file → Executable file
@@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
# edit_ssid.sh - edits hostapd.conf and sets a MAC address-based SSID
|
||||
cd `dirname $0`
|
||||
ssid=`ip link | awk '/wlan0/ {getline; print $2}' | awk -v FS=':' '{printf("TeraHz_%s%s%s\n", $4, $5, $6)}'`
|
||||
sed "/ssid=.*/s/ssid=.*/ssid=$ssid/" hostapd.conf > newconf
|
||||
mv newconf hostapd.conf
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
# install.sh - install TeraHz onto a Raspbian or DietPi installation
|
||||
cd `dirname $0`
|
||||
|
||||
apt -y update
|
||||
apt -y full-upgrade
|
||||
apt install -y python3 python3-pip lighttpd dnsmasq hostapd libatlas-base-dev
|
||||
pip3 install numpy pandas flask smbus2 pyserial
|
||||
pip3 install numpy pandas flask smbus2 pyserial gunicorn
|
||||
|
||||
cp -R hostapd/ /etc
|
||||
cp -R lighttpd/ /etc
|
||||
chmod +rx /etc/hostapd/edit_ssid.sh
|
||||
cp dnsmasq.conf /etc
|
||||
cp rc.local /etc
|
||||
cp interfaces-terahz /etc/network/interfaces.d/
|
||||
|
||||
cp -R ../frontend/* /var/www/html
|
||||
mkdir -p /usr/local/lib/terahz
|
||||
cp -R ../backend/* /usr/local/lib/terahz
|
||||
cd /etc/hostapd
|
||||
chmod +x edit_ssid.sh
|
||||
./edit_ssid.sh
|
||||
|
||||
systemctl unmask dnsmasq hostapd lighttpd
|
||||
systemctl enable dnsmasq hostapd lighttpd
|
||||
|
||||
3
etcs/interfaces-terahz
Normal file
3
etcs/interfaces-terahz
Normal file
@@ -0,0 +1,3 @@
|
||||
iface wlan0 inet static
|
||||
address 192.168.1.1
|
||||
netmask 255.255.255.0
|
||||
@@ -10,5 +10,7 @@
|
||||
# bits.
|
||||
#
|
||||
# By default this script does nothing.
|
||||
/etc/hostapd/edit_ssid.sh &
|
||||
/usr/local/lib/terahz/run.sh &
|
||||
disown
|
||||
sleep 3
|
||||
service hostapd restart # restart hostapd to prevent a weird startup race condition
|
||||
|
||||
@@ -5,5 +5,5 @@ nav:
|
||||
- Advanced guides:
|
||||
- Build guide: 'build.md'
|
||||
- Developer's guide: 'dev-guide.md'
|
||||
- Electrical connections: 'electrical.md'
|
||||
- Electrical guide: 'electrical.md'
|
||||
- Latest dependencies: 'dependencies.md'
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,131 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:p="http://www.evolus.vn/Namespace/Pencil" xmlns:pencil="http://www.evolus.vn/Namespace/Pencil" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:rdfa="http://docs.oasis-open.org/opendocument/meta/rdfa#" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:em="http://exslt.org/math" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="1385" height="852" id="exportedSVG" version="1.1" pencil:version="1.2.2" sodipodi:docname="demo"><g inkscape:label="Untitled Page" inkscape:groupmode="layer" id="layer_untitled_page"><g><rect x="0" y="0" width="1385" height="852" fill="none"/><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="316ea25d69264185809ddf96933f1b9d" transform="matrix(1,0,0,1,479,239)"><p:metadata><p:property name="box">379,359</p:property><p:property name="textPadding">18.95,23.933333333333334</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent"/><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="9f2cd04595654a2daa5c78cc57fc3ad1" transform="translate(0.5,0.5)" d="M 0 0 C 126 -1 253 -1 379 0 C 381 120 381 239 379 359 C 253 360 126 360 0 359 C -2 239 -2 120 0 0 z"/>
|
||||
<text p:name="text" id="22d3cf61288a4e6f8aea91fd9f11d090" transform="translate(190,180)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"/>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="f2992ac1faff43ef857d97b6097a3377" transform="matrix(1,0,0,1,545,295)"><p:metadata><p:property name="box">248,248</p:property><p:property name="imageData">1600,1600,ref://c549d0e3af32443d81f8de3a764ef050.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
|
||||
<defs>
|
||||
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="47f2743a5502445daeea6763b1824d99">
|
||||
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
|
||||
</filter>
|
||||
<g p:name="container" id="c807f25ef7cd4046b815700f4e8dd837">
|
||||
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="442171111cd644568d10f9aaa8367c03" transform="translate(0,0)" width="248" height="248"/>
|
||||
<g p:name="imageContainer" id="149b425645654fd99d93a63c39be92cd" transform="scale(0.155,0.155)">
|
||||
<image x="0" y="0" p:name="image" id="6f19a34b96bf4773969f6d58610f56bd" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/c549d0e3af32443d81f8de3a764ef050.png?token=1549194154000" width="1600" height="1600"/>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837" transform="translate(1, 1)" p:filter="url(#47f2743a5502445daeea6763b1824d99)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="f490b3b5a7e340a89dd6f4e992753bb7"/>
|
||||
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837"/>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="d874a4977f474fdeb875d7e6bf5944ed" transform="matrix(1,0,0,1,596,289)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Raspberry Pi 3 B+</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|18px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
|
||||
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="de7b105d7eec4d18b5eb971d7883baaa" width="147.640625" height="25.09375"/>
|
||||
<text xml:space="preserve" p:name="text" id="949a08b7fd4044b794cc5b69b6f83422" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Comic Sans MS"; font-size: 18px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Raspberry Pi 3 B+</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="4e6d928174bb44b2b165b10e439c8069" transform="matrix(1,0,0,1,215,144)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">AS7265X
|
||||
Spektrometerski senzor za IR in vidno svetlobo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="ba226d4a59e9445cb033b5f6cda2407c" transform="translate(0.5,0.5)" d="M 0 0 C 67 1 133 1 200 0 C 200 50 200 100 200 150 C 133 148 67 148 0 150 C -1 100 -1 50 0 0 z"/>
|
||||
<text p:name="text" id="2f20051133e249f78ca5e893035376d7" transform="translate(10,64)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="62.4609375" y="0">AS7265X</tspan><tspan x="5.109375" y="16">Spektrometerski senzor za IR</tspan><tspan x="43.7109375" y="32.390625">in vidno svetlobo</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="0788d286529c4856817fe87c11c8c576" transform="matrix(1,0,0,1,113,331)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">VEML6075
|
||||
Senzor UV svetlobe</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fb336ddc2a0e4285ab16ca92def9a93f" transform="translate(0.5,0.5)" d="M 0 0 C 67 2 133 2 200 0 C 199 50 199 100 200 150 C 133 151 67 151 0 150 C -1 100 -1 50 0 0 z"/>
|
||||
<text p:name="text" id="5a27b79034a142a182fbba178737c976" transform="translate(10,72)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="59.109375" y="0">VEML6075</tspan><tspan x="34.640625" y="16">Senzor UV svetlobe</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="fdd2989c419f44e49052af8a9433398b" transform="matrix(1,0,0,1,112,499)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">APDS-9301
|
||||
Merilec osvetljenosti
|
||||
(lux-meter)</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fd47ea2f4fea4465b5145099e062a50f" transform="translate(0.5,0.5)" d="M 0 0 C 67 0 133 0 200 0 C 198 50 198 100 200 150 C 133 151 67 151 0 150 C 1 100 1 50 0 0 z"/>
|
||||
<text p:name="text" id="3a1e6ef59bfe4e32a29f2d83e8a0eda4" transform="translate(10,64)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="57.8125" y="0">APDS-9301</tspan><tspan x="30.8671875" y="16">Merilec osvetljenosti</tspan><tspan x="57.859375" y="32.484375">(lux-meter)</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="6a62643b2a9e40ad92de2807a3b4d499" transform="matrix(1,0,0,1,898,448)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">2600 mAh Powerbank
|
||||
Napaja spektrometer</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="0e72ea3ae7374eae96f6d39616ad398b" transform="translate(0.5,0.5)" d="M 0 0 C 67 -1 133 -1 200 0 C 200 50 200 100 200 150 C 133 150 67 150 0 150 C 0 100 0 50 0 0 z"/>
|
||||
<text p:name="text" id="e73d9b50f41c4e3eb2efffdb7a5f58d9" transform="translate(10,72)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="29.6875" y="0">2600 mAh Powerbank</tspan><tspan x="30.125" y="16">Napaja spektrometer</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="bc03848225fa44a3a7c3339794d1aa4e" transform="matrix(1,0,0,1,479,127)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="92" ns9889235:viax="-54" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="4e6d928174bb44b2b165b10e439c8069">-64,92</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="102" ns9889235:viax="189.5" ns9889235:connectedOutletId="top-center" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d">189.5,112</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="590e362dc0a941558c8729fc4369cced" d="M -58 98 L -64 92 L -58 86 M -64 92 C -4 92 190 52 189.5 112 M 195 106 L 189.5 112 L 184 106"/>
|
||||
</defs>
|
||||
<use xlink:href="#590e362dc0a941558c8729fc4369cced" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#590e362dc0a941558c8729fc4369cced" p:name="outArrow1" id="9c47d44f84ff4e8c9c60f2427e5649e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="2027c38e59a7415ea356985839c3cb6a" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#590e362dc0a941558c8729fc4369cced" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="7e3a712874e84610942b4fa041b7a43b" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="3f86fad121f34239a0d9a0381b8e5293" transform="matrix(1,0,0,1,339,331)"><p:metadata><p:property name="box">76,316</p:property><p:property name="textPadding">3.8,21.066666666666663</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">I2C
|
||||
Vodilo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="38738b385aac4e4f882b7957ae3baf95" transform="translate(0.5,0.5)" d="M 0 0 C 25 -1 51 -1 76 0 C 76 105 76 211 76 316 C 51 315 25 315 0 316 C -1 211 -1 105 0 0 z"/>
|
||||
<text p:name="text" id="d829667556ac42128e839eeb4432aec3" transform="translate(4,155)" style="font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="23.645312500000003" y="0">I2C</tspan><tspan x="17.145312500000003" y="16">Vodilo</tspan></text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="e6e45b080a4640e2af58d063d5abe1ef" transform="matrix(1,0,0,1,562,696)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="0788d286529c4856817fe87c11c8c576" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-239" ns9889235:viay="-290">-249,-290</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="-233" ns9889235:viay="-207">-223,-207</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="564ef2543bfd4d4f8bf59146b4632c7c" d="M -243 -284 L -249 -290 L -243 -296 M -249 -290 C -206 -290 -266 -207 -223 -207 M -229 -213 L -223 -207 L -229 -201"/>
|
||||
</defs>
|
||||
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" p:name="outArrow1" id="9fd757e4b8d042a09378b6031cfe99e2" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="c73cdd327696416eab5fa6971dba53ef" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="221213c9b3fc487facaa478640fecf0f" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="540bab7bb3f3442f8429144581700e47" transform="matrix(1,0,0,1,324,705)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="-131" ns9889235:viax="-2" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="fdd2989c419f44e49052af8a9433398b">-12,-131</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5" ns9889235:viay="-216">15,-216</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b08f88ad83b84d5fad883f5b2d7152d9" d="M -6 -125 L -12 -131 L -6 -137 M -12 -131 C 33 -131 -30 -216 15 -216 M 9 -222 L 15 -216 L 9 -210"/>
|
||||
</defs>
|
||||
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" p:name="outArrow1" id="81869e15f13e41609130f9b556eef9d3" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="2fa0348639be49eda6fa12a65623e9b6" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="c544ec23fdcd4df998935ca8bb683723" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="0bc4ad3e83614786a0f2348a47ec40f2" transform="matrix(1,0,0,1,463,627)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-38" ns9889235:viay="-138">-48,-138</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5.999999999999972" ns9889235:viay="-208.5">16,-208</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="6c567c02fe2642e19f2b63f417f770a9" d="M -42 -132 L -48 -138 L -42 -144 M -48 -138 C -1 -138 -31 -210 16 -208 M 11 -214 L 16 -208 L 10 -203"/>
|
||||
</defs>
|
||||
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" p:name="outArrow1" id="cad90b7ac097400b8a9febcd1059b193" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="7256ea09df6f4b7ebf33202f1ced446d" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#6c567c02fe2642e19f2b63f417f770a9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="a3e251b119324498b6079771d87cf26b" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:RichTextBoxV2" id="f9704a7a815d4cdba445660a10c7ddc8" transform="matrix(1,0,0,1,479,601)"><p:metadata><p:property name="width">200,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="textContent"><span xmlns="http://www.w3.org/1999/xhtml" style="font-family: &quot;Comic Sans MS&quot;;">Računalnik na tiskanem vezju (SBC = single board computer)<br xmlns="http://www.w3.org/1999/xhtml" />Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter</span></p:property><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="customStyle">
|
||||
</p:property></p:metadata>
|
||||
|
||||
<foreignObject x="0" y="0" width="585" height="38" p:name="htmlObject" id="54de4aab4c144b8ead414b2e22841369" style="color: rgb(0, 0, 0); font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" p:name="textDiv" id="0cbc982da2c54d4aa9a65d4f6e9727c6" style="display: inline-block; white-space: nowrap; text-decoration: none;"><div><span style="font-family: "Comic Sans MS";">Računalnik na tiskanem vezju (SBC = single board computer)<br />Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter</span></div></div>
|
||||
</foreignObject>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="c884a2093fdc477bb3a74e719a1c7c77" transform="matrix(1,0,0,1,951,527)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-83" ns9889235:viay="-108.5">-93,-108</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="-4" ns9889235:viax="-63.000000000000014" ns9889235:connectedOutletId="middle-left" ns9889235:connectedShapeId="6a62643b2a9e40ad92de2807a3b4d499">-53,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="384134701047435fac995fc6eaee5932" d="M -87 -102 L -93 -108 L -87 -114 M -93 -108 C -37 -108 -109 -4 -53 -4 M -59 -10 L -53 -4 L -59 2"/>
|
||||
</defs>
|
||||
<use xlink:href="#384134701047435fac995fc6eaee5932" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#384134701047435fac995fc6eaee5932" p:name="outArrow1" id="14d921eb06d44f7d9acd062d2b01551a" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
|
||||
<text p:name="text" id="5485420a3edb40669c089909c03dc1f8" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#384134701047435fac995fc6eaee5932" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="428895865f0145dca4a5ff61843e80ef" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="b6ed13921fdd40f9a3eb0e1e3d0ecbe6" transform="matrix(1,0,0,1,1103,146)"><p:metadata><p:property name="box">195,276</p:property><p:property name="imageData">318,450,ref://7214557d80314d2ea0269f3a449d0000.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
|
||||
<defs>
|
||||
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="45b9efdc21654623a49f9d3d92738b31">
|
||||
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
|
||||
</filter>
|
||||
<g p:name="container" id="dc93c38161c8442faea0e63e5cbf5843">
|
||||
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="62e29bf3081e4449ae9fc80c6c967b71" transform="translate(0,0)" width="195" height="276"/>
|
||||
<g p:name="imageContainer" id="6f7191c994c644f183d02acf013570d0" transform="scale(0.6132075471698113,0.6133333333333333)">
|
||||
<image x="0" y="0" p:name="image" id="a6b907023988404f8a2a4c4daeb1b81e" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/7214557d80314d2ea0269f3a449d0000.png?token=1549194986000" width="318" height="450"/>
|
||||
</g>
|
||||
</g>
|
||||
</defs>
|
||||
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843" transform="translate(1, 1)" p:filter="url(#45b9efdc21654623a49f9d3d92738b31)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="02792f50e6104ccc99ad1f691c8d3a5e"/>
|
||||
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843"/>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="7b94aab3e1cd494696d3b5cf838dfde6" transform="matrix(1,0,0,1,858,286)"><p:metadata><p:property name="startPin">0,0</p:property><p:property name="endPin">282,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">4|3,3</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
|
||||
<defs>
|
||||
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b8c0a2b9697f46288c408540e8af57fa" d="M 11 11 L 0 0 L 11 -11 M 0 0 C 60 0 222 -3 282 -4 M 271 -15 L 282 -4 L 271 7"/>
|
||||
</defs>
|
||||
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
|
||||
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" p:name="outArrow1" id="97846f435e74416e996116a08ef664e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 4; stroke-dasharray: 3, 3;"/>
|
||||
<text p:name="text" id="987b10b79cfa4658917b6bb36a6005fb" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Liberation Sans", Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
|
||||
<textPath xlink:href="#b8c0a2b9697f46288c408540e8af57fa" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
|
||||
<tspan dy="-4.333333333333333" p:name="textSpan" id="667515745b224bb79dbd2e134284f6d2" dx="0"/>
|
||||
</textPath>
|
||||
</text>
|
||||
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="bb91f7fb7faf445a834363edc2cb287a" transform="matrix(1,0,0,1,1034,157)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Mobilni telefon preko WiFi povezave upravlja spektrometer</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
|
||||
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="c2e343a9ad3246cdb047e5e01ee1fb42" width="332.390625" height="16.484375"/>
|
||||
<text xml:space="preserve" p:name="text" id="357a8a3e3c4044bb8518698b7d607b60" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: "Comic Sans MS"; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Mobilni telefon preko WiFi povezave upravlja spektrometer</tspan></text>
|
||||
</g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
# sysprep - How to prepare a Raspberry Pi 3 B+ to run TeraHz
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB |
Binary file not shown.
@@ -1,76 +0,0 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "$1" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/root/projekti/TeraHz/utils/venv"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
if [ "x(venv) " != x ] ; then
|
||||
PS1="(venv) ${PS1:-}"
|
||||
else
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||
else
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||
fi
|
||||
fi
|
||||
export PS1
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
@@ -1,37 +0,0 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
if ("venv" != "") then
|
||||
set env_name = "venv"
|
||||
else
|
||||
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
||||
else
|
||||
set env_name = `basename "$VIRTUAL_ENV"`
|
||||
endif
|
||||
endif
|
||||
set prompt = "[$env_name] $prompt"
|
||||
unset env_name
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
@@ -1,75 +0,0 @@
|
||||
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
|
||||
# you cannot run it directly
|
||||
|
||||
function deactivate -d "Exit virtualenv and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
functions -e fish_prompt
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# save the current fish_prompt function as the function _old_fish_prompt
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# with the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command
|
||||
set -l old_status $status
|
||||
|
||||
# Prompt override?
|
||||
if test -n "(venv) "
|
||||
printf "%s%s" "(venv) " (set_color normal)
|
||||
else
|
||||
# ...Otherwise, prepend env
|
||||
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||
if test $_checkbase = "__"
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
|
||||
else
|
||||
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
|
||||
end
|
||||
end
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools.command.easy_install import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools.command.easy_install import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from numpy.f2py.f2py2e import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from numpy.f2py.f2py2e import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from numpy.f2py.f2py2e import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,976 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
#
|
||||
# Very simple serial terminal
|
||||
#
|
||||
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import serial
|
||||
from serial.tools.list_ports import comports
|
||||
from serial.tools import hexlify_codec
|
||||
|
||||
# pylint: disable=wrong-import-order,wrong-import-position
|
||||
|
||||
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
# pylint: disable=redefined-builtin,invalid-name
|
||||
raw_input = input # in python3 it's "raw"
|
||||
unichr = chr
|
||||
|
||||
|
||||
def key_description(character):
|
||||
"""generate a readable description for a key"""
|
||||
ascii_code = ord(character)
|
||||
if ascii_code < 32:
|
||||
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
|
||||
else:
|
||||
return repr(character)
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
class ConsoleBase(object):
|
||||
"""OS abstraction for console (input/output codec, no echo)"""
|
||||
|
||||
def __init__(self):
|
||||
if sys.version_info >= (3, 0):
|
||||
self.byte_output = sys.stdout.buffer
|
||||
else:
|
||||
self.byte_output = sys.stdout
|
||||
self.output = sys.stdout
|
||||
|
||||
def setup(self):
|
||||
"""Set console to read single characters, no echo"""
|
||||
|
||||
def cleanup(self):
|
||||
"""Restore default console settings"""
|
||||
|
||||
def getkey(self):
|
||||
"""Read a single key from the console"""
|
||||
return None
|
||||
|
||||
def write_bytes(self, byte_string):
|
||||
"""Write bytes (already encoded)"""
|
||||
self.byte_output.write(byte_string)
|
||||
self.byte_output.flush()
|
||||
|
||||
def write(self, text):
|
||||
"""Write string"""
|
||||
self.output.write(text)
|
||||
self.output.flush()
|
||||
|
||||
def cancel(self):
|
||||
"""Cancel getkey operation"""
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# context manager:
|
||||
# switch terminal temporary to normal mode (e.g. to get user input)
|
||||
|
||||
def __enter__(self):
|
||||
self.cleanup()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.setup()
|
||||
|
||||
|
||||
if os.name == 'nt': # noqa
|
||||
import msvcrt
|
||||
import ctypes
|
||||
|
||||
class Out(object):
|
||||
"""file-like wrapper that uses os.write"""
|
||||
|
||||
def __init__(self, fd):
|
||||
self.fd = fd
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def write(self, s):
|
||||
os.write(self.fd, s)
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
|
||||
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
|
||||
ctypes.windll.kernel32.SetConsoleCP(65001)
|
||||
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
|
||||
# the change of the code page is not propagated to Python, manually fix it
|
||||
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
|
||||
sys.stdout = self.output
|
||||
self.output.encoding = 'UTF-8' # needed for input
|
||||
|
||||
def __del__(self):
|
||||
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
|
||||
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
|
||||
|
||||
def getkey(self):
|
||||
while True:
|
||||
z = msvcrt.getwch()
|
||||
if z == unichr(13):
|
||||
return unichr(10)
|
||||
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
|
||||
msvcrt.getwch()
|
||||
else:
|
||||
return z
|
||||
|
||||
def cancel(self):
|
||||
# CancelIo, CancelSynchronousIo do not seem to work when using
|
||||
# getwch, so instead, send a key to the window with the console
|
||||
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
|
||||
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
|
||||
|
||||
elif os.name == 'posix':
|
||||
import atexit
|
||||
import termios
|
||||
import fcntl
|
||||
|
||||
class Console(ConsoleBase):
|
||||
def __init__(self):
|
||||
super(Console, self).__init__()
|
||||
self.fd = sys.stdin.fileno()
|
||||
self.old = termios.tcgetattr(self.fd)
|
||||
atexit.register(self.cleanup)
|
||||
if sys.version_info < (3, 0):
|
||||
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
|
||||
else:
|
||||
self.enc_stdin = sys.stdin
|
||||
|
||||
def setup(self):
|
||||
new = termios.tcgetattr(self.fd)
|
||||
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
|
||||
new[6][termios.VMIN] = 1
|
||||
new[6][termios.VTIME] = 0
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, new)
|
||||
|
||||
def getkey(self):
|
||||
c = self.enc_stdin.read(1)
|
||||
if c == unichr(0x7f):
|
||||
c = unichr(8) # map the BS key (which yields DEL) to backspace
|
||||
return c
|
||||
|
||||
def cancel(self):
|
||||
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
|
||||
|
||||
def cleanup(self):
|
||||
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
class Transform(object):
|
||||
"""do-nothing: forward all data unchanged"""
|
||||
def rx(self, text):
|
||||
"""text received from serial port"""
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
"""text to be sent to serial port"""
|
||||
return text
|
||||
|
||||
def echo(self, text):
|
||||
"""text to be sent but displayed on console"""
|
||||
return text
|
||||
|
||||
|
||||
class CRLF(Transform):
|
||||
"""ENTER sends CR+LF"""
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r\n')
|
||||
|
||||
|
||||
class CR(Transform):
|
||||
"""ENTER sends CR"""
|
||||
|
||||
def rx(self, text):
|
||||
return text.replace('\r', '\n')
|
||||
|
||||
def tx(self, text):
|
||||
return text.replace('\n', '\r')
|
||||
|
||||
|
||||
class LF(Transform):
|
||||
"""ENTER sends LF"""
|
||||
|
||||
|
||||
class NoTerminal(Transform):
|
||||
"""remove typical terminal control codes from input"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
def rx(self, text):
|
||||
return text.translate(self.REPLACEMENT_MAP)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class NoControls(NoTerminal):
|
||||
"""Remove all control codes, incl. CR+LF"""
|
||||
|
||||
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
|
||||
REPLACEMENT_MAP.update(
|
||||
{
|
||||
0x20: 0x2423, # visual space
|
||||
0x7F: 0x2421, # DEL
|
||||
0x9B: 0x2425, # CSI
|
||||
})
|
||||
|
||||
|
||||
class Printable(Transform):
|
||||
"""Show decimal code for all non-ASCII characters and replace most control codes"""
|
||||
|
||||
def rx(self, text):
|
||||
r = []
|
||||
for c in text:
|
||||
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
|
||||
r.append(c)
|
||||
elif c < ' ':
|
||||
r.append(unichr(0x2400 + ord(c)))
|
||||
else:
|
||||
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
|
||||
r.append(' ')
|
||||
return ''.join(r)
|
||||
|
||||
echo = rx
|
||||
|
||||
|
||||
class Colorize(Transform):
|
||||
"""Apply different colors for received and echo"""
|
||||
|
||||
def __init__(self):
|
||||
# XXX make it configurable, use colorama?
|
||||
self.input_color = '\x1b[37m'
|
||||
self.echo_color = '\x1b[31m'
|
||||
|
||||
def rx(self, text):
|
||||
return self.input_color + text
|
||||
|
||||
def echo(self, text):
|
||||
return self.echo_color + text
|
||||
|
||||
|
||||
class DebugIO(Transform):
|
||||
"""Print what is sent and received"""
|
||||
|
||||
def rx(self, text):
|
||||
sys.stderr.write(' [RX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
def tx(self, text):
|
||||
sys.stderr.write(' [TX:{}] '.format(repr(text)))
|
||||
sys.stderr.flush()
|
||||
return text
|
||||
|
||||
|
||||
# other ideas:
|
||||
# - add date/time for each newline
|
||||
# - insert newline after: a) timeout b) packet end character
|
||||
|
||||
EOL_TRANSFORMATIONS = {
|
||||
'crlf': CRLF,
|
||||
'cr': CR,
|
||||
'lf': LF,
|
||||
}
|
||||
|
||||
TRANSFORMATIONS = {
|
||||
'direct': Transform, # no transformation
|
||||
'default': NoTerminal,
|
||||
'nocontrol': NoControls,
|
||||
'printable': Printable,
|
||||
'colorize': Colorize,
|
||||
'debug': DebugIO,
|
||||
}
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
def ask_for_port():
|
||||
"""\
|
||||
Show a list of ports and ask the user for a choice. To make selection
|
||||
easier on systems with long device names, also allow the input of an
|
||||
index.
|
||||
"""
|
||||
sys.stderr.write('\n--- Available ports:\n')
|
||||
ports = []
|
||||
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
|
||||
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
|
||||
ports.append(port)
|
||||
while True:
|
||||
port = raw_input('--- Enter port index or full name: ')
|
||||
try:
|
||||
index = int(port) - 1
|
||||
if not 0 <= index < len(ports):
|
||||
sys.stderr.write('--- Invalid index!\n')
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
port = ports[index]
|
||||
return port
|
||||
|
||||
|
||||
class Miniterm(object):
|
||||
"""\
|
||||
Terminal application. Copy data from serial port to console and vice versa.
|
||||
Handle special keys from the console to show menu etc.
|
||||
"""
|
||||
|
||||
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
|
||||
self.console = Console()
|
||||
self.serial = serial_instance
|
||||
self.echo = echo
|
||||
self.raw = False
|
||||
self.input_encoding = 'UTF-8'
|
||||
self.output_encoding = 'UTF-8'
|
||||
self.eol = eol
|
||||
self.filters = filters
|
||||
self.update_transformations()
|
||||
self.exit_character = 0x1d # GS/CTRL+]
|
||||
self.menu_character = 0x14 # Menu: CTRL+T
|
||||
self.alive = None
|
||||
self._reader_alive = None
|
||||
self.receiver_thread = None
|
||||
self.rx_decoder = None
|
||||
self.tx_decoder = None
|
||||
|
||||
def _start_reader(self):
|
||||
"""Start reader thread"""
|
||||
self._reader_alive = True
|
||||
# start serial->console thread
|
||||
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
|
||||
self.receiver_thread.daemon = True
|
||||
self.receiver_thread.start()
|
||||
|
||||
def _stop_reader(self):
|
||||
"""Stop reader thread only, wait for clean exit of thread"""
|
||||
self._reader_alive = False
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def start(self):
|
||||
"""start worker threads"""
|
||||
self.alive = True
|
||||
self._start_reader()
|
||||
# enter console->serial loop
|
||||
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
|
||||
self.transmitter_thread.daemon = True
|
||||
self.transmitter_thread.start()
|
||||
self.console.setup()
|
||||
|
||||
def stop(self):
|
||||
"""set flag to stop worker threads"""
|
||||
self.alive = False
|
||||
|
||||
def join(self, transmit_only=False):
|
||||
"""wait for worker threads to terminate"""
|
||||
self.transmitter_thread.join()
|
||||
if not transmit_only:
|
||||
if hasattr(self.serial, 'cancel_read'):
|
||||
self.serial.cancel_read()
|
||||
self.receiver_thread.join()
|
||||
|
||||
def close(self):
|
||||
self.serial.close()
|
||||
|
||||
def update_transformations(self):
|
||||
"""take list of transformation classes and instantiate them for rx and tx"""
|
||||
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
|
||||
for f in self.filters]
|
||||
self.tx_transformations = [t() for t in transformations]
|
||||
self.rx_transformations = list(reversed(self.tx_transformations))
|
||||
|
||||
def set_rx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for received data"""
|
||||
self.input_encoding = encoding
|
||||
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
|
||||
|
||||
def set_tx_encoding(self, encoding, errors='replace'):
|
||||
"""set encoding for transmitted data"""
|
||||
self.output_encoding = encoding
|
||||
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
|
||||
|
||||
def dump_port_settings(self):
|
||||
"""Write current settings to sys.stderr"""
|
||||
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
|
||||
p=self.serial))
|
||||
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
|
||||
('active' if self.serial.rts else 'inactive'),
|
||||
('active' if self.serial.dtr else 'inactive'),
|
||||
('active' if self.serial.break_condition else 'inactive')))
|
||||
try:
|
||||
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
|
||||
('active' if self.serial.cts else 'inactive'),
|
||||
('active' if self.serial.dsr else 'inactive'),
|
||||
('active' if self.serial.ri else 'inactive'),
|
||||
('active' if self.serial.cd else 'inactive')))
|
||||
except serial.SerialException:
|
||||
# on RFC 2217 ports, it can happen if no modem state notification was
|
||||
# yet received. ignore this error.
|
||||
pass
|
||||
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
|
||||
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def reader(self):
|
||||
"""loop and copy serial->console"""
|
||||
try:
|
||||
while self.alive and self._reader_alive:
|
||||
# read all that is there or wait for one byte
|
||||
data = self.serial.read(self.serial.in_waiting or 1)
|
||||
if data:
|
||||
if self.raw:
|
||||
self.console.write_bytes(data)
|
||||
else:
|
||||
text = self.rx_decoder.decode(data)
|
||||
for transformation in self.rx_transformations:
|
||||
text = transformation.rx(text)
|
||||
self.console.write(text)
|
||||
except serial.SerialException:
|
||||
self.alive = False
|
||||
self.console.cancel()
|
||||
raise # XXX handle instead of re-raise?
|
||||
|
||||
def writer(self):
|
||||
"""\
|
||||
Loop and copy console->serial until self.exit_character character is
|
||||
found. When self.menu_character is found, interpret the next key
|
||||
locally.
|
||||
"""
|
||||
menu_active = False
|
||||
try:
|
||||
while self.alive:
|
||||
try:
|
||||
c = self.console.getkey()
|
||||
except KeyboardInterrupt:
|
||||
c = '\x03'
|
||||
if not self.alive:
|
||||
break
|
||||
if menu_active:
|
||||
self.handle_menu_key(c)
|
||||
menu_active = False
|
||||
elif c == self.menu_character:
|
||||
menu_active = True # next char will be for menu
|
||||
elif c == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
else:
|
||||
#~ if self.raw:
|
||||
text = c
|
||||
for transformation in self.tx_transformations:
|
||||
text = transformation.tx(text)
|
||||
self.serial.write(self.tx_encoder.encode(text))
|
||||
if self.echo:
|
||||
echo_text = c
|
||||
for transformation in self.tx_transformations:
|
||||
echo_text = transformation.echo(echo_text)
|
||||
self.console.write(echo_text)
|
||||
except:
|
||||
self.alive = False
|
||||
raise
|
||||
|
||||
def handle_menu_key(self, c):
|
||||
"""Implement a simple menu / settings"""
|
||||
if c == self.menu_character or c == self.exit_character:
|
||||
# Menu/exit character again -> send itself
|
||||
self.serial.write(self.tx_encoder.encode(c))
|
||||
if self.echo:
|
||||
self.console.write(c)
|
||||
elif c == '\x15': # CTRL+U -> upload file
|
||||
self.upload_file()
|
||||
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
|
||||
sys.stderr.write(self.get_help_text())
|
||||
elif c == '\x12': # CTRL+R -> Toggle RTS
|
||||
self.serial.rts = not self.serial.rts
|
||||
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
|
||||
elif c == '\x04': # CTRL+D -> Toggle DTR
|
||||
self.serial.dtr = not self.serial.dtr
|
||||
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
|
||||
elif c == '\x02': # CTRL+B -> toggle BREAK condition
|
||||
self.serial.break_condition = not self.serial.break_condition
|
||||
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
|
||||
elif c == '\x05': # CTRL+E -> toggle local echo
|
||||
self.echo = not self.echo
|
||||
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
|
||||
elif c == '\x06': # CTRL+F -> edit filters
|
||||
self.change_filter()
|
||||
elif c == '\x0c': # CTRL+L -> EOL mode
|
||||
modes = list(EOL_TRANSFORMATIONS) # keys
|
||||
eol = modes.index(self.eol) + 1
|
||||
if eol >= len(modes):
|
||||
eol = 0
|
||||
self.eol = modes[eol]
|
||||
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
|
||||
self.update_transformations()
|
||||
elif c == '\x01': # CTRL+A -> set encoding
|
||||
self.change_encoding()
|
||||
elif c == '\x09': # CTRL+I -> info
|
||||
self.dump_port_settings()
|
||||
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
|
||||
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
|
||||
elif c in 'pP': # P -> change port
|
||||
self.change_port()
|
||||
elif c in 'sS': # S -> suspend / open port temporarily
|
||||
self.suspend_port()
|
||||
elif c in 'bB': # B -> change baudrate
|
||||
self.change_baudrate()
|
||||
elif c == '8': # 8 -> change to 8 bits
|
||||
self.serial.bytesize = serial.EIGHTBITS
|
||||
self.dump_port_settings()
|
||||
elif c == '7': # 7 -> change to 8 bits
|
||||
self.serial.bytesize = serial.SEVENBITS
|
||||
self.dump_port_settings()
|
||||
elif c in 'eE': # E -> change to even parity
|
||||
self.serial.parity = serial.PARITY_EVEN
|
||||
self.dump_port_settings()
|
||||
elif c in 'oO': # O -> change to odd parity
|
||||
self.serial.parity = serial.PARITY_ODD
|
||||
self.dump_port_settings()
|
||||
elif c in 'mM': # M -> change to mark parity
|
||||
self.serial.parity = serial.PARITY_MARK
|
||||
self.dump_port_settings()
|
||||
elif c in 'sS': # S -> change to space parity
|
||||
self.serial.parity = serial.PARITY_SPACE
|
||||
self.dump_port_settings()
|
||||
elif c in 'nN': # N -> change to no parity
|
||||
self.serial.parity = serial.PARITY_NONE
|
||||
self.dump_port_settings()
|
||||
elif c == '1': # 1 -> change to 1 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE
|
||||
self.dump_port_settings()
|
||||
elif c == '2': # 2 -> change to 2 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_TWO
|
||||
self.dump_port_settings()
|
||||
elif c == '3': # 3 -> change to 1.5 stop bits
|
||||
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
|
||||
self.dump_port_settings()
|
||||
elif c in 'xX': # X -> change software flow control
|
||||
self.serial.xonxoff = (c == 'X')
|
||||
self.dump_port_settings()
|
||||
elif c in 'rR': # R -> change hardware flow control
|
||||
self.serial.rtscts = (c == 'R')
|
||||
self.dump_port_settings()
|
||||
else:
|
||||
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
|
||||
|
||||
def upload_file(self):
|
||||
"""Ask user for filenname and send its contents"""
|
||||
sys.stderr.write('\n--- File to upload: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
filename = sys.stdin.readline().rstrip('\r\n')
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
|
||||
while True:
|
||||
block = f.read(1024)
|
||||
if not block:
|
||||
break
|
||||
self.serial.write(block)
|
||||
# Wait for output buffer to drain.
|
||||
self.serial.flush()
|
||||
sys.stderr.write('.') # Progress indicator.
|
||||
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
|
||||
except IOError as e:
|
||||
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
|
||||
|
||||
def change_filter(self):
|
||||
"""change the i/o transformations"""
|
||||
sys.stderr.write('\n--- Available Filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'--- {:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
|
||||
with self.console:
|
||||
new_filters = sys.stdin.readline().lower().split()
|
||||
if new_filters:
|
||||
for f in new_filters:
|
||||
if f not in TRANSFORMATIONS:
|
||||
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
|
||||
break
|
||||
else:
|
||||
self.filters = new_filters
|
||||
self.update_transformations()
|
||||
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||
|
||||
def change_encoding(self):
|
||||
"""change encoding on the serial port"""
|
||||
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
|
||||
with self.console:
|
||||
new_encoding = sys.stdin.readline().strip()
|
||||
if new_encoding:
|
||||
try:
|
||||
codecs.lookup(new_encoding)
|
||||
except LookupError:
|
||||
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
|
||||
else:
|
||||
self.set_rx_encoding(new_encoding)
|
||||
self.set_tx_encoding(new_encoding)
|
||||
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||
|
||||
def change_baudrate(self):
|
||||
"""change the baudrate"""
|
||||
sys.stderr.write('\n--- Baudrate: ')
|
||||
sys.stderr.flush()
|
||||
with self.console:
|
||||
backup = self.serial.baudrate
|
||||
try:
|
||||
self.serial.baudrate = int(sys.stdin.readline().strip())
|
||||
except ValueError as e:
|
||||
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
|
||||
self.serial.baudrate = backup
|
||||
else:
|
||||
self.dump_port_settings()
|
||||
|
||||
def change_port(self):
|
||||
"""Have a conversation with the user to change the serial port"""
|
||||
with self.console:
|
||||
try:
|
||||
port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
port = None
|
||||
if port and port != self.serial.port:
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
# save settings
|
||||
settings = self.serial.getSettingsDict()
|
||||
try:
|
||||
new_serial = serial.serial_for_url(port, do_not_open=True)
|
||||
# restore settings and open
|
||||
new_serial.applySettingsDict(settings)
|
||||
new_serial.rts = self.serial.rts
|
||||
new_serial.dtr = self.serial.dtr
|
||||
new_serial.open()
|
||||
new_serial.break_condition = self.serial.break_condition
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
|
||||
new_serial.close()
|
||||
else:
|
||||
self.serial.close()
|
||||
self.serial = new_serial
|
||||
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
|
||||
def suspend_port(self):
|
||||
"""\
|
||||
open port temporarily, allow reconnect, exit and port change to get
|
||||
out of the loop
|
||||
"""
|
||||
# reader thread needs to be shut down
|
||||
self._stop_reader()
|
||||
self.serial.close()
|
||||
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
|
||||
do_change_port = False
|
||||
while not self.serial.is_open:
|
||||
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
|
||||
exit=key_description(self.exit_character)))
|
||||
k = self.console.getkey()
|
||||
if k == self.exit_character:
|
||||
self.stop() # exit app
|
||||
break
|
||||
elif k in 'pP':
|
||||
do_change_port = True
|
||||
break
|
||||
try:
|
||||
self.serial.open()
|
||||
except Exception as e:
|
||||
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
|
||||
if do_change_port:
|
||||
self.change_port()
|
||||
else:
|
||||
# and restart the reader thread
|
||||
self._start_reader()
|
||||
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
|
||||
|
||||
def get_help_text(self):
|
||||
"""return the help text"""
|
||||
# help text, starts with blank line!
|
||||
return """
|
||||
--- pySerial ({version}) - miniterm - help
|
||||
---
|
||||
--- {exit:8} Exit program
|
||||
--- {menu:8} Menu escape key, followed by:
|
||||
--- Menu keys:
|
||||
--- {menu:7} Send the menu character itself to remote
|
||||
--- {exit:7} Send the exit character itself to remote
|
||||
--- {info:7} Show info
|
||||
--- {upload:7} Upload file (prompt will be shown)
|
||||
--- {repr:7} encoding
|
||||
--- {filter:7} edit filters
|
||||
--- Toggles:
|
||||
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
|
||||
--- {echo:7} echo {eol:7} EOL
|
||||
---
|
||||
--- Port settings ({menu} followed by the following):
|
||||
--- p change port
|
||||
--- 7 8 set data bits
|
||||
--- N E O S M change parity (None, Even, Odd, Space, Mark)
|
||||
--- 1 2 3 set stop bits (1, 2, 1.5)
|
||||
--- b change baud rate
|
||||
--- x X disable/enable software flow control
|
||||
--- r R disable/enable hardware flow control
|
||||
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
|
||||
exit=key_description(self.exit_character),
|
||||
menu=key_description(self.menu_character),
|
||||
rts=key_description('\x12'),
|
||||
dtr=key_description('\x04'),
|
||||
brk=key_description('\x02'),
|
||||
echo=key_description('\x05'),
|
||||
info=key_description('\x09'),
|
||||
upload=key_description('\x15'),
|
||||
repr=key_description('\x01'),
|
||||
filter=key_description('\x06'),
|
||||
eol=key_description('\x0c'))
|
||||
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# default args can be used to override when calling main() from an other script
|
||||
# e.g to create a miniterm-my-device.py
|
||||
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
|
||||
"""Command line tool, entry point"""
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Miniterm - A simple terminal program for the serial port.")
|
||||
|
||||
parser.add_argument(
|
||||
"port",
|
||||
nargs='?',
|
||||
help="serial port name ('-' to show port list)",
|
||||
default=default_port)
|
||||
|
||||
parser.add_argument(
|
||||
"baudrate",
|
||||
nargs='?',
|
||||
type=int,
|
||||
help="set baud rate, default: %(default)s",
|
||||
default=default_baudrate)
|
||||
|
||||
group = parser.add_argument_group("port settings")
|
||||
|
||||
group.add_argument(
|
||||
"--parity",
|
||||
choices=['N', 'E', 'O', 'S', 'M'],
|
||||
type=lambda c: c.upper(),
|
||||
help="set parity, one of {N E O S M}, default: N",
|
||||
default='N')
|
||||
|
||||
group.add_argument(
|
||||
"--rtscts",
|
||||
action="store_true",
|
||||
help="enable RTS/CTS flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--xonxoff",
|
||||
action="store_true",
|
||||
help="enable software flow control (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--rts",
|
||||
type=int,
|
||||
help="set initial RTS line state (possible values: 0, 1)",
|
||||
default=default_rts)
|
||||
|
||||
group.add_argument(
|
||||
"--dtr",
|
||||
type=int,
|
||||
help="set initial DTR line state (possible values: 0, 1)",
|
||||
default=default_dtr)
|
||||
|
||||
group.add_argument(
|
||||
"--ask",
|
||||
action="store_true",
|
||||
help="ask again for port when open fails",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("data handling")
|
||||
|
||||
group.add_argument(
|
||||
"-e", "--echo",
|
||||
action="store_true",
|
||||
help="enable local echo (default off)",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--encoding",
|
||||
dest="serial_port_encoding",
|
||||
metavar="CODEC",
|
||||
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
|
||||
default='UTF-8')
|
||||
|
||||
group.add_argument(
|
||||
"-f", "--filter",
|
||||
action="append",
|
||||
metavar="NAME",
|
||||
help="add text transformation",
|
||||
default=[])
|
||||
|
||||
group.add_argument(
|
||||
"--eol",
|
||||
choices=['CR', 'LF', 'CRLF'],
|
||||
type=lambda c: c.upper(),
|
||||
help="end of line mode",
|
||||
default='CRLF')
|
||||
|
||||
group.add_argument(
|
||||
"--raw",
|
||||
action="store_true",
|
||||
help="Do no apply any encodings/transformations",
|
||||
default=False)
|
||||
|
||||
group = parser.add_argument_group("hotkeys")
|
||||
|
||||
group.add_argument(
|
||||
"--exit-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode of special character that is used to exit the application, default: %(default)s",
|
||||
default=0x1d) # GS/CTRL+]
|
||||
|
||||
group.add_argument(
|
||||
"--menu-char",
|
||||
type=int,
|
||||
metavar='NUM',
|
||||
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
|
||||
default=0x14) # Menu: CTRL+T
|
||||
|
||||
group = parser.add_argument_group("diagnostics")
|
||||
|
||||
group.add_argument(
|
||||
"-q", "--quiet",
|
||||
action="store_true",
|
||||
help="suppress non-error messages",
|
||||
default=False)
|
||||
|
||||
group.add_argument(
|
||||
"--develop",
|
||||
action="store_true",
|
||||
help="show Python traceback on error",
|
||||
default=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.menu_char == args.exit_char:
|
||||
parser.error('--exit-char can not be the same as --menu-char')
|
||||
|
||||
if args.filter:
|
||||
if 'help' in args.filter:
|
||||
sys.stderr.write('Available filters:\n')
|
||||
sys.stderr.write('\n'.join(
|
||||
'{:<10} = {.__doc__}'.format(k, v)
|
||||
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||
sys.stderr.write('\n')
|
||||
sys.exit(1)
|
||||
filters = args.filter
|
||||
else:
|
||||
filters = ['default']
|
||||
|
||||
while True:
|
||||
# no port given on command line -> ask user now
|
||||
if args.port is None or args.port == '-':
|
||||
try:
|
||||
args.port = ask_for_port()
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write('\n')
|
||||
parser.error('user aborted and port is not given')
|
||||
else:
|
||||
if not args.port:
|
||||
parser.error('port is not given')
|
||||
try:
|
||||
serial_instance = serial.serial_for_url(
|
||||
args.port,
|
||||
args.baudrate,
|
||||
parity=args.parity,
|
||||
rtscts=args.rtscts,
|
||||
xonxoff=args.xonxoff,
|
||||
do_not_open=True)
|
||||
|
||||
if not hasattr(serial_instance, 'cancel_read'):
|
||||
# enable timeout for alive flag polling if cancel_read is not available
|
||||
serial_instance.timeout = 1
|
||||
|
||||
if args.dtr is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
|
||||
serial_instance.dtr = args.dtr
|
||||
if args.rts is not None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
|
||||
serial_instance.rts = args.rts
|
||||
|
||||
serial_instance.open()
|
||||
except serial.SerialException as e:
|
||||
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
|
||||
if args.develop:
|
||||
raise
|
||||
if not args.ask:
|
||||
sys.exit(1)
|
||||
else:
|
||||
args.port = '-'
|
||||
else:
|
||||
break
|
||||
|
||||
miniterm = Miniterm(
|
||||
serial_instance,
|
||||
echo=args.echo,
|
||||
eol=args.eol.lower(),
|
||||
filters=filters)
|
||||
miniterm.exit_character = unichr(args.exit_char)
|
||||
miniterm.menu_character = unichr(args.menu_char)
|
||||
miniterm.raw = args.raw
|
||||
miniterm.set_rx_encoding(args.serial_port_encoding)
|
||||
miniterm.set_tx_encoding(args.serial_port_encoding)
|
||||
|
||||
if not args.quiet:
|
||||
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
|
||||
p=miniterm.serial))
|
||||
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
|
||||
key_description(miniterm.exit_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description(miniterm.menu_character),
|
||||
key_description('\x08')))
|
||||
|
||||
miniterm.start()
|
||||
try:
|
||||
miniterm.join(True)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
if not args.quiet:
|
||||
sys.stderr.write("\n--- exit ---\n")
|
||||
miniterm.join()
|
||||
miniterm.close()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/root/projekti/TeraHz/utils/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
UNKNOWN
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,25 +0,0 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: cycler
|
||||
Version: 0.10.0
|
||||
Summary: Composable style cycles
|
||||
Home-page: http://github.com/matplotlib/cycler
|
||||
Author: Thomas A Caswell
|
||||
Author-email: matplotlib-users@python.org
|
||||
License: BSD
|
||||
Keywords: cycle kwargs
|
||||
Platform: Cross platform (Linux
|
||||
Platform: Mac OSX
|
||||
Platform: Windows)
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Requires-Dist: six
|
||||
|
||||
UNKNOWN
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
__pycache__/cycler.cpython-36.pyc,,
|
||||
cycler-0.10.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10
|
||||
cycler-0.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
cycler-0.10.0.dist-info/METADATA,sha256=aWX1pyo7D2hSDNZ2Q6Zl7DxhUQdpyu1O5uNABnvz000,722
|
||||
cycler-0.10.0.dist-info/RECORD,,
|
||||
cycler-0.10.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
|
||||
cycler-0.10.0.dist-info/metadata.json,sha256=CCBpg-KQU-VRL1unJcHPWKQeQbB84G0j7-BeCj7YUbU,875
|
||||
cycler-0.10.0.dist-info/top_level.txt,sha256=D8BVVDdAAelLb2FOEz7lDpc6-AL21ylKPrMhtG6yzyE,7
|
||||
cycler.py,sha256=ed3G39unvVEBrBZVDwnE0FFroRNsOLkbJ_TwIT5CjCU,15959
|
||||
@@ -1,6 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.29.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"classifiers": ["Development Status :: 4 - Beta", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "matplotlib-users@python.org", "name": "Thomas A Caswell", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/matplotlib/cycler"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "keywords": ["cycle", "kwargs"], "license": "BSD", "metadata_version": "2.0", "name": "cycler", "platform": "Cross platform (Linux", "run_requires": [{"requires": ["six"]}], "summary": "Composable style cycles", "version": "0.10.0"}
|
||||
@@ -1 +0,0 @@
|
||||
cycler
|
||||
@@ -1,558 +0,0 @@
|
||||
"""
|
||||
Cycler
|
||||
======
|
||||
|
||||
Cycling through combinations of values, producing dictionaries.
|
||||
|
||||
You can add cyclers::
|
||||
|
||||
from cycler import cycler
|
||||
cc = (cycler(color=list('rgb')) +
|
||||
cycler(linestyle=['-', '--', '-.']))
|
||||
for d in cc:
|
||||
print(d)
|
||||
|
||||
Results in::
|
||||
|
||||
{'color': 'r', 'linestyle': '-'}
|
||||
{'color': 'g', 'linestyle': '--'}
|
||||
{'color': 'b', 'linestyle': '-.'}
|
||||
|
||||
|
||||
You can multiply cyclers::
|
||||
|
||||
from cycler import cycler
|
||||
cc = (cycler(color=list('rgb')) *
|
||||
cycler(linestyle=['-', '--', '-.']))
|
||||
for d in cc:
|
||||
print(d)
|
||||
|
||||
Results in::
|
||||
|
||||
{'color': 'r', 'linestyle': '-'}
|
||||
{'color': 'r', 'linestyle': '--'}
|
||||
{'color': 'r', 'linestyle': '-.'}
|
||||
{'color': 'g', 'linestyle': '-'}
|
||||
{'color': 'g', 'linestyle': '--'}
|
||||
{'color': 'g', 'linestyle': '-.'}
|
||||
{'color': 'b', 'linestyle': '-'}
|
||||
{'color': 'b', 'linestyle': '--'}
|
||||
{'color': 'b', 'linestyle': '-.'}
|
||||
"""
|
||||
|
||||
from __future__ import (absolute_import, division, print_function,
|
||||
unicode_literals)
|
||||
|
||||
import six
|
||||
from itertools import product, cycle
|
||||
from six.moves import zip, reduce
|
||||
from operator import mul, add
|
||||
import copy
|
||||
|
||||
__version__ = '0.10.0'
|
||||
|
||||
|
||||
def _process_keys(left, right):
|
||||
"""
|
||||
Helper function to compose cycler keys
|
||||
|
||||
Parameters
|
||||
----------
|
||||
left, right : iterable of dictionaries or None
|
||||
The cyclers to be composed
|
||||
Returns
|
||||
-------
|
||||
keys : set
|
||||
The keys in the composition of the two cyclers
|
||||
"""
|
||||
l_peek = next(iter(left)) if left is not None else {}
|
||||
r_peek = next(iter(right)) if right is not None else {}
|
||||
l_key = set(l_peek.keys())
|
||||
r_key = set(r_peek.keys())
|
||||
if l_key & r_key:
|
||||
raise ValueError("Can not compose overlapping cycles")
|
||||
return l_key | r_key
|
||||
|
||||
|
||||
class Cycler(object):
|
||||
"""
|
||||
Composable cycles
|
||||
|
||||
This class has compositions methods:
|
||||
|
||||
``+``
|
||||
for 'inner' products (zip)
|
||||
|
||||
``+=``
|
||||
in-place ``+``
|
||||
|
||||
``*``
|
||||
for outer products (itertools.product) and integer multiplication
|
||||
|
||||
``*=``
|
||||
in-place ``*``
|
||||
|
||||
and supports basic slicing via ``[]``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
left : Cycler or None
|
||||
The 'left' cycler
|
||||
|
||||
right : Cycler or None
|
||||
The 'right' cycler
|
||||
|
||||
op : func or None
|
||||
Function which composes the 'left' and 'right' cyclers.
|
||||
|
||||
"""
|
||||
def __call__(self):
|
||||
return cycle(self)
|
||||
|
||||
def __init__(self, left, right=None, op=None):
|
||||
"""Semi-private init
|
||||
|
||||
Do not use this directly, use `cycler` function instead.
|
||||
"""
|
||||
if isinstance(left, Cycler):
|
||||
self._left = Cycler(left._left, left._right, left._op)
|
||||
elif left is not None:
|
||||
# Need to copy the dictionary or else that will be a residual
|
||||
# mutable that could lead to strange errors
|
||||
self._left = [copy.copy(v) for v in left]
|
||||
else:
|
||||
self._left = None
|
||||
|
||||
if isinstance(right, Cycler):
|
||||
self._right = Cycler(right._left, right._right, right._op)
|
||||
elif right is not None:
|
||||
# Need to copy the dictionary or else that will be a residual
|
||||
# mutable that could lead to strange errors
|
||||
self._right = [copy.copy(v) for v in right]
|
||||
else:
|
||||
self._right = None
|
||||
|
||||
self._keys = _process_keys(self._left, self._right)
|
||||
self._op = op
|
||||
|
||||
@property
|
||||
def keys(self):
|
||||
"""
|
||||
The keys this Cycler knows about
|
||||
"""
|
||||
return set(self._keys)
|
||||
|
||||
def change_key(self, old, new):
|
||||
"""
|
||||
Change a key in this cycler to a new name.
|
||||
Modification is performed in-place.
|
||||
|
||||
Does nothing if the old key is the same as the new key.
|
||||
Raises a ValueError if the new key is already a key.
|
||||
Raises a KeyError if the old key isn't a key.
|
||||
|
||||
"""
|
||||
if old == new:
|
||||
return
|
||||
if new in self._keys:
|
||||
raise ValueError("Can't replace %s with %s, %s is already a key" %
|
||||
(old, new, new))
|
||||
if old not in self._keys:
|
||||
raise KeyError("Can't replace %s with %s, %s is not a key" %
|
||||
(old, new, old))
|
||||
|
||||
self._keys.remove(old)
|
||||
self._keys.add(new)
|
||||
|
||||
if self._right is not None and old in self._right.keys:
|
||||
self._right.change_key(old, new)
|
||||
|
||||
# self._left should always be non-None
|
||||
# if self._keys is non-empty.
|
||||
elif isinstance(self._left, Cycler):
|
||||
self._left.change_key(old, new)
|
||||
else:
|
||||
# It should be completely safe at this point to
|
||||
# assume that the old key can be found in each
|
||||
# iteration.
|
||||
self._left = [{new: entry[old]} for entry in self._left]
|
||||
|
||||
def _compose(self):
|
||||
"""
|
||||
Compose the 'left' and 'right' components of this cycle
|
||||
with the proper operation (zip or product as of now)
|
||||
"""
|
||||
for a, b in self._op(self._left, self._right):
|
||||
out = dict()
|
||||
out.update(a)
|
||||
out.update(b)
|
||||
yield out
|
||||
|
||||
@classmethod
|
||||
def _from_iter(cls, label, itr):
|
||||
"""
|
||||
Class method to create 'base' Cycler objects
|
||||
that do not have a 'right' or 'op' and for which
|
||||
the 'left' object is not another Cycler.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label : str
|
||||
The property key.
|
||||
|
||||
itr : iterable
|
||||
Finite length iterable of the property values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cycler : Cycler
|
||||
New 'base' `Cycler`
|
||||
"""
|
||||
ret = cls(None)
|
||||
ret._left = list({label: v} for v in itr)
|
||||
ret._keys = set([label])
|
||||
return ret
|
||||
|
||||
def __getitem__(self, key):
|
||||
# TODO : maybe add numpy style fancy slicing
|
||||
if isinstance(key, slice):
|
||||
trans = self.by_key()
|
||||
return reduce(add, (_cycler(k, v[key])
|
||||
for k, v in six.iteritems(trans)))
|
||||
else:
|
||||
raise ValueError("Can only use slices with Cycler.__getitem__")
|
||||
|
||||
def __iter__(self):
|
||||
if self._right is None:
|
||||
return iter(dict(l) for l in self._left)
|
||||
|
||||
return self._compose()
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Pair-wise combine two equal length cycles (zip)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler
|
||||
The second Cycler
|
||||
"""
|
||||
if len(self) != len(other):
|
||||
raise ValueError("Can only add equal length cycles, "
|
||||
"not {0} and {1}".format(len(self), len(other)))
|
||||
return Cycler(self, other, zip)
|
||||
|
||||
def __mul__(self, other):
|
||||
"""
|
||||
Outer product of two cycles (`itertools.product`) or integer
|
||||
multiplication.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler or int
|
||||
The second Cycler or integer
|
||||
"""
|
||||
if isinstance(other, Cycler):
|
||||
return Cycler(self, other, product)
|
||||
elif isinstance(other, int):
|
||||
trans = self.by_key()
|
||||
return reduce(add, (_cycler(k, v*other)
|
||||
for k, v in six.iteritems(trans)))
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __rmul__(self, other):
|
||||
return self * other
|
||||
|
||||
def __len__(self):
|
||||
op_dict = {zip: min, product: mul}
|
||||
if self._right is None:
|
||||
return len(self._left)
|
||||
l_len = len(self._left)
|
||||
r_len = len(self._right)
|
||||
return op_dict[self._op](l_len, r_len)
|
||||
|
||||
def __iadd__(self, other):
|
||||
"""
|
||||
In-place pair-wise combine two equal length cycles (zip)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler
|
||||
The second Cycler
|
||||
"""
|
||||
if not isinstance(other, Cycler):
|
||||
raise TypeError("Cannot += with a non-Cycler object")
|
||||
# True shallow copy of self is fine since this is in-place
|
||||
old_self = copy.copy(self)
|
||||
self._keys = _process_keys(old_self, other)
|
||||
self._left = old_self
|
||||
self._op = zip
|
||||
self._right = Cycler(other._left, other._right, other._op)
|
||||
return self
|
||||
|
||||
def __imul__(self, other):
|
||||
"""
|
||||
In-place outer product of two cycles (`itertools.product`)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Cycler
|
||||
The second Cycler
|
||||
"""
|
||||
if not isinstance(other, Cycler):
|
||||
raise TypeError("Cannot *= with a non-Cycler object")
|
||||
# True shallow copy of self is fine since this is in-place
|
||||
old_self = copy.copy(self)
|
||||
self._keys = _process_keys(old_self, other)
|
||||
self._left = old_self
|
||||
self._op = product
|
||||
self._right = Cycler(other._left, other._right, other._op)
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Check equality
|
||||
"""
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
if self.keys ^ other.keys:
|
||||
return False
|
||||
|
||||
return all(a == b for a, b in zip(self, other))
|
||||
|
||||
def __repr__(self):
|
||||
op_map = {zip: '+', product: '*'}
|
||||
if self._right is None:
|
||||
lab = self.keys.pop()
|
||||
itr = list(v[lab] for v in self)
|
||||
return "cycler({lab!r}, {itr!r})".format(lab=lab, itr=itr)
|
||||
else:
|
||||
op = op_map.get(self._op, '?')
|
||||
msg = "({left!r} {op} {right!r})"
|
||||
return msg.format(left=self._left, op=op, right=self._right)
|
||||
|
||||
def _repr_html_(self):
|
||||
# an table showing the value of each key through a full cycle
|
||||
output = "<table>"
|
||||
sorted_keys = sorted(self.keys, key=repr)
|
||||
for key in sorted_keys:
|
||||
output += "<th>{key!r}</th>".format(key=key)
|
||||
for d in iter(self):
|
||||
output += "<tr>"
|
||||
for k in sorted_keys:
|
||||
output += "<td>{val!r}</td>".format(val=d[k])
|
||||
output += "</tr>"
|
||||
output += "</table>"
|
||||
return output
|
||||
|
||||
def by_key(self):
|
||||
"""Values by key
|
||||
|
||||
This returns the transposed values of the cycler. Iterating
|
||||
over a `Cycler` yields dicts with a single value for each key,
|
||||
this method returns a `dict` of `list` which are the values
|
||||
for the given key.
|
||||
|
||||
The returned value can be used to create an equivalent `Cycler`
|
||||
using only `+`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
transpose : dict
|
||||
dict of lists of the values for each key.
|
||||
"""
|
||||
|
||||
# TODO : sort out if this is a bottle neck, if there is a better way
|
||||
# and if we care.
|
||||
|
||||
keys = self.keys
|
||||
# change this to dict comprehension when drop 2.6
|
||||
out = dict((k, list()) for k in keys)
|
||||
|
||||
for d in self:
|
||||
for k in keys:
|
||||
out[k].append(d[k])
|
||||
return out
|
||||
|
||||
# for back compatibility
|
||||
_transpose = by_key
|
||||
|
||||
def simplify(self):
|
||||
"""Simplify the Cycler
|
||||
|
||||
Returned as a composition using only sums (no multiplications)
|
||||
|
||||
Returns
|
||||
-------
|
||||
simple : Cycler
|
||||
An equivalent cycler using only summation"""
|
||||
# TODO: sort out if it is worth the effort to make sure this is
|
||||
# balanced. Currently it is is
|
||||
# (((a + b) + c) + d) vs
|
||||
# ((a + b) + (c + d))
|
||||
# I would believe that there is some performance implications
|
||||
|
||||
trans = self.by_key()
|
||||
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(trans)))
|
||||
|
||||
def concat(self, other):
|
||||
"""Concatenate this cycler and an other.
|
||||
|
||||
The keys must match exactly.
|
||||
|
||||
This returns a single Cycler which is equivalent to
|
||||
`itertools.chain(self, other)`
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> num = cycler('a', range(3))
|
||||
>>> let = cycler('a', 'abc')
|
||||
>>> num.concat(let)
|
||||
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : `Cycler`
|
||||
The `Cycler` to concatenate to this one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret : `Cycler`
|
||||
The concatenated `Cycler`
|
||||
"""
|
||||
return concat(self, other)
|
||||
|
||||
|
||||
def concat(left, right):
|
||||
"""Concatenate two cyclers.
|
||||
|
||||
The keys must match exactly.
|
||||
|
||||
This returns a single Cycler which is equivalent to
|
||||
`itertools.chain(left, right)`
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> num = cycler('a', range(3))
|
||||
>>> let = cycler('a', 'abc')
|
||||
>>> num.concat(let)
|
||||
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
|
||||
|
||||
Parameters
|
||||
----------
|
||||
left, right : `Cycler`
|
||||
The two `Cycler` instances to concatenate
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret : `Cycler`
|
||||
The concatenated `Cycler`
|
||||
"""
|
||||
if left.keys != right.keys:
|
||||
msg = '\n\t'.join(["Keys do not match:",
|
||||
"Intersection: {both!r}",
|
||||
"Disjoint: {just_one!r}"]).format(
|
||||
both=left.keys & right.keys,
|
||||
just_one=left.keys ^ right.keys)
|
||||
|
||||
raise ValueError(msg)
|
||||
|
||||
_l = left.by_key()
|
||||
_r = right.by_key()
|
||||
return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
|
||||
|
||||
|
||||
def cycler(*args, **kwargs):
|
||||
"""
|
||||
Create a new `Cycler` object from a single positional argument,
|
||||
a pair of positional arguments, or the combination of keyword arguments.
|
||||
|
||||
cycler(arg)
|
||||
cycler(label1=itr1[, label2=iter2[, ...]])
|
||||
cycler(label, itr)
|
||||
|
||||
Form 1 simply copies a given `Cycler` object.
|
||||
|
||||
Form 2 composes a `Cycler` as an inner product of the
|
||||
pairs of keyword arguments. In other words, all of the
|
||||
iterables are cycled simultaneously, as if through zip().
|
||||
|
||||
Form 3 creates a `Cycler` from a label and an iterable.
|
||||
This is useful for when the label cannot be a keyword argument
|
||||
(e.g., an integer or a name that has a space in it).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arg : Cycler
|
||||
Copy constructor for Cycler (does a shallow copy of iterables).
|
||||
|
||||
label : name
|
||||
The property key. In the 2-arg form of the function,
|
||||
the label can be any hashable object. In the keyword argument
|
||||
form of the function, it must be a valid python identifier.
|
||||
|
||||
itr : iterable
|
||||
Finite length iterable of the property values.
|
||||
Can be a single-property `Cycler` that would
|
||||
be like a key change, but as a shallow copy.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cycler : Cycler
|
||||
New `Cycler` for the given property
|
||||
|
||||
"""
|
||||
if args and kwargs:
|
||||
raise TypeError("cyl() can only accept positional OR keyword "
|
||||
"arguments -- not both.")
|
||||
|
||||
if len(args) == 1:
|
||||
if not isinstance(args[0], Cycler):
|
||||
raise TypeError("If only one positional argument given, it must "
|
||||
" be a Cycler instance.")
|
||||
return Cycler(args[0])
|
||||
elif len(args) == 2:
|
||||
return _cycler(*args)
|
||||
elif len(args) > 2:
|
||||
raise TypeError("Only a single Cycler can be accepted as the lone "
|
||||
"positional argument. Use keyword arguments instead.")
|
||||
|
||||
if kwargs:
|
||||
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(kwargs)))
|
||||
|
||||
raise TypeError("Must have at least a positional OR keyword arguments")
|
||||
|
||||
|
||||
def _cycler(label, itr):
|
||||
"""
|
||||
Create a new `Cycler` object from a property name and
|
||||
iterable of values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label : hashable
|
||||
The property key.
|
||||
|
||||
itr : iterable
|
||||
Finite length iterable of the property values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cycler : Cycler
|
||||
New `Cycler` for the given property
|
||||
"""
|
||||
if isinstance(itr, Cycler):
|
||||
keys = itr.keys
|
||||
if len(keys) != 1:
|
||||
msg = "Can not create Cycler from a multi-property Cycler"
|
||||
raise ValueError(msg)
|
||||
|
||||
lab = keys.pop()
|
||||
# Doesn't need to be a new list because
|
||||
# _from_iter() will be creating that new list anyway.
|
||||
itr = (v[lab] for v in itr)
|
||||
|
||||
return Cycler._from_iter(label, itr)
|
||||
@@ -1,8 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
from ._version import version as __version__
|
||||
except ImportError:
|
||||
__version__ = 'unknown'
|
||||
|
||||
__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
|
||||
'utils', 'zoneinfo']
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,43 +0,0 @@
|
||||
"""
|
||||
Common code used in multiple modules.
|
||||
"""
|
||||
|
||||
|
||||
class weekday(object):
|
||||
__slots__ = ["weekday", "n"]
|
||||
|
||||
def __init__(self, weekday, n=None):
|
||||
self.weekday = weekday
|
||||
self.n = n
|
||||
|
||||
def __call__(self, n):
|
||||
if n == self.n:
|
||||
return self
|
||||
else:
|
||||
return self.__class__(self.weekday, n)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
if self.weekday != other.weekday or self.n != other.n:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
return hash((
|
||||
self.weekday,
|
||||
self.n,
|
||||
))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __repr__(self):
|
||||
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
|
||||
if not self.n:
|
||||
return s
|
||||
else:
|
||||
return "%s(%+d)" % (s, self.n)
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
@@ -1,4 +0,0 @@
|
||||
# coding: utf-8
|
||||
# file generated by setuptools_scm
|
||||
# don't change, don't track in version control
|
||||
version = '2.7.5'
|
||||
@@ -1,89 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers a generic easter computing method for any given year, using
|
||||
Western, Orthodox or Julian algorithms.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
|
||||
|
||||
EASTER_JULIAN = 1
|
||||
EASTER_ORTHODOX = 2
|
||||
EASTER_WESTERN = 3
|
||||
|
||||
|
||||
def easter(year, method=EASTER_WESTERN):
|
||||
"""
|
||||
This method was ported from the work done by GM Arts,
|
||||
on top of the algorithm by Claus Tondering, which was
|
||||
based in part on the algorithm of Ouding (1940), as
|
||||
quoted in "Explanatory Supplement to the Astronomical
|
||||
Almanac", P. Kenneth Seidelmann, editor.
|
||||
|
||||
This algorithm implements three different easter
|
||||
calculation methods:
|
||||
|
||||
1 - Original calculation in Julian calendar, valid in
|
||||
dates after 326 AD
|
||||
2 - Original method, with date converted to Gregorian
|
||||
calendar, valid in years 1583 to 4099
|
||||
3 - Revised method, in Gregorian calendar, valid in
|
||||
years 1583 to 4099 as well
|
||||
|
||||
These methods are represented by the constants:
|
||||
|
||||
* ``EASTER_JULIAN = 1``
|
||||
* ``EASTER_ORTHODOX = 2``
|
||||
* ``EASTER_WESTERN = 3``
|
||||
|
||||
The default method is method 3.
|
||||
|
||||
More about the algorithm may be found at:
|
||||
|
||||
`GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_
|
||||
|
||||
and
|
||||
|
||||
`The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_
|
||||
|
||||
"""
|
||||
|
||||
if not (1 <= method <= 3):
|
||||
raise ValueError("invalid method")
|
||||
|
||||
# g - Golden year - 1
|
||||
# c - Century
|
||||
# h - (23 - Epact) mod 30
|
||||
# i - Number of days from March 21 to Paschal Full Moon
|
||||
# j - Weekday for PFM (0=Sunday, etc)
|
||||
# p - Number of days from March 21 to Sunday on or before PFM
|
||||
# (-6 to 28 methods 1 & 3, to 56 for method 2)
|
||||
# e - Extra days to add for method 2 (converting Julian
|
||||
# date to Gregorian date)
|
||||
|
||||
y = year
|
||||
g = y % 19
|
||||
e = 0
|
||||
if method < 3:
|
||||
# Old method
|
||||
i = (19*g + 15) % 30
|
||||
j = (y + y//4 + i) % 7
|
||||
if method == 2:
|
||||
# Extra dates to convert Julian to Gregorian date
|
||||
e = 10
|
||||
if y > 1600:
|
||||
e = e + y//100 - 16 - (y//100 - 16)//4
|
||||
else:
|
||||
# New method
|
||||
c = y//100
|
||||
h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
|
||||
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
|
||||
j = (y + y//4 + i + 2 - c + c//4) % 7
|
||||
|
||||
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
|
||||
# (later dates apply to method 2, although 23 May never actually occurs)
|
||||
p = i - j + e
|
||||
d = 1 + (p + 27 + (p + 6)//40) % 31
|
||||
m = 3 + (p + 26)//30
|
||||
return datetime.date(int(y), int(m), int(d))
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from ._parser import parse, parser, parserinfo
|
||||
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
|
||||
from ._parser import UnknownTimezoneWarning
|
||||
|
||||
from ._parser import __doc__
|
||||
|
||||
from .isoparser import isoparser, isoparse
|
||||
|
||||
__all__ = ['parse', 'parser', 'parserinfo',
|
||||
'isoparse', 'isoparser',
|
||||
'UnknownTimezoneWarning']
|
||||
|
||||
|
||||
###
|
||||
# Deprecate portions of the private interface so that downstream code that
|
||||
# is improperly relying on it is given *some* notice.
|
||||
|
||||
|
||||
def __deprecated_private_func(f):
|
||||
from functools import wraps
|
||||
import warnings
|
||||
|
||||
msg = ('{name} is a private function and may break without warning, '
|
||||
'it will be moved and or renamed in future versions.')
|
||||
msg = msg.format(name=f.__name__)
|
||||
|
||||
@wraps(f)
|
||||
def deprecated_func(*args, **kwargs):
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return deprecated_func
|
||||
|
||||
def __deprecate_private_class(c):
|
||||
import warnings
|
||||
|
||||
msg = ('{name} is a private class and may break without warning, '
|
||||
'it will be moved and or renamed in future versions.')
|
||||
msg = msg.format(name=c.__name__)
|
||||
|
||||
class private_class(c):
|
||||
__doc__ = c.__doc__
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
super(private_class, self).__init__(*args, **kwargs)
|
||||
|
||||
private_class.__name__ = c.__name__
|
||||
|
||||
return private_class
|
||||
|
||||
|
||||
from ._parser import _timelex, _resultbase
|
||||
from ._parser import _tzparser, _parsetz
|
||||
|
||||
_timelex = __deprecate_private_class(_timelex)
|
||||
_tzparser = __deprecate_private_class(_tzparser)
|
||||
_resultbase = __deprecate_private_class(_resultbase)
|
||||
_parsetz = __deprecated_private_func(_parsetz)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,406 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers a parser for ISO-8601 strings
|
||||
|
||||
It is intended to support all valid date, time and datetime formats per the
|
||||
ISO-8601 specification.
|
||||
|
||||
..versionadded:: 2.7.0
|
||||
"""
|
||||
from datetime import datetime, timedelta, time, date
|
||||
import calendar
|
||||
from dateutil import tz
|
||||
|
||||
from functools import wraps
|
||||
|
||||
import re
|
||||
import six
|
||||
|
||||
__all__ = ["isoparse", "isoparser"]
|
||||
|
||||
|
||||
def _takes_ascii(f):
|
||||
@wraps(f)
|
||||
def func(self, str_in, *args, **kwargs):
|
||||
# If it's a stream, read the whole thing
|
||||
str_in = getattr(str_in, 'read', lambda: str_in)()
|
||||
|
||||
# If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
|
||||
if isinstance(str_in, six.text_type):
|
||||
# ASCII is the same in UTF-8
|
||||
try:
|
||||
str_in = str_in.encode('ascii')
|
||||
except UnicodeEncodeError as e:
|
||||
msg = 'ISO-8601 strings should contain only ASCII characters'
|
||||
six.raise_from(ValueError(msg), e)
|
||||
|
||||
return f(self, str_in, *args, **kwargs)
|
||||
|
||||
return func
|
||||
|
||||
|
||||
class isoparser(object):
|
||||
def __init__(self, sep=None):
|
||||
"""
|
||||
:param sep:
|
||||
A single character that separates date and time portions. If
|
||||
``None``, the parser will accept any single character.
|
||||
For strict ISO-8601 adherence, pass ``'T'``.
|
||||
"""
|
||||
if sep is not None:
|
||||
if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
|
||||
raise ValueError('Separator must be a single, non-numeric ' +
|
||||
'ASCII character')
|
||||
|
||||
sep = sep.encode('ascii')
|
||||
|
||||
self._sep = sep
|
||||
|
||||
@_takes_ascii
|
||||
def isoparse(self, dt_str):
|
||||
"""
|
||||
Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.
|
||||
|
||||
An ISO-8601 datetime string consists of a date portion, followed
|
||||
optionally by a time portion - the date and time portions are separated
|
||||
by a single character separator, which is ``T`` in the official
|
||||
standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
|
||||
combined with a time portion.
|
||||
|
||||
Supported date formats are:
|
||||
|
||||
Common:
|
||||
|
||||
- ``YYYY``
|
||||
- ``YYYY-MM`` or ``YYYYMM``
|
||||
- ``YYYY-MM-DD`` or ``YYYYMMDD``
|
||||
|
||||
Uncommon:
|
||||
|
||||
- ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
|
||||
- ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day
|
||||
|
||||
The ISO week and day numbering follows the same logic as
|
||||
:func:`datetime.date.isocalendar`.
|
||||
|
||||
Supported time formats are:
|
||||
|
||||
- ``hh``
|
||||
- ``hh:mm`` or ``hhmm``
|
||||
- ``hh:mm:ss`` or ``hhmmss``
|
||||
- ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
|
||||
|
||||
Midnight is a special case for `hh`, as the standard supports both
|
||||
00:00 and 24:00 as a representation.
|
||||
|
||||
.. caution::
|
||||
|
||||
Support for fractional components other than seconds is part of the
|
||||
ISO-8601 standard, but is not currently implemented in this parser.
|
||||
|
||||
Supported time zone offset formats are:
|
||||
|
||||
- `Z` (UTC)
|
||||
- `±HH:MM`
|
||||
- `±HHMM`
|
||||
- `±HH`
|
||||
|
||||
Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
|
||||
with the exception of UTC, which will be represented as
|
||||
:class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
|
||||
as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
|
||||
|
||||
:param dt_str:
|
||||
A string or stream containing only an ISO-8601 datetime string
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.datetime` representing the string.
|
||||
Unspecified components default to their lowest value.
|
||||
|
||||
.. warning::
|
||||
|
||||
As of version 2.7.0, the strictness of the parser should not be
|
||||
considered a stable part of the contract. Any valid ISO-8601 string
|
||||
that parses correctly with the default settings will continue to
|
||||
parse correctly in future versions, but invalid strings that
|
||||
currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
|
||||
guaranteed to continue failing in future versions if they encode
|
||||
a valid date.
|
||||
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
components, pos = self._parse_isodate(dt_str)
|
||||
|
||||
if len(dt_str) > pos:
|
||||
if self._sep is None or dt_str[pos:pos + 1] == self._sep:
|
||||
components += self._parse_isotime(dt_str[pos + 1:])
|
||||
else:
|
||||
raise ValueError('String contains unknown ISO components')
|
||||
|
||||
return datetime(*components)
|
||||
|
||||
@_takes_ascii
|
||||
def parse_isodate(self, datestr):
|
||||
"""
|
||||
Parse the date portion of an ISO string.
|
||||
|
||||
:param datestr:
|
||||
The string portion of an ISO string, without a separator
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.date` object
|
||||
"""
|
||||
components, pos = self._parse_isodate(datestr)
|
||||
if pos < len(datestr):
|
||||
raise ValueError('String contains unknown ISO ' +
|
||||
'components: {}'.format(datestr))
|
||||
return date(*components)
|
||||
|
||||
@_takes_ascii
|
||||
def parse_isotime(self, timestr):
|
||||
"""
|
||||
Parse the time portion of an ISO string.
|
||||
|
||||
:param timestr:
|
||||
The time portion of an ISO string, without a separator
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.time` object
|
||||
"""
|
||||
return time(*self._parse_isotime(timestr))
|
||||
|
||||
@_takes_ascii
|
||||
def parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||
"""
|
||||
Parse a valid ISO time zone string.
|
||||
|
||||
See :func:`isoparser.isoparse` for details on supported formats.
|
||||
|
||||
:param tzstr:
|
||||
A string representing an ISO time zone offset
|
||||
|
||||
:param zero_as_utc:
|
||||
Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
|
||||
|
||||
:return:
|
||||
Returns :class:`dateutil.tz.tzoffset` for offsets and
|
||||
:class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
|
||||
specified) offsets equivalent to UTC.
|
||||
"""
|
||||
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
|
||||
|
||||
# Constants
|
||||
_MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
|
||||
_DATE_SEP = b'-'
|
||||
_TIME_SEP = b':'
|
||||
_MICRO_SEP = b'.'
|
||||
|
||||
def _parse_isodate(self, dt_str):
|
||||
try:
|
||||
return self._parse_isodate_common(dt_str)
|
||||
except ValueError:
|
||||
return self._parse_isodate_uncommon(dt_str)
|
||||
|
||||
def _parse_isodate_common(self, dt_str):
|
||||
len_str = len(dt_str)
|
||||
components = [1, 1, 1]
|
||||
|
||||
if len_str < 4:
|
||||
raise ValueError('ISO string too short')
|
||||
|
||||
# Year
|
||||
components[0] = int(dt_str[0:4])
|
||||
pos = 4
|
||||
if pos >= len_str:
|
||||
return components, pos
|
||||
|
||||
has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
|
||||
if has_sep:
|
||||
pos += 1
|
||||
|
||||
# Month
|
||||
if len_str - pos < 2:
|
||||
raise ValueError('Invalid common month')
|
||||
|
||||
components[1] = int(dt_str[pos:pos + 2])
|
||||
pos += 2
|
||||
|
||||
if pos >= len_str:
|
||||
if has_sep:
|
||||
return components, pos
|
||||
else:
|
||||
raise ValueError('Invalid ISO format')
|
||||
|
||||
if has_sep:
|
||||
if dt_str[pos:pos + 1] != self._DATE_SEP:
|
||||
raise ValueError('Invalid separator in ISO string')
|
||||
pos += 1
|
||||
|
||||
# Day
|
||||
if len_str - pos < 2:
|
||||
raise ValueError('Invalid common day')
|
||||
components[2] = int(dt_str[pos:pos + 2])
|
||||
return components, pos + 2
|
||||
|
||||
def _parse_isodate_uncommon(self, dt_str):
|
||||
if len(dt_str) < 4:
|
||||
raise ValueError('ISO string too short')
|
||||
|
||||
# All ISO formats start with the year
|
||||
year = int(dt_str[0:4])
|
||||
|
||||
has_sep = dt_str[4:5] == self._DATE_SEP
|
||||
|
||||
pos = 4 + has_sep # Skip '-' if it's there
|
||||
if dt_str[pos:pos + 1] == b'W':
|
||||
# YYYY-?Www-?D?
|
||||
pos += 1
|
||||
weekno = int(dt_str[pos:pos + 2])
|
||||
pos += 2
|
||||
|
||||
dayno = 1
|
||||
if len(dt_str) > pos:
|
||||
if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
|
||||
raise ValueError('Inconsistent use of dash separator')
|
||||
|
||||
pos += has_sep
|
||||
|
||||
dayno = int(dt_str[pos:pos + 1])
|
||||
pos += 1
|
||||
|
||||
base_date = self._calculate_weekdate(year, weekno, dayno)
|
||||
else:
|
||||
# YYYYDDD or YYYY-DDD
|
||||
if len(dt_str) - pos < 3:
|
||||
raise ValueError('Invalid ordinal day')
|
||||
|
||||
ordinal_day = int(dt_str[pos:pos + 3])
|
||||
pos += 3
|
||||
|
||||
if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
|
||||
raise ValueError('Invalid ordinal day' +
|
||||
' {} for year {}'.format(ordinal_day, year))
|
||||
|
||||
base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
|
||||
|
||||
components = [base_date.year, base_date.month, base_date.day]
|
||||
return components, pos
|
||||
|
||||
def _calculate_weekdate(self, year, week, day):
|
||||
"""
|
||||
Calculate the day of corresponding to the ISO year-week-day calendar.
|
||||
|
||||
This function is effectively the inverse of
|
||||
:func:`datetime.date.isocalendar`.
|
||||
|
||||
:param year:
|
||||
The year in the ISO calendar
|
||||
|
||||
:param week:
|
||||
The week in the ISO calendar - range is [1, 53]
|
||||
|
||||
:param day:
|
||||
The day in the ISO calendar - range is [1 (MON), 7 (SUN)]
|
||||
|
||||
:return:
|
||||
Returns a :class:`datetime.date`
|
||||
"""
|
||||
if not 0 < week < 54:
|
||||
raise ValueError('Invalid week: {}'.format(week))
|
||||
|
||||
if not 0 < day < 8: # Range is 1-7
|
||||
raise ValueError('Invalid weekday: {}'.format(day))
|
||||
|
||||
# Get week 1 for the specific year:
|
||||
jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it
|
||||
week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
|
||||
|
||||
# Now add the specific number of weeks and days to get what we want
|
||||
week_offset = (week - 1) * 7 + (day - 1)
|
||||
return week_1 + timedelta(days=week_offset)
|
||||
|
||||
def _parse_isotime(self, timestr):
|
||||
len_str = len(timestr)
|
||||
components = [0, 0, 0, 0, None]
|
||||
pos = 0
|
||||
comp = -1
|
||||
|
||||
if len(timestr) < 2:
|
||||
raise ValueError('ISO time too short')
|
||||
|
||||
has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP
|
||||
|
||||
while pos < len_str and comp < 5:
|
||||
comp += 1
|
||||
|
||||
if timestr[pos:pos + 1] in b'-+Z':
|
||||
# Detect time zone boundary
|
||||
components[-1] = self._parse_tzstr(timestr[pos:])
|
||||
pos = len_str
|
||||
break
|
||||
|
||||
if comp < 3:
|
||||
# Hour, minute, second
|
||||
components[comp] = int(timestr[pos:pos + 2])
|
||||
pos += 2
|
||||
if (has_sep and pos < len_str and
|
||||
timestr[pos:pos + 1] == self._TIME_SEP):
|
||||
pos += 1
|
||||
|
||||
if comp == 3:
|
||||
# Microsecond
|
||||
if timestr[pos:pos + 1] != self._MICRO_SEP:
|
||||
continue
|
||||
|
||||
pos += 1
|
||||
us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
|
||||
1)[0]
|
||||
|
||||
components[comp] = int(us_str) * 10**(6 - len(us_str))
|
||||
pos += len(us_str)
|
||||
|
||||
if pos < len_str:
|
||||
raise ValueError('Unused components in ISO string')
|
||||
|
||||
if components[0] == 24:
|
||||
# Standard supports 00:00 and 24:00 as representations of midnight
|
||||
if any(component != 0 for component in components[1:4]):
|
||||
raise ValueError('Hour may only be 24 at 24:00:00.000')
|
||||
components[0] = 0
|
||||
|
||||
return components
|
||||
|
||||
def _parse_tzstr(self, tzstr, zero_as_utc=True):
|
||||
if tzstr == b'Z':
|
||||
return tz.tzutc()
|
||||
|
||||
if len(tzstr) not in {3, 5, 6}:
|
||||
raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
|
||||
|
||||
if tzstr[0:1] == b'-':
|
||||
mult = -1
|
||||
elif tzstr[0:1] == b'+':
|
||||
mult = 1
|
||||
else:
|
||||
raise ValueError('Time zone offset requires sign')
|
||||
|
||||
hours = int(tzstr[1:3])
|
||||
if len(tzstr) == 3:
|
||||
minutes = 0
|
||||
else:
|
||||
minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
|
||||
|
||||
if zero_as_utc and hours == 0 and minutes == 0:
|
||||
return tz.tzutc()
|
||||
else:
|
||||
if minutes > 59:
|
||||
raise ValueError('Invalid minutes in time zone offset')
|
||||
|
||||
if hours > 23:
|
||||
raise ValueError('Invalid hours in time zone offset')
|
||||
|
||||
return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)
|
||||
|
||||
|
||||
DEFAULT_ISOPARSER = isoparser()
|
||||
isoparse = DEFAULT_ISOPARSER.isoparse
|
||||
@@ -1,590 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import calendar
|
||||
|
||||
import operator
|
||||
from math import copysign
|
||||
|
||||
from six import integer_types
|
||||
from warnings import warn
|
||||
|
||||
from ._common import weekday
|
||||
|
||||
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
|
||||
|
||||
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
|
||||
|
||||
|
||||
class relativedelta(object):
|
||||
"""
|
||||
The relativedelta type is based on the specification of the excellent
|
||||
work done by M.-A. Lemburg in his
|
||||
`mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
|
||||
However, notice that this type does *NOT* implement the same algorithm as
|
||||
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
|
||||
|
||||
There are two different ways to build a relativedelta instance. The
|
||||
first one is passing it two date/datetime classes::
|
||||
|
||||
relativedelta(datetime1, datetime2)
|
||||
|
||||
The second one is passing it any number of the following keyword arguments::
|
||||
|
||||
relativedelta(arg1=x,arg2=y,arg3=z...)
|
||||
|
||||
year, month, day, hour, minute, second, microsecond:
|
||||
Absolute information (argument is singular); adding or subtracting a
|
||||
relativedelta with absolute information does not perform an arithmetic
|
||||
operation, but rather REPLACES the corresponding value in the
|
||||
original datetime with the value(s) in relativedelta.
|
||||
|
||||
years, months, weeks, days, hours, minutes, seconds, microseconds:
|
||||
Relative information, may be negative (argument is plural); adding
|
||||
or subtracting a relativedelta with relative information performs
|
||||
the corresponding aritmetic operation on the original datetime value
|
||||
with the information in the relativedelta.
|
||||
|
||||
weekday:
|
||||
One of the weekday instances (MO, TU, etc). These
|
||||
instances may receive a parameter N, specifying the Nth
|
||||
weekday, which could be positive or negative (like MO(+1)
|
||||
or MO(-2). Not specifying it is the same as specifying
|
||||
+1. You can also use an integer, where 0=MO. Notice that
|
||||
if the calculated date is already Monday, for example,
|
||||
using MO(1) or MO(-1) won't change the day.
|
||||
|
||||
leapdays:
|
||||
Will add given days to the date found, if year is a leap
|
||||
year, and the date found is post 28 of february.
|
||||
|
||||
yearday, nlyearday:
|
||||
Set the yearday or the non-leap year day (jump leap days).
|
||||
These are converted to day/month/leapdays information.
|
||||
|
||||
There are relative and absolute forms of the keyword
|
||||
arguments. The plural is relative, and the singular is
|
||||
absolute. For each argument in the order below, the absolute form
|
||||
is applied first (by setting each attribute to that value) and
|
||||
then the relative form (by adding the value to the attribute).
|
||||
|
||||
The order of attributes considered when this relativedelta is
|
||||
added to a datetime is:
|
||||
|
||||
1. Year
|
||||
2. Month
|
||||
3. Day
|
||||
4. Hours
|
||||
5. Minutes
|
||||
6. Seconds
|
||||
7. Microseconds
|
||||
|
||||
Finally, weekday is applied, using the rule described above.
|
||||
|
||||
For example
|
||||
|
||||
>>> dt = datetime(2018, 4, 9, 13, 37, 0)
|
||||
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
|
||||
datetime(2018, 4, 2, 14, 37, 0)
|
||||
|
||||
First, the day is set to 1 (the first of the month), then 25 hours
|
||||
are added, to get to the 2nd day and 14th hour, finally the
|
||||
weekday is applied, but since the 2nd is already a Monday there is
|
||||
no effect.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, dt1=None, dt2=None,
|
||||
years=0, months=0, days=0, leapdays=0, weeks=0,
|
||||
hours=0, minutes=0, seconds=0, microseconds=0,
|
||||
year=None, month=None, day=None, weekday=None,
|
||||
yearday=None, nlyearday=None,
|
||||
hour=None, minute=None, second=None, microsecond=None):
|
||||
|
||||
if dt1 and dt2:
|
||||
# datetime is a subclass of date. So both must be date
|
||||
if not (isinstance(dt1, datetime.date) and
|
||||
isinstance(dt2, datetime.date)):
|
||||
raise TypeError("relativedelta only diffs datetime/date")
|
||||
|
||||
# We allow two dates, or two datetimes, so we coerce them to be
|
||||
# of the same type
|
||||
if (isinstance(dt1, datetime.datetime) !=
|
||||
isinstance(dt2, datetime.datetime)):
|
||||
if not isinstance(dt1, datetime.datetime):
|
||||
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
|
||||
elif not isinstance(dt2, datetime.datetime):
|
||||
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
|
||||
|
||||
self.years = 0
|
||||
self.months = 0
|
||||
self.days = 0
|
||||
self.leapdays = 0
|
||||
self.hours = 0
|
||||
self.minutes = 0
|
||||
self.seconds = 0
|
||||
self.microseconds = 0
|
||||
self.year = None
|
||||
self.month = None
|
||||
self.day = None
|
||||
self.weekday = None
|
||||
self.hour = None
|
||||
self.minute = None
|
||||
self.second = None
|
||||
self.microsecond = None
|
||||
self._has_time = 0
|
||||
|
||||
# Get year / month delta between the two
|
||||
months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
|
||||
self._set_months(months)
|
||||
|
||||
# Remove the year/month delta so the timedelta is just well-defined
|
||||
# time units (seconds, days and microseconds)
|
||||
dtm = self.__radd__(dt2)
|
||||
|
||||
# If we've overshot our target, make an adjustment
|
||||
if dt1 < dt2:
|
||||
compare = operator.gt
|
||||
increment = 1
|
||||
else:
|
||||
compare = operator.lt
|
||||
increment = -1
|
||||
|
||||
while compare(dt1, dtm):
|
||||
months += increment
|
||||
self._set_months(months)
|
||||
dtm = self.__radd__(dt2)
|
||||
|
||||
# Get the timedelta between the "months-adjusted" date and dt1
|
||||
delta = dt1 - dtm
|
||||
self.seconds = delta.seconds + delta.days * 86400
|
||||
self.microseconds = delta.microseconds
|
||||
else:
|
||||
# Check for non-integer values in integer-only quantities
|
||||
if any(x is not None and x != int(x) for x in (years, months)):
|
||||
raise ValueError("Non-integer years and months are "
|
||||
"ambiguous and not currently supported.")
|
||||
|
||||
# Relative information
|
||||
self.years = int(years)
|
||||
self.months = int(months)
|
||||
self.days = days + weeks * 7
|
||||
self.leapdays = leapdays
|
||||
self.hours = hours
|
||||
self.minutes = minutes
|
||||
self.seconds = seconds
|
||||
self.microseconds = microseconds
|
||||
|
||||
# Absolute information
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
self.microsecond = microsecond
|
||||
|
||||
if any(x is not None and int(x) != x
|
||||
for x in (year, month, day, hour,
|
||||
minute, second, microsecond)):
|
||||
# For now we'll deprecate floats - later it'll be an error.
|
||||
warn("Non-integer value passed as absolute information. " +
|
||||
"This is not a well-defined condition and will raise " +
|
||||
"errors in future versions.", DeprecationWarning)
|
||||
|
||||
if isinstance(weekday, integer_types):
|
||||
self.weekday = weekdays[weekday]
|
||||
else:
|
||||
self.weekday = weekday
|
||||
|
||||
yday = 0
|
||||
if nlyearday:
|
||||
yday = nlyearday
|
||||
elif yearday:
|
||||
yday = yearday
|
||||
if yearday > 59:
|
||||
self.leapdays = -1
|
||||
if yday:
|
||||
ydayidx = [31, 59, 90, 120, 151, 181, 212,
|
||||
243, 273, 304, 334, 366]
|
||||
for idx, ydays in enumerate(ydayidx):
|
||||
if yday <= ydays:
|
||||
self.month = idx+1
|
||||
if idx == 0:
|
||||
self.day = yday
|
||||
else:
|
||||
self.day = yday-ydayidx[idx-1]
|
||||
break
|
||||
else:
|
||||
raise ValueError("invalid year day (%d)" % yday)
|
||||
|
||||
self._fix()
|
||||
|
||||
def _fix(self):
|
||||
if abs(self.microseconds) > 999999:
|
||||
s = _sign(self.microseconds)
|
||||
div, mod = divmod(self.microseconds * s, 1000000)
|
||||
self.microseconds = mod * s
|
||||
self.seconds += div * s
|
||||
if abs(self.seconds) > 59:
|
||||
s = _sign(self.seconds)
|
||||
div, mod = divmod(self.seconds * s, 60)
|
||||
self.seconds = mod * s
|
||||
self.minutes += div * s
|
||||
if abs(self.minutes) > 59:
|
||||
s = _sign(self.minutes)
|
||||
div, mod = divmod(self.minutes * s, 60)
|
||||
self.minutes = mod * s
|
||||
self.hours += div * s
|
||||
if abs(self.hours) > 23:
|
||||
s = _sign(self.hours)
|
||||
div, mod = divmod(self.hours * s, 24)
|
||||
self.hours = mod * s
|
||||
self.days += div * s
|
||||
if abs(self.months) > 11:
|
||||
s = _sign(self.months)
|
||||
div, mod = divmod(self.months * s, 12)
|
||||
self.months = mod * s
|
||||
self.years += div * s
|
||||
if (self.hours or self.minutes or self.seconds or self.microseconds
|
||||
or self.hour is not None or self.minute is not None or
|
||||
self.second is not None or self.microsecond is not None):
|
||||
self._has_time = 1
|
||||
else:
|
||||
self._has_time = 0
|
||||
|
||||
@property
|
||||
def weeks(self):
|
||||
return int(self.days / 7.0)
|
||||
|
||||
@weeks.setter
|
||||
def weeks(self, value):
|
||||
self.days = self.days - (self.weeks * 7) + value * 7
|
||||
|
||||
def _set_months(self, months):
|
||||
self.months = months
|
||||
if abs(self.months) > 11:
|
||||
s = _sign(self.months)
|
||||
div, mod = divmod(self.months * s, 12)
|
||||
self.months = mod * s
|
||||
self.years = div * s
|
||||
else:
|
||||
self.years = 0
|
||||
|
||||
def normalized(self):
|
||||
"""
|
||||
Return a version of this object represented entirely using integer
|
||||
values for the relative attributes.
|
||||
|
||||
>>> relativedelta(days=1.5, hours=2).normalized()
|
||||
relativedelta(days=1, hours=14)
|
||||
|
||||
:return:
|
||||
Returns a :class:`dateutil.relativedelta.relativedelta` object.
|
||||
"""
|
||||
# Cascade remainders down (rounding each to roughly nearest microsecond)
|
||||
days = int(self.days)
|
||||
|
||||
hours_f = round(self.hours + 24 * (self.days - days), 11)
|
||||
hours = int(hours_f)
|
||||
|
||||
minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
|
||||
minutes = int(minutes_f)
|
||||
|
||||
seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
|
||||
seconds = int(seconds_f)
|
||||
|
||||
microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
|
||||
|
||||
# Constructor carries overflow back up with call to _fix()
|
||||
return self.__class__(years=self.years, months=self.months,
|
||||
days=days, hours=hours, minutes=minutes,
|
||||
seconds=seconds, microseconds=microseconds,
|
||||
leapdays=self.leapdays, year=self.year,
|
||||
month=self.month, day=self.day,
|
||||
weekday=self.weekday, hour=self.hour,
|
||||
minute=self.minute, second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, relativedelta):
|
||||
return self.__class__(years=other.years + self.years,
|
||||
months=other.months + self.months,
|
||||
days=other.days + self.days,
|
||||
hours=other.hours + self.hours,
|
||||
minutes=other.minutes + self.minutes,
|
||||
seconds=other.seconds + self.seconds,
|
||||
microseconds=(other.microseconds +
|
||||
self.microseconds),
|
||||
leapdays=other.leapdays or self.leapdays,
|
||||
year=(other.year if other.year is not None
|
||||
else self.year),
|
||||
month=(other.month if other.month is not None
|
||||
else self.month),
|
||||
day=(other.day if other.day is not None
|
||||
else self.day),
|
||||
weekday=(other.weekday if other.weekday is not None
|
||||
else self.weekday),
|
||||
hour=(other.hour if other.hour is not None
|
||||
else self.hour),
|
||||
minute=(other.minute if other.minute is not None
|
||||
else self.minute),
|
||||
second=(other.second if other.second is not None
|
||||
else self.second),
|
||||
microsecond=(other.microsecond if other.microsecond
|
||||
is not None else
|
||||
self.microsecond))
|
||||
if isinstance(other, datetime.timedelta):
|
||||
return self.__class__(years=self.years,
|
||||
months=self.months,
|
||||
days=self.days + other.days,
|
||||
hours=self.hours,
|
||||
minutes=self.minutes,
|
||||
seconds=self.seconds + other.seconds,
|
||||
microseconds=self.microseconds + other.microseconds,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
if not isinstance(other, datetime.date):
|
||||
return NotImplemented
|
||||
elif self._has_time and not isinstance(other, datetime.datetime):
|
||||
other = datetime.datetime.fromordinal(other.toordinal())
|
||||
year = (self.year or other.year)+self.years
|
||||
month = self.month or other.month
|
||||
if self.months:
|
||||
assert 1 <= abs(self.months) <= 12
|
||||
month += self.months
|
||||
if month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
elif month < 1:
|
||||
year -= 1
|
||||
month += 12
|
||||
day = min(calendar.monthrange(year, month)[1],
|
||||
self.day or other.day)
|
||||
repl = {"year": year, "month": month, "day": day}
|
||||
for attr in ["hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
repl[attr] = value
|
||||
days = self.days
|
||||
if self.leapdays and month > 2 and calendar.isleap(year):
|
||||
days += self.leapdays
|
||||
ret = (other.replace(**repl)
|
||||
+ datetime.timedelta(days=days,
|
||||
hours=self.hours,
|
||||
minutes=self.minutes,
|
||||
seconds=self.seconds,
|
||||
microseconds=self.microseconds))
|
||||
if self.weekday:
|
||||
weekday, nth = self.weekday.weekday, self.weekday.n or 1
|
||||
jumpdays = (abs(nth) - 1) * 7
|
||||
if nth > 0:
|
||||
jumpdays += (7 - ret.weekday() + weekday) % 7
|
||||
else:
|
||||
jumpdays += (ret.weekday() - weekday) % 7
|
||||
jumpdays *= -1
|
||||
ret += datetime.timedelta(days=jumpdays)
|
||||
return ret
|
||||
|
||||
def __radd__(self, other):
|
||||
return self.__add__(other)
|
||||
|
||||
def __rsub__(self, other):
|
||||
return self.__neg__().__radd__(other)
|
||||
|
||||
def __sub__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
return NotImplemented # In case the other object defines __rsub__
|
||||
return self.__class__(years=self.years - other.years,
|
||||
months=self.months - other.months,
|
||||
days=self.days - other.days,
|
||||
hours=self.hours - other.hours,
|
||||
minutes=self.minutes - other.minutes,
|
||||
seconds=self.seconds - other.seconds,
|
||||
microseconds=self.microseconds - other.microseconds,
|
||||
leapdays=self.leapdays or other.leapdays,
|
||||
year=(self.year if self.year is not None
|
||||
else other.year),
|
||||
month=(self.month if self.month is not None else
|
||||
other.month),
|
||||
day=(self.day if self.day is not None else
|
||||
other.day),
|
||||
weekday=(self.weekday if self.weekday is not None else
|
||||
other.weekday),
|
||||
hour=(self.hour if self.hour is not None else
|
||||
other.hour),
|
||||
minute=(self.minute if self.minute is not None else
|
||||
other.minute),
|
||||
second=(self.second if self.second is not None else
|
||||
other.second),
|
||||
microsecond=(self.microsecond if self.microsecond
|
||||
is not None else
|
||||
other.microsecond))
|
||||
|
||||
def __abs__(self):
|
||||
return self.__class__(years=abs(self.years),
|
||||
months=abs(self.months),
|
||||
days=abs(self.days),
|
||||
hours=abs(self.hours),
|
||||
minutes=abs(self.minutes),
|
||||
seconds=abs(self.seconds),
|
||||
microseconds=abs(self.microseconds),
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __neg__(self):
|
||||
return self.__class__(years=-self.years,
|
||||
months=-self.months,
|
||||
days=-self.days,
|
||||
hours=-self.hours,
|
||||
minutes=-self.minutes,
|
||||
seconds=-self.seconds,
|
||||
microseconds=-self.microseconds,
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
def __bool__(self):
|
||||
return not (not self.years and
|
||||
not self.months and
|
||||
not self.days and
|
||||
not self.hours and
|
||||
not self.minutes and
|
||||
not self.seconds and
|
||||
not self.microseconds and
|
||||
not self.leapdays and
|
||||
self.year is None and
|
||||
self.month is None and
|
||||
self.day is None and
|
||||
self.weekday is None and
|
||||
self.hour is None and
|
||||
self.minute is None and
|
||||
self.second is None and
|
||||
self.microsecond is None)
|
||||
# Compatibility with Python 2.x
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __mul__(self, other):
|
||||
try:
|
||||
f = float(other)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
return self.__class__(years=int(self.years * f),
|
||||
months=int(self.months * f),
|
||||
days=int(self.days * f),
|
||||
hours=int(self.hours * f),
|
||||
minutes=int(self.minutes * f),
|
||||
seconds=int(self.seconds * f),
|
||||
microseconds=int(self.microseconds * f),
|
||||
leapdays=self.leapdays,
|
||||
year=self.year,
|
||||
month=self.month,
|
||||
day=self.day,
|
||||
weekday=self.weekday,
|
||||
hour=self.hour,
|
||||
minute=self.minute,
|
||||
second=self.second,
|
||||
microsecond=self.microsecond)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, relativedelta):
|
||||
return NotImplemented
|
||||
if self.weekday or other.weekday:
|
||||
if not self.weekday or not other.weekday:
|
||||
return False
|
||||
if self.weekday.weekday != other.weekday.weekday:
|
||||
return False
|
||||
n1, n2 = self.weekday.n, other.weekday.n
|
||||
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
|
||||
return False
|
||||
return (self.years == other.years and
|
||||
self.months == other.months and
|
||||
self.days == other.days and
|
||||
self.hours == other.hours and
|
||||
self.minutes == other.minutes and
|
||||
self.seconds == other.seconds and
|
||||
self.microseconds == other.microseconds and
|
||||
self.leapdays == other.leapdays and
|
||||
self.year == other.year and
|
||||
self.month == other.month and
|
||||
self.day == other.day and
|
||||
self.hour == other.hour and
|
||||
self.minute == other.minute and
|
||||
self.second == other.second and
|
||||
self.microsecond == other.microsecond)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((
|
||||
self.weekday,
|
||||
self.years,
|
||||
self.months,
|
||||
self.days,
|
||||
self.hours,
|
||||
self.minutes,
|
||||
self.seconds,
|
||||
self.microseconds,
|
||||
self.leapdays,
|
||||
self.year,
|
||||
self.month,
|
||||
self.day,
|
||||
self.hour,
|
||||
self.minute,
|
||||
self.second,
|
||||
self.microsecond,
|
||||
))
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __div__(self, other):
|
||||
try:
|
||||
reciprocal = 1 / float(other)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
|
||||
return self.__mul__(reciprocal)
|
||||
|
||||
__truediv__ = __div__
|
||||
|
||||
def __repr__(self):
|
||||
l = []
|
||||
for attr in ["years", "months", "days", "leapdays",
|
||||
"hours", "minutes", "seconds", "microseconds"]:
|
||||
value = getattr(self, attr)
|
||||
if value:
|
||||
l.append("{attr}={value:+g}".format(attr=attr, value=value))
|
||||
for attr in ["year", "month", "day", "weekday",
|
||||
"hour", "minute", "second", "microsecond"]:
|
||||
value = getattr(self, attr)
|
||||
if value is not None:
|
||||
l.append("{attr}={value}".format(attr=attr, value=repr(value)))
|
||||
return "{classname}({attrs})".format(classname=self.__class__.__name__,
|
||||
attrs=", ".join(l))
|
||||
|
||||
|
||||
def _sign(x):
|
||||
return int(copysign(1, x))
|
||||
|
||||
# vim:ts=4:sw=4:et
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .tz import *
|
||||
from .tz import __doc__
|
||||
|
||||
#: Convenience constant providing a :class:`tzutc()` instance
|
||||
#:
|
||||
#: .. versionadded:: 2.7.0
|
||||
UTC = tzutc()
|
||||
|
||||
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
||||
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
|
||||
"enfold", "datetime_ambiguous", "datetime_exists",
|
||||
"resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]
|
||||
|
||||
|
||||
class DeprecatedTzFormatWarning(Warning):
|
||||
"""Warning raised when time zones are parsed from deprecated formats."""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,415 +0,0 @@
|
||||
from six import PY3
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
|
||||
|
||||
ZERO = timedelta(0)
|
||||
|
||||
__all__ = ['tzname_in_python2', 'enfold']
|
||||
|
||||
|
||||
def tzname_in_python2(namefunc):
|
||||
"""Change unicode output into bytestrings in Python 2
|
||||
|
||||
tzname() API changed in Python 3. It used to return bytes, but was changed
|
||||
to unicode strings
|
||||
"""
|
||||
def adjust_encoding(*args, **kwargs):
|
||||
name = namefunc(*args, **kwargs)
|
||||
if name is not None and not PY3:
|
||||
name = name.encode()
|
||||
|
||||
return name
|
||||
|
||||
return adjust_encoding
|
||||
|
||||
|
||||
# The following is adapted from Alexander Belopolsky's tz library
|
||||
# https://github.com/abalkin/tz
|
||||
if hasattr(datetime, 'fold'):
|
||||
# This is the pre-python 3.6 fold situation
|
||||
def enfold(dt, fold=1):
|
||||
"""
|
||||
Provides a unified interface for assigning the ``fold`` attribute to
|
||||
datetimes both before and after the implementation of PEP-495.
|
||||
|
||||
:param fold:
|
||||
The value for the ``fold`` attribute in the returned datetime. This
|
||||
should be either 0 or 1.
|
||||
|
||||
:return:
|
||||
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||
``fold`` for all versions of Python. In versions prior to
|
||||
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||
attribute added, if ``fold`` is 1.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
return dt.replace(fold=fold)
|
||||
|
||||
else:
|
||||
class _DatetimeWithFold(datetime):
|
||||
"""
|
||||
This is a class designed to provide a PEP 495-compliant interface for
|
||||
Python versions before 3.6. It is used only for dates in a fold, so
|
||||
the ``fold`` attribute is fixed at ``1``.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def replace(self, *args, **kwargs):
|
||||
"""
|
||||
Return a datetime with the same attributes, except for those
|
||||
attributes given new values by whichever keyword arguments are
|
||||
specified. Note that tzinfo=None can be specified to create a naive
|
||||
datetime from an aware datetime with no conversion of date and time
|
||||
data.
|
||||
|
||||
This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
|
||||
return a ``datetime.datetime`` even if ``fold`` is unchanged.
|
||||
"""
|
||||
argnames = (
|
||||
'year', 'month', 'day', 'hour', 'minute', 'second',
|
||||
'microsecond', 'tzinfo'
|
||||
)
|
||||
|
||||
for arg, argname in zip(args, argnames):
|
||||
if argname in kwargs:
|
||||
raise TypeError('Duplicate argument: {}'.format(argname))
|
||||
|
||||
kwargs[argname] = arg
|
||||
|
||||
for argname in argnames:
|
||||
if argname not in kwargs:
|
||||
kwargs[argname] = getattr(self, argname)
|
||||
|
||||
dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
|
||||
|
||||
return dt_class(**kwargs)
|
||||
|
||||
@property
|
||||
def fold(self):
|
||||
return 1
|
||||
|
||||
def enfold(dt, fold=1):
|
||||
"""
|
||||
Provides a unified interface for assigning the ``fold`` attribute to
|
||||
datetimes both before and after the implementation of PEP-495.
|
||||
|
||||
:param fold:
|
||||
The value for the ``fold`` attribute in the returned datetime. This
|
||||
should be either 0 or 1.
|
||||
|
||||
:return:
|
||||
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
|
||||
``fold`` for all versions of Python. In versions prior to
|
||||
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
|
||||
subclass of :py:class:`datetime.datetime` with the ``fold``
|
||||
attribute added, if ``fold`` is 1.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
if getattr(dt, 'fold', 0) == fold:
|
||||
return dt
|
||||
|
||||
args = dt.timetuple()[:6]
|
||||
args += (dt.microsecond, dt.tzinfo)
|
||||
|
||||
if fold:
|
||||
return _DatetimeWithFold(*args)
|
||||
else:
|
||||
return datetime(*args)
|
||||
|
||||
|
||||
def _validate_fromutc_inputs(f):
|
||||
"""
|
||||
The CPython version of ``fromutc`` checks that the input is a ``datetime``
|
||||
object and that ``self`` is attached as its ``tzinfo``.
|
||||
"""
|
||||
@wraps(f)
|
||||
def fromutc(self, dt):
|
||||
if not isinstance(dt, datetime):
|
||||
raise TypeError("fromutc() requires a datetime argument")
|
||||
if dt.tzinfo is not self:
|
||||
raise ValueError("dt.tzinfo is not self")
|
||||
|
||||
return f(self, dt)
|
||||
|
||||
return fromutc
|
||||
|
||||
|
||||
class _tzinfo(tzinfo):
|
||||
"""
|
||||
Base class for all ``dateutil`` ``tzinfo`` objects.
|
||||
"""
|
||||
|
||||
def is_ambiguous(self, dt):
|
||||
"""
|
||||
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||
zone.
|
||||
|
||||
:param dt:
|
||||
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||
|
||||
|
||||
:return:
|
||||
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
|
||||
dt = dt.replace(tzinfo=self)
|
||||
|
||||
wall_0 = enfold(dt, fold=0)
|
||||
wall_1 = enfold(dt, fold=1)
|
||||
|
||||
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
|
||||
same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
|
||||
|
||||
return same_dt and not same_offset
|
||||
|
||||
def _fold_status(self, dt_utc, dt_wall):
|
||||
"""
|
||||
Determine the fold status of a "wall" datetime, given a representation
|
||||
of the same datetime as a (naive) UTC datetime. This is calculated based
|
||||
on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
|
||||
datetimes, and that this offset is the actual number of hours separating
|
||||
``dt_utc`` and ``dt_wall``.
|
||||
|
||||
:param dt_utc:
|
||||
Representation of the datetime as UTC
|
||||
|
||||
:param dt_wall:
|
||||
Representation of the datetime as "wall time". This parameter must
|
||||
either have a `fold` attribute or have a fold-naive
|
||||
:class:`datetime.tzinfo` attached, otherwise the calculation may
|
||||
fail.
|
||||
"""
|
||||
if self.is_ambiguous(dt_wall):
|
||||
delta_wall = dt_wall - dt_utc
|
||||
_fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
|
||||
else:
|
||||
_fold = 0
|
||||
|
||||
return _fold
|
||||
|
||||
def _fold(self, dt):
|
||||
return getattr(dt, 'fold', 0)
|
||||
|
||||
def _fromutc(self, dt):
|
||||
"""
|
||||
Given a timezone-aware datetime in a given timezone, calculates a
|
||||
timezone-aware datetime in a new timezone.
|
||||
|
||||
Since this is the one time that we *know* we have an unambiguous
|
||||
datetime object, we take this opportunity to determine whether the
|
||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||
occurence, chronologically, of the ambiguous datetime).
|
||||
|
||||
:param dt:
|
||||
A timezone-aware :class:`datetime.datetime` object.
|
||||
"""
|
||||
|
||||
# Re-implement the algorithm from Python's datetime.py
|
||||
dtoff = dt.utcoffset()
|
||||
if dtoff is None:
|
||||
raise ValueError("fromutc() requires a non-None utcoffset() "
|
||||
"result")
|
||||
|
||||
# The original datetime.py code assumes that `dst()` defaults to
|
||||
# zero during ambiguous times. PEP 495 inverts this presumption, so
|
||||
# for pre-PEP 495 versions of python, we need to tweak the algorithm.
|
||||
dtdst = dt.dst()
|
||||
if dtdst is None:
|
||||
raise ValueError("fromutc() requires a non-None dst() result")
|
||||
delta = dtoff - dtdst
|
||||
|
||||
dt += delta
|
||||
# Set fold=1 so we can default to being in the fold for
|
||||
# ambiguous dates.
|
||||
dtdst = enfold(dt, fold=1).dst()
|
||||
if dtdst is None:
|
||||
raise ValueError("fromutc(): dt.dst gave inconsistent "
|
||||
"results; cannot convert")
|
||||
return dt + dtdst
|
||||
|
||||
@_validate_fromutc_inputs
|
||||
def fromutc(self, dt):
|
||||
"""
|
||||
Given a timezone-aware datetime in a given timezone, calculates a
|
||||
timezone-aware datetime in a new timezone.
|
||||
|
||||
Since this is the one time that we *know* we have an unambiguous
|
||||
datetime object, we take this opportunity to determine whether the
|
||||
datetime is ambiguous and in a "fold" state (e.g. if it's the first
|
||||
occurance, chronologically, of the ambiguous datetime).
|
||||
|
||||
:param dt:
|
||||
A timezone-aware :class:`datetime.datetime` object.
|
||||
"""
|
||||
dt_wall = self._fromutc(dt)
|
||||
|
||||
# Calculate the fold status given the two datetimes.
|
||||
_fold = self._fold_status(dt, dt_wall)
|
||||
|
||||
# Set the default fold value for ambiguous dates
|
||||
return enfold(dt_wall, fold=_fold)
|
||||
|
||||
|
||||
class tzrangebase(_tzinfo):
|
||||
"""
|
||||
This is an abstract base class for time zones represented by an annual
|
||||
transition into and out of DST. Child classes should implement the following
|
||||
methods:
|
||||
|
||||
* ``__init__(self, *args, **kwargs)``
|
||||
* ``transitions(self, year)`` - this is expected to return a tuple of
|
||||
datetimes representing the DST on and off transitions in standard
|
||||
time.
|
||||
|
||||
A fully initialized ``tzrangebase`` subclass should also provide the
|
||||
following attributes:
|
||||
* ``hasdst``: Boolean whether or not the zone uses DST.
|
||||
* ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
|
||||
representing the respective UTC offsets.
|
||||
* ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
|
||||
abbreviations in DST and STD, respectively.
|
||||
* ``_hasdst``: Whether or not the zone has DST.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
def __init__(self):
|
||||
raise NotImplementedError('tzrangebase is an abstract base class')
|
||||
|
||||
def utcoffset(self, dt):
|
||||
isdst = self._isdst(dt)
|
||||
|
||||
if isdst is None:
|
||||
return None
|
||||
elif isdst:
|
||||
return self._dst_offset
|
||||
else:
|
||||
return self._std_offset
|
||||
|
||||
def dst(self, dt):
|
||||
isdst = self._isdst(dt)
|
||||
|
||||
if isdst is None:
|
||||
return None
|
||||
elif isdst:
|
||||
return self._dst_base_offset
|
||||
else:
|
||||
return ZERO
|
||||
|
||||
@tzname_in_python2
|
||||
def tzname(self, dt):
|
||||
if self._isdst(dt):
|
||||
return self._dst_abbr
|
||||
else:
|
||||
return self._std_abbr
|
||||
|
||||
def fromutc(self, dt):
|
||||
""" Given a datetime in UTC, return local time """
|
||||
if not isinstance(dt, datetime):
|
||||
raise TypeError("fromutc() requires a datetime argument")
|
||||
|
||||
if dt.tzinfo is not self:
|
||||
raise ValueError("dt.tzinfo is not self")
|
||||
|
||||
# Get transitions - if there are none, fixed offset
|
||||
transitions = self.transitions(dt.year)
|
||||
if transitions is None:
|
||||
return dt + self.utcoffset(dt)
|
||||
|
||||
# Get the transition times in UTC
|
||||
dston, dstoff = transitions
|
||||
|
||||
dston -= self._std_offset
|
||||
dstoff -= self._std_offset
|
||||
|
||||
utc_transitions = (dston, dstoff)
|
||||
dt_utc = dt.replace(tzinfo=None)
|
||||
|
||||
isdst = self._naive_isdst(dt_utc, utc_transitions)
|
||||
|
||||
if isdst:
|
||||
dt_wall = dt + self._dst_offset
|
||||
else:
|
||||
dt_wall = dt + self._std_offset
|
||||
|
||||
_fold = int(not isdst and self.is_ambiguous(dt_wall))
|
||||
|
||||
return enfold(dt_wall, fold=_fold)
|
||||
|
||||
def is_ambiguous(self, dt):
|
||||
"""
|
||||
Whether or not the "wall time" of a given datetime is ambiguous in this
|
||||
zone.
|
||||
|
||||
:param dt:
|
||||
A :py:class:`datetime.datetime`, naive or time zone aware.
|
||||
|
||||
|
||||
:return:
|
||||
Returns ``True`` if ambiguous, ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
"""
|
||||
if not self.hasdst:
|
||||
return False
|
||||
|
||||
start, end = self.transitions(dt.year)
|
||||
|
||||
dt = dt.replace(tzinfo=None)
|
||||
return (end <= dt < end + self._dst_base_offset)
|
||||
|
||||
def _isdst(self, dt):
|
||||
if not self.hasdst:
|
||||
return False
|
||||
elif dt is None:
|
||||
return None
|
||||
|
||||
transitions = self.transitions(dt.year)
|
||||
|
||||
if transitions is None:
|
||||
return False
|
||||
|
||||
dt = dt.replace(tzinfo=None)
|
||||
|
||||
isdst = self._naive_isdst(dt, transitions)
|
||||
|
||||
# Handle ambiguous dates
|
||||
if not isdst and self.is_ambiguous(dt):
|
||||
return not self._fold(dt)
|
||||
else:
|
||||
return isdst
|
||||
|
||||
def _naive_isdst(self, dt, transitions):
|
||||
dston, dstoff = transitions
|
||||
|
||||
dt = dt.replace(tzinfo=None)
|
||||
|
||||
if dston < dstoff:
|
||||
isdst = dston <= dt < dstoff
|
||||
else:
|
||||
isdst = not dstoff <= dt < dston
|
||||
|
||||
return isdst
|
||||
|
||||
@property
|
||||
def _dst_base_offset(self):
|
||||
return self._dst_offset - self._std_offset
|
||||
|
||||
__hash__ = None
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(...)" % self.__class__.__name__
|
||||
|
||||
__reduce__ = object.__reduce__
|
||||
@@ -1,49 +0,0 @@
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class _TzSingleton(type):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instance = None
|
||||
super(_TzSingleton, cls).__init__(*args, **kwargs)
|
||||
|
||||
def __call__(cls):
|
||||
if cls.__instance is None:
|
||||
cls.__instance = super(_TzSingleton, cls).__call__()
|
||||
return cls.__instance
|
||||
|
||||
class _TzFactory(type):
|
||||
def instance(cls, *args, **kwargs):
|
||||
"""Alternate constructor that returns a fresh instance"""
|
||||
return type.__call__(cls, *args, **kwargs)
|
||||
|
||||
|
||||
class _TzOffsetFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
|
||||
def __call__(cls, name, offset):
|
||||
if isinstance(offset, timedelta):
|
||||
key = (name, offset.total_seconds())
|
||||
else:
|
||||
key = (name, offset)
|
||||
|
||||
instance = cls.__instances.get(key, None)
|
||||
if instance is None:
|
||||
instance = cls.__instances.setdefault(key,
|
||||
cls.instance(name, offset))
|
||||
return instance
|
||||
|
||||
|
||||
class _TzStrFactory(_TzFactory):
|
||||
def __init__(cls, *args, **kwargs):
|
||||
cls.__instances = {}
|
||||
|
||||
def __call__(cls, s, posix_offset=False):
|
||||
key = (s, posix_offset)
|
||||
instance = cls.__instances.get(key, None)
|
||||
|
||||
if instance is None:
|
||||
instance = cls.__instances.setdefault(key,
|
||||
cls.instance(s, posix_offset))
|
||||
return instance
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,331 +0,0 @@
|
||||
# This code was originally contributed by Jeffrey Harris.
|
||||
import datetime
|
||||
import struct
|
||||
|
||||
from six.moves import winreg
|
||||
from six import text_type
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
except ValueError:
|
||||
# ValueError is raised on non-Windows systems for some horrible reason.
|
||||
raise ImportError("Running tzwin on non-Windows system")
|
||||
|
||||
from ._common import tzrangebase
|
||||
|
||||
__all__ = ["tzwin", "tzwinlocal", "tzres"]
|
||||
|
||||
ONEWEEK = datetime.timedelta(7)
|
||||
|
||||
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
|
||||
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
|
||||
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||
|
||||
|
||||
def _settzkeyname():
|
||||
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
try:
|
||||
winreg.OpenKey(handle, TZKEYNAMENT).Close()
|
||||
TZKEYNAME = TZKEYNAMENT
|
||||
except WindowsError:
|
||||
TZKEYNAME = TZKEYNAME9X
|
||||
handle.Close()
|
||||
return TZKEYNAME
|
||||
|
||||
|
||||
TZKEYNAME = _settzkeyname()
|
||||
|
||||
|
||||
class tzres(object):
|
||||
"""
|
||||
Class for accessing `tzres.dll`, which contains timezone name related
|
||||
resources.
|
||||
|
||||
.. versionadded:: 2.5.0
|
||||
"""
|
||||
p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char
|
||||
|
||||
def __init__(self, tzres_loc='tzres.dll'):
|
||||
# Load the user32 DLL so we can load strings from tzres
|
||||
user32 = ctypes.WinDLL('user32')
|
||||
|
||||
# Specify the LoadStringW function
|
||||
user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
|
||||
wintypes.UINT,
|
||||
wintypes.LPWSTR,
|
||||
ctypes.c_int)
|
||||
|
||||
self.LoadStringW = user32.LoadStringW
|
||||
self._tzres = ctypes.WinDLL(tzres_loc)
|
||||
self.tzres_loc = tzres_loc
|
||||
|
||||
def load_name(self, offset):
|
||||
"""
|
||||
Load a timezone name from a DLL offset (integer).
|
||||
|
||||
>>> from dateutil.tzwin import tzres
|
||||
>>> tzr = tzres()
|
||||
>>> print(tzr.load_name(112))
|
||||
'Eastern Standard Time'
|
||||
|
||||
:param offset:
|
||||
A positive integer value referring to a string from the tzres dll.
|
||||
|
||||
..note:
|
||||
Offsets found in the registry are generally of the form
|
||||
`@tzres.dll,-114`. The offset in this case if 114, not -114.
|
||||
|
||||
"""
|
||||
resource = self.p_wchar()
|
||||
lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
|
||||
nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
|
||||
return resource[:nchar]
|
||||
|
||||
def name_from_string(self, tzname_str):
|
||||
"""
|
||||
Parse strings as returned from the Windows registry into the time zone
|
||||
name as defined in the registry.
|
||||
|
||||
>>> from dateutil.tzwin import tzres
|
||||
>>> tzr = tzres()
|
||||
>>> print(tzr.name_from_string('@tzres.dll,-251'))
|
||||
'Dateline Daylight Time'
|
||||
>>> print(tzr.name_from_string('Eastern Standard Time'))
|
||||
'Eastern Standard Time'
|
||||
|
||||
:param tzname_str:
|
||||
A timezone name string as returned from a Windows registry key.
|
||||
|
||||
:return:
|
||||
Returns the localized timezone string from tzres.dll if the string
|
||||
is of the form `@tzres.dll,-offset`, else returns the input string.
|
||||
"""
|
||||
if not tzname_str.startswith('@'):
|
||||
return tzname_str
|
||||
|
||||
name_splt = tzname_str.split(',-')
|
||||
try:
|
||||
offset = int(name_splt[1])
|
||||
except:
|
||||
raise ValueError("Malformed timezone string.")
|
||||
|
||||
return self.load_name(offset)
|
||||
|
||||
|
||||
class tzwinbase(tzrangebase):
|
||||
"""tzinfo class based on win32's timezones available in the registry."""
|
||||
def __init__(self):
|
||||
raise NotImplementedError('tzwinbase is an abstract base class')
|
||||
|
||||
def __eq__(self, other):
|
||||
# Compare on all relevant dimensions, including name.
|
||||
if not isinstance(other, tzwinbase):
|
||||
return NotImplemented
|
||||
|
||||
return (self._std_offset == other._std_offset and
|
||||
self._dst_offset == other._dst_offset and
|
||||
self._stddayofweek == other._stddayofweek and
|
||||
self._dstdayofweek == other._dstdayofweek and
|
||||
self._stdweeknumber == other._stdweeknumber and
|
||||
self._dstweeknumber == other._dstweeknumber and
|
||||
self._stdhour == other._stdhour and
|
||||
self._dsthour == other._dsthour and
|
||||
self._stdminute == other._stdminute and
|
||||
self._dstminute == other._dstminute and
|
||||
self._std_abbr == other._std_abbr and
|
||||
self._dst_abbr == other._dst_abbr)
|
||||
|
||||
@staticmethod
|
||||
def list():
|
||||
"""Return a list of all time zones known to the system."""
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
|
||||
result = [winreg.EnumKey(tzkey, i)
|
||||
for i in range(winreg.QueryInfoKey(tzkey)[0])]
|
||||
return result
|
||||
|
||||
def display(self):
|
||||
return self._display
|
||||
|
||||
def transitions(self, year):
|
||||
"""
|
||||
For a given year, get the DST on and off transition times, expressed
|
||||
always on the standard time side. For zones with no transitions, this
|
||||
function returns ``None``.
|
||||
|
||||
:param year:
|
||||
The year whose transitions you would like to query.
|
||||
|
||||
:return:
|
||||
Returns a :class:`tuple` of :class:`datetime.datetime` objects,
|
||||
``(dston, dstoff)`` for zones with an annual DST transition, or
|
||||
``None`` for fixed offset zones.
|
||||
"""
|
||||
|
||||
if not self.hasdst:
|
||||
return None
|
||||
|
||||
dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
|
||||
self._dsthour, self._dstminute,
|
||||
self._dstweeknumber)
|
||||
|
||||
dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
|
||||
self._stdhour, self._stdminute,
|
||||
self._stdweeknumber)
|
||||
|
||||
# Ambiguous dates default to the STD side
|
||||
dstoff -= self._dst_base_offset
|
||||
|
||||
return dston, dstoff
|
||||
|
||||
def _get_hasdst(self):
|
||||
return self._dstmonth != 0
|
||||
|
||||
@property
|
||||
def _dst_base_offset(self):
|
||||
return self._dst_base_offset_
|
||||
|
||||
|
||||
class tzwin(tzwinbase):
|
||||
|
||||
def __init__(self, name):
|
||||
self._name = name
|
||||
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
|
||||
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||
keydict = valuestodict(tzkey)
|
||||
|
||||
self._std_abbr = keydict["Std"]
|
||||
self._dst_abbr = keydict["Dlt"]
|
||||
|
||||
self._display = keydict["Display"]
|
||||
|
||||
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
|
||||
tup = struct.unpack("=3l16h", keydict["TZI"])
|
||||
stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
|
||||
dstoffset = stdoffset-tup[2] # + DaylightBias * -1
|
||||
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||
|
||||
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
|
||||
(self._stdmonth,
|
||||
self._stddayofweek, # Sunday = 0
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[4:9]
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstdayofweek, # Sunday = 0
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[12:17]
|
||||
|
||||
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||
self.hasdst = self._get_hasdst()
|
||||
|
||||
def __repr__(self):
|
||||
return "tzwin(%s)" % repr(self._name)
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (self._name,))
|
||||
|
||||
|
||||
class tzwinlocal(tzwinbase):
|
||||
def __init__(self):
|
||||
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
|
||||
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
|
||||
keydict = valuestodict(tzlocalkey)
|
||||
|
||||
self._std_abbr = keydict["StandardName"]
|
||||
self._dst_abbr = keydict["DaylightName"]
|
||||
|
||||
try:
|
||||
tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
|
||||
sn=self._std_abbr)
|
||||
with winreg.OpenKey(handle, tzkeyname) as tzkey:
|
||||
_keydict = valuestodict(tzkey)
|
||||
self._display = _keydict["Display"]
|
||||
except OSError:
|
||||
self._display = None
|
||||
|
||||
stdoffset = -keydict["Bias"]-keydict["StandardBias"]
|
||||
dstoffset = stdoffset-keydict["DaylightBias"]
|
||||
|
||||
self._std_offset = datetime.timedelta(minutes=stdoffset)
|
||||
self._dst_offset = datetime.timedelta(minutes=dstoffset)
|
||||
|
||||
# For reasons unclear, in this particular key, the day of week has been
|
||||
# moved to the END of the SYSTEMTIME structure.
|
||||
tup = struct.unpack("=8h", keydict["StandardStart"])
|
||||
|
||||
(self._stdmonth,
|
||||
self._stdweeknumber, # Last = 5
|
||||
self._stdhour,
|
||||
self._stdminute) = tup[1:5]
|
||||
|
||||
self._stddayofweek = tup[7]
|
||||
|
||||
tup = struct.unpack("=8h", keydict["DaylightStart"])
|
||||
|
||||
(self._dstmonth,
|
||||
self._dstweeknumber, # Last = 5
|
||||
self._dsthour,
|
||||
self._dstminute) = tup[1:5]
|
||||
|
||||
self._dstdayofweek = tup[7]
|
||||
|
||||
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
||||
self.hasdst = self._get_hasdst()
|
||||
|
||||
def __repr__(self):
|
||||
return "tzwinlocal()"
|
||||
|
||||
def __str__(self):
|
||||
# str will return the standard name, not the daylight name.
|
||||
return "tzwinlocal(%s)" % repr(self._std_abbr)
|
||||
|
||||
def __reduce__(self):
|
||||
return (self.__class__, ())
|
||||
|
||||
|
||||
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
|
||||
""" dayofweek == 0 means Sunday, whichweek 5 means last instance """
|
||||
first = datetime.datetime(year, month, 1, hour, minute)
|
||||
|
||||
# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
|
||||
# Because 7 % 7 = 0
|
||||
weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
|
||||
wd = weekdayone + ((whichweek - 1) * ONEWEEK)
|
||||
if (wd.month != month):
|
||||
wd -= ONEWEEK
|
||||
|
||||
return wd
|
||||
|
||||
|
||||
def valuestodict(key):
|
||||
"""Convert a registry key's values to a dictionary."""
|
||||
dout = {}
|
||||
size = winreg.QueryInfoKey(key)[1]
|
||||
tz_res = None
|
||||
|
||||
for i in range(size):
|
||||
key_name, value, dtype = winreg.EnumValue(key, i)
|
||||
if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
|
||||
# If it's a DWORD (32-bit integer), it's stored as unsigned - convert
|
||||
# that to a proper signed integer
|
||||
if value & (1 << 31):
|
||||
value = value - (1 << 32)
|
||||
elif dtype == winreg.REG_SZ:
|
||||
# If it's a reference to the tzres DLL, load the actual string
|
||||
if value.startswith('@tzres'):
|
||||
tz_res = tz_res or tzres()
|
||||
value = tz_res.name_from_string(value)
|
||||
|
||||
value = value.rstrip('\x00') # Remove trailing nulls
|
||||
|
||||
dout[key_name] = value
|
||||
|
||||
return dout
|
||||
@@ -1,2 +0,0 @@
|
||||
# tzwin has moved to dateutil.tz.win
|
||||
from .tz.win import *
|
||||
@@ -1,71 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module offers general convenience and utility functions for dealing with
|
||||
datetimes.
|
||||
|
||||
.. versionadded:: 2.7.0
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime, time
|
||||
|
||||
|
||||
def today(tzinfo=None):
|
||||
"""
|
||||
Returns a :py:class:`datetime` representing the current day at midnight
|
||||
|
||||
:param tzinfo:
|
||||
The time zone to attach (also used to determine the current day).
|
||||
|
||||
:return:
|
||||
A :py:class:`datetime.datetime` object representing the current day
|
||||
at midnight.
|
||||
"""
|
||||
|
||||
dt = datetime.now(tzinfo)
|
||||
return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
|
||||
|
||||
|
||||
def default_tzinfo(dt, tzinfo):
|
||||
"""
|
||||
Sets the the ``tzinfo`` parameter on naive datetimes only
|
||||
|
||||
This is useful for example when you are provided a datetime that may have
|
||||
either an implicit or explicit time zone, such as when parsing a time zone
|
||||
string.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> from dateutil.tz import tzoffset
|
||||
>>> from dateutil.parser import parse
|
||||
>>> from dateutil.utils import default_tzinfo
|
||||
>>> dflt_tz = tzoffset("EST", -18000)
|
||||
>>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
|
||||
2014-01-01 12:30:00+00:00
|
||||
>>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
|
||||
2014-01-01 12:30:00-05:00
|
||||
|
||||
:param dt:
|
||||
The datetime on which to replace the time zone
|
||||
|
||||
:param tzinfo:
|
||||
The :py:class:`datetime.tzinfo` subclass instance to assign to
|
||||
``dt`` if (and only if) it is naive.
|
||||
|
||||
:return:
|
||||
Returns an aware :py:class:`datetime.datetime`.
|
||||
"""
|
||||
if dt.tzinfo is not None:
|
||||
return dt
|
||||
else:
|
||||
return dt.replace(tzinfo=tzinfo)
|
||||
|
||||
|
||||
def within_delta(dt1, dt2, delta):
|
||||
"""
|
||||
Useful for comparing two datetimes that may a negilible difference
|
||||
to be considered equal.
|
||||
"""
|
||||
delta = abs(delta)
|
||||
difference = dt1 - dt2
|
||||
return -delta <= difference <= delta
|
||||
@@ -1,167 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import warnings
|
||||
import json
|
||||
|
||||
from tarfile import TarFile
|
||||
from pkgutil import get_data
|
||||
from io import BytesIO
|
||||
|
||||
from dateutil.tz import tzfile as _tzfile
|
||||
|
||||
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
|
||||
|
||||
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
||||
METADATA_FN = 'METADATA'
|
||||
|
||||
|
||||
class tzfile(_tzfile):
|
||||
def __reduce__(self):
|
||||
return (gettz, (self._filename,))
|
||||
|
||||
|
||||
def getzoneinfofile_stream():
|
||||
try:
|
||||
return BytesIO(get_data(__name__, ZONEFILENAME))
|
||||
except IOError as e: # TODO switch to FileNotFoundError?
|
||||
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
||||
return None
|
||||
|
||||
|
||||
class ZoneInfoFile(object):
|
||||
def __init__(self, zonefile_stream=None):
|
||||
if zonefile_stream is not None:
|
||||
with TarFile.open(fileobj=zonefile_stream) as tf:
|
||||
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
|
||||
for zf in tf.getmembers()
|
||||
if zf.isfile() and zf.name != METADATA_FN}
|
||||
# deal with links: They'll point to their parent object. Less
|
||||
# waste of memory
|
||||
links = {zl.name: self.zones[zl.linkname]
|
||||
for zl in tf.getmembers() if
|
||||
zl.islnk() or zl.issym()}
|
||||
self.zones.update(links)
|
||||
try:
|
||||
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
||||
metadata_str = metadata_json.read().decode('UTF-8')
|
||||
self.metadata = json.loads(metadata_str)
|
||||
except KeyError:
|
||||
# no metadata in tar file
|
||||
self.metadata = None
|
||||
else:
|
||||
self.zones = {}
|
||||
self.metadata = None
|
||||
|
||||
def get(self, name, default=None):
|
||||
"""
|
||||
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
|
||||
for retrieving zones from the zone dictionary.
|
||||
|
||||
:param name:
|
||||
The name of the zone to retrieve. (Generally IANA zone names)
|
||||
|
||||
:param default:
|
||||
The value to return in the event of a missing key.
|
||||
|
||||
.. versionadded:: 2.6.0
|
||||
|
||||
"""
|
||||
return self.zones.get(name, default)
|
||||
|
||||
|
||||
# The current API has gettz as a module function, although in fact it taps into
|
||||
# a stateful class. So as a workaround for now, without changing the API, we
|
||||
# will create a new "global" class instance the first time a user requests a
|
||||
# timezone. Ugly, but adheres to the api.
|
||||
#
|
||||
# TODO: Remove after deprecation period.
|
||||
_CLASS_ZONE_INSTANCE = []
|
||||
|
||||
|
||||
def get_zonefile_instance(new_instance=False):
|
||||
"""
|
||||
This is a convenience function which provides a :class:`ZoneInfoFile`
|
||||
instance using the data provided by the ``dateutil`` package. By default, it
|
||||
caches a single instance of the ZoneInfoFile object and returns that.
|
||||
|
||||
:param new_instance:
|
||||
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
|
||||
used as the cached instance for the next call. Otherwise, new instances
|
||||
are created only as necessary.
|
||||
|
||||
:return:
|
||||
Returns a :class:`ZoneInfoFile` object.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
if new_instance:
|
||||
zif = None
|
||||
else:
|
||||
zif = getattr(get_zonefile_instance, '_cached_instance', None)
|
||||
|
||||
if zif is None:
|
||||
zif = ZoneInfoFile(getzoneinfofile_stream())
|
||||
|
||||
get_zonefile_instance._cached_instance = zif
|
||||
|
||||
return zif
|
||||
|
||||
|
||||
def gettz(name):
|
||||
"""
|
||||
This retrieves a time zone from the local zoneinfo tarball that is packaged
|
||||
with dateutil.
|
||||
|
||||
:param name:
|
||||
An IANA-style time zone name, as found in the zoneinfo file.
|
||||
|
||||
:return:
|
||||
Returns a :class:`dateutil.tz.tzfile` time zone object.
|
||||
|
||||
.. warning::
|
||||
It is generally inadvisable to use this function, and it is only
|
||||
provided for API compatibility with earlier versions. This is *not*
|
||||
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
|
||||
time zone based on the inputs, favoring system zoneinfo. This is ONLY
|
||||
for accessing the dateutil-specific zoneinfo (which may be out of
|
||||
date compared to the system zoneinfo).
|
||||
|
||||
.. deprecated:: 2.6
|
||||
If you need to use a specific zoneinfofile over the system zoneinfo,
|
||||
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
|
||||
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
|
||||
|
||||
Use :func:`get_zonefile_instance` to retrieve an instance of the
|
||||
dateutil-provided zoneinfo.
|
||||
"""
|
||||
warnings.warn("zoneinfo.gettz() will be removed in future versions, "
|
||||
"to use the dateutil-provided zoneinfo files, instantiate a "
|
||||
"ZoneInfoFile object and use ZoneInfoFile.zones.get() "
|
||||
"instead. See the documentation for details.",
|
||||
DeprecationWarning)
|
||||
|
||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
||||
|
||||
|
||||
def gettz_db_metadata():
|
||||
""" Get the zonefile metadata
|
||||
|
||||
See `zonefile_metadata`_
|
||||
|
||||
:returns:
|
||||
A dictionary with the database metadata
|
||||
|
||||
.. deprecated:: 2.6
|
||||
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
|
||||
query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
|
||||
"""
|
||||
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
|
||||
"versions, to use the dateutil-provided zoneinfo files, "
|
||||
"ZoneInfoFile object and query the 'metadata' attribute "
|
||||
"instead. See the documentation for details.",
|
||||
DeprecationWarning)
|
||||
|
||||
if len(_CLASS_ZONE_INSTANCE) == 0:
|
||||
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
||||
return _CLASS_ZONE_INSTANCE[0].metadata
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,53 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import json
|
||||
from subprocess import check_call
|
||||
from tarfile import TarFile
|
||||
|
||||
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
|
||||
|
||||
|
||||
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
||||
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
||||
|
||||
filename is the timezone tarball from ``ftp.iana.org/tz``.
|
||||
|
||||
"""
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zonedir = os.path.join(tmpdir, "zoneinfo")
|
||||
moduledir = os.path.dirname(__file__)
|
||||
try:
|
||||
with TarFile.open(filename) as tf:
|
||||
for name in zonegroups:
|
||||
tf.extract(name, tmpdir)
|
||||
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
||||
try:
|
||||
check_call(["zic", "-d", zonedir] + filepaths)
|
||||
except OSError as e:
|
||||
_print_on_nosuchfile(e)
|
||||
raise
|
||||
# write metadata file
|
||||
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
||||
json.dump(metadata, f, indent=4, sort_keys=True)
|
||||
target = os.path.join(moduledir, ZONEFILENAME)
|
||||
with TarFile.open(target, "w:%s" % format) as tf:
|
||||
for entry in os.listdir(zonedir):
|
||||
entrypath = os.path.join(zonedir, entry)
|
||||
tf.add(entrypath, entry)
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
def _print_on_nosuchfile(e):
|
||||
"""Print helpful troubleshooting message
|
||||
|
||||
e is an exception raised by subprocess.check_call()
|
||||
|
||||
"""
|
||||
if e.errno == 2:
|
||||
logging.error(
|
||||
"Could not find zic. Perhaps you need to install "
|
||||
"libc-bin or some other package that provides it, "
|
||||
"or it's not in your PATH?")
|
||||
@@ -1,5 +0,0 @@
|
||||
"""Run the EasyInstall command"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
from setuptools.command.easy_install import main
|
||||
main()
|
||||
@@ -1,16 +0,0 @@
|
||||
Welcome to Kiwi
|
||||
===============
|
||||
|
||||
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
|
||||
:target: https://travis-ci.org/nucleic/kiwi
|
||||
|
||||
Kiwi is an efficient C++ implementation of the Cassowary constraint solving
|
||||
algorithm. Kiwi is an implementation of the algorithm based on the seminal
|
||||
Cassowary paper. It is *not* a refactoring of the original C++ solver. Kiwi
|
||||
has been designed from the ground up to be lightweight and fast. Kiwi ranges
|
||||
from 10x to 500x faster than the original Cassowary solver with typical use
|
||||
cases gaining a 40x improvement. Memory savings are consistently > 5x.
|
||||
|
||||
In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,28 +0,0 @@
|
||||
Metadata-Version: 2.0
|
||||
Name: kiwisolver
|
||||
Version: 1.0.1
|
||||
Summary: A fast implementation of the Cassowary constraint solver
|
||||
Home-page: https://github.com/nucleic/kiwi
|
||||
Author: The Nucleic Development Team
|
||||
Author-email: sccolbert@gmail.com
|
||||
License: UNKNOWN
|
||||
Description-Content-Type: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
Requires-Dist: setuptools
|
||||
|
||||
Welcome to Kiwi
|
||||
===============
|
||||
|
||||
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
|
||||
:target: https://travis-ci.org/nucleic/kiwi
|
||||
|
||||
Kiwi is an efficient C++ implementation of the Cassowary constraint solving
|
||||
algorithm. Kiwi is an implementation of the algorithm based on the seminal
|
||||
Cassowary paper. It is *not* a refactoring of the original C++ solver. Kiwi
|
||||
has been designed from the ground up to be lightweight and fast. Kiwi ranges
|
||||
from 10x to 500x faster than the original Cassowary solver with typical use
|
||||
cases gaining a 40x improvement. Memory savings are consistently > 5x.
|
||||
|
||||
In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
kiwisolver-1.0.1.dist-info/DESCRIPTION.rst,sha256=Qy5sjKaN4toH_Q7EUHWgwRVmpxBgeGUwEcyB75zRwSI,676
|
||||
kiwisolver-1.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
kiwisolver-1.0.1.dist-info/METADATA,sha256=bFtjRI423l1EopYow7-FVdw80WP942cTW4I37mnnunU,1006
|
||||
kiwisolver-1.0.1.dist-info/RECORD,,
|
||||
kiwisolver-1.0.1.dist-info/WHEEL,sha256=xLbWRW0PO79-mIB5Y7BBxUY9L97LDdQCs-obTuQSLEg,109
|
||||
kiwisolver-1.0.1.dist-info/metadata.json,sha256=H-tQ6em1_t3jGeivswhlBiHFJ7CBAI6G_RZAsT-QHCs,535
|
||||
kiwisolver-1.0.1.dist-info/top_level.txt,sha256=xqwWj7oSHlpIjcw2QMJb8puTFPdjDBO78AZp9gjTh9c,11
|
||||
kiwisolver.cpython-36m-x86_64-linux-gnu.so,sha256=4GkBV_S_pJ93VLqTL5uPrsTtcIaTx6RIvdblto_AmVg,3860760
|
||||
@@ -1,5 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.30.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp36-cp36m-manylinux1_x86_64
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "sccolbert@gmail.com", "name": "The Nucleic Development Team", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/nucleic/kiwi"}}}, "extras": [], "generator": "bdist_wheel (0.30.0)", "metadata_version": "2.0", "name": "kiwisolver", "run_requires": [{"requires": ["setuptools"]}], "summary": "A fast implementation of the Cassowary constraint solver", "version": "1.0.1"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user