synclenote 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f6318984ebbcbba1c14f54fac700a2ac544cb1f2f1ab4fca0acb3246e26fa1a6
4
+ data.tar.gz: 74008eba2e3644861cc2bea68aa99ec40ddeed7c8747fec898bcf3d878757db6
5
+ SHA512:
6
+ metadata.gz: 138083fdf90abc8648ffc29e2c999a99978c2bc116992a7fa6f360e586d8e9ebc32ac975190d2d67591de0d49a5b85b8c971bef72777a7fc2e12f833fdc4446a
7
+ data.tar.gz: 6c2af8a37616982b59f8f5c8951a94c679ab6fb5c947aa61e7385e2958c6e0736bd4a799954acdde787282a6a5c2f695438394eabc1c9b09a1969dac9c456683
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+ *.bundle
18
+ *.so
19
+ *.o
20
+ *.a
21
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - ruby-head
4
+ - 2.1
5
+ - 2.0.0
6
+ before_install:
7
+ - sudo apt-get update -qq
8
+ - sudo apt-get install -qq libicu-dev
9
+ - gem update --remote bundler
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in synclenote.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ synclenote (0.0.0)
5
+ evernote_oauth
6
+ redcarpet
7
+ thor
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ evernote-thrift (1.25.2)
13
+ evernote_oauth (0.2.3)
14
+ evernote-thrift
15
+ oauth (>= 0.4.1)
16
+ oauth (0.5.6)
17
+ rake (13.0.6)
18
+ redcarpet (3.5.1)
19
+ thor (1.1.0)
20
+
21
+ PLATFORMS
22
+ x86_64-linux
23
+
24
+ DEPENDENCIES
25
+ bundler
26
+ rake
27
+ synclenote!
28
+
29
+ BUNDLED WITH
30
+ 2.2.23
data/Guardfile ADDED
@@ -0,0 +1,4 @@
1
+ guard :bundler do
2
+ watch("Gemfile")
3
+ watch(/\A.+\.gemspec\z/)
4
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Yuya.Nishida.
2
+
3
+ X11 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,40 @@
1
+ # Synclenote
2
+
3
+ A synchronization tool for GFM (GitHub Flavored Markdown) files and Evernote.
4
+
5
+ [![License X11](https://img.shields.io/badge/license-X11-brightgreen.svg)](https://raw.githubusercontent.com/nishidayuya/synclenote/master/LICENSE.txt)
6
+ [![Build Status](https://travis-ci.org/nishidayuya/synclenote.svg?branch=master)](https://travis-ci.org/nishidayuya/synclenote)
7
+
8
+ ## Requirements
9
+
10
+ - Ruby
11
+
12
+ ## Installation
13
+
14
+ ```console
15
+ $ gem install synclenote
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Create configuration directory:
21
+
22
+ ```console
23
+ $ synclenote init
24
+ ```
25
+
26
+ Open "~/.synclenote/config" by your favorite text editor and edit "TODO:" section.
27
+
28
+ Sync your GFM files and Evernote:
29
+
30
+ ```console
31
+ $ synclenote sync
32
+ ```
33
+
34
+ ## Contributing
35
+
36
+ 1. Fork it ( https://github.com/nishidayuya/synclenote/fork )
37
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
38
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
39
+ 4. Push to the branch (`git push origin my-new-feature`)
40
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task default: %i(build)
data/bin/synclenote ADDED
@@ -0,0 +1,5 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "synclenote/command"
4
+
5
+ Synclenote::Command.start
@@ -0,0 +1,276 @@
1
+ require "synclenote"
2
+
3
+ require "yaml"
4
+ require "pathname"
5
+ require "logger"
6
+ require "tempfile"
7
+
8
+ require "thor"
9
+ require "evernote_oauth"
10
+ require "redcarpet"
11
+
12
+ class Synclenote::Command < Thor
13
+ attr_accessor :logger
14
+
15
+ def initialize(*args, &block)
16
+ super
17
+ self.logger = Logger.new(STDOUT)
18
+ end
19
+
20
+ desc "init", "Create profile directory"
21
+ def init
22
+ if profile_path.exist?
23
+ $stderr.puts(<<EOS)
24
+ Profile directory "#{profile_path}" is already exist.
25
+
26
+ Please check it and rename/remove it.
27
+ EOS
28
+ exit(1)
29
+ end
30
+ profile_path.mkpath
31
+ config_path.open("w") do |f|
32
+ f.write(<<EOS)
33
+ # -*- mode: ruby -*-
34
+ # vi: set ft=ruby :
35
+
36
+ Synclenote.configure(1) do |config|
37
+ # TODO: Replace your notes directory.
38
+ config.local.directory = "~/notes"
39
+ config.local.pattern = "**/*.{md,txt}"
40
+ # You can use either whitelist_tags or blacklist_tags.
41
+ # config.local.whitelist_tags = %w(smartphone tablet)
42
+ # config.local.blacklist_tags = %w(noremote supersecret)
43
+
44
+ config.remote.type = :evernote
45
+ # You must fill your developer token.
46
+ # See https://www.evernote.com/api/DeveloperToken.action
47
+ config.remote.developer_token = "TODO: Write your developer token."
48
+ end
49
+ EOS
50
+ end
51
+ sync_statuses_path.mkpath
52
+ last_sync_path.open("w") do |f|
53
+ f.puts(YAML_HEADER)
54
+ f.puts({last_sync_datetime: Time.at(0)}.to_yaml)
55
+ end
56
+ puts(<<EOS)
57
+ Creating new profile directory is succeeded.
58
+
59
+ Please check TODO in configuration file.
60
+ $ vi #{config_path}
61
+ EOS
62
+ end
63
+
64
+ desc "sync", "Sync all notes at once"
65
+ def sync
66
+ logger.debug("loading configuration file: %s" % config_path)
67
+ load(config_path)
68
+ c = Synclenote::Configuration.data
69
+ token = c.remote.developer_token
70
+ logger.debug("loaded configuration file: %s" % config_path)
71
+
72
+ logger.debug("invoking client.")
73
+ client = EvernoteOAuth::Client.new(token: token,
74
+ sandbox: !!c.remote.sandbox)
75
+ logger.debug("invoked client.")
76
+
77
+ logger.debug("invoking user_store.")
78
+ user_store = client.user_store
79
+ logger.debug("invoked user_store.")
80
+ if !user_store.version_valid?
81
+ raise "Invalid Evernote API version. Please update evernote_oauth.gem and synclenote.gem."
82
+ end
83
+ logger.debug("done EvernoteOAuth version check.")
84
+ logger.debug("loading note_store.")
85
+ note_store = client.note_store
86
+ logger.debug("loaded note_store.")
87
+
88
+ local_top_path = Pathname(c.local.directory).expand_path
89
+ last_sync_status = YAML.load_file(last_sync_path)
90
+ last_sync_datetime = last_sync_status[:last_sync_datetime]
91
+ if Time.now - last_sync_datetime <= min_sync_interval
92
+ allowed_sync_time = last_sync_datetime + min_sync_interval
93
+ raise "too less interval sync after #{allowed_sync_time}."
94
+ end
95
+
96
+ processed = []
97
+ Pathname.glob(local_top_path + c.local.pattern) do |note_path|
98
+ relative_note_path = note_path.relative_path_from(local_top_path)
99
+ note_sync_status_path = sync_statuses_path + relative_note_path
100
+ processed << note_sync_status_path
101
+
102
+ # create note
103
+ if !note_sync_status_path.exist?
104
+ create_remote_note(token, note_store, note_path, note_sync_status_path)
105
+ next
106
+ end
107
+
108
+ # update note
109
+ note_sync_status = YAML.load_file(note_sync_status_path)
110
+ if note_sync_status[:last_sync_datetime] < note_path.mtime
111
+ update_remote_note(token, note_store, note_path, note_sync_status_path,
112
+ note_sync_status[:guid])
113
+ next
114
+ end
115
+
116
+ logger.debug("nothing: %s" % note_path)
117
+ rescue Evernote::EDAM::Error::EDAMUserException => e
118
+ logger.error(e.inspect)
119
+ raise
120
+ end
121
+
122
+ # remove note
123
+ removed = Pathname.glob(sync_statuses_path + c.local.pattern) - processed
124
+ removed.each do |note_sync_status_path|
125
+ remove_remote_note(token, note_store, note_sync_status_path)
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ YAML_HEADER = <<EOS.freeze
132
+ # -*- mode: yaml -*-
133
+ # vi: set ft=yaml :
134
+
135
+ EOS
136
+
137
+ HEADER = <<EOS.freeze
138
+ <?xml version="1.0" encoding="UTF-8"?>
139
+ <!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
140
+ <en-note>
141
+ EOS
142
+
143
+ FOOTER = <<EOS.freeze
144
+ </en-note>
145
+ EOS
146
+
147
+ def profile_path
148
+ return @profile_path ||= Pathname("~/.synclenote").expand_path
149
+ end
150
+
151
+ def config_path
152
+ return @config_path ||= profile_path + "config"
153
+ end
154
+
155
+ def sync_statuses_path
156
+ return @sync_statuses_path ||= profile_path + "sync_statuses"
157
+ end
158
+
159
+ def last_sync_path
160
+ return @last_sync_path ||= profile_path + "last_sync"
161
+ end
162
+
163
+ def min_sync_interval
164
+ # return 15 * 60 # 15 min for production
165
+ return 4 # 4 sec for sandbox
166
+ end
167
+
168
+ def build_html(markdown_text)
169
+ @formatter ||= Redcarpet::Markdown.new(
170
+ Redcarpet::Render::HTML.new(
171
+ filter_html: true,
172
+ hard_wrap: true,
173
+ ),
174
+ underline: true,
175
+ lax_spacing: true,
176
+ footnotes: true,
177
+ no_intra_emphasis: true,
178
+ superscript: true,
179
+ strikethrough: true,
180
+ tables: true,
181
+ space_after_headers: true,
182
+ fenced_code_blocks: true,
183
+ # autolink: true,
184
+ )
185
+ html = @formatter.render(markdown_text)
186
+ return html
187
+ end
188
+
189
+ def create_note(note_path, options = {})
190
+ title = nil
191
+ tags = ["syncle"]
192
+ body = nil
193
+ note_path.open do |f|
194
+ title = f.gets.chomp.sub(/\A#\s*/, "")
195
+ if md = /\A(?<tags>(?:\[.*?\])+)(?:\s|\z)/.match(title)
196
+ tags += md[:tags].scan(/\[(.*?)\]/).flatten
197
+ title = md.post_match
198
+ end
199
+ body = f.read
200
+ end
201
+ html = build_html(body)
202
+ content = HEADER +
203
+ html.gsub(/ class=\".*?\"/, "").gsub(/<(br|hr|img).*?>/, "\\&</\\1>") +
204
+ FOOTER
205
+ o = options.merge(title: title, content: content, tagNames: tags)
206
+ return Evernote::EDAM::Type::Note.new(o)
207
+ end
208
+
209
+ def target_note?(note)
210
+ c = Synclenote::Configuration.data
211
+ if c.local.whitelist_tags
212
+ return note.tagNames.any? { |name|
213
+ c.local.whitelist_tags.include?(name)
214
+ }
215
+ elsif c.local.blacklist_tags
216
+ return !note.tagNames.any? { |name|
217
+ c.local.blacklist_tags.include?(name)
218
+ }
219
+ end
220
+ return true
221
+ end
222
+
223
+ def create_note_sync_status_file(path, note, sync_datetime)
224
+ Tempfile.open("synclenote") do |tmp_file|
225
+ tmp_file.puts(YAML_HEADER)
226
+ tmp_file.puts({
227
+ last_sync_datetime: sync_datetime,
228
+ guid: note.guid,
229
+ }.to_yaml)
230
+ tmp_file.close
231
+ path.parent.mkpath
232
+ FileUtils.mv(tmp_file.path, path)
233
+ end
234
+ end
235
+
236
+ def create_remote_note(token, note_store, note_path, note_sync_status_path)
237
+ c = Synclenote::Configuration.data
238
+ new_note = create_note(note_path)
239
+ if !target_note?(new_note)
240
+ logger.info("skip creating: %s" % note_path)
241
+ return
242
+ end
243
+ logger.debug("doing createNote: %s" % note_path)
244
+ created_note = note_store.createNote(token, new_note)
245
+ logger.debug("done createNote.")
246
+ create_note_sync_status_file(note_sync_status_path, created_note,
247
+ Time.now)
248
+ logger.debug("created: %s" % note_sync_status_path)
249
+ end
250
+
251
+ def update_remote_note(token, note_store, note_path, note_sync_status_path,
252
+ guid)
253
+ new_note = create_note(note_path, guid: guid)
254
+ if !target_note?(new_note)
255
+ logger.info("skip updating: %s" % note_path)
256
+ remove_remote_note(token, note_store, note_sync_status_path)
257
+ return
258
+ end
259
+ logger.debug("doing updateNote: %s %s" % [note_path, guid])
260
+ updated_note = note_store.updateNote(token, new_note)
261
+ logger.debug("done updateNote.")
262
+ create_note_sync_status_file(note_sync_status_path, updated_note, Time.now)
263
+ logger.debug("created: %s" % note_sync_status_path)
264
+ end
265
+
266
+ def remove_remote_note(token, note_store, note_sync_status_path)
267
+ note_sync_status = YAML.load_file(note_sync_status_path)
268
+ guid = note_sync_status[:guid]
269
+ logger.debug("doing deleteNote: %s" % guid)
270
+ note_store.deleteNote(token, guid)
271
+ logger.debug("done deleteNote.")
272
+
273
+ note_sync_status_path.delete
274
+ logger.debug("removed: %s" % note_sync_status_path)
275
+ end
276
+ end
@@ -0,0 +1,21 @@
1
+ require "synclenote"
2
+
3
+ require "ostruct"
4
+
5
+ module Synclenote::Configuration
6
+ # TODO: better code and multiple configuration support.
7
+ @data = OpenStruct.new(local: OpenStruct.new(directory: "~/.synclenote",
8
+ pattern: "**/*.{md,txt}",
9
+ whitelist_tags: nil,
10
+ blacklist_tags: nil),
11
+ remote: OpenStruct.new(type: nil))
12
+
13
+ def self.run(version, &_block)
14
+ fail "unknown API version: version=<#{version.inspect}>" if version != 1
15
+ yield(@data)
16
+ end
17
+
18
+ def self.data
19
+ return @data
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Synclenote
2
+ VERSION = "0.0.0"
3
+ end
data/lib/synclenote.rb ADDED
@@ -0,0 +1,11 @@
1
+ # This module is a namespace for this gem.
2
+ module Synclenote
3
+ autoload :VERSION, "synclenote/varsion"
4
+
5
+ autoload :Command, "synclenote/command"
6
+ autoload :Configuration, "synclenote/configuration"
7
+
8
+ def self.configure(version, &block)
9
+ Configuration.run(version, &block)
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'synclenote/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "synclenote"
8
+ spec.version = Synclenote::VERSION
9
+ spec.authors = ["Yuya.Nishida."]
10
+ spec.email = ["yuya@j96.org"]
11
+ spec.summary = File.readlines(File.join(__dir__, "README.md"))
12
+ .reject { |l| /\A\s*\z|\A\#/ === l }.first.chomp
13
+ spec.description = spec.summary
14
+ spec.homepage = "https://github.com/nishidayuya/" + spec.name
15
+ spec.license = "X11"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency "thor"
23
+ spec.add_runtime_dependency "evernote_oauth"
24
+ spec.add_runtime_dependency "redcarpet"
25
+ spec.add_development_dependency "bundler"
26
+ spec.add_development_dependency "rake"
27
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: synclenote
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Yuya.Nishida.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
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'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: redcarpet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A synchronization tool for GFM (GitHub Flavored Markdown) files and Evernote.
84
+ email:
85
+ - yuya@j96.org
86
+ executables:
87
+ - synclenote
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - Guardfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - bin/synclenote
100
+ - lib/synclenote.rb
101
+ - lib/synclenote/command.rb
102
+ - lib/synclenote/configuration.rb
103
+ - lib/synclenote/version.rb
104
+ - synclenote.gemspec
105
+ homepage: https://github.com/nishidayuya/synclenote
106
+ licenses:
107
+ - X11
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubygems_version: 3.2.22
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: A synchronization tool for GFM (GitHub Flavored Markdown) files and Evernote.
128
+ test_files: []