everdone 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +95 -0
- data/Rakefile +10 -0
- data/bin/everdone +5 -0
- data/everdone.gemspec +31 -0
- data/lib/everdone.rb +89 -0
- data/lib/everdone/config.rb +65 -0
- data/lib/everdone/default_config.json +19 -0
- data/lib/everdone/enmlformatter.rb +103 -0
- data/lib/everdone/evernote.rb +73 -0
- data/lib/everdone/sync.rb +63 -0
- data/lib/everdone/todoItem.rb +61 -0
- data/lib/everdone/todoist.rb +111 -0
- data/lib/everdone/version.rb +3 -0
- data/test/configTest.rb +28 -0
- data/test/config_1.json +10 -0
- data/test/config_partial +7 -0
- data/test/enmlformatterTest.rb +57 -0
- data/test/evernoteTest.rb +19 -0
- data/test/todoistTest.rb +19 -0
- metadata +199 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f6a233c262926369ff671d05bc39ca7c4851473b
|
4
|
+
data.tar.gz: 243e02fe226c50a3ef411ba9579dbf83522fde1e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57f67c9233a73baddda008eda39c9d503ec45e4ddc70f8c6cf32cb2703eebca87af26c803d361442171ae1f43855ac5e066f7856b6ccac2e431944af34d72380
|
7
|
+
data.tar.gz: 123e4d163fe238d566d665ae132dc6abf60f828adaf06917d36fdc99cd0f1d1815e4a68d560e1d735a2de2aa3f3b06f8816dde34502bb702f6b5a8d13d99c05e
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
everdone.sublime-project
|
19
|
+
everdone.sublime-workspace
|
20
|
+
.DS_Store
|
21
|
+
*.swp
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Steve Heckt
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Everdone
|
2
|
+
|
3
|
+
A command line utility that takes completed tasks from Todooist and inserts them as entries into Evernote.
|
4
|
+
|
5
|
+
_Full disclosure: [Zapier](http://zapier.com) now supports pushing Todoist items to Evernote_
|
6
|
+
|
7
|
+
I still like my solution better as it gives finer control - and I wrote it. :)
|
8
|
+
|
9
|
+
Likely this best serves as simple examples of using the Evernote and Todoist API's from Ruby.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'everdone'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install everdone
|
24
|
+
|
25
|
+
This utility depends on Todoist having a project named "EvernoteSync". An item is added to this project to track all the Todoist item IDs that have already been added to Evernote. This keeps the utility from needing to query Evernote to see if an item has already been copied. Not as elegant a solution as I'd like. (BTW, the utility does double-check with Evernote before adding a new item.)
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Everdone was written to be a command line utility, but to use it from a Ruby script...
|
30
|
+
|
31
|
+
require 'everdone'
|
32
|
+
|
33
|
+
Everdone.sync
|
34
|
+
|
35
|
+
Someday it should take options at the sync() call, but for now it starts with default options (included in this gem) and merges in options specified in the '.everdone' file in the caller's home directory. The full set of options are:
|
36
|
+
|
37
|
+
```json
|
38
|
+
{
|
39
|
+
"default_notebook" : "<set to one of your Evernote notebooks>",
|
40
|
+
"tag" : "Todoist",
|
41
|
+
"evernote_datetime_format" : "%a %e %b %Y %H:%M",
|
42
|
+
"use_evernote_sandbox" : false,
|
43
|
+
"evernote_token" : "<get from: https://www.evernote.com/api/DeveloperToken.action>",
|
44
|
+
|
45
|
+
"todoist_token" : "<get from your Todoist settings Account tab>",
|
46
|
+
"todoist_completed_window" : "past 2 weeks",
|
47
|
+
"todoist_datetime_format" : "%a %e %b %Y %H:%M:%S %z",
|
48
|
+
"todoist_project_url" : "https://todoist.com/app?v=204#project/",
|
49
|
+
"todoist_label_url" : "https://todoist.com/app?v=204#agenda/@",
|
50
|
+
"todoist_sync_tracking_project" : "EvernoteSync",
|
51
|
+
"todoist_content_tag" : "TodoistItemId",
|
52
|
+
"todoist_link_title_regex" : "/\[\[[\w\s]+=[\h]+\s*,\s*([ -~]+)\]\]/",
|
53
|
+
|
54
|
+
"todoist_evernote_map" : {
|
55
|
+
}
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
A basic set of options, in JSON format within your .everdone file, might look like:
|
60
|
+
|
61
|
+
$ cat ~/.everdone
|
62
|
+
{
|
63
|
+
"default_notebook" : "My Work Notebook",
|
64
|
+
"evernote_token" : "S=s1:U=1f14:E=14c1<your Evernote dev token bits>de0626b34bc5ede7",
|
65
|
+
"todoist_token" : "582a9<your Todoist API token bits>71ef5",
|
66
|
+
"todoist_evernote_map" : {
|
67
|
+
"Home" : "My Personal Notebook",
|
68
|
+
"Hobby" : "My Hobby Notebook"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
From the command line it looks like:
|
73
|
+
|
74
|
+
$ everdone
|
75
|
+
INFO: Returned 39 items
|
76
|
+
INFO: Done! Of 39 found...
|
77
|
+
1 added to Evernote
|
78
|
+
All time total processed now 340
|
79
|
+
|
80
|
+
## The Present
|
81
|
+
|
82
|
+
Contains a nacent set of tests.
|
83
|
+
|
84
|
+
## The Future
|
85
|
+
|
86
|
+
See everdone.rb for a list of TODO's I have in mind. Other, more minor TODO ideas are sprinked throughout the code.
|
87
|
+
But I suspect this need will soon be fully met by Zapier.com or IFTTT.com.
|
88
|
+
|
89
|
+
## Contributing
|
90
|
+
|
91
|
+
1. Fork it ( http://github.com/heck/everdone/fork )
|
92
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
93
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
94
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
95
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/everdone
ADDED
data/everdone.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'everdone/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "everdone"
|
8
|
+
spec.version = Everdone::VERSION
|
9
|
+
spec.authors = ["Steve Heckt"]
|
10
|
+
spec.email = ["home@u2me.com"]
|
11
|
+
spec.summary = %q{Syncs completed Todoist items into Evernote}
|
12
|
+
spec.description = %q{Uses Todoist and Evernote web services (and their respective API tokesn) }
|
13
|
+
spec.homepage = "http://github.com/heck/everdone"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "evernote-thrift", "~> 1.25"
|
22
|
+
spec.add_runtime_dependency "evernote_oauth", "~> 0.2"
|
23
|
+
spec.add_runtime_dependency "httparty", "~> 0.13"
|
24
|
+
spec.add_runtime_dependency "json", "~> 1.8"
|
25
|
+
spec.add_runtime_dependency "oauth", "~> 0.4"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
28
|
+
spec.add_development_dependency "rake"
|
29
|
+
spec.add_development_dependency "minitest"
|
30
|
+
spec.add_development_dependency "awesome_print"
|
31
|
+
end
|
data/lib/everdone.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# TODO: Add real error and corner case handling
|
2
|
+
# TODO: command line options such as "create user config", "pre-flight test", "dry run", "verbose"
|
3
|
+
# TODO: Test on Windows
|
4
|
+
# TODO: Templatize Evernote entry format
|
5
|
+
# TODO: Allow Todoist content to be in markdown and covert before posting to Evernote
|
6
|
+
|
7
|
+
require "everdone/version"
|
8
|
+
|
9
|
+
require 'awesome_print'
|
10
|
+
require 'time'
|
11
|
+
|
12
|
+
# Everdone local stuff
|
13
|
+
require 'everdone/config'
|
14
|
+
require 'everdone/evernote'
|
15
|
+
require 'everdone/enmlformatter'
|
16
|
+
require 'everdone/todoist'
|
17
|
+
require 'everdone/sync'
|
18
|
+
|
19
|
+
module Everdone
|
20
|
+
def self.init
|
21
|
+
@@config = Config.new(File.expand_path("../everdone/default_config.json", __FILE__), "#{Dir.home}/.everdone")
|
22
|
+
@@evernote = Evernote.new(@@config)
|
23
|
+
@@todoist = Todoist.new(@@config)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.get_notebook_from_project(project)
|
27
|
+
notebook = @@config.todoist_evernote_map[project] # It is possible the project is in the map but the value is nil. This means: don't add items from this project to Evernote
|
28
|
+
if not @@config.todoist_evernote_map.has_key?(project) # If the project is not in the map at all then put it in the default notebook
|
29
|
+
notebook = @@config.default_notebook
|
30
|
+
end
|
31
|
+
return notebook
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.sync
|
35
|
+
self.init
|
36
|
+
|
37
|
+
sync = Sync.new(@@config, @@todoist)
|
38
|
+
|
39
|
+
items = @@todoist.get_completed_items()
|
40
|
+
puts "INFO: Returned #{items.length} items"
|
41
|
+
processed = [] # list of Todoist item ids that were processed
|
42
|
+
found = [] # list of Todoist item ids not in the already processed list but subsequently found in Evernote
|
43
|
+
excluded = [] # a list of Todoist item ids that were not added because they belong to a project that is blacklisted
|
44
|
+
items.each { |item|
|
45
|
+
if not sync.is_already_processed(item.id)
|
46
|
+
# Map the project to an Evernote notebook
|
47
|
+
notebook = get_notebook_from_project(item.projects[0])
|
48
|
+
find_count = @@evernote.find_note_counts("#{@@config.todoist_content_tag}#{item.id}", notebook) if notebook
|
49
|
+
if notebook.nil?
|
50
|
+
excluded.push(item.id) # project did not map to any notebook => don't add to Evernote
|
51
|
+
elsif find_count > 0
|
52
|
+
found.push(item.id) # Todoist item already in Evernote => don't add
|
53
|
+
else # not already processed nor in an ignored project nor was it found in Evernote. Make a new one!
|
54
|
+
# Create the note's content
|
55
|
+
content = EnmlFormatter.new(@@config)
|
56
|
+
content.text("Project: ").link(item.projects[0], item.get_project_url(0))
|
57
|
+
if item.labels.length > 0
|
58
|
+
content.space.space.space.space
|
59
|
+
content.text("Labels: ")
|
60
|
+
item.labels.each { |label|
|
61
|
+
content.link(label, item.get_label_url(label)).space
|
62
|
+
}
|
63
|
+
end
|
64
|
+
content.space.space.space.text("#{@@config.todoist_content_tag}#{item.id}")
|
65
|
+
item.notes.each { |note|
|
66
|
+
content.h3("Note created #{content.datetime_to_string(note.created, @@config.todoist_datetime_format)} [Todoist note id: #{note.id}]")
|
67
|
+
content.rawtext(note.content)
|
68
|
+
}
|
69
|
+
|
70
|
+
# Create the note in Evernote
|
71
|
+
@@evernote.create_note(item.title,
|
72
|
+
content.to_s,
|
73
|
+
notebook,
|
74
|
+
Evernote.convert_text_to_timestamp(item.created, @@config.todoist_datetime_format))
|
75
|
+
end
|
76
|
+
processed.push(item.id) # whether item was added to Evernote or not don't process it again.
|
77
|
+
end
|
78
|
+
}
|
79
|
+
|
80
|
+
# Finish up by doing the syncing bookwork
|
81
|
+
sync.close(processed)
|
82
|
+
|
83
|
+
puts "INFO: Done! Of #{items.length} found..."
|
84
|
+
puts " #{processed.length - (found.length+excluded.length)} added to Evernote"
|
85
|
+
puts " #{found.length} were already in Evernote" if found.length > 0
|
86
|
+
puts " #{excluded.length} were not added as they are in blacklisted Todoist projects" if excluded.length > 0
|
87
|
+
puts " All time total processed now #{sync.get_processed_total()}"
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#
|
2
|
+
# Class to handle reading, writing, and encapsulating JSON-based app config
|
3
|
+
|
4
|
+
# TODO: Preflight check vs. config. Tokens work? Projects, notebooks, tags needed all there?
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Everdone
|
9
|
+
class Config
|
10
|
+
def initialize(default_hash_file, user_settings_file)
|
11
|
+
@defaults = unmarshall_from_file(default_hash_file)
|
12
|
+
@user_settings_file = user_settings_file
|
13
|
+
@users = unmarshall_from_file(@user_settings_file)
|
14
|
+
@current = {}
|
15
|
+
|
16
|
+
collate_settings()
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def define_instance_var(name, value)
|
22
|
+
self.class.module_eval { attr_accessor name.to_sym }
|
23
|
+
instance_variable_set("@#{name}", value)
|
24
|
+
end
|
25
|
+
|
26
|
+
# return a string of JSON for the current settings
|
27
|
+
def marshall_to_string()
|
28
|
+
current = JSON.generate(@current)
|
29
|
+
return current
|
30
|
+
end
|
31
|
+
|
32
|
+
def unmarshall_from_string(new_config)
|
33
|
+
return JSON.parse(new_config)
|
34
|
+
end
|
35
|
+
|
36
|
+
def marshall_to_file(filename)
|
37
|
+
end
|
38
|
+
|
39
|
+
def unmarshall_from_file(filename)
|
40
|
+
users = {}
|
41
|
+
return users if not File.exist?(filename)
|
42
|
+
File.open(filename, "r") do |f|
|
43
|
+
users = JSON.load(f)
|
44
|
+
end
|
45
|
+
return users
|
46
|
+
end
|
47
|
+
|
48
|
+
def collate_settings
|
49
|
+
# add all the settings (every setting has a default) as a member variable of this class
|
50
|
+
@defaults.each { |name, val|
|
51
|
+
define_instance_var(name,val)
|
52
|
+
}
|
53
|
+
# combine the default settings and the settings from the user file into the current settings
|
54
|
+
@current = @defaults
|
55
|
+
@users.each { |name, val|
|
56
|
+
if @current.has_key?(name)
|
57
|
+
@current[name] = val
|
58
|
+
instance_variable_set("@#{name}", val)
|
59
|
+
else
|
60
|
+
puts "WARN: the settings file #{@user_settings_file} contained an unrecognized setting #{name} (value = #{val})"
|
61
|
+
end
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"default_notebook" : "<set to one of your Evernote notebooks>",
|
3
|
+
"tag" : "Todoist",
|
4
|
+
"evernote_datetime_format" : "%a %e %b %Y %H:%M",
|
5
|
+
"use_evernote_sandbox" : false,
|
6
|
+
"evernote_token" : "<get from: https://www.evernote.com/api/DeveloperToken.action>",
|
7
|
+
|
8
|
+
"todoist_token" : "<get from your Todoist settings Account tab>",
|
9
|
+
"todoist_completed_window" : "past 2 weeks",
|
10
|
+
"todoist_datetime_format" : "%a %e %b %Y %H:%M:%S %z",
|
11
|
+
"todoist_project_url" : "https://todoist.com/app?v=204#project/",
|
12
|
+
"todoist_label_url" : "https://todoist.com/app?v=204#agenda/@",
|
13
|
+
"todoist_sync_tracking_project" : "EvernoteSync",
|
14
|
+
"todoist_content_tag" : "TodoistItemId",
|
15
|
+
"todoist_link_title_regex" : "/\[\[[\w\s]+=[\h]+\s*,\s*([ -~]+)\]\]/",
|
16
|
+
|
17
|
+
"todoist_evernote_map" : {
|
18
|
+
}
|
19
|
+
}
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'time'
|
3
|
+
require 'awesome_print'
|
4
|
+
|
5
|
+
require 'everdone/config'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Class to help create the ENML (EverNote Markup Language) content
|
9
|
+
#
|
10
|
+
|
11
|
+
module Everdone
|
12
|
+
class EnmlFormatter
|
13
|
+
attr_reader :body
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@config = config
|
17
|
+
@body = ""
|
18
|
+
end
|
19
|
+
|
20
|
+
def text(text)
|
21
|
+
@body = @body + text
|
22
|
+
return self
|
23
|
+
end
|
24
|
+
|
25
|
+
# from: http://code.tutsplus.com/tutorials/8-regular-expressions-you-should-know--net-6149
|
26
|
+
# match only url's that take the form http://..., https://...
|
27
|
+
# but NOT <whatever>@<url> or <host>.<domain>.<tld>
|
28
|
+
URL_REGEX = /((https?:\/\/)([\da-zA-Z\.-]+)\.([a-z\.]{2,6})(:[\d]+)?([\/\w \.?%_&=+-]*)*\/?)/
|
29
|
+
def rawtext(text)
|
30
|
+
# Take text and do some cooking
|
31
|
+
#
|
32
|
+
# Escape HTML tags so Evernote doesn't freak out on them
|
33
|
+
text = CGI::escapeHTML(text)
|
34
|
+
# Remove newlines and insert HTML breaks (<br/>)
|
35
|
+
text_lines = text.split(/\n/)
|
36
|
+
text = ""
|
37
|
+
text_lines.each { |line|
|
38
|
+
# Find URL-looking text and turn it into a link
|
39
|
+
url_match = line.match(URL_REGEX)
|
40
|
+
if url_match
|
41
|
+
url = url_match[1]
|
42
|
+
line.gsub!(url, "<a href='#{url}'>#{url}</a>") if url
|
43
|
+
end
|
44
|
+
text = text + "#{line}<br/>"
|
45
|
+
}
|
46
|
+
# Fix up some Todoist crap: It’s -> It's
|
47
|
+
text.gsub!("’", "'");
|
48
|
+
# Put it in the body
|
49
|
+
@body = @body + text
|
50
|
+
return self
|
51
|
+
end
|
52
|
+
|
53
|
+
def newline()
|
54
|
+
@body = @body + "<br/>"
|
55
|
+
return self
|
56
|
+
end
|
57
|
+
|
58
|
+
def h1(text)
|
59
|
+
@body = @body + "<h1>#{text}</h1>"
|
60
|
+
return self
|
61
|
+
end
|
62
|
+
|
63
|
+
def h2(text)
|
64
|
+
@body = @body + "<h2>#{text}</h2>"
|
65
|
+
return self
|
66
|
+
end
|
67
|
+
|
68
|
+
def h3(text)
|
69
|
+
@body = @body + "<h3>#{text}</h3>"
|
70
|
+
return self
|
71
|
+
end
|
72
|
+
|
73
|
+
def space
|
74
|
+
@body = @body + " "
|
75
|
+
return self
|
76
|
+
end
|
77
|
+
|
78
|
+
def link(text, url)
|
79
|
+
@body = @body + "<a href='#{url}'>#{text}</a>"
|
80
|
+
return self
|
81
|
+
end
|
82
|
+
|
83
|
+
def datetime_to_string(text, source_format)
|
84
|
+
return DateTime.strptime(text, source_format).new_offset(DateTime.now.offset).strftime(@config.evernote_datetime_format)
|
85
|
+
end
|
86
|
+
|
87
|
+
def datetime(text, source_format)
|
88
|
+
@body = @body + self.datetime_to_string(text, source_format)
|
89
|
+
return self
|
90
|
+
end
|
91
|
+
|
92
|
+
def clear
|
93
|
+
@body = ""
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
ret = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
98
|
+
ret = "<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">"
|
99
|
+
ret += "<en-note>#{@body}</en-note>"
|
100
|
+
return ret
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Load libraries required by the Evernote OAuth
|
2
|
+
require 'time'
|
3
|
+
require 'oauth'
|
4
|
+
require 'oauth/consumer'
|
5
|
+
|
6
|
+
# Load Thrift & Evernote Ruby libraries
|
7
|
+
require "evernote_oauth"
|
8
|
+
|
9
|
+
require "everdone/config"
|
10
|
+
|
11
|
+
module Everdone
|
12
|
+
class Evernote
|
13
|
+
def initialize(config)
|
14
|
+
@config = config
|
15
|
+
# Set up the NoteStore client
|
16
|
+
@evernote_client = EvernoteOAuth::Client.new(
|
17
|
+
token: @config.evernote_token,
|
18
|
+
sandbox: @config.use_evernote_sandbox
|
19
|
+
)
|
20
|
+
@note_store = @evernote_client.note_store
|
21
|
+
@notebook_guids = {}
|
22
|
+
notebooks = @note_store.listNotebooks
|
23
|
+
notebooks.each do |notebook|
|
24
|
+
@notebook_guids[notebook.name] = notebook.guid
|
25
|
+
end
|
26
|
+
@tags = @note_store.listTags
|
27
|
+
@tags.each { |tag|
|
28
|
+
if @config.tag and tag.name == @config.tag then
|
29
|
+
@tag_guid = tag.guid
|
30
|
+
break
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_note(title, content, notebook, created)
|
36
|
+
new_note = ::Evernote::EDAM::Type::Note.new()
|
37
|
+
new_note.notebookGuid = @notebook_guids[notebook]
|
38
|
+
new_note.title = title.strip.slice(0..254).scan(/[[:print:]]/).join
|
39
|
+
new_note.created = created
|
40
|
+
new_note.content = content
|
41
|
+
new_note.tagNames = [@config.tag] if @config.tag
|
42
|
+
|
43
|
+
begin
|
44
|
+
created_note = @note_store.createNote(@config.evernote_token, new_note)
|
45
|
+
rescue => err
|
46
|
+
puts "ERROR: ----------- Evernote exception ------------------!!!"
|
47
|
+
ap err
|
48
|
+
puts "Note info:"
|
49
|
+
puts "Title: #{title}"
|
50
|
+
puts "Created: #{created}"
|
51
|
+
puts "Content:\n#{content}"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# returns the count of notes within the context:
|
57
|
+
# In the notebook NOTEBOOK_TARGET with tag (if defined) TAG_WITH and having content (if defined) content_text
|
58
|
+
def find_note_counts(content_text, notebook)
|
59
|
+
filter = ::Evernote::EDAM::NoteStore::NoteFilter.new()
|
60
|
+
filter.notebookGuid = @notebook_guids[notebook] if notebook
|
61
|
+
filter.tagGuids = [@tag_guid] if @config.tag
|
62
|
+
filter.words = content_text if content_text
|
63
|
+
ret = @note_store.findNoteCounts(filter, false)
|
64
|
+
# also: ret.tagCounts[@@tag_guid]
|
65
|
+
return !ret.notebookCounts.nil? && notebook && ret.notebookCounts[@notebook_guids[notebook]] ? ret.notebookCounts[@notebook_guids[notebook]] : 0
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.convert_text_to_timestamp(dateText, date_format)
|
69
|
+
ret = DateTime.strptime(dateText, date_format).to_time.to_i * 1000
|
70
|
+
return ret
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#
|
2
|
+
# Class that keeps track of what's been synced
|
3
|
+
#
|
4
|
+
require 'time'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require 'everdone/todoist.rb'
|
8
|
+
|
9
|
+
module Everdone
|
10
|
+
class Sync
|
11
|
+
def initialize(config, todoist)
|
12
|
+
@config = config
|
13
|
+
@todoist = todoist
|
14
|
+
@data = {}
|
15
|
+
@current_item_id = nil
|
16
|
+
items = @todoist.get_items_by_project_name(@config.todoist_sync_tracking_project)
|
17
|
+
raise "Too many items in Todoist project used for syncing (#{@config.todoist_sync_tracking_project}): #{items.length}" if items.length > 1
|
18
|
+
if items.length == 0 then
|
19
|
+
initData()
|
20
|
+
else
|
21
|
+
@current_item_id = items[0]['id']
|
22
|
+
notes = @todoist.get_notes(@current_item_id)
|
23
|
+
unmarshall(notes[0]['content']) if notes and notes.length > 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def init_data
|
28
|
+
@data['created'] = DateTime.now
|
29
|
+
@data['processed'] = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_already_processed(item_id)
|
33
|
+
return !@data['processed'].find_index(item_id).nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def close(new_processed)
|
37
|
+
@data['processed'].concat(new_processed)
|
38
|
+
new_item_id = @todoist.add_item_to_project_by_name(@config.todoist_sync_tracking_project,
|
39
|
+
"Evernote sync data from #{DateTime.now.to_s} total processed = #{self.get_processed_total()}",
|
40
|
+
1)
|
41
|
+
new_data = self.marshall
|
42
|
+
@todoist.add_note(new_item_id, new_data)
|
43
|
+
@todoist.delete_item_by_id(@current_item_id) if @current_item_id
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_processed_total()
|
47
|
+
return @data['processed'].length
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_to_processed(new_processed)
|
51
|
+
@data['processed'].push(new_processed)
|
52
|
+
end
|
53
|
+
|
54
|
+
def marshall
|
55
|
+
data = JSON.generate(@data)
|
56
|
+
return data
|
57
|
+
end
|
58
|
+
|
59
|
+
def unmarshall(data)
|
60
|
+
@data = JSON.parse(data)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#
|
2
|
+
# TodoItem
|
3
|
+
# Handle mapping a single Todoist itme to something EverTodo can work with
|
4
|
+
#
|
5
|
+
require 'awesome_print'
|
6
|
+
|
7
|
+
require 'everdone/config'
|
8
|
+
|
9
|
+
module Everdone
|
10
|
+
class TodoNote
|
11
|
+
attr_reader :id, :created, :content
|
12
|
+
def initialize(todoist_note)
|
13
|
+
note = todoist_note
|
14
|
+
@id = note['id']
|
15
|
+
@created = note['posted']
|
16
|
+
@content = note['content']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class TodoItem
|
21
|
+
attr_reader :id, :title, :created, :projects, :project_ids, :labels, :notes, :priority, :due_date
|
22
|
+
def initialize(config, todoist_item, projects, labels)
|
23
|
+
@config = config
|
24
|
+
item = todoist_item
|
25
|
+
@id = item['id']
|
26
|
+
@title = item['content']
|
27
|
+
clean_title() # strip off all (known) Todoist detritus
|
28
|
+
@created = item['completed_date']
|
29
|
+
@projects = [] # Just the parent for now. TODO: array of projects starting with senior parent and working down to immediate parent
|
30
|
+
@projects.push(projects[item['project_id']])
|
31
|
+
@project_ids = [] # Just the parent for now. TODO: array of project ids starting with senior... (see above)
|
32
|
+
@project_ids.push(item['project_id'])
|
33
|
+
@labels = [] # arrary of associated labels (random order)
|
34
|
+
item['labels'].each { |labelId|
|
35
|
+
@labels.push(labels[labelId])
|
36
|
+
}
|
37
|
+
@priority = item['priority']
|
38
|
+
@due_date = item['due_date']
|
39
|
+
@notes = [] # array of notes
|
40
|
+
if not item['notes'].nil?
|
41
|
+
item['notes'].each { |note|
|
42
|
+
new_note = TodoNote.new(note)
|
43
|
+
@notes.push(new_note)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def clean_title()
|
49
|
+
cleaned_title = @title.match(@config.todoist_link_title_regex)
|
50
|
+
@title = cleaned_title.nil? ? @title : cleaned_title[1].strip
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_project_url(project_index)
|
54
|
+
return @config.todoist_project_url + @project_ids[project_index].to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_label_url(label)
|
58
|
+
return @config.todoist_label_url + label
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'awesome_print'
|
3
|
+
|
4
|
+
require 'everdone/config'
|
5
|
+
|
6
|
+
require 'everdone/todoItem'
|
7
|
+
|
8
|
+
module Everdone
|
9
|
+
class Todoist
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
@projects = {} # map of project id's to project names
|
13
|
+
@project_id_by_name = {} # map of project names to id's
|
14
|
+
projects = self.call_api('https://todoist.com/API/getProjects')
|
15
|
+
projects.each { |project|
|
16
|
+
@projects[project['id']] = project['name']
|
17
|
+
@project_id_by_name[project['name']] = project['id']
|
18
|
+
}
|
19
|
+
@labels = {} # map of label id's to their name strings
|
20
|
+
labels = self.call_api('https://todoist.com/API/getLabels')
|
21
|
+
labels.each { |label|
|
22
|
+
@labels[label[1]['id']] = label[1]['name']
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# helper function for making RESTful calls
|
27
|
+
def call_api(url, params=nil)
|
28
|
+
query = { 'token' => @config.todoist_token }
|
29
|
+
query.merge!(params) if params
|
30
|
+
ret = HTTParty.post(url, {
|
31
|
+
:query => query
|
32
|
+
})
|
33
|
+
|
34
|
+
if ret.response.code != "200"
|
35
|
+
raise "ERROR: Error calling Todoist API #{url}\n Response Code: #{ret.response.code}\n Response: \n#{ret.response.body}"
|
36
|
+
end
|
37
|
+
return ret
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_item_to_project_by_name(project_name, content, priority)
|
41
|
+
ret = self.call_api('https://todoist.com/API/addItem',
|
42
|
+
params={'project_id'=>@project_id_by_name[project_name],'content'=>content, 'priority'=>priority})
|
43
|
+
return ret['id']
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_note(item_id, content)
|
47
|
+
ret = self.call_api('https://todoist.com/API/addNote', params={'item_id'=>item_id,'content'=>content})
|
48
|
+
return ret
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_completed_items()
|
52
|
+
ret = [] # list of completed items converted into TodoItem objects
|
53
|
+
interval = @config.todoist_completed_window
|
54
|
+
items = self.call_api('https://todoist.com/API/getAllCompletedItems', params={'interval'=>interval})
|
55
|
+
items['items'].each { |item|
|
56
|
+
todo_item = TodoItem.new(@config, item, @projects, @labels)
|
57
|
+
ret.push(todo_item)
|
58
|
+
}
|
59
|
+
return ret
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_item_by_id(id)
|
63
|
+
items = self.call_api('https://todoist.com/API/getItemsById', params={'ids'=>id}) # get a single item based on id
|
64
|
+
todo_item = TodoItem.new(@config, items[0], @projects, @labels)
|
65
|
+
return todo_item
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_items_by_project_name(project_name)
|
69
|
+
ret = self.call_api('https://todoist.com/API/getUncompletedItems', params={'project_id'=>@project_id_by_name[project_name]})
|
70
|
+
return ret
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_notes(item_id)
|
74
|
+
ret = self.call_api('https://todoist.com/API/getNotes', params={'item_id'=>item_id})
|
75
|
+
return ret
|
76
|
+
end
|
77
|
+
|
78
|
+
def complete_item_by_id(id)
|
79
|
+
ret = self.call_api('https://todoist.com/API/completeItems', params={'ids'=>"[#{id}]"})
|
80
|
+
return ret
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_item_by_id(id)
|
84
|
+
ret = self.call_api('https://todoist.com/API/deleteItems', params={'ids'=>"[#{id}]"})
|
85
|
+
return ret
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
## getting a token via login
|
91
|
+
#
|
92
|
+
#login = HTTParty.post('https://todoist.com/API/login', {
|
93
|
+
# :query => {
|
94
|
+
# 'email'=>'home@u2me.com',
|
95
|
+
# 'password'=>'<insert password here>'
|
96
|
+
# }
|
97
|
+
#})
|
98
|
+
#
|
99
|
+
#if login.response.code != "200"
|
100
|
+
# raise "Login unsuccessful.\n#{login.response.code}\n#{login.response.body}"
|
101
|
+
#end
|
102
|
+
#
|
103
|
+
#puts "Login response was:"
|
104
|
+
#puts login.body
|
105
|
+
#puts
|
106
|
+
#
|
107
|
+
|
108
|
+
# for JSON responses, you can address keys in the response directly
|
109
|
+
#token = login['token']
|
110
|
+
|
111
|
+
end
|
data/test/configTest.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'everdone'
|
2
|
+
require 'awesome_print'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
require 'everdone/config.rb'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
class ConfigTest < MiniTest::Unit::TestCase
|
10
|
+
def setup
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_initialize
|
14
|
+
config = Everdone::Config.new("test/config_1.json", "test/config_empty")
|
15
|
+
assert_equal "this is string_1", config.string_1
|
16
|
+
assert_equal 1, config.int_1
|
17
|
+
assert_equal 1.1, config.float_1
|
18
|
+
assert_equal "second_string", config.string_array_1[1]
|
19
|
+
assert_equal "value 1", config.hash_1["key_1"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_initialze_with_user_settings
|
23
|
+
config = Everdone::Config.new("test/config_1.json", "test/config_partial")
|
24
|
+
assert_equal "this is THE NEW string_2", config.string_2
|
25
|
+
assert_equal 2.2, config.float_1
|
26
|
+
assert_equal 3, config.hash_1["key_2"]
|
27
|
+
end
|
28
|
+
end
|
data/test/config_1.json
ADDED
data/test/config_partial
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'everdone'
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'everdone/config'
|
6
|
+
require 'everdone/enmlformatter'
|
7
|
+
|
8
|
+
class EnmlFormatterTest < MiniTest::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
config = Everdone::Config.new("lib/everdone/default_config.json", "#{Dir.home}/.everdone")
|
11
|
+
@targ = Everdone::EnmlFormatter.new(config)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_text
|
15
|
+
# assert_equal "Hello World!", @main.hello
|
16
|
+
added_text = "this is some text"
|
17
|
+
@targ.text(added_text)
|
18
|
+
assert_equal added_text, @targ.body
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_rawtext_todoist_crap
|
22
|
+
@targ.rawtext("It’s")
|
23
|
+
assert_match "It's", @targ.body
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_url_good(url_text)
|
27
|
+
@targ.rawtext(url_text)
|
28
|
+
assert_match "<a href='#{url_text}'>#{url_text}</a>", @targ.body
|
29
|
+
@targ.clear
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_url_bad(url_text)
|
33
|
+
@targ.rawtext(url_text)
|
34
|
+
refute_match "<a href=", @targ.body
|
35
|
+
@targ.clear
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_rawtext_url_links
|
39
|
+
# the good
|
40
|
+
check_url_good "http://www.google.com"
|
41
|
+
check_url_good "https://www.google.com"
|
42
|
+
check_url_good "http://code.tutsplus.com/tutorials/8-regular-expressions-you-should-know--net-6149"
|
43
|
+
check_url_good "http://jira.nmwco.com:8080"
|
44
|
+
check_url_good "http://jira.nmwco.com:8080/browse/LOC-6847"
|
45
|
+
check_url_good "http://jira.nmwco.com:8080/browse/LOC-6847?filter=14192"
|
46
|
+
check_url_good "https://d1x0mwiac2rqwt.cloudfront.net/fec77b1148704f78bdf87a49d0a6e6b3/as/2014-03-18_Performance_log_Olson%2C_Erik.docx"
|
47
|
+
# the bad
|
48
|
+
check_url_bad "www.google.com"
|
49
|
+
check_url_bad "google.com"
|
50
|
+
check_url_bad "Knopf@NetMotionWireless.com"
|
51
|
+
end
|
52
|
+
|
53
|
+
TESTING_DATETIME_FORMAT = "%a %e %b %Y %H:%M:%S %z"
|
54
|
+
def test_datetimeToString
|
55
|
+
assert_equal "Mon 10 Mar 2014 08:45", @targ.datetime_to_string("Mon 10 Mar 2014 15:45:01 +0000", TESTING_DATETIME_FORMAT)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'everdone'
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'everdone/config'
|
6
|
+
require 'everdone/evernote'
|
7
|
+
|
8
|
+
class EvernoteTest < MiniTest::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@config = Everdone::Config.new("lib/everdone/default_config.json", "#{Dir.home}/.everdone")
|
11
|
+
@evernote = Everdone::Evernote.new(@config)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_findNoteCounts
|
15
|
+
assert_equal 0, @evernote.find_note_counts("%$$%%$^@&& NOT IN EVERNOTE RIGHT? ^^{$^&*", @config.default_notebook) # should not be found
|
16
|
+
assert_equal 1, @evernote.find_note_counts("#{@config.todoist_content_tag}4653498", @config.default_notebook) # known to be
|
17
|
+
assert_equal 0, @evernote.find_note_counts("#{@config.todoist_content_tag}4653498", nil) # known but not in this notebook
|
18
|
+
end
|
19
|
+
end
|
data/test/todoistTest.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'everdone'
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'everdone/config'
|
6
|
+
require 'everdone/todoist'
|
7
|
+
require 'everdone/todoItem'
|
8
|
+
|
9
|
+
class TodoistTest < MiniTest::Unit::TestCase
|
10
|
+
def setup
|
11
|
+
@config = Everdone::Config.new("lib/everdone/default_config.json", "#{Dir.home}/.everdone")
|
12
|
+
@todoist = Everdone::Todoist.new(@config)
|
13
|
+
end
|
14
|
+
|
15
|
+
KNOWN_TODOIST_COMPLETED_ID = 1155724
|
16
|
+
def test_todoItem
|
17
|
+
todo_item = @todoist.get_item_by_id(KNOWN_TODOIST_COMPLETED_ID)
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: everdone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Heckt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: evernote-thrift
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.25'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.25'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: evernote_oauth
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: httparty
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.13'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.13'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.8'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: oauth
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.4'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: awesome_print
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: 'Uses Todoist and Evernote web services (and their respective API tokesn) '
|
140
|
+
email:
|
141
|
+
- home@u2me.com
|
142
|
+
executables:
|
143
|
+
- everdone
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- .gitignore
|
148
|
+
- Gemfile
|
149
|
+
- LICENSE.txt
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- bin/everdone
|
153
|
+
- everdone.gemspec
|
154
|
+
- lib/everdone.rb
|
155
|
+
- lib/everdone/config.rb
|
156
|
+
- lib/everdone/default_config.json
|
157
|
+
- lib/everdone/enmlformatter.rb
|
158
|
+
- lib/everdone/evernote.rb
|
159
|
+
- lib/everdone/sync.rb
|
160
|
+
- lib/everdone/todoItem.rb
|
161
|
+
- lib/everdone/todoist.rb
|
162
|
+
- lib/everdone/version.rb
|
163
|
+
- test/configTest.rb
|
164
|
+
- test/config_1.json
|
165
|
+
- test/config_partial
|
166
|
+
- test/enmlformatterTest.rb
|
167
|
+
- test/evernoteTest.rb
|
168
|
+
- test/todoistTest.rb
|
169
|
+
homepage: http://github.com/heck/everdone
|
170
|
+
licenses:
|
171
|
+
- MIT
|
172
|
+
metadata: {}
|
173
|
+
post_install_message:
|
174
|
+
rdoc_options: []
|
175
|
+
require_paths:
|
176
|
+
- lib
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - '>='
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
requirements: []
|
188
|
+
rubyforge_project:
|
189
|
+
rubygems_version: 2.0.3
|
190
|
+
signing_key:
|
191
|
+
specification_version: 4
|
192
|
+
summary: Syncs completed Todoist items into Evernote
|
193
|
+
test_files:
|
194
|
+
- test/configTest.rb
|
195
|
+
- test/config_1.json
|
196
|
+
- test/config_partial
|
197
|
+
- test/enmlformatterTest.rb
|
198
|
+
- test/evernoteTest.rb
|
199
|
+
- test/todoistTest.rb
|