yokadi-1.2.0/ 0000775 0001750 0001750 00000000000 13430006221 014415 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/man/ 0000775 0001750 0001750 00000000000 13430006221 015170 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/man/yokadi.1 0000664 0001750 0001750 00000002575 13430006220 016542 0 ustar aurelien aurelien 0000000 0000000 .TH YOKADI 1 "July 10, 2009"
.SH NAME
yokadi \- commandline todo system
.SH SYNOPSIS
.B yokadi
.RI [ options ]...
.br
.SH DESCRIPTION
.B yokadi
is a command-line oriented, SQLite powered, TODO list tool. It helps
you organize all the things you have to do and you must not forget. It aims to
be simple, intuitive and very efficient. In Yokadi you manage projects, which
contains tasks. At the minimum, a task has a title, but it can also have a
description, a due date, an urgency or keywords. Keywords can be any word that
help you to find and sort your tasks.
.PP
.SH OPTIONS
These programs follow the usual GNU command line syntax, with long
options starting with two dashes (`-').
A summary of options is included below.
.TP
.B \-\-datadir=
Database directory.
.TP
.B \-c, \-\-create-only
Just create an empty database.
.TP
.B \-u, \-\-update
Update database to the latest version.
.TP
.B \-h, \-\-help
Show summary of options and exit.
.TP
.B \-v, \-\-version
Show version of program and exit.
.SH SEE ALSO
.BR yokadid (1).
.br
.SH SEE ALSO
Website: http://yokadi.github.io
Mailing List: http://sequanux.org/cgi-bin/mailman/listinfo/ml-yokadi
.SH AUTHOR
yokadi was written by Aurélien Gâteau and Sébastien Renard .
.PP
This manual page was written by Kartik Mistry ,
for the Debian project (and may be used by others).
yokadi-1.2.0/man/yokadid.1 0000664 0001750 0001750 00000002543 13430006220 016701 0 ustar aurelien aurelien 0000000 0000000 .TH YOKADID 1 "July 10, 2009"
.SH NAME
yokadid \- commandline todo system
.SH SYNOPSIS
.B yokadid
.RI [ options ]...
.br
.SH DESCRIPTION
.B yokadid
is a Yokadi daemon that remind you due tasks. If you want to be automatically
reminded of due tasks, you can use the Yokadi daemon. The Yokadi daemon can be
launched via desktop autostart services.
In most desktop environments, you just need to create a symbolic link to yokadid
(or a shell script that calls it) in $HOME/.config/autostart/.
ln \-s \`which yokadid\` $HOME/.config/autostart/
.PP
.SH OPTIONS
These programs follow the usual GNU command line syntax, with long
options starting with two dashes (`-').
A summary of options is included below.
.TP
.B \-\-datadir=
Database directory.
.TP
.B \-k, \-\-kill
Kill Yokadi Daemon (you can specify database with \-db if you run multiple
Yokadid).
.TP
.B \-f, \-\-foreground
Don't fork background. Useful for debug.
.TP
.B \-h, \-\-help
Show summary of options and exit.
.SH SEE ALSO
.BR yokadi (1).
.br
.SH SEE ALSO
Website: http://yokadi.github.io
Mailing List: http://sequanux.org/cgi-bin/mailman/listinfo/ml-yokadi
.SH AUTHOR
yokadi was written by Aurélien Gâteau and Sébastien Renard .
.PP
This manual page was written by Kartik Mistry ,
for the Debian project (and may be used by others).
yokadi-1.2.0/scripts/ 0000775 0001750 0001750 00000000000 13430006221 016104 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/scripts/coverage 0000775 0001750 0001750 00000000474 13430006220 017631 0 ustar aurelien aurelien 0000000 0000000 #!/bin/sh
set -e
cd $(dirname $0)/..
COVERAGE_BIN=${COVERAGE_BIN=python3-coverage}
if ! which $COVERAGE_BIN > /dev/null ; then
echo "Could not find $COVERAGE_BIN make sure Coverage is installed"
exit 1
fi
$COVERAGE_BIN run --source=yokadi --omit="yokadi/tests/*" yokadi/tests/tests.py
$COVERAGE_BIN html
yokadi-1.2.0/scripts/diffinst 0000775 0001750 0001750 00000003730 13430006220 017642 0 ustar aurelien aurelien 0000000 0000000 #!/usr/bin/env python3
"""
@author: Aurélien Gâteau
@license: GPL v3 or newer
"""
import argparse
import fnmatch
import os
import tarfile
import subprocess
import sys
DESCRIPTION = """\
Compare a tarball and a git tree, list files unique on each side.
"""
GIT_IGNORE = (
'.gitignore',
)
TARBALL_IGNORE = (
'PKG-INFO',
)
def list_git_dir(root):
out = subprocess.check_output(['git', 'ls-files'], cwd=root)
return [x.decode() for x in out.splitlines()]
def remove_first_dir(path):
lst = path.split(os.sep)
return os.path.join(*lst[1:])
def list_tarball(tarball):
with tarfile.open(tarball) as tf:
for info in tf.getmembers():
if info.isfile():
yield remove_first_dir(info.name)
def apply_blacklist(lst, blacklist):
for item in lst:
for pattern in blacklist:
if fnmatch.fnmatch(item, pattern):
break
else:
yield item
def print_set(st):
for item in sorted(list(st)):
print(item)
def main():
parser = argparse.ArgumentParser()
parser.description = DESCRIPTION
parser.add_argument('-q', '--quiet', action='store_true',
help='Do not list changes')
parser.add_argument('tarball')
parser.add_argument('git_dir', nargs='?', default='.')
args = parser.parse_args()
dir_set = set(apply_blacklist(list_git_dir(args.git_dir), GIT_IGNORE))
tb_set = set(apply_blacklist(list_tarball(args.tarball), TARBALL_IGNORE))
only_in_dir = dir_set.difference(tb_set)
only_in_tb = tb_set.difference(dir_set)
if not args.quiet:
if only_in_dir:
print('# Only in {}'.format(args.git_dir))
print_set(only_in_dir)
if only_in_tb:
print('# Only in {}'.format(args.tarball))
print_set(only_in_tb)
if only_in_dir or only_in_tb:
return 1
else:
return 0
if __name__ == '__main__':
sys.exit(main())
# vi: ts=4 sw=4 et
yokadi-1.2.0/scripts/mkdist.sh 0000775 0001750 0001750 00000002437 13430006220 017743 0 ustar aurelien aurelien 0000000 0000000 #!/bin/sh
set -e
PROGNAME="$(basename "$0")"
die() {
echo "$PROGNAME: ERROR: $*" | fold -s -w "${COLUMNS:-80}" >&2
exit 1
}
log() {
echo "### $*" >&2
}
[ $# = 1 ] || die "USAGE: $PROGNAME "
SRC_DIR=$(cd "$(dirname $0)/.." ; pwd)
DST_DIR=$(cd "$1" ; pwd)
[ -d "$DST_DIR" ] || die "Destination dir '$SRC_DIR' does not exist"
WORK_DIR=$(mktemp -d "$DST_DIR/yokadi-dist.XXXXXX")
log "Copying source"
cp -a --no-target-directory "$SRC_DIR" "$WORK_DIR"
log "Cleaning"
cd "$WORK_DIR"
git reset --hard HEAD
git clean -q -dxf
log "Building archives"
./setup.py -q sdist --formats=gztar,zip
log "Installing archive"
cd dist/
YOKADI_TARGZ=$(ls ./*.tar.gz)
tar xf "$YOKADI_TARGZ"
ARCHIVE_DIR="$PWD/${YOKADI_TARGZ%.tar.gz}"
virtualenv --python python3 "$WORK_DIR/venv"
(
. "$WORK_DIR/venv/bin/activate"
# Install Yokadi in the virtualenv and make sure it can be started
# That ensures dependencies got installed by pip
log "Smoke test"
pip3 install "$ARCHIVE_DIR"
yokadi exit
log "Installing extra requirements"
pip3 install -r "$ARCHIVE_DIR/extra-requirements.txt"
log "Running tests"
"$ARCHIVE_DIR/yokadi/tests/tests.py"
)
log "Moving archives out of work dir"
cd "$WORK_DIR/dist"
mv ./*.tar.gz ./*.zip "$DST_DIR"
rm -rf "$WORK_DIR"
log "Done"
yokadi-1.2.0/doc/ 0000775 0001750 0001750 00000000000 13430006221 015162 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/doc/tips.md 0000664 0001750 0001750 00000004410 13430006220 016461 0 ustar aurelien aurelien 0000000 0000000 # Intro
This document presents practical advices on how to get the best out of Yokadi.
# Completion
Yokadi supports completion of command names, and in many commands it can
complete project names. Do not hesitate to try the `[tab]` key!
# Setting up a project hierarchy
You can set up a project hierarchy by adopting a name convention. For example if
you want to track tasks related to a program which is made of many plugins, you
could have the main project named `fooplayer`, all tasks for the .ogg plugin
stored in `fooplayer_ogg` and all tasks about the .s3m plugin in
`fooplayer_s3m`.
This makes it easy to categorize your tasks and also to have a general overview.
For example to list all `fooplayer` related tasks you can use:
t_list fooplayer%
# Using keywords
Keywords are great to group tasks in different ways. For example you can create
a keyword named `phone`, and assign it to tasks which you must accomplish on
the phone.
Another useful keyword is `diy_store`: Every time you find that you need to buy
some supply from a do-it-yourself store, add it with this keyword. Next time you
are planning a trip to the store, get the list of what to buy with:
t_list @diy_store
Or even nicer, directly print your list (from the shell):
yokadi "t_list @diy_store --format plain" | lp
# Keep track of your meetings
To track my meetings, I like to use a `meeting` keyword together with an
assigned due date. Yokadi ability to add long descriptions to tasks is also
handy to associate address or contact information to a meeting task.
# Keep track of tasks you delegate to people
When you delegate a task to someone, add a keyword with its name to the task.
So you can check that people really do what they promise to do even if they
are not as organized as you are.
To list all tasks assigned to Bob:
t_list @bob
To check all task that Bob should have done:
t_list --overdue @bob
# Some useful shortcuts
Yokadi relies on readline library, so you can use very useful readline
shortcuts such as:
- up/down arrows to browse history
- ctrl-r to search backward in Yokadi history
- ctrl-l to clear the screen
- ctrl-t to swap two letters
- ctrl-a to go the begin of the line
- ctrl-e to go the end of the line
- ctrl-w delete last word
yokadi-1.2.0/doc/bugtracking.md 0000664 0001750 0001750 00000006510 13430006220 020005 0 ustar aurelien aurelien 0000000 0000000 # Introduction
Yokadi comes with a set of commands tailored to help you track bugs. These
commands are `bug_add` and `bug_edit`. They are similar to `t_add` and `t_edit`
except they will ask you a few questions to help you decide which bug to fix
next.
# Entering a bug
Enter a new bug like you would enter a new task:
bug_add fooplayer Fooplayer crashes when opening a .bar file
Before adding the task to the project "fooplayer", `bug_add` will ask you the
severity of the bug:
1: Documentation
2: Localization
3: Aesthetic issues
4: Balancing: Enables degenerate usage strategies that harm the experience
5: Minor usability: Impairs usability in secondary scenarios
6: Major usability: Impairs usability in key scenarios
7: Crash: Bug causes crash or data loss. Asserts in the Debug release
Severity: _
Enter 7 here, this is a crash. Now `bug_add` wants to know about the likelihood
of the bug:
1: Will affect almost no one
2: Will only affect a few users
3: Will affect average number of users
4: Will affect most users
5: Will affect all users
Likelihood: _
.bar files are quite uncommon, enter 2 here. We reach the last question:
bug: _
This last question is optional: `bug_add` wants to know the id of this bug.
This is where you can enter the Bugzilla/Trac/Mantis/... id of the bug. If you
just noticed this bug and have not yet entered it in a centralized bug tracker,
just press Enter. Yokadi will now add a task for your bug:
Added bug 'Fooplayer crashes when opening a .bar file' (id=12, urgency=40)
If you edit the task with `t_edit 12` you will only be able to fix the task
title. To be asked for severity, likelihood and bug id again, use
`bug_edit 12`.
# What's next?
Based on the severity and likelihood, Yokadi computes the urgency of the bug.
The formula used is:
likelihood * severity * 100
urgency = -----------------------------
max_likelihood * max_severity
This is based on the concept of "User Pain", as described by Danc here:
Now, when you list your tasks with `t_list`, the most urgent tasks will be
listed first, making it easy to fix the most important bugs first.
# Behind the scenes
Likelihood, severity and bug are stored as Yokadi keywords (Yokadi keywords can
be associated with an integer value).
The bug urgency is computed from likelihood and severity, then stored in the
task urgency field. Yes, this means there is duplication and you may get
likelihood/severity and urgency out of sync if you manually adjust urgency with
`t_set_urgency`. In practice, I found it was not a problem.
# Tricks
Here are a few tricks I came up with while using Yokadi to do bug tracking:
- List all crashers: `t_list fooplayer -k severity=7`
- Make use of Yokadi keywords. For example I often use:
- backport: I should backport the fix when done
- i18n: This bug requires translation changes, better fix it before i18n freeze
- patch: This bug as an attached patch (You can paste the patch in the bug
description with `t_describe`)
- Find a bug by id: `t_list fooplayer -k bug=12`
- I often keep two projects in Yokadi, one for the stable release, another for
development. For example I have `yokadi_stable` and `yokadi_dev`.
yokadi-1.2.0/doc/dev/ 0000775 0001750 0001750 00000000000 13430006221 015740 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/doc/dev/release.md 0000664 0001750 0001750 00000002047 13430006220 017704 0 ustar aurelien aurelien 0000000 0000000 # Release check list
## Introduction
This doc assumes there is a checkout of yokadi.github.com next to the checkout
of yokadi.
## In yokadi checkout
export version=
Check dev is clean
git checkout dev
git pull
git status
Update `NEWS` file (add changes, check release date)
Ensure `yokadi/__init__.py` file contains $version
Build archives
./scripts/mkdist.sh ../yokadi.github.com/download
Push changes
git push
When CI has checked the branch, merge changes in master
git checkout master
git pull
git merge dev
git push
Tag the release
git tag -a $version -m "Releasing $version"
git push --tags
## In yokadi.github.com checkout
Ensure checkout is up to date
Update documentation
./updatedoc.py ../yokadi .
Update version in download page (`download.md`)
Write a blog entry in `_posts/`
Test it:
jekyll serve
Upload archives on PyPI
cd download/
twine upload yokadi-.*
Publish blog post
git add .
git commit -m "Releasing $version"
git push
yokadi-1.2.0/doc/dev/debug.md 0000664 0001750 0001750 00000000245 13430006220 017350 0 ustar aurelien aurelien 0000000 0000000 # Debugging
## Show SQL commands
If you set the `YOKADI_SQL_DEBUG` environment variable to a value different
from "0", all SQL commands will be printed to stdout.
yokadi-1.2.0/doc/dev/db-updates.md 0000664 0001750 0001750 00000003062 13430006220 020312 0 ustar aurelien aurelien 0000000 0000000 # Database updates
## How the update system works
Lets assume current version is x and target version is x+n.
The update process goes like this:
- Copy yokadi.db to work.db
- for each v between x and x + n - 1:
- run `updateto.update()`
- Create an empty database in recreated.db
- Fill recreated.db with the content of work.db
- If we are updating the database in place, rename yokadi.db to yokadi-$date.db
and recreated.db to yokadi.db
- If we are creating a new database (only possible by directly calling
update/update.py), rename recreated.db to the destination name;
The recreation steps ensure that:
- All fields are created in the same order (when adding a new column, you can't
specify its position)
- All constraints are in place (when adding a new column, you can't mark it
'non null')
- The updated database has the exact same structure as a brand new database.
## Database schema changes
If you want to modify the database schema (adding, removing, changing tables or
fields). You should:
- Present the changes on the mailing-list
- Implement your changes in db.py
- Increase the database version number (`DB_VERSION` in db.py)
- Write an update script in update/
- When the changes are merged in master, tag the merge commit using the tag
name `db-v`, like this:
# Note the -a!
git tag -a db-v
git push --tags
Note: up to db-v4, `db-v*` have been created on the last commit before the
update to a new version, so `db-v4` is on the last commit before `DB_VERSION`
was bumped to 5.
yokadi-1.2.0/doc/dev/hacking.md 0000664 0001750 0001750 00000004446 13430006220 017675 0 ustar aurelien aurelien 0000000 0000000 # Coding style
## Naming
Classes use CamelCase. Functions use mixedCase. Here is an example:
class MyClass(object):
def myMethod(self, arg1, arg2):
pass
def anotherMethod(self, arg1, *args, **kwargs):
pass
Exception: Classes which implement command methods should use underscores,
since the name of the method is used to create the name of the command:
class MyCmd(object):
def do_t_cmd1(self, line):
pass
def parser_t_cmd1(self):
return SomeParser
def someMethod(self):
pass
Note: This naming convention is historic, we would like to switch to a more
PEP-8 compliant coding style where words in function and variable names are
separated with `_`. If you feel like doing the conversion, get in touch.
Filenames are lowercase. If they contain a class they should match the name of
the class they contain.
Internal functions and methods should be prefixed with `_`.
## Spacing
Indentation is 4 spaces.
Try to keep two blank lines between functions.
One space before and after operators, except in optional arguments.
a = 12
if a > 14 or a == 15:
print a
myFunction(a, verbose=True)
## Import
Use one import per line:
import os
import sys
Avoid polluting the local namespace with `from module import function`.
Good:
import os
os.listdir(x)
Bad:
from os import listdir
listdir(x)
You should however import classes like this:
from module import SomeClass
Keep import in blocks, in this order:
1. Standard Python modules
2. Third-party modules
3. Yokadi modules
Keep import blocks sorted. It makes it easier to check if an import line is
already there.
# Command docstrings
All commands are documented either through their parser or using the command
docstring. To ensure consistency all usage string should follow the same
guidelines.
For example assuming your command is named `t_my_command`, which accepts a few
options, two mandatory arguments (a task id and a search text) and an optional
filename argument. The usage string should look like this:
t_my_command [options] []
No need to detail the options in the usage string, they will be listed by the
parser below the usage string.
yokadi-1.2.0/doc/ical.md 0000664 0001750 0001750 00000004566 13430006220 016426 0 ustar aurelien aurelien 0000000 0000000 # Intro
This document presents how to use Yokadi with a third party calendar/todolist
application that supports the ical format (RFC2445).
To use ical Yokadi features, start the Yokadi daemon with the --icalserver
switch. This daemon also manages alarms for due tasks.
The ical server listens on TCP port 8000. You can choose another TCP port with
the --port switch. For example, to start Yokadi daemon with the icalserver on
TCP port 9000:
yokadid --icalserver --port=9000
# Read your Yokadi tasks in a third party tool
If your third party tool supports ical format and is able to read it through
HTTP, just set it up to read on localhost:8000 (or whatever port you setup) and
enjoy.
If your calendar/todo tool only supports local files:
* complain to your software broker to include ical over HTTP ;-)
* make a simple shell script that downloads the ical file and put it on your
crontab. You can use wget for that:
wget -O yokadi.ical http://localhost:8000
Each Yokadi task is defined as an ical VTODO object. Yokadi projects are
represented as special tasks to which included tasks are related.
# Create and update yokadi tasks from a third party tool
On the same TCP socket, you can write tasks with the PUT HTTP method. Only
new and updated tasks will be considered.
# Supported third party ical tool
Yokadi should support any tool which implements RFC2345. But we are not in a
perfect world.
The following tools are known to work properly with Yokadi ical server:
- Kontact/KOrganizer (4.4) from the KDE Software Compilation
If you successfully plugged Yokadi with another calendar/todolist tool, please
let us now in order to complete this list.
# Some security considerations
By default, the ical server only listens on localhost (loopback). You can
bypass this restriction with the --listen switch which makes the ical server
listen on all interfaces.
If you do this, you will be able to access to the ical HTTP stream from another
computer. But this have some security issues if you don't setup a firewall to
restrict who can access to your Yokadi daemon:
* everybody could access to your task list
* even worse, everybody could be able to modify you task list
* the ical server has not been build with strong security as design goals.
You have been warned. That's why listening only to localhost (which is the
default) is strongly recommended.
yokadi-1.2.0/bin/ 0000775 0001750 0001750 00000000000 13430006221 015165 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/bin/yokadid 0000775 0001750 0001750 00000000525 13430006220 016540 0 ustar aurelien aurelien 0000000 0000000 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""This is just a wrapper to yokadi package that rely in standard python site-package
This wrapper is intended to be placed in user PATH and to be executable
@author: Sébastien Renard (sebastien.renard@digitalfox.org)
@license:GPL v3 or later
"""
from yokadi import yokadid
yokadid.main()
yokadi-1.2.0/bin/yokadi 0000775 0001750 0001750 00000000524 13430006220 016373 0 ustar aurelien aurelien 0000000 0000000 #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""This is just a wrapper to yokadi package that rely in standard python site-package
This wrapper is intended to be placed in user PATH and to be executable
@author: Sébastien Renard (sebastien.renard@digitalfox.org)
@license:GPL v3 or later
"""
from yokadi.ycli import main
main.main()
yokadi-1.2.0/icon/ 0000775 0001750 0001750 00000000000 13430006221 015345 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/icon/22x22/ 0000775 0001750 0001750 00000000000 13430006221 016124 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/icon/22x22/yokadi.png 0000664 0001750 0001750 00000001112 13430006220 020104 0 ustar aurelien aurelien 0000000 0000000 PNG
IHDR Ĵl; sBIT|d pHYs tEXtSoftware www.inkscape.org< IDAT8=oPDCKoHCT!g``ÐNUKC,lK0vH BkDD[Q,ϵs?d8Βg(@ZŶ꺎imut:bdnn&ՃRD" Q,H`2
f
2L(d
-䢫^G ͢i:y}%9WP(4Pg>{wo_XJa R)! a 0˲hZ
}(}N~U=<|>DaPaqT*v-oM8lnb+~XzB>m|}vaQX:8xWϡP~?`>Ry,zcQX]P@<,7@؏Git]vzxLNԶXY~I۶s IENDB` yokadi-1.2.0/icon/64x64/ 0000775 0001750 0001750 00000000000 13430006221 016140 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/icon/64x64/yokadi.png 0000664 0001750 0001750 00000001371 13430006220 020127 0 ustar aurelien aurelien 0000000 0000000 PNG
IHDR @ @ iq sBIT|d pHYs 7] 7]F] tEXtSoftware www.inkscape.org< vIDATxMrPFOn=4I܀q-nB>* 3CFH ½ܟnK1P}\._8F LΟDr<m?0 .@:W=nnY.c0ưC=2ϙf!j%`}_E L&!
VUbn4 nWۏ
,Au]s88NTU5Hi7`,Ȳ3ɞpIY_O>dLKM$x?
+|[8$]{DKK 룳P%y:ܯĂ =* s=Ub<
jz/n曨
H E$A )xP @<WSlZxywM(x!w $h38`)w,!fpZ!Pw~,.w>ѕ`RKHwTR: 0b%K<|-A
<CIyix0+A<Jg4Cma*|KFx4oK
x$@ ?uU鈿8 IENDB` yokadi-1.2.0/icon/128x128/ 0000775 0001750 0001750 00000000000 13430006221 016302 5 ustar aurelien aurelien 0000000 0000000 yokadi-1.2.0/icon/128x128/yokadi.png 0000664 0001750 0001750 00000002533 13430006220 020272 0 ustar aurelien aurelien 0000000 0000000 PNG
IHDR >a sBIT|d pHYs n nޱ tEXtSoftware www.inkscape.org< IDATx?n[GQZ'ހ%/WK@"% !Qs
;R'sualnY/@آ nnnrC$zpppppppNy:c20>Dq Bǜ[/-/\c
W |-滯 l1}`+ [w_b
W |-滯 l 8x1>^w=8- ~Lv V1"z}˟?*rcgX,n1"("U{)F
rL~E
^f :#內'tD*x^L&oE{տ }w~|>g>7y ʯN*"@K~EH
3Wt@d$.(L_6\+D`~w-1aۙ?߆_V:c-YDEk
"
`ݲX,њ>ɇB8LSRZ7P` Álz^J+(
{nNߒ vՊrb~KB}o/8}HD7At`(AfHAbhA4fA4bA2d N2t A>(O"<xã|P _ \ ^oMjz|\s?7%gs?7 rkz\$2`1SS|OE!S s?6ʇ&i