iated 0.0.1
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.
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/Guardfile +24 -0
- data/LICENSE +23 -0
- data/Makefile +72 -0
- data/README.md +87 -0
- data/Rakefile +10 -0
- data/bin/iated +13 -0
- data/config.ru +5 -0
- data/extensions/chrome/background.html +5 -0
- data/extensions/chrome/background.js +105 -0
- data/extensions/chrome/contentscript.css +0 -0
- data/extensions/chrome/contentscript.js +110 -0
- data/extensions/chrome/jquery-ui.js +45 -0
- data/extensions/chrome/jquery.js +16 -0
- data/extensions/chrome/jquery.updater.js +46 -0
- data/extensions/chrome/manifest.json +19 -0
- data/extensions/chrome/yaml.js +489 -0
- data/extensions/tests/simple.html +23 -0
- data/features/extension_authenticates.feature +30 -0
- data/features/extension_edits.feature +45 -0
- data/features/step_definitions/extension_steps.rb +134 -0
- data/features/support/env.rb +47 -0
- data/features/support/hooks.rb +11 -0
- data/iated.gemspec +48 -0
- data/lib/iated.rb +111 -0
- data/lib/iated/browser_token_db.rb +76 -0
- data/lib/iated/edit_session.rb +221 -0
- data/lib/iated/helpers.rb +9 -0
- data/lib/iated/mcp.rb +144 -0
- data/lib/iated/page_helpers.rb +33 -0
- data/lib/iated/public/jquery-ui.js +101 -0
- data/lib/iated/public/jquery.js +2 -0
- data/lib/iated/public/robots.txt +5 -0
- data/lib/iated/server.rb +162 -0
- data/lib/iated/sys_pref.rb +201 -0
- data/lib/iated/version.rb +3 -0
- data/lib/iated/views/hello.haml +13 -0
- data/lib/iated/views/preferences.haml +27 -0
- data/lib/iated/views/reference.coffee +79 -0
- data/lib/iated/views/reference.haml +94 -0
- data/lib/iated/views/reference.scss +36 -0
- data/lib/iated/views/root.haml +13 -0
- data/spec/lib/iated/browser_token_db_spec.rb +68 -0
- data/spec/lib/iated/edit_session_spec.rb +157 -0
- data/spec/lib/iated/mcp_spec.rb +86 -0
- data/spec/lib/iated/sys_pref_spec.rb +40 -0
- data/spec/protocol/edit_spec.rb +88 -0
- data/spec/protocol/hello_spec.rb +18 -0
- data/spec/protocol/notfound_spec.rb +11 -0
- data/spec/protocol/ping_spec.rb +10 -0
- data/spec/protocol/preferences_spec.rb +35 -0
- data/spec/spec_helper.rb +21 -0
- metadata +460 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pathname'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
module Iated
|
6
|
+
## The database for storing authorized browser tokens
|
7
|
+
class BrowserTokenDB
|
8
|
+
|
9
|
+
def initialize fname
|
10
|
+
@fname = Pathname.new fname
|
11
|
+
@tokens = {}
|
12
|
+
|
13
|
+
# Write an empty file.
|
14
|
+
if @fname.exist?
|
15
|
+
read
|
16
|
+
else
|
17
|
+
write
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
## Add a token to the database.
|
22
|
+
# This stores it persistently.
|
23
|
+
# @return [String] the new token
|
24
|
+
def add user_agent
|
25
|
+
token = generate_token
|
26
|
+
@tokens[token] = user_agent.to_s
|
27
|
+
write
|
28
|
+
return token
|
29
|
+
end
|
30
|
+
|
31
|
+
## Does the token exist in the db?
|
32
|
+
# @return [Boolean] Does this token exist in the store?
|
33
|
+
def has_token? token
|
34
|
+
@tokens.key? token
|
35
|
+
end
|
36
|
+
|
37
|
+
## List of all tokens in DB.
|
38
|
+
# @return [Array] All tokens stored in DB.
|
39
|
+
def tokens
|
40
|
+
@tokens.keys
|
41
|
+
end
|
42
|
+
|
43
|
+
## What is the user_agent for the token?
|
44
|
+
# @return [String] The user agent
|
45
|
+
def user_agent token
|
46
|
+
@tokens[token]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
## Generate a token
|
52
|
+
# @return [String] a hex string, 32 characters long
|
53
|
+
def generate_token
|
54
|
+
# FIXME this could be better.
|
55
|
+
digest = Digest::MD5.new
|
56
|
+
digest << Time.now.to_s
|
57
|
+
digest << rand.to_s
|
58
|
+
return digest.hexdigest
|
59
|
+
end
|
60
|
+
|
61
|
+
## Write the data to the store
|
62
|
+
# @return [nil]
|
63
|
+
def write
|
64
|
+
@fname.dirname.mkpath unless @fname.dirname.directory?
|
65
|
+
@fname.open('w') do |f|
|
66
|
+
f.puts @tokens.to_yaml
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
## Read the data from the store
|
71
|
+
# @return [nil]
|
72
|
+
def read
|
73
|
+
@tokens = YAML::load_file @fname.to_s
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'set'
|
3
|
+
require 'addressable/uri'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'pathname'
|
6
|
+
require 'digest/md5'
|
7
|
+
|
8
|
+
module Iated
|
9
|
+
## An Edit Session
|
10
|
+
#
|
11
|
+
# A single session for editing a textarea. It tracks the editor, etc.
|
12
|
+
class EditSession
|
13
|
+
# @return [String,nil] The URL the textarea came from
|
14
|
+
attr_reader :url
|
15
|
+
# @return [String,nil] The textarea id
|
16
|
+
attr_reader :tid
|
17
|
+
# @return [String,nil] The extension the text file should use
|
18
|
+
attr_reader :extension
|
19
|
+
# @return [String] The session id
|
20
|
+
attr_reader :sid
|
21
|
+
|
22
|
+
##
|
23
|
+
# @param [Hash] options Various optional arguments (:url, :tid, :extension)
|
24
|
+
def initialize options=nil
|
25
|
+
normalized_options = normalize_keys options
|
26
|
+
@url = normalized_options[:url]
|
27
|
+
@tid = normalized_options[:tid]
|
28
|
+
@extension = normalized_options[:extension]
|
29
|
+
@change_id = 0
|
30
|
+
@sid = self.class.calculate_sid normalized_options
|
31
|
+
@last_checksum = :nochecksum
|
32
|
+
|
33
|
+
# Save session.
|
34
|
+
Iated::sessions[@sid] = self
|
35
|
+
|
36
|
+
# Save the text, if passed in the original options
|
37
|
+
self.text = options[:text] if options.key?(:text)
|
38
|
+
end
|
39
|
+
|
40
|
+
## Finds an existing session
|
41
|
+
# @return [EditSession]
|
42
|
+
def self.find search=nil
|
43
|
+
# TODO Once the sid is random, then find needs to work via a data store.
|
44
|
+
if search.is_a? String
|
45
|
+
tok = search
|
46
|
+
else
|
47
|
+
tok = calculate_sid search
|
48
|
+
end
|
49
|
+
Iated::sessions[tok]
|
50
|
+
end
|
51
|
+
|
52
|
+
## Finds an existing session or creates a new one
|
53
|
+
# @return [EditSession]
|
54
|
+
def self.find_or_create search=nil
|
55
|
+
sess = find search
|
56
|
+
sess.nil? ? self.new(search) : sess
|
57
|
+
end
|
58
|
+
|
59
|
+
## Calculate a sid based on url, id, ext
|
60
|
+
# @return [String] A session id
|
61
|
+
def self.calculate_sid options
|
62
|
+
options = normalize_keys options
|
63
|
+
|
64
|
+
# TODO Should retrieve sid from datastore if it exists.
|
65
|
+
# TODO The sid calculation needs a random number
|
66
|
+
# TODO The sid calculation needs the current time or date
|
67
|
+
digest = Digest::MD5.new
|
68
|
+
digest << "url: #{options[:url]}"
|
69
|
+
digest << "tid: #{options[:tid]}"
|
70
|
+
digest << "extension: #{options[:extension]}"
|
71
|
+
return digest.hexdigest
|
72
|
+
end
|
73
|
+
|
74
|
+
## Increment the change_id
|
75
|
+
# @return [Integer] The new number of changes
|
76
|
+
def increment_change_id
|
77
|
+
@change_id = @change_id + 1
|
78
|
+
end
|
79
|
+
|
80
|
+
## Return the change_id
|
81
|
+
# @returns [Integer] The new number of changes
|
82
|
+
def change_id
|
83
|
+
if filename.exist?
|
84
|
+
new_checksum = filename.open('r') do |f|
|
85
|
+
Digest::MD5.hexdigest(f.read)
|
86
|
+
end
|
87
|
+
|
88
|
+
if @last_checksum != new_checksum
|
89
|
+
increment_change_id
|
90
|
+
@last_checksum = new_checksum
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@change_id
|
94
|
+
end
|
95
|
+
|
96
|
+
## Returns true if the editor is running.
|
97
|
+
# @return [Boolean] True if the editor is running
|
98
|
+
def running?
|
99
|
+
# TODO This should check to see if a process is running or not.
|
100
|
+
return true
|
101
|
+
end
|
102
|
+
|
103
|
+
## Opens the user's configured editor.
|
104
|
+
def edit
|
105
|
+
editor = Iated.mcp.prefs.editor.to_s
|
106
|
+
if Iated::environment == :test
|
107
|
+
# We don't want to fire up real editors in testing mode.
|
108
|
+
#$stderr.puts "I would have edited #{filename.to_s.inspect} with #{editor.inspect}"
|
109
|
+
return
|
110
|
+
else
|
111
|
+
if RUBY_ENGINE == "jruby"
|
112
|
+
edit_jruby
|
113
|
+
else
|
114
|
+
edit_ruby
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
## The Ruby version of edit
|
120
|
+
def edit_ruby
|
121
|
+
editor = Iated.mcp.prefs.editor.to_s
|
122
|
+
cmd = []
|
123
|
+
if RUBY_PLATFORM =~ /darwin/ && editor =~ /\.app$/
|
124
|
+
cmd << "/usr/bin/open"
|
125
|
+
cmd << "-a"
|
126
|
+
end
|
127
|
+
cmd << editor
|
128
|
+
cmd << filename.to_s
|
129
|
+
# TODO This doesn't get the exit code...
|
130
|
+
system *cmd
|
131
|
+
end
|
132
|
+
|
133
|
+
## The JRuby version of edit
|
134
|
+
def edit_jruby
|
135
|
+
editor = Iated.mcp.prefs.editor.to_s
|
136
|
+
|
137
|
+
cmd = nil # We will store the CommandLine object here.
|
138
|
+
java_import org.apache.commons.exec.OS
|
139
|
+
java_import org.apache.commons.exec.CommandLine
|
140
|
+
java_import org.apache.commons.exec.DefaultExecutor
|
141
|
+
|
142
|
+
if OS.is_family_mac && editor =~ /\.app$/
|
143
|
+
# It's a Mac .app
|
144
|
+
cmd = CommandLine.new "/usr/bin/open"
|
145
|
+
cmd.add_argument("-a").add_argument(editor)
|
146
|
+
else
|
147
|
+
cmd = CommandLine.new editor
|
148
|
+
end
|
149
|
+
cmd.add_argument(filename.to_s)
|
150
|
+
|
151
|
+
executor = DefaultExecutor.new
|
152
|
+
executor.execute(cmd)
|
153
|
+
end
|
154
|
+
|
155
|
+
## Returns the file where the session is saved.
|
156
|
+
# @return [Pathname] The filename and path the text was saved to.
|
157
|
+
def filename
|
158
|
+
if @filename.nil?
|
159
|
+
bad_chars = /[^a-zA-Z0-9._-]+/
|
160
|
+
url = Addressable::URI.parse(@url)
|
161
|
+
config_dir = Iated::mcp.prefs.config_dir
|
162
|
+
if url.host
|
163
|
+
# TODO Handle case where url is the root and filename is empty.
|
164
|
+
@filename = config_dir + url.host.gsub(bad_chars, '') +
|
165
|
+
"#{url.basename.gsub(bad_chars, '')}#{@extension}"
|
166
|
+
else
|
167
|
+
@filename = config_dir + "#{@url.gsub(bad_chars, '')}#{@extension}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
return @filename
|
171
|
+
end
|
172
|
+
|
173
|
+
## Returns the text of the filename
|
174
|
+
def text
|
175
|
+
if filename.exist?
|
176
|
+
filename.read
|
177
|
+
else
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def text= value
|
183
|
+
increment_change_id if filename.exist?
|
184
|
+
filename.dirname.mkpath
|
185
|
+
# TODO: locking
|
186
|
+
filename.open('w') do |f|
|
187
|
+
f.write value
|
188
|
+
end
|
189
|
+
@last_checksum = Digest::MD5.hexdigest(value)
|
190
|
+
end
|
191
|
+
|
192
|
+
## Normalizes the search options (`:url`, `:tid`, `:extension`)
|
193
|
+
#
|
194
|
+
# Used by other functions
|
195
|
+
# @return [Hash]
|
196
|
+
def self.normalize_keys hash=nil
|
197
|
+
norm_hash = {}
|
198
|
+
hash = {} if hash.nil?
|
199
|
+
|
200
|
+
[:url, :tid, :extension].each do |key|
|
201
|
+
norm_hash[key] = hash[key] || hash[key.to_s] || (:extension == key ? '.txt' : nil)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Verify the keys are sane. We ignore :text.
|
205
|
+
accepted_keys = Set.new((hash.keys + [:text]).map {|x| [x, x.to_s]}.flatten)
|
206
|
+
unexpected_keys = Set.new(hash.keys) - accepted_keys
|
207
|
+
raise "Invalid keys: #{unexpected_keys.inspect}" if unexpected_keys.count > 0
|
208
|
+
return norm_hash
|
209
|
+
end
|
210
|
+
|
211
|
+
## Alias for the class-method `normalize_keys`
|
212
|
+
# @see Iated::EditSession.normalize_keys
|
213
|
+
# @return [Hash]
|
214
|
+
def normalize_keys hash
|
215
|
+
self.class.normalize_keys hash
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
data/lib/iated/mcp.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
## Iated Master Controller Package
|
2
|
+
# This is the coordination point for
|
3
|
+
# all the data stored, and the UI.
|
4
|
+
|
5
|
+
require 'sinatra'
|
6
|
+
require 'pathname'
|
7
|
+
require 'iated/sys_pref'
|
8
|
+
require 'iated/browser_token_db'
|
9
|
+
|
10
|
+
module Iated
|
11
|
+
## The Master Control Progrom
|
12
|
+
#
|
13
|
+
# Yes, I've been watching Tron lately, why do ask?
|
14
|
+
class MCP
|
15
|
+
|
16
|
+
attr_accessor :debug
|
17
|
+
|
18
|
+
attr_reader :browser_token_db
|
19
|
+
|
20
|
+
# @return [SysPref] The current System Preferencs object
|
21
|
+
attr_reader :prefs
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@debug = false
|
25
|
+
@prefs = SysPref.new
|
26
|
+
@browser_token_db = Iated::BrowserTokenDB.new(@prefs.config_dir + 'browser_tokens.yml')
|
27
|
+
end
|
28
|
+
|
29
|
+
## Called by the main program to get everything started.
|
30
|
+
def begin!
|
31
|
+
start_server
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String,Symbol] The currently showing secret or the symbol `:notset`
|
35
|
+
def secret
|
36
|
+
showing_secret? ? @secret : :notset
|
37
|
+
end
|
38
|
+
|
39
|
+
## Confirm the secret
|
40
|
+
#
|
41
|
+
# @param [String] guess The string to check against the secret
|
42
|
+
# @return [Boolean] Was the guess right?
|
43
|
+
def confirm_secret guess
|
44
|
+
success = (guess == secret)
|
45
|
+
hide_secret
|
46
|
+
return success
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Boolean] Is the secret being shown to the user?
|
50
|
+
def showing_secret?
|
51
|
+
@is_showing_secret
|
52
|
+
end
|
53
|
+
|
54
|
+
## Show the secret to the user
|
55
|
+
# @return [nil]
|
56
|
+
def show_secret
|
57
|
+
generate_secret
|
58
|
+
# TODO This should do something different for text, vs. gui.
|
59
|
+
puts " ** A browser requested authorization: #{@secret}" unless :test == ui
|
60
|
+
## require 'java'
|
61
|
+
## Thread.new do
|
62
|
+
## puts " ** A browser requested authorization: #{@secret}"
|
63
|
+
### TODO This needs to be abstracted. There is no reason a purely command line version can't be done. Also, loading swing will break CI.
|
64
|
+
### import javax.swing.JOptionPane
|
65
|
+
### JOptionPane.showMessageDialog(nil,
|
66
|
+
### "A browser has requested authorization: #{@secret}\nDo not press 'OK' until after you enter the number",
|
67
|
+
### "Authorize Browser",
|
68
|
+
### JOptionPane::INFORMATION_MESSAGE)
|
69
|
+
## @is_showing_secret = false
|
70
|
+
## end
|
71
|
+
end
|
72
|
+
|
73
|
+
## Hide the secret again
|
74
|
+
# @return [nil]
|
75
|
+
def hide_secret
|
76
|
+
@is_showing_secret = false
|
77
|
+
# TODO secret should be using a JPanel or something, not a dialog.
|
78
|
+
end
|
79
|
+
|
80
|
+
## The magic value shown to user to confirm a connection
|
81
|
+
# @return [String] The string
|
82
|
+
def generate_secret
|
83
|
+
@secret = (1..4).map{ rand(10).to_s }.join ""
|
84
|
+
@is_showing_secret = true
|
85
|
+
return @secret
|
86
|
+
end
|
87
|
+
|
88
|
+
## The magic value registering a browser.
|
89
|
+
# @return [String] The token
|
90
|
+
def generate_token user_agent
|
91
|
+
@browser_token_db.add user_agent
|
92
|
+
end
|
93
|
+
|
94
|
+
## Is the auth token a valid one?
|
95
|
+
# @return [Boolean] True if the token is valid and exist
|
96
|
+
def is_token_valid? token
|
97
|
+
@browser_token_db.has_token? token
|
98
|
+
end
|
99
|
+
|
100
|
+
def ui= ui_type
|
101
|
+
if [:text, :test, :gui].include? ui_type
|
102
|
+
@ui = ui_type
|
103
|
+
else
|
104
|
+
raise "Invalid UI type: #{ui_type.inspect}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def ui
|
109
|
+
# Default UI
|
110
|
+
@ui ||= (:test == Iated.environment ? :test : :text)
|
111
|
+
end
|
112
|
+
|
113
|
+
def start_server
|
114
|
+
# TODO this needs to be in a separate thread.
|
115
|
+
set :server, %w[webrick]
|
116
|
+
set :bind, 'localhost'
|
117
|
+
set :port, prefs.port
|
118
|
+
if @debug
|
119
|
+
set :show_exceptions, true
|
120
|
+
set :environment, :development
|
121
|
+
else
|
122
|
+
set :show_exceptions, false
|
123
|
+
set :environment, :production
|
124
|
+
end
|
125
|
+
|
126
|
+
@is_running = true
|
127
|
+
Sinatra::Application.run!
|
128
|
+
end
|
129
|
+
|
130
|
+
def stop_server
|
131
|
+
@is_running = false
|
132
|
+
Sinatra::Application.quit!
|
133
|
+
end
|
134
|
+
|
135
|
+
def cucumber_coverage_check
|
136
|
+
@cucumber_check = true
|
137
|
+
end
|
138
|
+
|
139
|
+
def rspec_coverage_check
|
140
|
+
@rspec_check = true
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|