Skip to content

Commit

Permalink
Support partial string matching.
Browse files Browse the repository at this point in the history
  • Loading branch information
wizonesolutions committed Sep 12, 2014
1 parent e9f2363 commit 08358ee
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 13 deletions.
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ ejson
wizonesolutions:todoist
http
meteorhacks:async
wizonesolutions:underscore-string

1 change: 1 addition & 0 deletions .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ [email protected]
[email protected]
[email protected]
wizonesolutions:[email protected]
wizonesolutions:[email protected]
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,51 @@ breaks your train of thought if you process your tasks GTD style.

**This app fixes that.**

## Usage

When you create tasks in Todoist, simply add `##<project name>` (e.g. `##work`)
anywhere in your task name (much like you would use `@label`s).

You can also use partial project names, such as `##w`. That will match `work`
as long as it is the shortest project name beginning with `w`. You can use partial
names from anywhere in the project name, so `##proc` will match `work/process-improvement`.

You cannot use spaces, and matches are still case-sensitive (it's on the roadmap to make them case-insensitive).


Every 5 minutes (by default), this app will use the
Todoist API to move those tasks into the desired projects. You can also stop
and start the app to do it immediately.

# Installation (command line required)
## Installation (command line required)

It is written in [Meteor](http://meteor.com), so you have to install that to
use it. It's super-easy. Just copy and paste one line.

Then clone this app from GitHub and run it with
`meteor --settings=settings.json` AFTER you configure it..

# Configuration
## Configuration

Copy `settings.json.example` to `settings.json` and replace the parameters with
your Todoist credentials and desired update frequency (you can actually delete
the line with update frequency if you want; the default is 5 minutes). There are
a few API calls each time, so I wouldn't update too often or Todoist might think
it's abuse. I haven't had any issues with a 5-minute interval so far.

## Privacy/security
### Privacy/security

These are used by the app to talk directly to the
Todoist API and are not sent anywhere else. I store the Todoist token in a local
Mongo database using Meteor's APIs so that you don't have to log in for every
check (your credentials are transferred with HTTPS, but this minimizes how
often they have to be).

# Running
## Running

`meteor --settings=settings.json`

# Reporting bugs, requesting features, asking questions
## Reporting bugs, requesting features, asking questions

First see known issues below.

Expand All @@ -50,11 +59,11 @@ Use the [issue list](https://github.com/wizonesolutions/todoist-sorter/issues) o
If there is no GitHub issue for the known issue, you are
welcome to open one.

# Stopping
## Stopping

You can stop Meteor apps by pressing `Ctrl + C`.

# Known issues
## Known issues

- Won't work if the project name has spaces.
- I don't know if Todoist login tokens expire. If they do, you'll start getting
Expand All @@ -63,17 +72,14 @@ again. This is safe since the only thing that is stored is your user info from
Todoist (by the [todoist](https://github.com/wizonesolutions/meteor-todoist)
package).

# Roadmap
## Roadmap

- Support spaces in project name, maybe with dashes or something like
`#project# instead of ##project?`
- Support partial project name matching, e.g. `#impo` instead of
`##work/important-project`, assuming that `work/important-project` is the only
project with `impo` in its name.
- Case-insensitive project name matching.
- Maybe label tasks that are moved by this app (for premium users)?

# Author
## Author

This Meteor package was written by [WizOne Solutions](http://www.wizonesolutions.com), a Meteor and Drupal CMS developer.

Expand Down
58 changes: 57 additions & 1 deletion todoist-sorter.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,73 @@ checkForNewTasks = function () {
data2 = projectsRes.result;
}

// @todo: Lowercase all names in data2 so the later findWhere calls can match
// case-insensitively. Store todoistSorterOriginalName with the original case for when
// we display messages later.

// What word actually matched?
projectName = matches[2];
console.log("Project name: " + projectName)

// OK great, now try to find a project with this name.
var project = _.findWhere(data2, { name: projectName });

if (_.isEmpty(project)) {
// See if we have a partial match with exactly one project.
var projectNames = _.pluck(data2, 'name');
var matchingProjects = _.filter(projectNames, function (value) {
return _s.include(value, projectName);
});

console.log('Might mean: ');
console.log(matchingProjects);

if (matchingProjects.length == 1) {
project = _.findWhere(data2, { name: _.first(matchingProjects) });
}
else {
console.log('Using closest match...');
// Compile array of Levenshtein distances
var matchDistances = [];
_.each(matchingProjects, function (match) {
matchDistances.push({ name: match, distance: _.levenshtein(projectName, match), length: match.length });
});

// console.log(matchDistances);

// Use the name from the object having the lowest Levenshtein distance.
var sortedMatches = _.sortBy(matchDistances, 'distance');
// console.log(sortedMatches);
var firstMatch = _.first(sortedMatches);
// console.log(firstMatch);

// Are there other elements with the same distance?
var contenders = _.where(sortedMatches, { distance: firstMatch.distance });

if (contenders.length == 1) {
project = _.findWhere(data2, { name: firstMatch.name });
}
else {
console.log("Still multiple matches, going with the one sorted higher in Todoist...")
// If we have multiple matches, go with the shorter name.
// If they are the same length, _.min() will return the first one.
// We'll sort by project name length to make sure.
var shortest = _.min(_.sortBy(contenders, 'length'), function (contender) {
return contender.length;
});

project = _.findWhere(data2, { name: shortest.name });
}

// Partial matching ain't easy.
}
}

if (project) {
var projectId = project.id;

console.log("Project ID: " + projectId)
console.log("Matched project name: " + project.name);
// console.log("Project ID: " + projectId)

// Finally, move the item to this project. Then we are done.
var projectMapping = {};
Expand Down

0 comments on commit 08358ee

Please sign in to comment.