├── .gitignore ├── .rspec ├── .rubocop_todo.yml ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console ├── ever2boost └── setup ├── docs ├── api_error.md ├── development.md ├── emergency.md └── images │ ├── img1.png │ ├── img2.png │ ├── img3.png │ ├── img4.png │ └── img5.png ├── ever2boost.gemspec ├── exe └── ever2boost ├── lib ├── ever2boost.rb └── ever2boost │ ├── cli.rb │ ├── cson_generator.rb │ ├── enex_converter.rb │ ├── evernote_authorizer.rb │ ├── json_generator.rb │ ├── md_converter.rb │ ├── note.rb │ ├── note_list.rb │ ├── util.rb │ └── version.rb └── spec ├── cson_generator_spec.rb ├── enex_converter_spec.rb ├── ever2boost_spec.rb ├── json_generator_spec.rb ├── lorem.enex ├── md_converter_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | /spec/dist 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2017-01-27 09:19:36 +0900 using RuboCop version 0.44.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 3 10 | # Cop supports --auto-correct. 11 | Lint/LiteralInInterpolation: 12 | Exclude: 13 | - 'lib/ever2boost/note.rb' 14 | 15 | # Offense count: 15 16 | Lint/ParenthesesAsGroupedExpression: 17 | Exclude: 18 | - 'spec/cson_generator_spec.rb' 19 | - 'spec/json_generator_spec.rb' 20 | - 'spec/md_converter_spec.rb' 21 | 22 | # Offense count: 1 23 | # Cop supports --auto-correct. 24 | # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. 25 | Lint/UnusedMethodArgument: 26 | Exclude: 27 | - 'lib/ever2boost/note_list.rb' 28 | 29 | # Offense count: 2 30 | Lint/UselessAssignment: 31 | Exclude: 32 | - 'lib/ever2boost/cson_generator.rb' 33 | - 'lib/ever2boost/evernote_authorizer.rb' 34 | 35 | # Offense count: 2 36 | Metrics/AbcSize: 37 | Max: 32 38 | 39 | # Offense count: 25 40 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives. 41 | # URISchemes: http, https 42 | Metrics/LineLength: 43 | Max: 193 44 | 45 | # Offense count: 5 46 | # Configuration parameters: CountComments. 47 | Metrics/MethodLength: 48 | Max: 19 49 | 50 | # Offense count: 10 51 | Style/Documentation: 52 | Exclude: 53 | - 'spec/**/*' 54 | - 'test/**/*' 55 | - 'lib/ever2boost.rb' 56 | - 'lib/ever2boost/cli.rb' 57 | - 'lib/ever2boost/cson_generator.rb' 58 | - 'lib/ever2boost/enex_converter.rb' 59 | - 'lib/ever2boost/evernote_authorizer.rb' 60 | - 'lib/ever2boost/json_generator.rb' 61 | - 'lib/ever2boost/md_converter.rb' 62 | - 'lib/ever2boost/note.rb' 63 | - 'lib/ever2boost/note_list.rb' 64 | - 'lib/ever2boost/util.rb' 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.3.0 5 | - 2.2.0 6 | - 2.1.0 7 | - 2.0.0 8 | before_install: gem install bundler 9 | install: 10 | - bundle install 11 | - gem instal rubocop 12 | script: rubocop --config .rubocop_todo.yml --fail-level=W 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ever2boost.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 asmsuechan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ever2boost 2 | [![Build Status](https://travis-ci.org/BoostIO/ever2boost.svg?branch=master)](https://travis-ci.org/BoostIO/ever2boost) 3 | 4 | ever2boost is a CLI tool for conversion Evernote to Boostnote. 5 | 6 |
7 | 8 |
9 | 10 | # Quick start 11 | Get Evernote developer token from [here](https://www.evernote.com/api/DeveloperToken.action). 12 | 13 | ``` 14 | $ gem install ever2boost 15 | $ ever2boost import 16 | DEVELOPER_TOKEN: 17 | ``` 18 | 19 | Limitation? Go [here](docs/api_error.md) 20 | 21 | ## Connect to your Boostnote 22 | You need to connect it to Boostnote. Go Menu -> Add Storage and add it. 23 | 24 | ![how_to_add_storage](docs/images/img2.png) 25 | 26 | And select your storage. It's at `~/evernote_storage` by default on `import`. 27 | 28 | ![how_to_chose_the_directory](docs/images/img3.png) 29 | 30 | **** 31 | 32 | ## ever2boost command 33 | Ever2boost has 2 commands for conversion `convert` and `import`. 34 | 35 | ``` 36 | $ ever2boost -h 37 | Commands: 38 | ever2boost convert # convert from .enex 39 | ever2boost help [COMMAND] # Describe available commands or one specific co... 40 | ever2boost import # import from evernote 41 | ``` 42 | 43 | ### Create notes storage from Evernote 44 | First, you need to create new Boostnote storage by ever2boost. And you can choose which command do you use, `import` or `convert`. 45 | 46 | #### import 47 | Import all of notes from cloud storage at Evernote. 48 | 49 | `import` command has 1 option `d` which specify output directory. 50 | 51 | ``` 52 | $ ever2boost help import 53 | Usage: 54 | ever2boost import 55 | 56 | Options: 57 | d, [--directory=DIRCTORY_PATH] # make Boostnote storage in the directory default: ~/evernote_storage 58 | 59 | import from evernote 60 | ``` 61 | 62 | 1. get your developer token from https://www.evernote.com/api/DeveloperToken.action 63 | ![how_to_get_your_developer_token](docs/images/img1.png) 64 | 65 | 2. run `ever2boost import` 66 | 67 | ``` 68 | $ ever2boost import 69 | DEVELOPER_TOKEN: 70 | ``` 71 | And `import` will start. It uses EvernoteAPI, thus you should take care access limitations. 72 | 73 | #### convert 74 | Convert notes from `.enex` file which is **exported file from Evernote**. 75 | 76 | You can get how to export from [official document](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks). 77 | 78 | `convert` has 1 option `d` which specify output directory. 79 | 80 | ``` 81 | $ ever2boost help convert 82 | Usage: 83 | ever2boost convert 84 | 85 | Options: 86 | d, [--directory=DIRCTORY_PATH] # make Boostnote storage in the directory default: ~/evernote_storage 87 | 88 | convert from .enex 89 | ``` 90 | 91 | ## Requirements 92 | Ruby: 2.0.0 or above 93 | bundler: Corresponding to Ruby 94 | 95 | if you don't have bundler: 96 | 97 | ``` 98 | $ gem install bundler 99 | ``` 100 | 101 | > if it fails by permission, you can run as sudo (perhaps you're using preinstalled Ruby in the OS) 102 | 103 | 104 | 105 | ## Something happens 106 | If your Boostnote would be broken: 107 | 108 | * Notes and folders disappear 109 | * Folder names are something wrong (e.g: Unknown 1) 110 | 111 | First of all, look over [this document](docs/emergency.md). After that, if you would not find how to solve your error, please report an issue. 112 | 113 | ## Contributing 114 | Bug reports and pull requests are welcome on GitHub at https://github.com/BoostIO/ever2boost. 115 | 116 | More information: [how_to_develop_ever2boost](docs/development.md) 117 | 118 | ## License 119 | 120 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 121 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'ever2boost' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/ever2boost: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'ever2boost' 4 | Ever2boost::CLI.start 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /docs/api_error.md: -------------------------------------------------------------------------------- 1 | # EvernoteAPI 2 | ever2boost uses EvernoteAPI, therefore there are some limitations. 3 | 4 | ## Limitations 5 | * [Warning] Up to **250** notes in each folder 6 | 7 | When the folder which ever2boost try to import has over 250 notes, ever2boost downloads first 250 notes from latest. For example, when a folder has 260 notes, the rest 10 old notes are ignored. Then the warning is appear: 8 | 9 | ``` 10 | Ignore first 10 notes due to EvernoteAPI access limitation in this notebook. 11 | ``` 12 | 13 | You need to try `convert` command if you just want to import the folder. 14 | 15 | * [Fatal] Up to **500** notes on import 16 | 17 | When it happens, ever2boost tells us an error below: 18 | ![error_on_import](images/img5.png) 19 | 20 | Actually I don't know the **exact** terms of number of notes, I just experienced in my environment. And **the limitation is reset every hour**. Further more, I've confirmed the error happens in case of capacity limit. 21 | 22 | **IMPORTANT**: The import is success (until the limitation) unless the error happens, thus you can connect the directory to Boostnote normaly. 23 | 24 | ## References 25 | * [Rate Limites](https://dev.evernote.com/doc/articles/rate_limits.php) 26 | * [Rate Limit Best Practices](https://dev.evernote.com/doc/articles/rate_limit_best_practices.php) 27 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # How to develop ever2boost 2 | To develop ever2boost, `Ruby` (above 2.0.0) and `bundler` is required. You can check them by the commands below: 3 | 4 | ``` 5 | $ ruby -v 6 | $ gem which bundler 7 | ``` 8 | 9 | And setup ever2boost: 10 | 11 | ``` 12 | $ git clone https://github.com/BoostIO/ever2boost.git 13 | $ cd ever2boost 14 | $ bundle install 15 | $ bundle exec exec/ever2boost import 16 | DEVELOPER_TOKEN: 17 | ``` 18 | 19 | # Testing 20 | RSpec is used for testing ever2boost. 21 | 22 | ``` 23 | $ rspec 24 | ``` 25 | 26 | And also Rubocop is used, thus you need to install Rubocop if you don't have: 27 | 28 | ``` 29 | $ gem install rubocop 30 | $ rubocop -c .rubocop_todo.yml 31 | ``` 32 | 33 | # Release 34 | First, you need to register your access key to rubygems. And build and release. 35 | 36 | ``` 37 | $ bundle exec rake build 38 | ever2boost x.x.x built to pkg/ever2boost-0.1.0.gem. 39 | $ bundle exec rake release 40 | ever2boost x.x.x built to pkg/ever2boost-0.1.0.gem. 41 | Tagged vx.x.x. 42 | Pushed git commits and tags. 43 | Pushed ever2boost x.x.x to rubygems.org. 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/emergency.md: -------------------------------------------------------------------------------- 1 | # Something went wrong? 2 | Unfortunately, ever2boost is not a perfect tool. Therefore it may contain some bugs. However I am convinced that it will not break your notes/folders. 3 | 4 | # Folders/notes disappear 5 | When your folders/notes disappear after import, you need to run `rm -rf ~/evernote_storage` and reload Boostnote. After that perhaps your Boostnote come back life. If your Boostnote is stil dead, please report an issue. 6 | -------------------------------------------------------------------------------- /docs/images/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoostIO/ever2boost/57d336d68042e55d630f3992dfe98b5a5163a6d2/docs/images/img1.png -------------------------------------------------------------------------------- /docs/images/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoostIO/ever2boost/57d336d68042e55d630f3992dfe98b5a5163a6d2/docs/images/img2.png -------------------------------------------------------------------------------- /docs/images/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoostIO/ever2boost/57d336d68042e55d630f3992dfe98b5a5163a6d2/docs/images/img3.png -------------------------------------------------------------------------------- /docs/images/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoostIO/ever2boost/57d336d68042e55d630f3992dfe98b5a5163a6d2/docs/images/img4.png -------------------------------------------------------------------------------- /docs/images/img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoostIO/ever2boost/57d336d68042e55d630f3992dfe98b5a5163a6d2/docs/images/img5.png -------------------------------------------------------------------------------- /ever2boost.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'ever2boost/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'ever2boost' 8 | spec.version = Ever2boost::VERSION 9 | spec.authors = ['asmsuechan'] 10 | spec.email = ['suenagaryoutaabc@gmail.com'] 11 | 12 | spec.summary = 'Convert Evernote to Boostnote' 13 | spec.description = 'ever2boost converts the all of your notes in Evernote into Boostnote' 14 | spec.homepage = 'https://github.com/BoostIO/ever2boost' 15 | spec.license = 'MIT' 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 18 | f.match(%r{^(test|spec|features)/}) 19 | end 20 | spec.bindir = 'exe' 21 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 22 | spec.require_paths = ['lib'] 23 | 24 | spec.add_dependency 'evernote-thrift' 25 | spec.add_dependency 'thor' 26 | 27 | spec.add_development_dependency 'pry-byebug' 28 | spec.add_development_dependency 'bundler', '~> 1.13' 29 | spec.add_development_dependency 'rake', '~> 10.0' 30 | spec.add_development_dependency 'rspec', '~> 3.0' 31 | end 32 | -------------------------------------------------------------------------------- /exe/ever2boost: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'ever2boost' 4 | Ever2boost::CLI.start 5 | -------------------------------------------------------------------------------- /lib/ever2boost.rb: -------------------------------------------------------------------------------- 1 | require 'ever2boost/version' 2 | require 'ever2boost/evernote_authorizer' 3 | require 'ever2boost/note' 4 | require 'ever2boost/md_converter' 5 | require 'ever2boost/cson_generator' 6 | require 'ever2boost/json_generator' 7 | require 'ever2boost/cli' 8 | -------------------------------------------------------------------------------- /lib/ever2boost/cli.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'ever2boost/evernote_authorizer' 3 | require 'ever2boost/enex_converter' 4 | require 'ever2boost/util' 5 | 6 | module Ever2boost 7 | class CLI < Thor 8 | DEFAULT_OUTPUT_DIR = "#{ENV['HOME']}/evernote_storage".freeze 9 | DEFAULT_OUTPUT_DIR_ENEX = "#{ENV['HOME']}/evernote_storage_enex".freeze 10 | 11 | desc 'import', 'import from evernote' 12 | option :directory, aliases: :d, banner: 'DIRCTORY_PATH', desc: 'make Boostnote storage in the directory default: ~/evernote_storage' 13 | def import 14 | output_dir = options[:directory] || DEFAULT_OUTPUT_DIR 15 | developer_token = ask('DEVELOPER_TOKEN:') 16 | EvernoteAuthorizer.new(developer_token).import(output_dir) 17 | end 18 | 19 | desc 'convert', 'convert from .enex' 20 | option :directory, aliases: :d, banner: 'DIRCTORY_PATH', desc: 'make Boostnote storage in the directory default: ~/evernote_storage' 21 | def convert(path) 22 | output_dir = options[:directory] || DEFAULT_OUTPUT_DIR_ENEX 23 | abort Util.red_output("Error! No such file or directory: #{path}") unless File.exist?(path) 24 | enex = File.read(path) 25 | filename = File.basename(path, '.enex') 26 | EnexConverter.convert(enex, output_dir, filename) 27 | end 28 | 29 | map %w[--version -v] => :__print_version 30 | desc "--version, -v", "print the version" 31 | def __print_version 32 | puts Ever2boost::VERSION 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/ever2boost/cson_generator.rb: -------------------------------------------------------------------------------- 1 | require 'ever2boost/util' 2 | 3 | module Ever2boost 4 | class CsonGenerator 5 | class << self 6 | def build(folder_hash, note) 7 | <<-EOS 8 | type: "MARKDOWN_NOTE" 9 | folder: "#{folder_hash}" 10 | title: "#{note.title}" 11 | content: ''' 12 | # #{note.title} 13 | #{note.md_content} 14 | ''' 15 | tags: [] 16 | isStarred: false 17 | createdAt: "#{timestamp}" 18 | updatedAt: "#{timestamp}" 19 | EOS 20 | end 21 | 22 | def timestamp 23 | Time.new.getutc.strftime('%Y-%m-%dT%H:%M:%S') 24 | end 25 | 26 | def output(folder_hash, note, output_dir) 27 | Util.make_notes_dir(output_dir) 28 | File.open("#{output_dir}/notes/#{note.file_name}.cson", 'w') do |f| 29 | f.write(build(folder_hash, note)) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/ever2boost/enex_converter.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | require 'ever2boost/util' 3 | 4 | module Ever2boost 5 | class EnexConverter 6 | class << self 7 | def convert(enex, output_dir, filename) 8 | puts 'converting...' 9 | en_notes = parse_plural_notes(enex, output_dir) 10 | notebook_list = [NoteList.new(title: filename)] 11 | JsonGenerator.output(notebook_list, output_dir) 12 | en_notes.each do |note| 13 | puts "converting #{note.title}" 14 | CsonGenerator.output(notebook_list.first.hash, note, output_dir) 15 | end 16 | puts Util.green_output("The notes are created at #{output_dir}") 17 | end 18 | 19 | # enex: String 20 | # "(.*)" 21 | # return: Array 22 | # include Note objects 23 | # note.content = "(.*)" 24 | # comment: 25 | # A .enex file include plural ntoes. Thus I need to handle separation into each note. 26 | def parse_plural_notes(enex, output_dir) 27 | REXML::Document.new(enex).elements['en-export'].map do |el| 28 | if el != "\n" 29 | xml_document = REXML::Document.new(el.to_s).elements 30 | Note.new({ 31 | title: xml_document['note/title'].text, 32 | content: "
#{xml_document['note/content/text()'].to_s.sub(/<\?xml(.*?)\?>(.*?)<\!DOCTYPE(.*?)>/m, '')}
", 33 | output_dir: output_dir 34 | }) 35 | end 36 | end.compact.flatten 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/ever2boost/evernote_authorizer.rb: -------------------------------------------------------------------------------- 1 | require 'evernote-thrift' 2 | require 'ever2boost/note' 3 | require 'ever2boost/note_list' 4 | require 'ever2boost/util' 5 | 6 | module Ever2boost 7 | class EvernoteAuthorizer 8 | EVERNOTE_HOST = 'www.evernote.com'.freeze 9 | 10 | attr_accessor :developer_token, :note_store 11 | 12 | def initialize(developer_token) 13 | user_store_url = "https://#{EVERNOTE_HOST}/edam/user" 14 | user_store_transport = Thrift::HTTPClientTransport.new(user_store_url) 15 | user_store_protocol = Thrift::BinaryProtocol.new(user_store_transport) 16 | user_store = Evernote::EDAM::UserStore::UserStore::Client.new(user_store_protocol) 17 | note_store_url = user_store.getNoteStoreUrl(developer_token) 18 | note_store_transport = Thrift::HTTPClientTransport.new(note_store_url) 19 | note_store_protocol = Thrift::BinaryProtocol.new(note_store_transport) 20 | note_store = Evernote::EDAM::NoteStore::NoteStore::Client.new(note_store_protocol) 21 | @developer_token = developer_token 22 | @note_store = note_store 23 | rescue => e 24 | abort_with_message e 25 | end 26 | 27 | def fetch_notebook_list 28 | note_store.listNotebooks(developer_token) 29 | end 30 | 31 | def notebook_guids 32 | fetch_notebook_list.map(&:guid) 33 | end 34 | 35 | def notebook_list 36 | fetch_notebook_list.map { |nl| Ever2boost::NoteList.new(title: nl.name, guid: nl.guid) } 37 | end 38 | 39 | def number_of_note(filter) 40 | note_counts_hash = note_store.findNoteCounts(developer_token, filter, true).notebookCounts 41 | note_counts_hash.nil? ? 0 : note_counts_hash.values.last 42 | end 43 | 44 | def fetch_notes(filter) 45 | spec = Evernote::EDAM::NoteStore::NotesMetadataResultSpec.new(includeTitle: true, includeNotebookGuid: true) 46 | number_of_note = self.number_of_note(filter) 47 | 48 | warn Util.yellow_output("Ignore first #{(number_of_note - 250)} notes due to EvernoteAPI access limitation in this notebook.") if number_of_note > 250 49 | start_index = number_of_note > 250 ? number_of_note - 250 : 0 50 | note_store.findNotesMetadata(developer_token, filter, start_index, number_of_note, spec) 51 | end 52 | 53 | # Download the all of notes from Evernote and generate Boostnote storage from it 54 | def import(output_dir) 55 | puts 'processing...' 56 | FileUtils.mkdir_p(output_dir) unless FileTest.exist?(output_dir) 57 | notebook_list = self.notebook_list 58 | 59 | Ever2boost::JsonGenerator.output(notebook_list, output_dir) 60 | 61 | notebook_guids.map do |notebook_guid| 62 | filter = Evernote::EDAM::NoteStore::NoteFilter.new(notebookGuid: notebook_guid) 63 | note_guids = fetch_notes(filter).notes.map(&:guid) 64 | puts "importing #{note_store.getNotebook(developer_token, notebook_guid).name}" 65 | # TODO: assign the booleans 66 | en_notes = note_guids.map { |note_guid| note_store.getNote(developer_token, note_guid, true, true, true, false) } 67 | en_notes.each do |en_note| 68 | download_image(en_note, output_dir) unless en_note.resources.nil? 69 | note = Note.new(title: en_note.title, content: en_note.content, notebook_guid: en_note.notebookGuid, output_dir: output_dir) 70 | # puts "importing #{find_notebook_by_guid_from_notebook_list(notebook_list, note).title}" 71 | notebook_list.each do |list| 72 | CsonGenerator.output(list.hash, note, output_dir) if list.guid == note.notebook_guid 73 | end 74 | end 75 | end 76 | puts Util.green_output('Successfully finished!') 77 | puts Util.green_output("Imported notes are located at #{output_dir}, mount it to Boostnote!") 78 | rescue => e 79 | abort_with_message e 80 | end 81 | 82 | def abort_with_message(exception) 83 | if exception.class == Evernote::EDAM::Error::EDAMUserException 84 | abort Util.red_output('Error! Confirm your developer token.') 85 | elsif exception.class == Evernote::EDAM::Error::EDAMSystemException 86 | abort Util.red_output("Error! You reached EvernoteAPI rate limitation.\nThe notes processed so far have been created successfully.\nMore information: https://github.com/BoostIO/ever2boost/tree/master/docs/api_error.md") 87 | else 88 | raise exception 89 | end 90 | end 91 | 92 | def find_notebook_by_guid_from_notebook_list(notebook_list, note) 93 | notebook_list.find { |nl| note.notebook_guid == nl.guid } 94 | end 95 | 96 | # TODO: handle with not image file 97 | def download_image(en_note, output_dir) 98 | en_note.resources.each do |resource| 99 | imagename = resource.data.bodyHash.unpack("H*").first 100 | extension = resource.mime.gsub(/(.+?)\//, '') 101 | Util.make_images_dir(output_dir) 102 | File.open("#{output_dir}/images/#{imagename}.#{extension}", 'w+b' ) do |f| 103 | f.write note_store.getResourceData(developer_token, resource.guid) 104 | end 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/ever2boost/json_generator.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'ever2boost/util' 3 | 4 | module Ever2boost 5 | class JsonGenerator 6 | class << self 7 | def build(notebook_list) 8 | folders = notebook_list.map do |list| 9 | { 10 | key: list.hash, 11 | name: list.title, 12 | color: list.color 13 | } 14 | end 15 | { folders: folders, version: '1.0' }.to_json 16 | end 17 | 18 | def output(notebook_list, output_dir) 19 | Util.make_notes_dir(output_dir) 20 | File.open("#{output_dir}/boostnote.json", 'w') do |f| 21 | f.write(build(notebook_list)) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/ever2boost/md_converter.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | 3 | module Ever2boost 4 | class MdConverter 5 | class << self 6 | # params: String 7 | # "(/.*/)" (import) 8 | # or 9 | # "(/.*/)" (convert) 10 | # return: 11 | # markdown content 12 | def convert(note_content) 13 | 14 | en_note = nil 15 | REXML::Document.new(note_content).elements.each('*') { |el| en_note = el.to_s || en_note + el.to_s } 16 | if en_note.nil? 17 | note_content 18 | else 19 | # build table 20 | en_note.sub(/(.*?)<\/tr>/m, '') 21 | number_of_row = $2.nil? ? 0 : $2.scan(/<\/td>/).size 22 | 23 | en_note.gsub(/(.*?)<\/div><\/div>/, '\n```\n\4\n```') 24 | code_block = $4 25 | 26 | en_note.gsub(/(.*?)<\/en-note>/m, '\2') 27 | .gsub(//, '') 28 | .gsub(/<\/en-note>/, '') 29 | .gsub(/\\n(\ *)/, '\n') 30 | .gsub(/(\ *?)/m, '') 31 | .gsub(/^\s*/, '') 32 | .gsub(/(.*?)<\/div><\/div>/, "\n```\n#{code_block}\n```") 33 | .gsub(/(.*?)<\/div>/m, '\2') 34 | .gsub(//, '') 35 | .gsub(/<\/div>/, '') 36 | .gsub(/#+/, '\0 ') 37 | .gsub(/(.*?)<\/span>/m, '\2') 38 | .gsub(//, '\2') 39 | .gsub(/<\/span>/, '') 40 | .gsub(/(.*?)<\/p>/m, '\2') 41 | .gsub(/(.*?)/m, '\2') 42 | .gsub(//m, '') 43 | .gsub(/(.*?)<\/ul>/m, '\2') 44 | .gsub(//, '\n') 45 | .gsub(/(.*?)<\/li>/, '* \2') 46 | .gsub(/(.*?)<\/a>/m, '[\3](\1)') 47 | .gsub(//, '*\ [\ ]\ ') 48 | .gsub(/\\n<\/strong>/, '') 49 | .gsub(/(.*?)<\/strong>/, '**\2**') 50 | .gsub(/(.*?)<\/center>/, '') 51 | .gsub(/\\n<\/s>/, '') 52 | .gsub(/([^\n].+?)<\/s>/, '~~\1~~') 53 | .gsub(/\\n<\/i>/, '') 54 | .gsub(/([^\n].+?)<\/i>/, '*\1*') 55 | .gsub(/(.*?)<\/i>/m, '*\2*') 56 | .gsub(/\\n<\/em>/, '') 57 | .gsub(/([^\n].+?)<\/em>/, '_\1_') 58 | .gsub(/(.*?)<\/em>/, '_\2_') 59 | .gsub(/([^\n].+?)<\/b>/, '**\1**') 60 | .gsub(/\\n<\/b>/, '') 61 | .gsub(/(.*?)<\/font>/, '\2') 62 | .gsub(/(.*?)<\/tr>(\n*?)/m, '|\2') 63 | .gsub(/(.*?)\n\\n<\/td>/m, '\2|') 64 | .gsub(/(.*?)<\/td>/m, '\2|') 65 | .gsub(/<\/tbody>/, '') 66 | .gsub(/<\/table>/, '') 67 | .gsub(//, "#{'|' * (number_of_row + 1)}\n#{('|-' * (number_of_row + 1)).chop}") 68 | .gsub(//, '') 69 | .gsub(/(.*?)<\/p>/, '\2') 70 | .gsub(/(.*?)<\/h1>/, '#\ \2') 71 | .gsub(/(.*?)<\/h2>/, '##\ \2') 72 | .gsub(/(.*?)<\/h3>/, '###\ \2') 73 | .gsub(/(.*?)<\/h4>/, '####\ \2') 74 | .gsub(/(.*?)<\/h5>/, '#####\ \2') 75 | .gsub(/(.*?)(.*?)<\/code><\/pre>/, '```\n\4\n```') 76 | .gsub(/(.*?)<\/code>/, '\n```\n\2\n```\n') 77 | .gsub(/<\/pre>/, '') 78 | .gsub(//, '****') 79 | .gsub(/ /m, ' ') 80 | .gsub(/>/, '>') 81 | .gsub(/</, '<') 82 | .gsub(/&/, '&') 83 | .gsub(/"/, '"') 84 | .gsub(/\\n\\n/m, '') 85 | end 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/ever2boost/note.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | module Ever2boost 4 | class Note 5 | DEFAULT_BYTES_NUMBER = 10 6 | attr_accessor :title, :content, :hash, :notebook_guid, :file_name, :output_dir 7 | def initialize(title: nil, content: nil, notebook_guid: nil, output_dir: nil) 8 | @title = title 9 | @content = MdConverter.convert(content) 10 | @notebook_guid = notebook_guid 11 | @file_name = SecureRandom.hex(DEFAULT_BYTES_NUMBER) 12 | @output_dir = output_dir 13 | end 14 | 15 | def md_content 16 | build_image_link(self.content) 17 | end 18 | 19 | def build_image_link(content_str) 20 | content_str.gsub(//, "![#{'\1'}](#{self.output_dir}/images/#{'\1'}.#{'\4'})") 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/ever2boost/note_list.rb: -------------------------------------------------------------------------------- 1 | require 'securerandom' 2 | 3 | module Ever2boost 4 | class NoteList 5 | DEFAULT_BYTES_NUMBER = 6 6 | 7 | FOLDER_COLORS = [ 8 | '#E10051', 9 | '#FF8E00', 10 | '#E8D252', 11 | '#3FD941', 12 | '#30D5C8', 13 | '#2BA5F7', 14 | '#B013A4' 15 | ].freeze 16 | 17 | attr_accessor :title, :hash, :guid, :color 18 | 19 | def initialize(title: nil, hash: nil, guid: nil, color: nil) 20 | @title = title 21 | @hash = SecureRandom.hex(DEFAULT_BYTES_NUMBER) 22 | @guid = guid 23 | @color = FOLDER_COLORS.sample 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/ever2boost/util.rb: -------------------------------------------------------------------------------- 1 | module Ever2boost 2 | class Util 3 | class << self 4 | def make_output_dir(output_dir, dir_name) 5 | FileUtils.mkdir_p("#{output_dir}/#{dir_name}") unless FileTest.exist?("#{output_dir}/#{dir_name}") 6 | end 7 | 8 | def make_notes_dir(output_dir) 9 | make_output_dir(output_dir, 'notes') 10 | end 11 | 12 | def make_images_dir(output_dir) 13 | make_output_dir(output_dir, 'images') 14 | end 15 | 16 | def red_output(str) 17 | "\e[31m#{str}\e[0m" 18 | end 19 | 20 | def green_output(str) 21 | "\e[32m#{str}\e[0m" 22 | end 23 | 24 | def yellow_output(str) 25 | "\e[33m#{str}\e[0m" 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/ever2boost/version.rb: -------------------------------------------------------------------------------- 1 | module Ever2boost 2 | VERSION = '0.1.5'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/cson_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'ever2boost/note' 3 | require 'ever2boost/cson_generator' 4 | require 'fileutils' 5 | 6 | describe Ever2boost::CsonGenerator do 7 | let (:folder_hash) { '0123abcdef' } 8 | let (:title) { 'lorem' } 9 | let (:content) { 'lorem ipsum' } 10 | let (:note) { Ever2boost::Note.new(title: title, content: content) } 11 | let (:md_note_content) { Ever2boost::MdConverter.convert(note.content) } 12 | let (:timestamp) { Ever2boost::CsonGenerator.timestamp } 13 | let (:output_dir) { 'spec/dist/evernote_storage' } 14 | let (:filename) { `ls spec/dist/evernote_storage/notes`.lines.first.chomp } 15 | 16 | let (:cson) do 17 | <<-EOS 18 | type: "MARKDOWN_NOTE" 19 | folder: "#{folder_hash}" 20 | title: "#{note.title}" 21 | content: ''' 22 | # #{note.title} 23 | #{md_note_content} 24 | ''' 25 | tags: [] 26 | isStarred: false 27 | createdAt: "#{timestamp}" 28 | updatedAt: "#{timestamp}" 29 | EOS 30 | end 31 | 32 | describe '#build' do 33 | it 'should return a cson' do 34 | expect(Ever2boost::CsonGenerator.build(folder_hash, note)).to eq(cson) 35 | end 36 | end 37 | 38 | describe '#output' do 39 | around(:each) do |example| 40 | Ever2boost::CsonGenerator.output(folder_hash, note, output_dir) 41 | example.run 42 | FileUtils.rm_r(output_dir) 43 | end 44 | 45 | it 'should create notes' do 46 | expect(File.exist?("#{output_dir}/notes#{filename}")) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/enex_converter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fileutils' 3 | 4 | describe Ever2boost::EnexConverter do 5 | let(:enex) { File.read('spec/lorem.enex') } 6 | let(:output_dir) { 'spec/dist/evernote_storage' } 7 | let(:filename) { 'lorem' } 8 | let(:cson_folder_hash) do 9 | # I use `ls` because the filename of cson is random. 10 | cson_filename = `ls spec/dist/evernote_storage/notes`.lines.first.chomp 11 | File.read("#{output_dir}/notes/#{cson_filename}").lines[1].chomp.slice(/[a-f0-9]{12}/) 12 | end 13 | let(:json_folder_hash) do 14 | json = File.read("#{output_dir}/boostnote.json") 15 | JSON.parse(json)['folders'].first['key'] 16 | end 17 | 18 | describe '#convert' do 19 | around(:each) do |example| 20 | Ever2boost::EnexConverter.convert(enex, output_dir, filename) 21 | example.run 22 | FileUtils.rm_r(output_dir) 23 | end 24 | 25 | it 'should generate boostnote.json' do 26 | expect(File.exist?("#{output_dir}/boostnote.json")).to be_truthy 27 | end 28 | 29 | it 'should generate notes/*.cson' do 30 | expect(File.exist?("#{output_dir}/notes")).to be_truthy 31 | end 32 | 33 | it 'generate a note which has correct folder hash' do 34 | expect(cson_folder_hash).to eq(json_folder_hash) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/ever2boost_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Ever2boost do 4 | it 'has a version number' do 5 | expect(Ever2boost::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/json_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'fileutils' 3 | 4 | describe Ever2boost::JsonGenerator do 5 | let (:notelist1) { Ever2boost::NoteList.new(title: 'title1', guid: '012345abcdef') } 6 | let (:notelist2) { Ever2boost::NoteList.new(title: 'title2', guid: '0123456abcde') } 7 | let (:notebook_list) { [notelist1, notelist2] } 8 | let (:json) { '{"folders":[{"key":"012345abcdef","name":"title1","color":"#E10051"},{"key":"0123456abcde","name":"title2","color":"#E10051"}],"version":"1.0"}' } 9 | let (:output_dir) { 'spec/dist/evernote_storage' } 10 | 11 | describe '#build' do 12 | before do 13 | notelist1.hash = '012345abcdef' 14 | notelist2.hash = '0123456abcde' 15 | notelist1.color = '#E10051' 16 | notelist2.color = '#E10051' 17 | end 18 | 19 | it 'should return a json' do 20 | expect(Ever2boost::JsonGenerator.build(notebook_list)).to eq(json) 21 | end 22 | end 23 | 24 | describe '#output' do 25 | around(:each) do |example| 26 | Ever2boost::JsonGenerator.output(notebook_list, output_dir) 27 | example.run 28 | FileUtils.rm_r(output_dir) 29 | end 30 | 31 | it 'should create boostnote.json' do 32 | expect(File.exist?("#{output_dir}/boostnote.json")).to be_truthy 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/lorem.enex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Lorem ipsum 5 | 6 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse blandit dolor vel tellus dignissim porta non id erat. Proin orci ipsum, pellentesque id mi vitae, aliquam imperdiet metus. Vestibulum tempus a arcu sit amet consequat. Nulla a ornare mauris, non semper tortor. Sed mollis neque eu arcu scelerisque lacinia. Sed blandit massa est, vel condimentum magna vulputate quis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In blandit metus vitae magna fermentum laoreet. Phasellus vel nisl elementum, egestas purus eu, elementum nibh. Integer in sem a lacus scelerisque ultrices ut sed lorem. Cras volutpat metus in quam auctor, ac feugiat est consequat.

7 | ]]>
20170126T155759Z20170126T155808Zasmsuechandesktop.mac0
8 |
9 | -------------------------------------------------------------------------------- /spec/md_converter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Ever2boost::MdConverter do 4 | let (:note_content) { 'lorem ipsum' } 5 | 6 | describe '#convert' do 7 | it 'should return md style string' do 8 | expect(Ever2boost::MdConverter.convert(note_content)).to eq('lorem ipsum') 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'ever2boost' 3 | --------------------------------------------------------------------------------