turbot 0.1.36 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +15 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/README.md +44 -25
  9. data/Rakefile +16 -0
  10. data/appveyor.yml +35 -0
  11. data/bin/turbot +2 -16
  12. data/data/schema.json +134 -0
  13. data/{templates → data/templates}/LICENSE.txt +0 -0
  14. data/{templates → data/templates}/manifest.json +0 -0
  15. data/{templates → data/templates}/python/scraper.py +0 -0
  16. data/{templates → data/templates}/ruby/scraper.rb +0 -0
  17. data/dist/deb.rake +32 -0
  18. data/dist/gem.rake +16 -0
  19. data/dist/manifest.rake +9 -0
  20. data/dist/pkg.rake +60 -0
  21. data/dist/resources/deb/control +10 -0
  22. data/dist/resources/deb/postinst +45 -0
  23. data/dist/resources/deb/turbot +25 -0
  24. data/dist/resources/deb/turbot-release-key.txt +30 -0
  25. data/dist/resources/pkg/Distribution.erb +15 -0
  26. data/dist/resources/pkg/PackageInfo.erb +6 -0
  27. data/dist/resources/pkg/postinstall +45 -0
  28. data/dist/resources/pkg/turbot +24 -0
  29. data/dist/resources/tgz/turbot +24 -0
  30. data/dist/rpm.rake +35 -0
  31. data/dist/tgz.rake +26 -0
  32. data/dist/zip.rake +40 -0
  33. data/lib/turbot.rb +18 -15
  34. data/lib/turbot/cli.rb +10 -27
  35. data/lib/turbot/command.rb +59 -212
  36. data/lib/turbot/command/auth.rb +72 -34
  37. data/lib/turbot/command/base.rb +22 -61
  38. data/lib/turbot/command/bots.rb +251 -300
  39. data/lib/turbot/command/help.rb +57 -110
  40. data/lib/turbot/command/version.rb +6 -10
  41. data/lib/turbot/handlers/base_handler.rb +21 -0
  42. data/lib/turbot/handlers/dump_handler.rb +10 -0
  43. data/lib/turbot/handlers/preview_handler.rb +30 -0
  44. data/lib/turbot/handlers/validation_handler.rb +17 -0
  45. data/lib/turbot/helpers.rb +14 -482
  46. data/lib/turbot/helpers/api_helper.rb +41 -0
  47. data/lib/turbot/helpers/netrc_helper.rb +66 -0
  48. data/lib/turbot/helpers/shell_helper.rb +36 -0
  49. data/lib/turbot/version.rb +1 -1
  50. data/spec/fixtures/bad_permissions +0 -0
  51. data/spec/fixtures/empty +0 -0
  52. data/spec/fixtures/netrc +6 -0
  53. data/spec/spec_helper.rb +17 -219
  54. data/spec/support/bot_helper.rb +102 -0
  55. data/spec/support/command_helper.rb +20 -0
  56. data/spec/support/custom_matchers.rb +5 -0
  57. data/spec/support/fixture_helper.rb +9 -0
  58. data/spec/support/netrc_helper.rb +21 -0
  59. data/spec/turbot/command/auth_spec.rb +202 -20
  60. data/spec/turbot/command/base_spec.rb +22 -58
  61. data/spec/turbot/command/bots_spec.rb +580 -89
  62. data/spec/turbot/command/help_spec.rb +32 -75
  63. data/spec/turbot/command/version_spec.rb +11 -10
  64. data/spec/turbot/command_spec.rb +55 -87
  65. data/spec/turbot/helpers_spec.rb +28 -44
  66. data/turbot.gemspec +31 -0
  67. metadata +88 -178
  68. data/data/cacert.pem +0 -3988
  69. data/lib/turbot/auth.rb +0 -315
  70. data/lib/turbot/client.rb +0 -757
  71. data/lib/turbot/client/cisaurus.rb +0 -25
  72. data/lib/turbot/client/pgbackups.rb +0 -113
  73. data/lib/turbot/client/rendezvous.rb +0 -111
  74. data/lib/turbot/client/ssl_endpoint.rb +0 -25
  75. data/lib/turbot/client/turbot_postgresql.rb +0 -148
  76. data/lib/turbot/command/ssl.rb +0 -43
  77. data/lib/turbot/deprecated.rb +0 -5
  78. data/lib/turbot/deprecated/help.rb +0 -38
  79. data/lib/turbot/distribution.rb +0 -9
  80. data/lib/turbot/errors.rb +0 -28
  81. data/lib/turbot/excon.rb +0 -11
  82. data/lib/turbot/helpers/log_displayer.rb +0 -70
  83. data/lib/turbot/helpers/pg_dump_restore.rb +0 -115
  84. data/lib/turbot/helpers/turbot_postgresql.rb +0 -213
  85. data/lib/turbot/plugin.rb +0 -165
  86. data/lib/turbot/updater.rb +0 -171
  87. data/lib/vendor/turbot/okjson.rb +0 -598
  88. data/spec/helper/legacy_help.rb +0 -16
  89. data/spec/helper/pg_dump_restore_spec.rb +0 -67
  90. data/spec/spec.opts +0 -1
  91. data/spec/support/display_message_matcher.rb +0 -49
  92. data/spec/support/dummy_api.rb +0 -120
  93. data/spec/support/openssl_mock_helper.rb +0 -8
  94. data/spec/support/organizations_mock_helper.rb +0 -11
  95. data/spec/turbot/auth_spec.rb +0 -214
  96. data/spec/turbot/client/pgbackups_spec.rb +0 -43
  97. data/spec/turbot/client/rendezvous_spec.rb +0 -62
  98. data/spec/turbot/client/ssl_endpoint_spec.rb +0 -48
  99. data/spec/turbot/client/turbot_postgresql_spec.rb +0 -71
  100. data/spec/turbot/client_spec.rb +0 -548
  101. data/spec/turbot/helpers/turbot_postgresql_spec.rb +0 -181
  102. data/spec/turbot/plugin_spec.rb +0 -172
  103. data/spec/turbot/updater_spec.rb +0 -44
