synclenote 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []