179 lines
5.9 KiB
Python
179 lines
5.9 KiB
Python
"""
|
|
Helpers for deprecating voice commands, actions, and captures. Since Talon can
|
|
be an important part of people's workflows providing a warning before removing
|
|
functionality is encouraged.
|
|
|
|
The normal deprecation process in `community` is as follows:
|
|
|
|
1. For 6 months from deprecation a deprecated action or command should
|
|
continue working. Put an entry in the BREAKING_CHANGES.txt file in the
|
|
project root to mark the deprecation and potentially explain how users can
|
|
migrate. Use the user.deprecate_command, user.deprecate_action, or
|
|
user.deprecate_capture actions to notify users.
|
|
2. After 6 months you can delete the deprecated command, action, or capture.
|
|
Leave the note in BREAKING_CHANGES.txt so people who missed the
|
|
notifications can see what happened.
|
|
|
|
If for some reason you can't keep the functionality working for 6 months,
|
|
just put the information in BREAKING_CHANGES.txt so people can look there to
|
|
see what happened.
|
|
|
|
Usages:
|
|
|
|
# myfile.talon - demonstrate voice command deprecation
|
|
...
|
|
old legacy command:
|
|
# This will show a notification to say use 'new command' instead of
|
|
# 'old legacy command'. No removal of functionality is allowed.
|
|
user.deprecate_command("2022-11-10", "old legacy command", "new command")
|
|
# perform command
|
|
|
|
new command:
|
|
# perform command
|
|
|
|
# myfile.py - demonstrate action deprecation
|
|
from talon import actions
|
|
|
|
@mod.action_class
|
|
class Actions:
|
|
def legacy_action():
|
|
actions.user.deprecate_action("2022-10-01", "user.legacy_action")
|
|
# Perform action
|
|
|
|
# otherfile.py - demostrate capture deprecation
|
|
@mod.capture(rule="...")
|
|
def legacy_capture(m) -> str:
|
|
actions.user.deprecate_capture("2023-09-03", "user.legacy_capture")
|
|
# implement capture
|
|
|
|
See https://github.com/talonhub/community/issues/940 for original discussion
|
|
"""
|
|
|
|
import datetime
|
|
import os.path
|
|
import warnings
|
|
|
|
from talon import Module, actions, settings, speech_system
|
|
|
|
REPO_DIR = os.path.dirname(os.path.dirname(__file__))
|
|
|
|
mod = Module()
|
|
mod.setting(
|
|
"deprecate_warning_interval_hours",
|
|
type=float,
|
|
desc="""How long, in hours, to wait before notifying the user again of a
|
|
deprecated action/command/capture.""",
|
|
default=24,
|
|
)
|
|
|
|
# Tells us the last time a notification was shown so we can
|
|
# decide when to re-show it without annoying the user too
|
|
# much
|
|
notification_last_shown = {}
|
|
|
|
# This gets reset on every phrase, so we avoid notifying more than once per
|
|
# phrase.
|
|
notified_in_phrase = set()
|
|
|
|
|
|
def calculate_rule_info():
|
|
"""
|
|
Try to work out the .talon file and line of the command that is executing
|
|
"""
|
|
try:
|
|
current_command = actions.core.current_command__unstable()
|
|
start_line = current_command[0].target.start_line
|
|
filename = current_command[0].target.filename
|
|
rule = " ".join(current_command[1]._unmapped)
|
|
return f'\nTriggered from "{rule}" ({filename}:{start_line})'
|
|
except Exception as e:
|
|
return ""
|
|
|
|
|
|
def deprecate_notify(id: str, message: str):
|
|
"""
|
|
Notify the user about a deprecation/deactivation. id uniquely
|
|
identifies this deprecation.
|
|
"""
|
|
|
|
maybe_last_shown = notification_last_shown.get(id)
|
|
now = datetime.datetime.now()
|
|
interval = settings.get("user.deprecate_warning_interval_hours")
|
|
threshold = now - datetime.timedelta(hours=interval)
|
|
if maybe_last_shown is not None and maybe_last_shown > threshold:
|
|
return
|
|
|
|
actions.app.notify(message, "Deprecation warning")
|
|
notification_last_shown[id] = now
|
|
|
|
|
|
def post_phrase(_ignored):
|
|
global notified_in_phrase
|
|
notified_in_phrase = set()
|
|
|
|
|
|
speech_system.register("post:phrase", post_phrase)
|
|
|
|
|
|
@mod.action_class
|
|
class Actions:
|
|
def deprecate_command(time_deprecated: str, name: str, replacement: str):
|
|
"""
|
|
Notify the user that the given voice command is deprecated and should
|
|
not be used into the future; the command `replacement` should be used
|
|
instead.
|
|
"""
|
|
|
|
if name in notified_in_phrase:
|
|
return
|
|
|
|
# Want to tell users every time they use a deprecated command since
|
|
# they should immediately be retraining to use {replacement}. Also
|
|
# so if they repeat the command they get another chance to read
|
|
# the popup message.
|
|
notified_in_phrase.add(name)
|
|
msg = (
|
|
f'The "{name}" command is deprecated. Instead, say: "{replacement}".'
|
|
f" See log for more."
|
|
)
|
|
actions.app.notify(msg, "Deprecation warning")
|
|
msg = (
|
|
f'The "{name}" command is deprecated since {time_deprecated}.'
|
|
f' Instead, say: "{replacement}".'
|
|
f' See {os.path.join(REPO_DIR, "BREAKING_CHANGES.txt")}'
|
|
)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
def deprecate_capture(time_deprecated: str, name: str):
|
|
"""
|
|
Notify the user that the given capture is deprecated and should
|
|
not be used into the future.
|
|
"""
|
|
|
|
id = f"capture.{name}.{time_deprecated}"
|
|
|
|
deprecate_notify(id, f"The `{name}` capture is deprecated. See log for more.")
|
|
|
|
msg = (
|
|
f"The `{name}` capture is deprecated since {time_deprecated}."
|
|
f' See {os.path.join(REPO_DIR, "BREAKING_CHANGES.txt")}'
|
|
f"{calculate_rule_info()}"
|
|
)
|
|
warnings.warn(msg, DeprecationWarning, stacklevel=3)
|
|
|
|
def deprecate_action(time_deprecated: str, name: str):
|
|
"""
|
|
Notify the user that the given action is deprecated and should
|
|
not be used into the future.
|
|
"""
|
|
|
|
id = f"action.{name}.{time_deprecated}"
|
|
|
|
deprecate_notify(id, f"The `{name}` action is deprecated. See log for more.")
|
|
|
|
msg = (
|
|
f"The `{name}` action is deprecated since {time_deprecated}."
|
|
f' See {os.path.join(REPO_DIR, "BREAKING_CHANGES.txt")}'
|
|
f"{calculate_rule_info()}"
|
|
)
|
|
warnings.warn(msg, DeprecationWarning, stacklevel=5)
|