-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
590 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
#!/bin/bash | ||
# | ||
# An example hook script to verify what is about to be committed. | ||
# Called by "git commit" with no arguments. The hook should | ||
# exit with non-zero status after issuing an appropriate message if | ||
# it wants to stop the commit. | ||
# | ||
# To enable this hook, rename this file to "pre-commit". | ||
|
||
if git rev-parse --verify HEAD >/dev/null 2>&1 | ||
then | ||
against=HEAD | ||
else | ||
# Initial commit: diff against an empty tree object | ||
against=$(git hash-object -t tree /dev/null) | ||
fi | ||
|
||
# If you want to allow non-ASCII filenames set this variable to true. | ||
allownonascii=$(git config --type=bool hooks.allownonascii) | ||
|
||
# Redirect output to stderr. | ||
exec 1>&2 | ||
|
||
# Cross platform projects tend to avoid non-ASCII filenames; prevent | ||
# them from being added to the repository. We exploit the fact that the | ||
# printable range starts at the space character and ends with tilde. | ||
if [ "$allownonascii" != "true" ] && | ||
# Note that the use of brackets around a tr range is ok here, (it's | ||
# even required, for portability to Solaris 10's /usr/bin/tr), since | ||
# the square bracket bytes happen to fall in the designated range. | ||
test $(git diff --cached --name-only --diff-filter=A -z $against | | ||
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 | ||
then | ||
cat <<\EOF | ||
Error: Attempt to add a non-ASCII file name. | ||
This can cause problems if you want to work with people on other platforms. | ||
To be portable it is advisable to rename the file. | ||
If you know what you are doing you can disable this check using: | ||
git config hooks.allownonascii true | ||
EOF | ||
exit 1 | ||
fi | ||
|
||
# Run flutter format, analyze and test | ||
( | ||
git stash --include-untracked --keep-index && | ||
trap 'r=$?; git stash pop; exit $r' EXIT | ||
git status | ||
dart format --summary none --set-exit-if-changed -o none lib test || exit $? | ||
dart analyze || exit $? | ||
dart test || exit $? | ||
) || exit $? | ||
|
||
# If there are whitespace errors, print the offending file names and fail. | ||
exec git diff-index --check --cached $against -- | ||
|
||
# vim: set et sw=2 sts=2 : |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#!/bin/sh | ||
|
||
# An example hook script to verify what is about to be pushed. Called by "git | ||
# push" after it has checked the remote status, but before anything has been | ||
# pushed. If this script exits with a non-zero status nothing will be pushed. | ||
# | ||
# This hook is called with the following parameters: | ||
# | ||
# $1 -- Name of the remote to which the push is being done | ||
# $2 -- URL to which the push is being done | ||
# | ||
# If pushing without using a named remote those arguments will be equal. | ||
# | ||
# Information about the commits which are being pushed is supplied as lines to | ||
# the standard input in the form: | ||
# | ||
# <local ref> <local sha1> <remote ref> <remote sha1> | ||
# | ||
# This sample shows how to prevent push of commits where the log message starts | ||
# with "WIP" (work in progress). | ||
|
||
remote="$1" | ||
url="$2" | ||
|
||
z40=0000000000000000000000000000000000000000 | ||
|
||
while read local_ref local_sha remote_ref remote_sha | ||
do | ||
if echo "$local_ref" | grep -q '/local/' | ||
then | ||
echo "Found local ref name '$local_ref' has '/local/' in it." >&2 | ||
echo "Not pushing refs with containing that as they are " >&2 | ||
echo "supposed to be local only." >&2 | ||
exit 1 | ||
fi | ||
if [ "$local_sha" = $z40 ] | ||
then | ||
# Handle delete | ||
: | ||
else | ||
if [ "$remote_sha" = $z40 ] | ||
then | ||
# New branch, examine all commits | ||
range="$local_sha" | ||
else | ||
# Update to existing branch, examine new commits | ||
range="$remote_sha..$local_sha" | ||
fi | ||
|
||
# Check for WIP commit | ||
commit=`git rev-list -n 1 --grep '^\(WIP\|fixup!\|squash!\)' "$range"` | ||
if [ -n "$commit" ] | ||
then | ||
echo "Found WIP commit in $local_ref, not pushing" >&2 | ||
exit 1 | ||
fi | ||
fi | ||
done | ||
|
||
exit 0 | ||
|
||
# vim: set et sw=2 sts=2 : |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## 0.1.0 | ||
|
||
- Initial version. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,69 @@ | ||
# pausable_timer | ||
A [Dart](https://dart.dev/) [timer](https://api.dart.dev/stable/dart-async/Timer/Timer.html) that can be paused, resumed and reset. | ||
# pausable\_timer | ||
|
||
A [Dart](https://dart.dev/) | ||
[timer](https://api.dart.dev/stable/dart-async/Timer/Timer.html) that can be | ||
paused, resumed and reset. | ||
|
||
## Example | ||
|
||
```dart | ||
import 'package:pausable_timer/pausable_timer.dart'; | ||
void main() async { | ||
final timer = PausableTimer(Duration(seconds: 1), () => print('yes!')); | ||
// PausableTimer starts paused, so we have to start it manually. | ||
timer.start(); | ||
Future<void>.delayed(timer.duration ~/ 2); | ||
print('Not yet fired, still 1/2 second to go!'); | ||
timer.pause(); | ||
// When paused, time can pass but the timer won't be fired | ||
Future<void>.delayed(timer.duration); | ||
// Now we can resume the timer | ||
timer.start(); | ||
Future<void>.delayed(timer.duration ~/ 2); | ||
// Now it should have fired and "yes!" should have been printed, but we can | ||
// re-arm the timer via reset() and use it again. | ||
timer.reset(); | ||
timer.start(); | ||
Future<void>.delayed(timer.duration); | ||
// And it should fire again. | ||
print('We should have 2 ticks now: ${timer.ticks}'); | ||
// And we can arm it again | ||
timer.reset(); | ||
timer.start(); | ||
// And we can cancel it, but once the timer is cancelled, it can't be armed | ||
// again, but it can still be queried for information. | ||
timer.cancel(); | ||
print('${timer.duration} ${timer.elapsed} ${timer.ticks} ${timer.isPaused}'); | ||
} | ||
``` | ||
|
||
## Development | ||
|
||
### Git Hooks | ||
|
||
This repository provides some useful Git hooks to make sure new commits have | ||
some basic health. | ||
|
||
The hooks are provided in the `.githooks/` directory and can be easily used by | ||
configuring git to use this directory for hooks instead of the default | ||
`.git/hooks/`: | ||
|
||
```sh | ||
git config core.hooksPath .githooks | ||
``` | ||
|
||
So far there is a hook to prevent commits with the `WIP` word in the message to | ||
be pushed, and one hook to run `flutter analyze` and `flutter test` before | ||
a new commit is created. The later can take some time, but it can be easily | ||
disabled temporarily by using `git commit --no-verify` if you are, for example, | ||
just changing the README file or amending a commit message. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
include: package:pedantic/analysis_options.1.9.0.yaml | ||
|
||
analyzer: | ||
strong-mode: | ||
implicit-casts: false | ||
implicit-dynamic: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import 'dart:async' show Timer, Zone; | ||
|
||
import 'package:clock/clock.dart' show clock; | ||
|
||
/// A [Timer] that can be paused, resumed and reset. | ||
/// | ||
/// Based on: | ||
/// https://github.com/dart-lang/sdk/issues/43329#issuecomment-687024252 | ||
class PausableTimer implements Timer { | ||
/// The [Zone] where the [_callback] will be run. | ||
/// | ||
/// Dart generally calls asynchronous callbacks in the zone where they were | ||
/// originally "created". | ||
/// | ||
/// Such callbacks are first registered, because that makes it possible for | ||
/// special zones to record more information about the point where the | ||
/// callback is created (say, remember the stack trace, which is what the | ||
/// stack_trace package does). | ||
/// | ||
/// That is, we call [Zone.registeCallback] to enable zones to know about the | ||
/// callback creation as well as the later [Zone.run] for running it. | ||
/// | ||
/// If you just store the callback, but don't register it at the time it's | ||
/// stored, then we can still run it in the correct zone when necessary, but | ||
/// such special zones would stop working for that callback. | ||
/// | ||
/// This explanation comes from: | ||
/// https://github.com/dart-lang/sdk/issues/43329#issuecomment-687720625 | ||
final Zone _zone; | ||
|
||
/// The [Stopwatch] used to keep track of the elapsed time. | ||
/// | ||
/// This allows us to pause the timer and resume from where it left of. | ||
/// | ||
/// When the timer expires, this stopwatch is set to null. | ||
Stopwatch _stopwatch = clock.stopwatch(); | ||
|
||
/// The currently active [Timer]. | ||
/// | ||
/// This is null whenever this timer is not currently active. | ||
Timer _timer; | ||
|
||
/// The callback to call when this timer expires. | ||
/// | ||
/// If this timer was [cancel]ed, then this callback is null. | ||
void Function() _callback; | ||
|
||
/// The number of times this timer has expired. | ||
int _tick = 0; | ||
|
||
/// Starts the [_timer] to run [_callback] in [_zone] and increment [_tick]. | ||
/// | ||
/// It also starts the [_stopwatch] and clears [_timer] and [_stopwatch] when | ||
/// the [_timer] expires. | ||
void _startTimer() { | ||
_timer = _zone.createTimer(_originalDuration - _stopwatch.elapsed, () { | ||
_tick++; | ||
_timer = null; | ||
_stopwatch = null; | ||
_zone.run(_callback); | ||
}); | ||
_stopwatch.start(); | ||
} | ||
|
||
/// Creates a new timer. | ||
/// | ||
/// The [callback] is invoked after the given [duration], but can be [pause]d | ||
/// in between or [reset]. The [elapsed] time is only accounted for while the | ||
/// timer [isActive]. | ||
/// | ||
/// The timer [isPaused] when created, and must be [start]ed manually. | ||
/// | ||
/// The [duration] must be non-null and equals or bigger than [Duration.zero]. | ||
/// If it is [Duration.zero], the [callback] will still not be called until | ||
/// the timer is [start]ed. | ||
/// | ||
/// [callback] must be non-null. | ||
PausableTimer(Duration duration, void Function() callback) | ||
: assert(duration != null), | ||
assert(duration >= Duration.zero), | ||
assert(callback != null), | ||
_originalDuration = duration, | ||
_zone = Zone.current { | ||
_callback = _zone.bindCallback(callback); | ||
} | ||
|
||
/// The original duration this [Timer] was created with. | ||
Duration get duration => _originalDuration; | ||
final Duration _originalDuration; | ||
|
||
/// The time this [Timer] have been active. | ||
/// | ||
/// If the timer is paused, the elapsed time is also not computed anymore, so | ||
/// [elapsed] is always less than or equals to the [duration]. | ||
Duration get elapsed => _stopwatch?.elapsed ?? _originalDuration; | ||
|
||
/// True if this [Timer] is armed but not currently active. | ||
/// | ||
/// If this timer [isExpired] or [isCancelled], it is not considered to be | ||
/// paused. | ||
bool get isPaused => _timer == null && !isExpired && !isCancelled; | ||
|
||
/// True if this [Timer] has expired. | ||
bool get isExpired => _stopwatch == null; | ||
|
||
/// True if this [Timer] was cancelled. | ||
bool get isCancelled => _callback == null; | ||
|
||
/// True if this [Timer] is armed and counting. | ||
@override | ||
bool get isActive => _timer != null; | ||
|
||
@override | ||
int get tick => _tick; | ||
|
||
/// Cancels the timer. | ||
/// | ||
/// Once a [Timer] has been canceled, the callback function will not be called | ||
/// by the timer and the timer can't be activated again. Calling [start], | ||
/// [pause] or [reset] will have no effect. Calling [cancel] more than once on | ||
/// a [Timer] is also allowed, and will have no further effect. | ||
@override | ||
void cancel() { | ||
_stopwatch?.stop(); | ||
_timer?.cancel(); | ||
_timer = null; | ||
_callback = null; | ||
} | ||
|
||
/// Starts (or resumes) the timer. | ||
/// | ||
/// Starts counting for the original duration or from where it was left of if | ||
/// [pause]ed. | ||
/// | ||
/// It does nothing if the timer [isActive], [isExpired] or [isCancelled]. | ||
void start() { | ||
if (isActive || isExpired || isCancelled) return; | ||
_startTimer(); | ||
} | ||
|
||
/// Pauses an active timer. | ||
/// | ||
/// The [elapsed] time is not accounted anymore and the timer will not be | ||
/// fired until it is [start]ed again. | ||
/// | ||
/// Nothing happens if the timer [isPaused], [isExpired] or [isCancelled]. | ||
void pause() { | ||
_stopwatch?.stop(); | ||
_timer?.cancel(); | ||
_timer = null; | ||
} | ||
|
||
/// Resets the timer. | ||
/// | ||
/// Sets the timer to its original [duration] and rearms it if it was already | ||
/// expired (so it can be started again). | ||
/// | ||
/// Does not change whether the timer [isActive] or [isPaused]. | ||
void reset() { | ||
if (isCancelled) return; | ||
_stopwatch = clock.stopwatch(); | ||
if (isActive) { | ||
_timer.cancel(); | ||
_startTimer(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: pausable_timer | ||
description: A timer that can be paused, resumed and reset. | ||
version: 0.1.0 | ||
|
||
dependencies: | ||
clock: '^1.0.1' | ||
|
||
dev_dependencies: | ||
test: '^1.15.2' | ||
pedantic: '^1.9.0' | ||
fake_async: '^1.0.1' |
Oops, something went wrong.