├── .travis.yml
├── Gemfile
├── lib
├── evernote_editor
│ ├── version.rb
│ └── editor.rb
└── evernote_editor.rb
├── Rakefile
├── .gitignore
├── spec
├── spec_helper.rb
└── evernote_editor
│ └── editor_spec.rb
├── bin
└── evned
├── LICENSE.txt
├── README.md
└── evernote-editor.gemspec
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 2.0.0
3 | - 2.1.0
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec
3 |
--------------------------------------------------------------------------------
/lib/evernote_editor/version.rb:
--------------------------------------------------------------------------------
1 | module EvernoteEditor
2 | VERSION = "0.1.9"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/evernote_editor.rb:
--------------------------------------------------------------------------------
1 | require "evernote_editor/editor"
2 | require "evernote_editor/version"
3 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require 'rspec/core/rake_task'
3 |
4 | RSpec::Core::RakeTask.new('spec')
5 |
6 | # If you want to make this the default task
7 | task :default => :spec
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 | require 'fakefs/spec_helpers'
4 |
5 | require 'evernote_editor'
6 |
7 | RSpec.configure do |config|
8 | config.include FakeFS::SpecHelpers
9 |
10 | config.expect_with :rspec do |c|
11 | c.syntax = :should
12 | end
13 | config.mock_with :rspec do |c|
14 | c.syntax = :should
15 | end
16 |
17 | def write_fakefs_config
18 | File.open(File.expand_path("~/.evned"), 'w') do |f|
19 | f.write( { token: '123', editor: 'vim' }.to_json )
20 | end
21 | end
22 | end
23 |
24 |
--------------------------------------------------------------------------------
/bin/evned:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4 | $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5 |
6 | require 'optparse'
7 | require 'evernote_editor'
8 |
9 | options = {}
10 | OptionParser.new do |opts|
11 | opts.banner = "Usage: evned [options] title tag1,tag2"
12 |
13 | options[:edit] = false
14 | options[:sandbox] = false
15 |
16 | opts.on( '-s', '--sandbox', 'Use the Evernote sandbox server' ) do
17 | options[:sandbox] = true
18 | end
19 |
20 | opts.on( '-e', '--edit', 'Search for and edit an existing note by title' ) do
21 | options[:edit] = true
22 | end
23 |
24 | opts.on( '-h', '--help', 'Display this screen' ) do
25 | puts opts
26 | exit
27 | end
28 |
29 | end.parse!
30 |
31 | enved = EvernoteEditor::Editor.new(ARGV, options)
32 | enved.run
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Henry Poydar
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/henrypoydar/evernote-editor)
2 |
3 | # Evernote Editor
4 |
5 | Simple gem that provides command line creation and editing of Evernote notes.
6 | Uses your favorite editor and Markdown formatting.
7 |
8 | ## Requirements
9 |
10 | Only tested on OSX and Ruby 2.0, 2.1 with `vim`, `mvim` and `sublime` editors. YMMV.
11 |
12 | ## Installation
13 |
14 | gem install evernote-editor
15 |
16 | ## Usage
17 |
18 | You'll need a developer token (http://dev.evernote.com/start/core/authentication.php#devtoken)
19 | to use this tool. The first time you run it you will be prompted for it.
20 | You will also be prompted for the path to your editor.
21 | You can modify both values later by editing `~/.enved`
22 |
23 | evned [options] title tag1,tag2
24 | -s, --sandbox Use the Evernote sandbox server
25 | -e, --edit Search for and edit an existing note by title
26 | -h, --help Display this screen
27 |
28 | Title is optional on note creation. Tags are ignored when a note is edited.
29 |
30 | ## Development
31 |
32 | bundle
33 | bx rspec spec
34 |
--------------------------------------------------------------------------------
/evernote-editor.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'evernote_editor/version'
5 |
6 | Gem::Specification.new do |gem|
7 |
8 | gem.name = "evernote-editor"
9 | gem.version = EvernoteEditor::VERSION
10 | gem.authors = ["hpoydar"]
11 | gem.email = ["henry@poydar.com"]
12 | gem.summary = %q{Command line creation and editing of Evernote notes}
13 | gem.description = %q{Simple command line creation and editing of Evernote notes in Markdown format with your favorite editor via a gem installed binary}
14 | gem.homepage = "https://github.com/hpoydar/evernote-editor"
15 |
16 | gem.files = `git ls-files`.split($/)
17 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19 | gem.require_paths = ["lib"]
20 |
21 | gem.add_runtime_dependency "evernote_oauth", "~> 0.2.3"
22 | gem.add_runtime_dependency "highline", "~> 1.7"
23 | gem.add_runtime_dependency "redcarpet", "~> 3.2"
24 | gem.add_runtime_dependency "reverse_markdown", "0.8.1"
25 | gem.add_runtime_dependency "sanitize", "3.1"
26 |
27 | gem.add_development_dependency "fakefs", "~> 0.6.7"
28 | gem.add_development_dependency "rake", "~> 10.4"
29 | gem.add_development_dependency "rspec", "~> 3.2"
30 |
31 | end
32 |
--------------------------------------------------------------------------------
/lib/evernote_editor/editor.rb:
--------------------------------------------------------------------------------
1 | require 'evernote_oauth'
2 | require 'fileutils'
3 | require 'tempfile'
4 | require "highline/import"
5 | require "json"
6 | require "redcarpet"
7 | require "reverse_markdown"
8 | require "sanitize"
9 |
10 | module EvernoteEditor
11 |
12 | class Editor
13 |
14 | CONFIGURATION_FILE = File.expand_path("~/.evned")
15 | attr_accessor :configuration
16 |
17 | def initialize(*args, opts)
18 | @title = args.flatten[0] || ""
19 | @tags = (args.flatten[1] || '').split(',')
20 | @options = opts
21 | @sandbox = opts[:sandbox]
22 | @mkdout = Redcarpet::Markdown.new(Redcarpet::Render::HTML,
23 | autolink: true, space_after_headers: true, no_intra_emphasis: true)
24 | @notebooks = []
25 | end
26 |
27 | def run
28 | configure
29 | @options[:edit] ? edit_note : create_note
30 | end
31 |
32 | def configure
33 | FileUtils.touch(CONFIGURATION_FILE) unless File.exist?(CONFIGURATION_FILE)
34 | #@configuration = YAML::load(File.open(CONFIGURATION_FILE)) || {}
35 | @configuration = JSON::load(File.open(CONFIGURATION_FILE)) || {}
36 | #@configuration = JSON.parse( IO.read(CONFIGURATION_FILE) || {} )
37 | store_key unless @configuration['token']
38 | store_editor unless @configuration['editor']
39 | end
40 |
41 | def create_note
42 | markdown = invoke_editor
43 | begin
44 | evn_client = EvernoteOAuth::Client.new(token: @configuration['token'], sandbox: @sandbox)
45 | note_store = evn_client.note_store
46 | note = Evernote::EDAM::Type::Note.new
47 | note.title = @title.empty? ? "Untitled note" : @title
48 | note.tagNames = @tags unless @tags.empty?
49 | note.content = note_markup(markdown)
50 | created_note = note_store.createNote(@configuration['token'], note)
51 | say "Successfully created new note '#{created_note.title}'"
52 | rescue Evernote::EDAM::Error::EDAMSystemException,
53 | Evernote::EDAM::Error::EDAMUserException,
54 | Evernote::EDAM::Error::EDAMNotFoundException => e
55 | say "Sorry, an error occurred saving the note to Evernote (#{e.message})"
56 | graceful_failure(markdown)
57 | end
58 | end
59 |
60 | def graceful_failure(markdown)
61 | say "Here's the markdown you were trying to save:"
62 | say ""
63 | say "--BEGIN--"
64 | say markdown
65 | say "--END--"
66 | say ""
67 | end
68 |
69 | def edit_note
70 |
71 | found_notes = search_notes(@title)
72 | return unless found_notes
73 | if found_notes.empty?
74 | say "No notes were found matching '#{@title}'"
75 | return
76 | end
77 |
78 | choice = choose do |menu|
79 | menu.prompt = "Which note would you like to edit:"
80 | found_notes.each do |n|
81 | menu.choice("#{Time.at(n.updated/1000).strftime('%Y %b %d %H:%M')} [#{lookup_notebook_name(n.notebookGuid)}] #{n.title}") do
82 | n.guid
83 | end
84 | end
85 | menu.choice("None") { nil }
86 | end
87 | return if choice.nil?
88 |
89 | begin
90 | evn_client = EvernoteOAuth::Client.new(token: @configuration['token'], sandbox: @sandbox)
91 | note_store = evn_client.note_store
92 | note = note_store.getNote(@configuration['token'], choice, true, true, false, false)
93 | rescue Evernote::EDAM::Error::EDAMSystemException,
94 | Evernote::EDAM::Error::EDAMUserException,
95 | Evernote::EDAM::Error::EDAMNotFoundException => e
96 | say "Sorry, an error occurred communicating with Evernote (#{e.message})"
97 | return
98 | end
99 |
100 | markdown = invoke_editor(note_markdown(note.content))
101 | note.content = note_markup(markdown)
102 | note.updated = Time.now.to_i * 1000
103 |
104 | begin
105 | note_store.updateNote(@configuration['token'], note)
106 | say "Successfully updated note '#{note.title}'"
107 | rescue Evernote::EDAM::Error::EDAMSystemException,
108 | Evernote::EDAM::Error::EDAMUserException,
109 | Evernote::EDAM::Error::EDAMNotFoundException => e
110 | say "Sorry, an error occurred saving the note to Evernote (#{e.message})"
111 | graceful_failure(markdown)
112 | end
113 |
114 | end
115 |
116 | def search_notes(term = '')
117 | begin
118 | evn_client = EvernoteOAuth::Client.new(token: @configuration['token'], sandbox: @sandbox)
119 | note_store = evn_client.note_store
120 | note_filter = Evernote::EDAM::NoteStore::NoteFilter.new
121 | note_filter.words = term
122 | results = note_store.findNotes(@configuration['token'], note_filter, 0, 10).notes
123 | rescue Evernote::EDAM::Error::EDAMSystemException,
124 | Evernote::EDAM::Error::EDAMUserException,
125 | Evernote::EDAM::Error::EDAMNotFoundException => e
126 | say "Sorry, an error occurred communicating with Evernote (#{e.inspect})"
127 | false
128 | end
129 |
130 | end
131 |
132 | def note_markup(markdown)
133 | "
This is bongos<\/em>, indeed.<\/p>/
277 | end
278 |
279 | it "inserts XHTML into the ENML" do
280 | enved.note_markup("This is *bongos*, indeed.").should =~
281 | / This is bongos<\/em>, indeed.<\/p>\s*<\/en-note>/
282 | end
283 |
284 | end
285 |
286 | describe "#note_markdown" do
287 |
288 | let(:enved) { EvernoteEditor::Editor.new('a note', {}) }
289 |
290 | it "converts ENML/XHTML to markdown" do
291 | str = "# Interesting!\n\n- Alpha\n- Bravo\n"
292 | markup = enved.note_markup(str)
293 | enved.note_markdown(markup).should eq str
294 | end
295 |
296 | it "converts nested indentation to markdown" do
297 | # pending "https://github.com/xijo/reverse_markdown/issues/29"
298 | str = "# Interesting!\n\n- Alpha\n - Zebra\n - Yankee\n- Bravo\n"
299 | markup = enved.note_markup(str)
300 | enved.note_markdown(markup).should eq str
301 | end
302 |
303 | end
304 |
305 | describe "#lookup_notebook_name" do
306 |
307 | before { write_fakefs_config }
308 | let(:enved) { EvernoteEditor::Editor.new('a note', {}) }
309 |
310 | it "looks up a notebook name by guid" do
311 | enved.configure
312 | EvernoteOAuth::Client.stub(:new).and_return(
313 | double("EvernoteOAuth::Client", note_store: @mock_note_store))
314 | enved.lookup_notebook_name('456').should eq 'Zebra Notebook'
315 | end
316 |
317 | it "returns 'unknown notebook' when there is a communication error" do
318 | enved.configure
319 | EvernoteOAuth::Client.stub(:new).and_return(
320 | double("EvernoteOAuth::Client", note_store: @mock_note_store))
321 | @mock_note_store.stub(:listNotebooks).and_raise(Evernote::EDAM::Error::EDAMSystemException)
322 | enved.lookup_notebook_name('456').should eq 'unknown notebook'
323 | end
324 |
325 | it "first looks in locally stored value for the notebook attributes" do
326 | enved.configure
327 | enved.instance_variable_set(:@notebooks, [
328 | double("notebook", guid: "456", name: "Yankee Notebook"),
329 | double("notebook", guid: "789", name: "Xray Notebook")])
330 | EvernoteOAuth::Client.should_not_receive(:new)
331 | enved.lookup_notebook_name('456').should eq 'Yankee Notebook'
332 |
333 | end
334 |
335 | end
336 |
337 | end
338 |
--------------------------------------------------------------------------------