data/lib/turbot/plugin.rb DELETED
@@ -1,165 +0,0 @@
1
- # based on the Rails Plugin
2
-
3
- module Turbot
4
- class Plugin
5
- include Turbot::Helpers
6
- extend Turbot::Helpers
7
-
8
- class ErrorUpdatingSymlinkPlugin < StandardError; end
9
-
10
- DEPRECATED_PLUGINS = %w(
11
- turbot-cedar
12
- turbot-certs
13
- turbot-credentials
14
- turbot-dyno-size
15
- turbot-kill
16
- turbot-labs
17
- turbot-logging
18
- turbot-netrc
19
- turbot-pgdumps
20
- turbot-postgresql
21
- turbot-releases
22
- turbot-shared-postgresql
23
- turbot-sql-console
24
- turbot-status
25
- turbot-stop
26
- turbot-suggest
27
- pgbackups-automate
28
- pgcmd
29
- turbot-fork
30
- turbot-orgs
31
- )
32
-
33
- attr_reader :name, :uri
34
-
35
- def self.directory
36
- File.expand_path("#{home_directory}/.turbot/plugins")
37
- end
38
-
39
- def self.list
40
- Dir["#{directory}/*"].sort.map do |folder|
41
- File.basename(folder)
42
- end
43
- end
44
-
45
- def self.load!
46
- list.each do |plugin|
47
- check_for_deprecation(plugin)
48
- next if skip_plugins.include?(plugin)
49
- load_plugin(plugin)
50
- end
51
- # check to see if we are using ddollar/turbot-accounts
52
- if list.include?('turbot-accounts') && Turbot::Auth.methods.include?(:fetch_from_account)
53
- # setup netrc to match the default, if one exists
54
- if default_account = %x{ git config turbot.account }.chomp
55
- account = Turbot::Auth.extract_account rescue nil
56
- if account && Turbot::Auth.read_credentials != [Turbot::Auth.user, Turbot::Auth.password]
57
- Turbot::Auth.credentials = [Turbot::Auth.user, Turbot::Auth.password]
58
- Turbot::Auth.write_credentials
59
- load("#{File.dirname(__FILE__)}/command/accounts.rb")
60
- # kill memoization in case '--account' was passed
61
- Turbot::Auth.instance_variable_set(:@account, nil)
62
- end
63
- end
64
- end
65
- end
66
-
67
- def self.load_plugin(plugin)
68
- begin
69
- folder = "#{self.directory}/#{plugin}"
70
- $: << "#{folder}/lib" if File.directory? "#{folder}/lib"
71
- load "#{folder}/init.rb" if File.exists? "#{folder}/init.rb"
72
- rescue ScriptError, StandardError => error
73
- styled_error(error, "Unable to load plugin #{plugin}.")
74
- false
75
- end
76
- end
77
-
78
- def self.remove_plugin(plugin)
79
- FileUtils.rm_rf("#{self.directory}/#{plugin}")
80
- end
81
-
82
- def self.check_for_deprecation(plugin)
83
- return unless STDIN.isatty
84
-
85
- if DEPRECATED_PLUGINS.include?(plugin)
86
- if confirm "The plugin #{plugin} has been deprecated. Would you like to remove it? (y/N)"
87
- remove_plugin(plugin)
88
- end
89
- end
90
- end
91
-
92
- def self.skip_plugins
93
- @skip_plugins ||= ENV["SKIP_PLUGINS"].to_s.split(/[ ,]/)
94
- end
95
-
96
- def initialize(uri)
97
- @uri = uri
98
- guess_name(uri)
99
- end
100
-
101
- def to_s
102
- name
103
- end
104
-
105
- def path
106
- "#{self.class.directory}/#{name}"
107
- end
108
-
109
- def install
110
- if File.directory?(path)
111
- uninstall
112
- end
113
- FileUtils.mkdir_p(self.class.directory)
114
- Dir.chdir(self.class.directory) do
115
- git("clone #{uri}")
116
- unless $?.success?
117
- FileUtils.rm_rf path
118
- return false
119
- end
120
- end
121
- true
122
- end
123
-
124
- def uninstall
125
- ensure_plugin_exists
126
- FileUtils.rm_r(path)
127
- end
128
-
129
- def update
130
- ensure_plugin_exists
131
- if File.symlink?(path)
132
- raise Turbot::Plugin::ErrorUpdatingSymlinkPlugin
133
- else
134
- Dir.chdir(path) do
135
- unless git('config --get branch.master.remote').empty?
136
- message = git("pull")
137
- unless $?.success?
138
- error("Unable to update #{name}.\n" + message)
139
- end
140
- else
141
- error(<<-ERROR)
142
- #{name} is a legacy plugin installation.
143
- Enable updating by reinstalling with `turbot plugins:install`.
144
- ERROR
145
- end
146
- end
147
- end
148
- end
149
-
150
- private
151
-
152
- def ensure_plugin_exists
153
- unless File.directory?(path)
154
- error("#{name} plugin not found.")
155
- end
156
- end
157
-
158
- def guess_name(url)
159
- @name = File.basename(url)
160
- @name = File.basename(File.dirname(url)) if @name.empty?
161
- @name.gsub!(/\.git$/, '') if @name =~ /\.git$/
162
- end
163
-
164
- end
165
- end
@@ -1,171 +0,0 @@
1
- require "digest"
2
- require "fileutils"
3
- require "turbot/helpers"
4
-
5
- module Turbot
6
- module Updater
7
-
8
- def self.error(message)
9
- raise Turbot::Command::CommandFailed.new(message)
10
- end
11
-
12
- def self.updating_lock_path
13
- File.join(Turbot::Helpers.home_directory, ".turbot", "updating")
14
- end
15
-
16
- def self.installed_client_path
17
- File.expand_path("../../..", __FILE__)
18
- end
19
-
20
- def self.updated_client_path
21
- File.join(Turbot::Helpers.home_directory, ".turbot", "client")
22
- end
23
-
24
- def self.latest_local_version
25
- installed_version = client_version_from_path(installed_client_path)
26
- updated_version = client_version_from_path(updated_client_path)
27
- if compare_versions(updated_version, installed_version) > 0
28
- updated_version
29
- else
30
- installed_version
31
- end
32
- end
33
-
34
- def self.client_version_from_path(path)
35
- version_file = File.join(path, "lib/turbot/version.rb")
36
- if File.exists?(version_file)
37
- File.read(version_file).match(/VERSION = "([^"]+)"/)[1]
38
- else
39
- '0.0.0'
40
- end
41
- end
42
-
43
- def self.disable(message=nil)
44
- @disable = message if message
45
- @disable
46
- end
47
-
48
- def self.check_disabled!
49
- if disable
50
- Turbot::Helpers.error(disable)
51
- end
52
- end
53
-
54
- def self.wait_for_lock(path, wait_for=5, check_every=0.5)
55
- start = Time.now.to_i
56
- while File.exists?(path)
57
- sleep check_every
58
- if (Time.now.to_i - start) > wait_for
59
- Turbot::Helpers.error "Unable to acquire update lock"
60
- end
61
- end
62
- begin
63
- FileUtils.touch path
64
- ret = yield
65
- ensure
66
- FileUtils.rm_f path
67
- end
68
- ret
69
- end
70
-
71
- def self.autoupdate?
72
- true
73
- end
74
-
75
- def self.update(url, autoupdate=false)
76
- wait_for_lock(updating_lock_path, 5) do
77
- require "excon"
78
- require "turbot"
79
- require "turbot/excon"
80
- require "tmpdir"
81
- require "zip/zip"
82
-
83
- latest_version = Excon.get_with_redirect("http://assets.turbot.com/turbot-client/VERSION", :nonblock => false).body.chomp
84
-
85
- if compare_versions(latest_version, latest_local_version) > 0
86
- Dir.mktmpdir do |download_dir|
87
- File.open("#{download_dir}/turbot.zip", "wb") do |file|
88
- file.print Excon.get_with_redirect(url, :nonblock => false).body
89
- end
90
-
91
- hash = Digest::SHA256.file("#{download_dir}/turbot.zip").hexdigest
92
- official_hash = Excon.get_with_redirect("https://toolbelt.turbot.com/update/hash", :nonblock => false).body.chomp
93
-
94
- error "Update hash signature mismatch" unless hash == official_hash
95
-
96
- Zip::ZipFile.open("#{download_dir}/turbot.zip") do |zip|
97
- zip.each do |entry|
98
- target = File.join(download_dir, entry.to_s)
99
- FileUtils.mkdir_p File.dirname(target)
100
- zip.extract(entry, target) { true }
101
- end
102
- end
103
-
104
- FileUtils.rm "#{download_dir}/turbot.zip"
105
-
106
- old_version = latest_local_version
107
- new_version = client_version_from_path(download_dir)
108
-
109
- if compare_versions(new_version, old_version) < 0 && !autoupdate
110
- Turbot::Helpers.error("Installed version (#{old_version}) is newer than the latest available update (#{new_version})")
111
- end
112
-
113
- FileUtils.rm_rf updated_client_path
114
- FileUtils.mkdir_p File.dirname(updated_client_path)
115
- FileUtils.cp_r download_dir, updated_client_path
116
-
117
- new_version
118
- end
119
- else
120
- false # already up to date
121
- end
122
- end
123
- ensure
124
- FileUtils.rm_f(updating_lock_path)
125
- end
126
-
127
- def self.compare_versions(first_version, second_version)
128
- first_version.split('.').map {|part| Integer(part) rescue part} <=> second_version.split('.').map {|part| Integer(part) rescue part}
129
- end
130
-
131
- def self.inject_libpath
132
- old_version = client_version_from_path(installed_client_path)
133
- new_version = client_version_from_path(updated_client_path)
134
-
135
- if compare_versions(new_version, old_version) > 0
136
- $:.unshift File.join(updated_client_path, "lib")
137
- vendored_gems = Dir[File.join(updated_client_path, "vendor", "gems", "*")]
138
- vendored_gems.each do |vendored_gem|
139
- $:.unshift File.join(vendored_gem, "lib")
140
- end
141
- load('turbot/updater.rb') # reload updated updater
142
- end
143
-
144
- background_update!
145
- end
146
-
147
- def self.last_autoupdate_path
148
- File.join(Turbot::Helpers.home_directory, ".turbot", "autoupdate.last")
149
- end
150
-
151
- def self.background_update!
152
- # if we've updated in the last 300 seconds, dont try again
153
- if File.exists?(last_autoupdate_path)
154
- return if (Time.now.to_i - File.mtime(last_autoupdate_path).to_i) < 300
155
- end
156
- log_path = File.join(Turbot::Helpers.home_directory, '.turbot', 'autoupdate.log')
157
- FileUtils.mkdir_p File.dirname(log_path)
158
- turbot_binary = File.expand_path($0)
159
- pid = if defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
160
- fork do
161
- exec("\"#{turbot_binary}\" update &> #{log_path} 2>&1")
162
- end
163
- else
164
- spawn("\"#{turbot_binary}\" update", {:err => log_path, :out => log_path})
165
- end
166
- Process.detach(pid)
167
- FileUtils.mkdir_p File.dirname(last_autoupdate_path)
168
- FileUtils.touch last_autoupdate_path
169
- end
170
- end
171
- end
@@ -1,598 +0,0 @@
1
- # encoding: UTF-8
2
- #
3
- # Copyright 2011, 2012 Keith Rarick
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
-
23
- # See https://github.com/kr/okjson for updates.
24
-
25
- require 'stringio'
26
-
27
- # Some parts adapted from
28
- # http://golang.org/src/pkg/json/decode.go and
29
- # http://golang.org/src/pkg/utf8/utf8.go
30
- module Turbot
31
- module OkJson
32
- extend self
33
-
34
-
35
- # Decodes a json document in string s and
36
- # returns the corresponding ruby value.
37
- # String s must be valid UTF-8. If you have
38
- # a string in some other encoding, convert
39
- # it first.
40
- #
41
- # String values in the resulting structure
42
- # will be UTF-8.
43
- def decode(s)
44
- ts = lex(s)
45
- v, ts = textparse(ts)
46
- if ts.length > 0
47
- raise Error, 'trailing garbage'
48
- end
49
- v
50
- end
51
-
52
-
53
- # Parses a "json text" in the sense of RFC 4627.
54
- # Returns the parsed value and any trailing tokens.
55
- # Note: this is almost the same as valparse,
56
- # except that it does not accept atomic values.
57
- def textparse(ts)
58
- if ts.length < 0
59
- raise Error, 'empty'
60
- end
61
-
62
- typ, _, val = ts[0]
63
- case typ
64
- when '{' then objparse(ts)
65
- when '[' then arrparse(ts)
66
- else
67
- raise Error, "unexpected #{val.inspect}"
68
- end
69
- end
70
-
71
-
72
- # Parses a "value" in the sense of RFC 4627.
73
- # Returns the parsed value and any trailing tokens.
74
- def valparse(ts)
75
- if ts.length < 0
76
- raise Error, 'empty'
77
- end
78
-
79
- typ, _, val = ts[0]
80
- case typ
81
- when '{' then objparse(ts)
82
- when '[' then arrparse(ts)
83
- when :val,:str then [val, ts[1..-1]]
84
- else
85
- raise Error, "unexpected #{val.inspect}"
86
- end
87
- end
88
-
89
-
90
- # Parses an "object" in the sense of RFC 4627.
91
- # Returns the parsed value and any trailing tokens.
92
- def objparse(ts)
93
- ts = eat('{', ts)
94
- obj = {}
95
-
96
- if ts[0][0] == '}'
97
- return obj, ts[1..-1]
98
- end
99
-
100
- k, v, ts = pairparse(ts)
101
- obj[k] = v
102
-
103
- if ts[0][0] == '}'
104
- return obj, ts[1..-1]
105
- end
106
-
107
- loop do
108
- ts = eat(',', ts)
109
-
110
- k, v, ts = pairparse(ts)
111
- obj[k] = v
112
-
113
- if ts[0][0] == '}'
114
- return obj, ts[1..-1]
115
- end
116
- end
117
- end
118
-
119
-
120
- # Parses a "member" in the sense of RFC 4627.
121
- # Returns the parsed values and any trailing tokens.
122
- def pairparse(ts)
123
- (typ, _, k), ts = ts[0], ts[1..-1]
124
- if typ != :str
125
- raise Error, "unexpected #{k.inspect}"
126
- end
127
- ts = eat(':', ts)
128
- v, ts = valparse(ts)
129
- [k, v, ts]
130
- end
131
-
132
-
133
- # Parses an "array" in the sense of RFC 4627.
134
- # Returns the parsed value and any trailing tokens.
135
- def arrparse(ts)
136
- ts = eat('[', ts)
137
- arr = []
138
-
139
- if ts[0][0] == ']'
140
- return arr, ts[1..-1]
141
- end
142
-
143
- v, ts = valparse(ts)
144
- arr << v
145
-
146
- if ts[0][0] == ']'
147
- return arr, ts[1..-1]
148
- end
149
-
150
- loop do
151
- ts = eat(',', ts)
152
-
153
- v, ts = valparse(ts)
154
- arr << v
155
-
156
- if ts[0][0] == ']'
157
- return arr, ts[1..-1]
158
- end
159
- end
160
- end
161
-
162
-
163
- def eat(typ, ts)
164
- if ts[0][0] != typ
165
- raise Error, "expected #{typ} (got #{ts[0].inspect})"
166
- end
167
- ts[1..-1]
168
- end
169
-
170
-
171
- # Scans s and returns a list of json tokens,
172
- # excluding white space (as defined in RFC 4627).
173
- def lex(s)
174
- ts = []
175
- while s.length > 0
176
- typ, lexeme, val = tok(s)
177
- if typ == nil
178
- raise Error, "invalid character at #{s[0,10].inspect}"
179
- end
180
- if typ != :space
181
- ts << [typ, lexeme, val]
182
- end
183
- s = s[lexeme.length..-1]
184
- end
185
- ts
186
- end
187
-
188
-
189
- # Scans the first token in s and
190
- # returns a 3-element list, or nil
191
- # if s does not begin with a valid token.
192
- #
193
- # The first list element is one of
194
- # '{', '}', ':', ',', '[', ']',
195
- # :val, :str, and :space.
196
- #
197
- # The second element is the lexeme.
198
- #
199
- # The third element is the value of the
200
- # token for :val and :str, otherwise
201
- # it is the lexeme.
202
- def tok(s)
203
- case s[0]
204
- when ?{ then ['{', s[0,1], s[0,1]]
205
- when ?} then ['}', s[0,1], s[0,1]]
206
- when ?: then [':', s[0,1], s[0,1]]
207
- when ?, then [',', s[0,1], s[0,1]]
208
- when ?[ then ['[', s[0,1], s[0,1]]
209
- when ?] then [']', s[0,1], s[0,1]]
210
- when ?n then nulltok(s)
211
- when ?t then truetok(s)
212
- when ?f then falsetok(s)
213
- when ?" then strtok(s)
214
- when Spc then [:space, s[0,1], s[0,1]]
215
- when ?\t then [:space, s[0,1], s[0,1]]
216
- when ?\n then [:space, s[0,1], s[0,1]]
217
- when ?\r then [:space, s[0,1], s[0,1]]
218
- else numtok(s)
219
- end
220
- end
221
-
222
-
223
- def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
224
- def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
225
- def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
226
-
227
-
228
- def numtok(s)
229
- m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
230
- if m && m.begin(0) == 0
231
- if m[3] && !m[2]
232
- [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
233
- elsif m[2]
234
- [:val, m[0], Float(m[0])]
235
- else
236
- [:val, m[0], Integer(m[0])]
237
- end
238
- else
239
- []
240
- end
241
- end
242
-
243
-
244
- def strtok(s)
245
- m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
246
- if ! m
247
- raise Error, "invalid string literal at #{abbrev(s)}"
248
- end
249
- [:str, m[0], unquote(m[0])]
250
- end
251
-
252
-
253
- def abbrev(s)
254
- t = s[0,10]
255
- p = t['`']
256
- t = t[0,p] if p
257
- t = t + '...' if t.length < s.length
258
- '`' + t + '`'
259
- end
260
-
261
-
262
- # Converts a quoted json string literal q into a UTF-8-encoded string.
263
- # The rules are different than for Ruby, so we cannot use eval.
264
- # Unquote will raise an error if q contains control characters.
265
- def unquote(q)
266
- q = q[1...-1]
267
- rubydoesenc = false
268
- # In ruby >= 1.9, a[w] is a codepoint, not a byte.
269
- if q.class.method_defined?(:force_encoding)
270
- q.force_encoding('UTF-8')
271
- rubydoesenc = true
272
- end
273
- a = q.dup # allocate a big enough string
274
- r, w = 0, 0
275
- while r < q.length
276
- c = q[r]
277
- case true
278
- when c == ?\\
279
- r += 1
280
- if r >= q.length
281
- raise Error, "string literal ends with a \"\\\": \"#{q}\""
282
- end
283
-
284
- case q[r]
285
- when ?",?\\,?/,?'
286
- a[w] = q[r]
287
- r += 1
288
- w += 1
289
- when ?b,?f,?n,?r,?t
290
- a[w] = Unesc[q[r]]
291
- r += 1
292
- w += 1
293
- when ?u
294
- r += 1
295
- uchar = begin
296
- hexdec4(q[r,4])
297
- rescue RuntimeError => e
298
- raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
299
- end
300
- r += 4
301
- if surrogate? uchar
302
- if q.length >= r+6
303
- uchar1 = hexdec4(q[r+2,4])
304
- uchar = subst(uchar, uchar1)
305
- if uchar != Ucharerr
306
- # A valid pair; consume.
307
- r += 6
308
- end
309
- end
310
- end
311
- if rubydoesenc
312
- a[w] = '' << uchar
313
- w += 1
314
- else
315
- w += ucharenc(a, w, uchar)
316
- end
317
- else
318
- raise Error, "invalid escape char #{q[r]} in \"#{q}\""
319
- end
320
- when c == ?", c < Spc
321
- raise Error, "invalid character in string literal \"#{q}\""
322
- else
323
- # Copy anything else byte-for-byte.
324
- # Valid UTF-8 will remain valid UTF-8.
325
- # Invalid UTF-8 will remain invalid UTF-8.
326
- # In ruby >= 1.9, c is a codepoint, not a byte,
327
- # in which case this is still what we want.
328
- a[w] = c
329
- r += 1
330
- w += 1
331
- end
332
- end
333
- a[0,w]
334
- end
335
-
336
-
337
- # Encodes unicode character u as UTF-8
338
- # bytes in string a at position i.
339
- # Returns the number of bytes written.
340
- def ucharenc(a, i, u)
341
- case true
342
- when u <= Uchar1max
343
- a[i] = (u & 0xff).chr
344
- 1
345
- when u <= Uchar2max
346
- a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
347
- a[i+1] = (Utagx | (u&Umaskx)).chr
348
- 2
349
- when u <= Uchar3max
350
- a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
351
- a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
352
- a[i+2] = (Utagx | (u&Umaskx)).chr
353
- 3
354
- else
355
- a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
356
- a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
357
- a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
358
- a[i+3] = (Utagx | (u&Umaskx)).chr
359
- 4
360
- end
361
- end
362
-
363
-
364
- def hexdec4(s)
365
- if s.length != 4
366
- raise Error, 'short'
367
- end
368
- (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
369
- end
370
-
371
-
372
- def subst(u1, u2)
373
- if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
374
- return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
375
- end
376
- return Ucharerr
377
- end
378
-
379
-
380
- def surrogate?(u)
381
- Usurr1 <= u && u < Usurr3
382
- end
383
-
384
-
385
- def nibble(c)
386
- case true
387
- when ?0 <= c && c <= ?9 then c.ord - ?0.ord
388
- when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
389
- when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
390
- else
391
- raise Error, "invalid hex code #{c}"
392
- end
393
- end
394
-
395
-
396
- # Encodes x into a json text. It may contain only
397
- # Array, Hash, String, Numeric, true, false, nil.
398
- # (Note, this list excludes Symbol.)
399
- # X itself must be an Array or a Hash.
400
- # No other value can be encoded, and an error will
401
- # be raised if x contains any other value, such as
402
- # Nan, Infinity, Symbol, and Proc, or if a Hash key
403
- # is not a String.
404
- # Strings contained in x must be valid UTF-8.
405
- def encode(x)
406
- case x
407
- when Hash then objenc(x)
408
- when Array then arrenc(x)
409
- else
410
- raise Error, 'root value must be an Array or a Hash'
411
- end
412
- end
413
-
414
-
415
- def valenc(x)
416
- case x
417
- when Hash then objenc(x)
418
- when Array then arrenc(x)
419
- when String then strenc(x)
420
- when Numeric then numenc(x)
421
- when true then "true"
422
- when false then "false"
423
- when nil then "null"
424
- else
425
- raise Error, "cannot encode #{x.class}: #{x.inspect}"
426
- end
427
- end
428
-
429
-
430
- def objenc(x)
431
- '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
432
- end
433
-
434
-
435
- def arrenc(a)
436
- '[' + a.map{|x| valenc(x)}.join(',') + ']'
437
- end
438
-
439
-
440
- def keyenc(k)
441
- case k
442
- when String then strenc(k)
443
- else
444
- raise Error, "Hash key is not a string: #{k.inspect}"
445
- end
446
- end
447
-
448
-
449
- def strenc(s)
450
- t = StringIO.new
451
- t.putc(?")
452
- r = 0
453
-
454
- # In ruby >= 1.9, s[r] is a codepoint, not a byte.
455
- rubydoesenc = s.class.method_defined?(:encoding)
456
-
457
- while r < s.length
458
- case s[r]
459
- when ?" then t.print('\\"')
460
- when ?\\ then t.print('\\\\')
461
- when ?\b then t.print('\\b')
462
- when ?\f then t.print('\\f')
463
- when ?\n then t.print('\\n')
464
- when ?\r then t.print('\\r')
465
- when ?\t then t.print('\\t')
466
- else
467
- c = s[r]
468
- case true
469
- when rubydoesenc
470
- begin
471
- c.ord # will raise an error if c is invalid UTF-8
472
- t.write(c)
473
- rescue
474
- t.write(Ustrerr)
475
- end
476
- when Spc <= c && c <= ?~
477
- t.putc(c)
478
- else
479
- n = ucharcopy(t, s, r) # ensure valid UTF-8 output
480
- r += n - 1 # r is incremented below
481
- end
482
- end
483
- r += 1
484
- end
485
- t.putc(?")
486
- t.string
487
- end
488
-
489
-
490
- def numenc(x)
491
- if ((x.nan? || x.infinite?) rescue false)
492
- raise Error, "Numeric cannot be represented: #{x}"
493
- end
494
- "#{x}"
495
- end
496
-
497
-
498
- # Copies the valid UTF-8 bytes of a single character
499
- # from string s at position i to I/O object t, and
500
- # returns the number of bytes copied.
501
- # If no valid UTF-8 char exists at position i,
502
- # ucharcopy writes Ustrerr and returns 1.
503
- def ucharcopy(t, s, i)
504
- n = s.length - i
505
- raise Utf8Error if n < 1
506
-
507
- c0 = s[i].ord
508
-
509
- # 1-byte, 7-bit sequence?
510
- if c0 < Utagx
511
- t.putc(c0)
512
- return 1
513
- end
514
-
515
- raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
516
-
517
- raise Utf8Error if n < 2 # need continuation byte
518
- c1 = s[i+1].ord
519
- raise Utf8Error if c1 < Utagx || Utag2 <= c1
520
-
521
- # 2-byte, 11-bit sequence?
522
- if c0 < Utag3
523
- raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
524
- t.putc(c0)
525
- t.putc(c1)
526
- return 2
527
- end
528
-
529
- # need second continuation byte
530
- raise Utf8Error if n < 3
531
-
532
- c2 = s[i+2].ord
533
- raise Utf8Error if c2 < Utagx || Utag2 <= c2
534
-
535
- # 3-byte, 16-bit sequence?
536
- if c0 < Utag4
537
- u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
538
- raise Utf8Error if u <= Uchar2max
539
- t.putc(c0)
540
- t.putc(c1)
541
- t.putc(c2)
542
- return 3
543
- end
544
-
545
- # need third continuation byte
546
- raise Utf8Error if n < 4
547
- c3 = s[i+3].ord
548
- raise Utf8Error if c3 < Utagx || Utag2 <= c3
549
-
550
- # 4-byte, 21-bit sequence?
551
- if c0 < Utag5
552
- u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
553
- raise Utf8Error if u <= Uchar3max
554
- t.putc(c0)
555
- t.putc(c1)
556
- t.putc(c2)
557
- t.putc(c3)
558
- return 4
559
- end
560
-
561
- raise Utf8Error
562
- rescue Utf8Error
563
- t.write(Ustrerr)
564
- return 1
565
- end
566
-
567
-
568
- class Utf8Error < ::StandardError
569
- end
570
-
571
-
572
- class Error < ::StandardError
573
- end
574
-
575
-
576
- Utagx = 0x80 # 1000 0000
577
- Utag2 = 0xc0 # 1100 0000
578
- Utag3 = 0xe0 # 1110 0000
579
- Utag4 = 0xf0 # 1111 0000
580
- Utag5 = 0xF8 # 1111 1000
581
- Umaskx = 0x3f # 0011 1111
582
- Umask2 = 0x1f # 0001 1111
583
- Umask3 = 0x0f # 0000 1111
584
- Umask4 = 0x07 # 0000 0111
585
- Uchar1max = (1<<7) - 1
586
- Uchar2max = (1<<11) - 1
587
- Uchar3max = (1<<16) - 1
588
- Ucharerr = 0xFFFD # unicode "replacement char"
589
- Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
590
- Usurrself = 0x10000
591
- Usurr1 = 0xd800
592
- Usurr2 = 0xdc00
593
- Usurr3 = 0xe000
594
-
595
- Spc = ' '[0]
596
- Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
597
- end
598
- end