Compare commits
4 commits
92678ea6bf
...
main
Author | SHA1 | Date | |
---|---|---|---|
6df22f8cd5 | |||
03dd1a485d | |||
b0f8c48e99 | |||
031145db01 |
13 changed files with 318 additions and 57 deletions
86
.woodpecker.yml
Normal file
86
.woodpecker.yml
Normal file
|
@ -0,0 +1,86 @@
|
|||
branches: main
|
||||
|
||||
pipeline:
|
||||
code_lint:
|
||||
image: python
|
||||
commands:
|
||||
- pip install poetry
|
||||
- poetry install
|
||||
- pip install black
|
||||
- echo "----------------- running lint ------------------"
|
||||
- python --version && poetry --version && black --version
|
||||
- poetry run black .
|
||||
|
||||
unit_tests:
|
||||
image: thekevjames/nox
|
||||
commands:
|
||||
- pip install poetry
|
||||
- poetry install
|
||||
- echo "----------------- running tests ------------------"
|
||||
- python --version && poetry --version && nox --version
|
||||
- poetry run nox
|
||||
|
||||
static_analysis:
|
||||
image: python
|
||||
commands:
|
||||
- pip install poetry
|
||||
- poetry install
|
||||
- pip install mypy
|
||||
- echo "----------------- running analysis ------------------"
|
||||
- python --version && poetry --version && mypy --version
|
||||
- poetry run mypy .
|
||||
|
||||
build_dist:
|
||||
image: python
|
||||
commands:
|
||||
- pip install poetry
|
||||
- poetry install
|
||||
- echo "----------------- running analysis ------------------"
|
||||
- python --version && poetry --version
|
||||
- poetry build
|
||||
when:
|
||||
branch: main
|
||||
|
||||
release_prep:
|
||||
image: python
|
||||
commands:
|
||||
- echo "----------------- preparing release ------------------"
|
||||
- python tools/extract-changelog.py
|
||||
|
||||
gitea_release:
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: gitea_release_token
|
||||
base_url: https://git.martyoeh.me
|
||||
files: dist/*
|
||||
title: NEWEST_VERSION.md
|
||||
note: NEWEST_CHANGES.md
|
||||
when:
|
||||
event: tag
|
||||
tag: v*
|
||||
|
||||
pypi_release:
|
||||
image: python
|
||||
commands:
|
||||
- pip install poetry
|
||||
- poetry install
|
||||
- echo "----------------- publishing to pypi ------------------"
|
||||
- poetry publish --username "$PYPI_USERNAME" --password "$PYPI_PASSWORD"
|
||||
when:
|
||||
event: tag
|
||||
tag: v*
|
||||
|
||||
notify_matrix:
|
||||
image: plugins/matrix
|
||||
settings:
|
||||
homeserver: https://matrix.org
|
||||
roomid:
|
||||
from_secret: matrix_roomid
|
||||
userid:
|
||||
from_secret: matrix_userid
|
||||
accesstoken:
|
||||
from_secret: matrix_token
|
||||
when:
|
||||
status: [ success, failure ]
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -5,9 +5,19 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project tries to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
<!-- ## [Unreleased] -->
|
||||
|
||||
## [0.4] - 2021-12-06
|
||||
## [0.4.1] - 2022-01-05
|
||||
|
||||
### Added
|
||||
|
||||
* Added pypi release publication
|
||||
|
||||
### Changed
|
||||
|
||||
* Compatible with Python stretching back to version 3.7
|
||||
|
||||
## [0.4.0] - 2021-12-06
|
||||
|
||||
### Added
|
||||
|
||||
|
|
64
README.md
64
README.md
|
@ -1,44 +1,64 @@
|
|||
# habit-migrate
|
||||
# habitmove
|
||||
|
||||
Can take an export of nomie habits in json format and convert it to be importable in Loop Habit Tracker.
|
||||
Takes habit in one habit-tracking application and transforms them ready to use for another.
|
||||
|
||||
Confirmed working for nomie version 5.6.4 and Loop Habit Tracker version 2.0.2.
|
||||
Currently can take an export of nomie habits in json format and convert it to be importable in Loop Habit Tracker.
|
||||
Plans for reverse migration are on the roadmap, and ultimately this tool ideally understands more and more habit formats to prevent app lock-in.
|
||||
|
||||
Confirmed working for nomie version 5.6.4 and Loop Habit Tracker version 2.0.2 and 2.0.3.
|
||||
Presumably works for other nomie 5.x versions and other Loop 2.x versions as well,
|
||||
but that is untested.
|
||||
|
||||
## Installation
|
||||
|
||||
Installation can be accomplished through *pip*:
|
||||
|
||||
```bash
|
||||
pip install habitmove
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
`habitmove` requires at least Python 3.7.
|
||||
It has only been tested on GNU/Linux (amd64) though it should work on other platforms.
|
||||
|
||||
## Usage
|
||||
|
||||
Run as a commandline utility habit migrate currently takes a single argument, the nomie database `.json` file.
|
||||
The output as importable Loop Habit Tracker database will be written to `output.db` in present working directory.
|
||||
Run as a cli utility `habitmove` currently takes a single argument: the nomie database `.json` file to import habits from.
|
||||
|
||||
Invoked like: `habitmove nomie-export.json`.
|
||||
|
||||
The output as a Loop Habit Tracker database will be written to `output.db` in the present working directory.
|
||||
|
||||
Can also take an existing Loop Habit database (exported from the application),
|
||||
and add the nomie exported habits and checkmarks to it.
|
||||
Simply put the exported Loop database in the same directory and call it `output.db`,
|
||||
it will not (should not™️) overwrite anything.
|
||||
If there are any duplicated habits however,
|
||||
it will add duplications of the existing repetitions into the database.
|
||||
|
||||
Invoked like: `python run.py nomie-export.json`.
|
||||
Note, however, that -- until a packaged version is released -- you will need to have some packages in your environment.
|
||||
If you wish to run it un-packaged, install [poetry](https://python-poetry.org/) and let it do all dependency management by doing:
|
||||
## Development
|
||||
|
||||
```
|
||||
To enable easy development on the app,
|
||||
install [poetry](https://python-poetry.org/) and let it do all dependency management for you by doing:
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
poetry run habitmove <nomie-json>
|
||||
```
|
||||
|
||||
In the future there might be an easier road to using this package but that's the way it is for now.
|
||||
To see a set up more closely resembling the final cli environment,
|
||||
with its libraries loaded as environmental dependencies enter the poetry shell:
|
||||
|
||||
The package can also be used as a library to load nomie data
|
||||
or move data into Loop Habit Tracker.
|
||||
```bash
|
||||
poetry shell
|
||||
```
|
||||
|
||||
The package can eventually also be used as a library to load nomie data to work with in Python,
|
||||
or to move data into Loop Habit Tracker.
|
||||
Take a look at the `Parser` and `Transformer` interfaces respectively.
|
||||
|
||||
## Roadmap
|
||||
|
||||
* [ ] clean up README
|
||||
* [ ] begin adding tests
|
||||
* [ ] add some unit tests for various functions
|
||||
* [ ] and at least an integration test for the stable database (loop-2021-12-02.db or equivalent)
|
||||
* [ ] abstract migration target away from loop
|
||||
* [ ] abstract import away from nomie
|
||||
* [ ] abstract importer/migrator themselves to work with other targets
|
||||
* [ ] allow migration to/from nomie/loop
|
||||
To run tests for the app, simply invoke `pytest` through `poetry run pytest` or from within the `poetry shell`.
|
||||
To run larger scale test automation, make sure you habe nox installed and run `poetry run ` or again through the shell.
|
||||
|
||||
You can exclude integration tests that take longer and inspect the complete database output of the program through the parameters `-m "not e2e"` for both `pytest` and `nox` (which also does it automatically).
|
||||
|
|
9
noxfile.py
Normal file
9
noxfile.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
import nox
|
||||
|
||||
|
||||
@nox.session(python=["3.7", "3.8", "3.9"])
|
||||
def tests(session):
|
||||
args = session.posargs or ["--cov"]
|
||||
session.run("poetry", "install", external=True)
|
||||
session.run("pytest", *args)
|
||||
pass
|
68
poetry.lock
generated
68
poetry.lock
generated
|
@ -30,6 +30,7 @@ python-versions = ">=3.6"
|
|||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
|
@ -53,6 +54,21 @@ tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
|
|||
[package.extras]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "0.23"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3"
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "rst.linker"]
|
||||
testing = ["packaging", "importlib-resources"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
|
@ -80,6 +96,9 @@ category = "dev"
|
|||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
@ -115,6 +134,7 @@ python-versions = ">=3.6"
|
|||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
|
@ -139,6 +159,20 @@ pytest = ">=4.6"
|
|||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-mock"
|
||||
version = "3.6.1"
|
||||
description = "Thin-wrapper around the mock package for easier use with pytest"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=5.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox", "pytest-asyncio"]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
|
@ -149,16 +183,28 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "1.2.2"
|
||||
version = "2.0.0"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.6.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "47104052627c5737e341ecfb58eb57c259f5baf5c99d7251dc860b331e18bee0"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "fec236ab912efe582781c62e4cd2c4cd7e609557e284e067fa875f30ab806d93"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = [
|
||||
|
@ -226,6 +272,10 @@ coverage = [
|
|||
{file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
|
||||
{file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-0.23-py2.py3-none-any.whl", hash = "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"},
|
||||
{file = "importlib_metadata-0.23.tar.gz", hash = "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26"},
|
||||
]
|
||||
iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
|
@ -254,11 +304,19 @@ pytest-cov = [
|
|||
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
|
||||
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
|
||||
]
|
||||
pytest-mock = [
|
||||
{file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"},
|
||||
{file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"},
|
||||
{file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"},
|
||||
{file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"},
|
||||
{file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
|
||||
{file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
|
||||
]
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
[tool.poetry]
|
||||
name = "habitmove"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
description = "migrate nomie data to loop habits tracker"
|
||||
license="GPL-3.0-only"
|
||||
readme="README.md"
|
||||
repository="https://git.martyoeh.me/Marty/habit-migrate"
|
||||
authors = ["Marty Oehme <marty.oehme@gmail.com>"]
|
||||
packages = [
|
||||
{ include = "habitmove", from = "src"},
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
importlib-metadata = {version = "^1.0", python = "<3.8"}
|
||||
python = "^3.9"
|
||||
importlib-metadata = {version = "^0.23", python = "<3.8"}
|
||||
python = "^3.7"
|
||||
click = "^8.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^6.2"
|
||||
coverage = {extras = ["toml"], version = "^6.2"}
|
||||
pytest-cov = "^3.0.0"
|
||||
pytest-mock = "^3.6.1"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
habitmove = "habitmove.cli:main"
|
||||
|
@ -34,3 +38,13 @@ show_missing = true
|
|||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"click",
|
||||
"click.testing",
|
||||
"pytest",
|
||||
"nox",
|
||||
"importlib-metadata"
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# init.py
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
try:
|
||||
from importlib.metadata import version as metadata_version
|
||||
else:
|
||||
from importlib_metadata import version as metadata_version
|
||||
except ImportError:
|
||||
from importlib_metadata import version as metadata_version # type: ignore
|
||||
|
||||
__version__ = str(metadata_version(__name__))
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
|
||||
from habitmove.nomiedata import Tracker
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Union
|
||||
from dataclasses import dataclass, field
|
||||
import re
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
@ -53,10 +55,11 @@ def habit_list_add_ids(c: sqlite3.Cursor, habitlist: list[Habit]) -> dict[int, H
|
|||
:return habit_id_dict: The habit collection as a dict with the keys
|
||||
consisting of the habit's sqlite database ID.
|
||||
"""
|
||||
with_id = {}
|
||||
with_id: dict[int, Habit] = {}
|
||||
for h in habitlist:
|
||||
sql_id = fetch_habit_id(c, h.uuid or "")
|
||||
with_id[sql_id] = h
|
||||
if sql_id is not None:
|
||||
with_id[sql_id] = h
|
||||
|
||||
return with_id
|
||||
|
||||
|
@ -72,6 +75,8 @@ def fetch_habit_id(cursor: sqlite3.Cursor, uuid: str) -> Optional[int]:
|
|||
if id is not None:
|
||||
return id[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def add_to_database(
|
||||
cursor: sqlite3.Cursor, habits: dict[int, Habit], repetition: Repetition
|
||||
|
@ -92,7 +97,8 @@ def add_to_database(
|
|||
(sql_id, repetition.timestamp, repetition.value),
|
||||
)
|
||||
except sqlite3.IntegrityError:
|
||||
# TODO better error handling
|
||||
# FIXME better error handling
|
||||
# TODO think about adapting this to allow importing into existing databases
|
||||
print(
|
||||
f"{sql_id}, {habit.name}: timestamp {datetime.fromtimestamp(repetition.timestamp/1000)} not unique, moving timestamp slightly."
|
||||
)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import sqlite3
|
||||
import sys
|
||||
|
||||
|
||||
def create_database(name):
|
||||
def create_database(db_file: str = ":memory:") -> sqlite3.Connection:
|
||||
"""create a database connection to the SQLite database
|
||||
specified by db_file
|
||||
:param db_file: database file
|
||||
|
@ -9,16 +10,14 @@ def create_database(name):
|
|||
"""
|
||||
conn = None
|
||||
try:
|
||||
conn = sqlite3.connect(name)
|
||||
conn = sqlite3.connect(db_file)
|
||||
return conn
|
||||
except sqlite3.Error as e:
|
||||
print(e)
|
||||
|
||||
return conn
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_tables(db):
|
||||
c = db.cursor()
|
||||
def create_tables(c: sqlite3.Cursor):
|
||||
c.execute(
|
||||
""" CREATE TABLE IF NOT EXISTS Habits (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
|
@ -51,8 +50,7 @@ def create_tables(db):
|
|||
)
|
||||
|
||||
|
||||
def create_constraints(db):
|
||||
c = db.cursor()
|
||||
def create_constraints(c: sqlite3.Cursor):
|
||||
c.execute(
|
||||
""" CREATE UNIQUE INDEX IF NOT EXISTS idx_repetitions_habit_timestamp
|
||||
on Repetitions( habit, timestamp);
|
||||
|
@ -60,15 +58,15 @@ def create_constraints(db):
|
|||
)
|
||||
|
||||
|
||||
def create_pragma(db):
|
||||
c = db.cursor()
|
||||
def create_pragma(c: sqlite3.Cursor):
|
||||
c.execute(""" PRAGMA user_version = 24; """)
|
||||
c.execute(""" PRAGMA schema_version = 30; """)
|
||||
|
||||
|
||||
def migrate(name):
|
||||
db = create_database(name)
|
||||
create_tables(db)
|
||||
create_constraints(db)
|
||||
create_pragma(db)
|
||||
def migrate(fname):
|
||||
db = create_database(fname)
|
||||
c = db.cursor()
|
||||
create_tables(c)
|
||||
create_constraints(c)
|
||||
create_pragma(c)
|
||||
return db
|
||||
|
|
|
@ -37,8 +37,8 @@ def trackerlist():
|
|||
|
||||
|
||||
def test_simple_habit_transform_from_tracker(trackerlist):
|
||||
sut = trackerlist[0]
|
||||
assert habits.trackers_to_habits([sut]) == [
|
||||
result = habits.trackers_to_habits([trackerlist[0]])
|
||||
assert result == [
|
||||
loopdata.Habit(
|
||||
name="🧪 Testing",
|
||||
description="testtrack",
|
||||
|
@ -51,8 +51,8 @@ def test_simple_habit_transform_from_tracker(trackerlist):
|
|||
|
||||
|
||||
def test_range_habit_transform_from_tracker(trackerlist):
|
||||
sut = trackerlist[1]
|
||||
assert habits.trackers_to_habits([sut]) == [
|
||||
result = habits.trackers_to_habits([trackerlist[1]])
|
||||
assert result == [
|
||||
loopdata.Habit(
|
||||
name="🧪 Testing",
|
||||
description="testtrack",
|
||||
|
|
56
tools/extract-changelog.py
Normal file
56
tools/extract-changelog.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
import re
|
||||
|
||||
## Extracts the version and newest changes from a semantic changelog.
|
||||
#
|
||||
# Important, it only works with three-parted version numbers
|
||||
# a-la 1.2.3 or 313.01.1888 -- needs \d.\d.\d to work.
|
||||
#
|
||||
# The version number and changeset will be put in `NEWEST_VERSION.md`
|
||||
# and `NEWEST_CHANGES.md` respectively, for further use in releases.
|
||||
OUTPUT_FILE_VERSION = "NEWEST_VERSION.md"
|
||||
OUTPUT_FILE_CHANGES = "NEWEST_CHANGES.md"
|
||||
|
||||
|
||||
def getVersion(file):
|
||||
for line in file:
|
||||
m = re.match(r"^## \[(\d+\.\d+\.\d+)\]", line)
|
||||
if m and m.group(1):
|
||||
return m.group(1)
|
||||
|
||||
|
||||
def getSection(file):
|
||||
inRecordingMode = False
|
||||
for line in file:
|
||||
if not inRecordingMode:
|
||||
if re.match(r"^## \[\d+\.\d+\.\d+\]", line):
|
||||
inRecordingMode = True
|
||||
elif re.match(r"^## \[\d+\.\d+\.\d+\]", line):
|
||||
inRecordingMode = False
|
||||
break
|
||||
elif re.match(r"^$", line):
|
||||
pass
|
||||
else:
|
||||
yield line
|
||||
|
||||
|
||||
def toFile(fname, content):
|
||||
file = open(fname, "w")
|
||||
file.write(content)
|
||||
file.close()
|
||||
|
||||
|
||||
with open("CHANGELOG.md") as file:
|
||||
title = getVersion(file)
|
||||
print(title)
|
||||
toFile(OUTPUT_FILE_VERSION, title)
|
||||
|
||||
with open("CHANGELOG.md") as file:
|
||||
newest_changes_gen = getSection(file)
|
||||
newest_changes = ""
|
||||
for line in newest_changes_gen:
|
||||
newest_changes += line
|
||||
print("[Extracted Changelog]")
|
||||
print(newest_changes)
|
||||
toFile(OUTPUT_FILE_CHANGES, newest_changes)
|
||||
|
||||
file.close()
|
Loading…
Reference in a new issue