simple_english 0.3.0 → 0.4.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 +4 -4
- data/README.md +47 -31
- data/docs/RULES.md +1 -1
- data/lib/simple_english/cli.rb +36 -13
- data/lib/simple_english/client/daemon.rb +225 -0
- data/lib/simple_english/client/language_tool.rb +95 -0
- data/lib/simple_english/daemon/engine.rb +40 -0
- data/lib/simple_english/{http.rb → daemon/http.rb} +10 -4
- data/lib/simple_english/{server.rb → daemon/server.rb} +113 -7
- data/lib/simple_english/lint/lint_plan.rb +22 -0
- data/lib/simple_english/lint/plain_text.rb +30 -0
- data/lib/simple_english/{result.rb → lint/result.rb} +1 -1
- data/lib/simple_english/{config.rb → setup/config.rb} +3 -2
- data/lib/simple_english/setup/fingerprint.rb +38 -0
- data/lib/simple_english/{install.rb → setup/install.rb} +19 -9
- data/lib/simple_english/setup/languagetool.rb +226 -0
- data/lib/simple_english/version.rb +1 -1
- data/lib/simple_english.rb +25 -43
- metadata +39 -36
- data/lib/simple_english/client.rb +0 -166
- data/lib/simple_english/engine.rb +0 -31
- data/lib/simple_english/languagetool.rb +0 -143
- data/lib/simple_english/plain_text.rb +0 -17
- /data/lib/simple_english/{annotated_text.rb → lint/annotated_text.rb} +0 -0
- /data/lib/simple_english/{counts.rb → lint/counts.rb} +0 -0
- /data/lib/simple_english/{extractor.rb → lint/extractor.rb} +0 -0
- /data/lib/simple_english/{finding.rb → lint/finding.rb} +0 -0
- /data/lib/simple_english/{markdown.rb → lint/markdown.rb} +0 -0
- /data/lib/simple_english/{paragraph.rb → lint/paragraph.rb} +0 -0
- /data/lib/simple_english/{segment.rb → lint/segment.rb} +0 -0
- /data/lib/simple_english/{span.rb → lint/span.rb} +0 -0
- /data/lib/simple_english/{suppressions.rb → lint/suppressions.rb} +0 -0
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simple_english
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TonyCTHsu
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tree_sitter_language_pack
|
|
@@ -39,63 +39,63 @@ dependencies:
|
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '1.0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: rake
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
48
|
-
type: :
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '13.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: minitest
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
61
|
+
version: '6.0'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
68
|
+
version: '6.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: minitest
|
|
70
|
+
name: minitest-mock
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
75
|
+
version: '5.27'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
82
|
+
version: '5.27'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: rubyzip
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
89
|
+
version: '3.0'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
96
|
+
version: '3.0'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
98
|
+
name: rexml
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
101
|
- - ">="
|
|
@@ -109,7 +109,7 @@ dependencies:
|
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
112
|
+
name: json
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
115
|
- - ">="
|
|
@@ -135,25 +135,28 @@ files:
|
|
|
135
135
|
- bin/se
|
|
136
136
|
- docs/RULES.md
|
|
137
137
|
- lib/simple_english.rb
|
|
138
|
-
- lib/simple_english/annotated_text.rb
|
|
139
138
|
- lib/simple_english/cli.rb
|
|
140
|
-
- lib/simple_english/client.rb
|
|
141
|
-
- lib/simple_english/
|
|
142
|
-
- lib/simple_english/
|
|
143
|
-
- lib/simple_english/
|
|
144
|
-
- lib/simple_english/
|
|
145
|
-
- lib/simple_english/
|
|
146
|
-
- lib/simple_english/
|
|
147
|
-
- lib/simple_english/
|
|
148
|
-
- lib/simple_english/
|
|
149
|
-
- lib/simple_english/
|
|
150
|
-
- lib/simple_english/
|
|
151
|
-
- lib/simple_english/
|
|
152
|
-
- lib/simple_english/
|
|
153
|
-
- lib/simple_english/
|
|
154
|
-
- lib/simple_english/
|
|
155
|
-
- lib/simple_english/span.rb
|
|
156
|
-
- lib/simple_english/suppressions.rb
|
|
139
|
+
- lib/simple_english/client/daemon.rb
|
|
140
|
+
- lib/simple_english/client/language_tool.rb
|
|
141
|
+
- lib/simple_english/daemon/engine.rb
|
|
142
|
+
- lib/simple_english/daemon/http.rb
|
|
143
|
+
- lib/simple_english/daemon/server.rb
|
|
144
|
+
- lib/simple_english/lint/annotated_text.rb
|
|
145
|
+
- lib/simple_english/lint/counts.rb
|
|
146
|
+
- lib/simple_english/lint/extractor.rb
|
|
147
|
+
- lib/simple_english/lint/finding.rb
|
|
148
|
+
- lib/simple_english/lint/lint_plan.rb
|
|
149
|
+
- lib/simple_english/lint/markdown.rb
|
|
150
|
+
- lib/simple_english/lint/paragraph.rb
|
|
151
|
+
- lib/simple_english/lint/plain_text.rb
|
|
152
|
+
- lib/simple_english/lint/result.rb
|
|
153
|
+
- lib/simple_english/lint/segment.rb
|
|
154
|
+
- lib/simple_english/lint/span.rb
|
|
155
|
+
- lib/simple_english/lint/suppressions.rb
|
|
156
|
+
- lib/simple_english/setup/config.rb
|
|
157
|
+
- lib/simple_english/setup/fingerprint.rb
|
|
158
|
+
- lib/simple_english/setup/install.rb
|
|
159
|
+
- lib/simple_english/setup/languagetool.rb
|
|
157
160
|
- lib/simple_english/version.rb
|
|
158
161
|
- rules/simple-english.xml
|
|
159
162
|
homepage: https://github.com/TonyCTHsu/simple-english
|
|
@@ -162,7 +165,7 @@ licenses:
|
|
|
162
165
|
metadata:
|
|
163
166
|
homepage_uri: https://github.com/TonyCTHsu/simple-english
|
|
164
167
|
source_code_uri: https://github.com/TonyCTHsu/simple-english
|
|
165
|
-
changelog_uri: https://github.com/TonyCTHsu/simple-english/blob/v0.
|
|
168
|
+
changelog_uri: https://github.com/TonyCTHsu/simple-english/blob/v0.4.0/CHANGELOG.md
|
|
166
169
|
post_install_message:
|
|
167
170
|
rdoc_options: []
|
|
168
171
|
require_paths:
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# HTTP client for the LanguageTool server and the se daemon.
|
|
4
|
-
|
|
5
|
-
require "json"
|
|
6
|
-
require "net/http"
|
|
7
|
-
require "rbconfig"
|
|
8
|
-
require "uri"
|
|
9
|
-
|
|
10
|
-
require_relative "plain_text"
|
|
11
|
-
require_relative "languagetool"
|
|
12
|
-
|
|
13
|
-
module SimpleEnglish
|
|
14
|
-
module Client
|
|
15
|
-
module_function
|
|
16
|
-
|
|
17
|
-
# LanguageTool reports offsets and lengths in Java UTF-16 code units.
|
|
18
|
-
# Return a 1-based line and UTF-16 column for its 0-based offset.
|
|
19
|
-
def offset_to_position(text, offset)
|
|
20
|
-
line = 1
|
|
21
|
-
column = 1
|
|
22
|
-
units = 0
|
|
23
|
-
text.each_char do |char|
|
|
24
|
-
return [line, column] if units >= offset
|
|
25
|
-
units += (char.ord > 0xFFFF) ? 2 : 1
|
|
26
|
-
if char == "\n"
|
|
27
|
-
line += 1
|
|
28
|
-
column = 1
|
|
29
|
-
else
|
|
30
|
-
column += (char.ord > 0xFFFF) ? 2 : 1
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
[line, column]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
DEFAULT_PORT = 8181
|
|
37
|
-
REQUEST_TIMEOUT = 30
|
|
38
|
-
|
|
39
|
-
def url
|
|
40
|
-
ENV.fetch("SE_SERVER_URL") { "http://localhost:#{DEFAULT_PORT}" }
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def post(uri, params, read_timeout: REQUEST_TIMEOUT)
|
|
44
|
-
Net::HTTP.start(uri.host, uri.port, open_timeout: 5,
|
|
45
|
-
read_timeout: read_timeout) do |http|
|
|
46
|
-
request = Net::HTTP::Post.new(uri.request_uri)
|
|
47
|
-
request.set_form_data(params)
|
|
48
|
-
http.request(request)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
private_class_method :post
|
|
53
|
-
|
|
54
|
-
# Pattern rules. `payload` is Markdown-stripped text (String) or an
|
|
55
|
-
# AnnotatedText::Result (code comments): one payload interface,
|
|
56
|
-
# #lt_params and #locate, either side of the daemon's LT request.
|
|
57
|
-
def check(payload, base_url: url)
|
|
58
|
-
payload = to_payload(payload)
|
|
59
|
-
params = {"language" => "en",
|
|
60
|
-
"enabledRules" => SimpleEnglish::LanguageTool.rule_ids.join(","),
|
|
61
|
-
"enabledOnly" => "true"}.merge(payload.lt_params)
|
|
62
|
-
response = post(URI("#{base_url}/v2/check"), params)
|
|
63
|
-
parse_matches(JSON.parse(response.body).fetch("matches"), payload)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def parse_matches(matches, payload)
|
|
67
|
-
payload = to_payload(payload)
|
|
68
|
-
matches.map do |match|
|
|
69
|
-
offset = match.fetch("offset")
|
|
70
|
-
line, column = payload.locate(offset)
|
|
71
|
-
end_line, end_column = payload.locate(offset + match.fetch("length"))
|
|
72
|
-
Finding.new(line: line, column: column,
|
|
73
|
-
end_line: end_line, end_column: end_column,
|
|
74
|
-
rule: match.fetch("rule").fetch("id"),
|
|
75
|
-
message: with_context(match.fetch("message"), match))
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Prefix the offending text, so a finding says what to change,
|
|
80
|
-
# not only how. Context positions use Java UTF-16 code units.
|
|
81
|
-
def with_context(message, match)
|
|
82
|
-
context = match["context"] or return message
|
|
83
|
-
matched = utf16_slice(context.fetch("text", ""),
|
|
84
|
-
context.fetch("offset", 0).to_i, context.fetch("length", 0).to_i)
|
|
85
|
-
matched.empty? ? message : "\"#{matched}\" - #{message}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def utf16_slice(text, offset, length)
|
|
89
|
-
first = utf16_index(text, offset)
|
|
90
|
-
last = utf16_index(text, offset + length)
|
|
91
|
-
text[first...last]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def utf16_index(text, offset)
|
|
95
|
-
units = 0
|
|
96
|
-
text.each_char.with_index do |char, index|
|
|
97
|
-
return index if units >= offset
|
|
98
|
-
units += (char.ord > 0xFFFF) ? 2 : 1
|
|
99
|
-
end
|
|
100
|
-
text.length
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def to_payload(payload)
|
|
104
|
-
payload.is_a?(String) ? PlainText.new(payload) : payload
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
private_class_method :to_payload, :with_context, :utf16_slice, :utf16_index
|
|
108
|
-
|
|
109
|
-
# Full lint via the se daemon. Raw Markdown in, or code
|
|
110
|
-
# source with a language for the comment pipeline. nil when
|
|
111
|
-
# unreachable or the response is unusable, so callers can fall
|
|
112
|
-
# back without seeing a stack trace.
|
|
113
|
-
def lint(text, base_url: url, language: nil)
|
|
114
|
-
params = {"text" => text}
|
|
115
|
-
params["language"] = language if language
|
|
116
|
-
response = post(URI("#{base_url}/lint"), params)
|
|
117
|
-
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
118
|
-
body = JSON.parse(response.body)
|
|
119
|
-
return nil unless body.is_a?(Array)
|
|
120
|
-
body.map do |hash|
|
|
121
|
-
Finding.new(line: hash.fetch("line"), column: hash["column"],
|
|
122
|
-
end_line: hash["end_line"], end_column: hash["end_column"],
|
|
123
|
-
rule: hash.fetch("rule"), message: hash.fetch("message"))
|
|
124
|
-
end
|
|
125
|
-
rescue SystemCallError, SocketError, Timeout::Error,
|
|
126
|
-
JSON::ParserError, TypeError
|
|
127
|
-
nil
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def up?(base_url: url)
|
|
131
|
-
uri = URI(base_url)
|
|
132
|
-
Net::HTTP.start(uri.host, uri.port, open_timeout: 1,
|
|
133
|
-
read_timeout: 2) { |http| http.head("/") }
|
|
134
|
-
true
|
|
135
|
-
rescue Errno::ECONNREFUSED, SocketError, Timeout::Error
|
|
136
|
-
false
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# True when the daemon answers. Starts it when we own the default
|
|
140
|
-
# URL. A custom SE_SERVER_URL belongs to someone else, so
|
|
141
|
-
# never spawn against it. It returns false when unusable. The
|
|
142
|
-
# caller owns the exit status. Diagnostics go to stderr here, where the cause
|
|
143
|
-
# is known.
|
|
144
|
-
def ensure_up(install: SimpleEnglish::Install.from_env)
|
|
145
|
-
return true if up?
|
|
146
|
-
if ENV["SE_SERVER_URL"]
|
|
147
|
-
warn "error: SE_SERVER_URL is set but #{url} does not answer."
|
|
148
|
-
return false
|
|
149
|
-
end
|
|
150
|
-
unless File.exist?(install.server_jar)
|
|
151
|
-
warn install.setup_error
|
|
152
|
-
return false
|
|
153
|
-
end
|
|
154
|
-
warn "se: daemon not running; starting it (first lint takes ~15s)..."
|
|
155
|
-
bin = File.expand_path("../../bin/se", __dir__)
|
|
156
|
-
Process.spawn(RbConfig.ruby, bin, "serve", out: File::NULL, err: File::NULL)
|
|
157
|
-
deadline = Time.now + 90
|
|
158
|
-
until Time.now > deadline
|
|
159
|
-
return true if up?
|
|
160
|
-
sleep 0.5
|
|
161
|
-
end
|
|
162
|
-
warn "error: se daemon did not come up. Run `se serve` and read its output."
|
|
163
|
-
false
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# The complete lint engine, daemon-side. Never call SimpleEnglish.lint_text
|
|
4
|
-
# here: that call probes the daemon and recurses into the server.
|
|
5
|
-
|
|
6
|
-
require "json"
|
|
7
|
-
|
|
8
|
-
module SimpleEnglish
|
|
9
|
-
module Engine
|
|
10
|
-
module_function
|
|
11
|
-
|
|
12
|
-
def lint(text, base_url:, language: nil)
|
|
13
|
-
if language
|
|
14
|
-
spans = SimpleEnglish::Extractor.comment_spans(text, language)
|
|
15
|
-
spans.empty? ? [] :
|
|
16
|
-
SimpleEnglish::Client.check(
|
|
17
|
-
SimpleEnglish::AnnotatedText.build(text, spans), base_url: base_url
|
|
18
|
-
)
|
|
19
|
-
else
|
|
20
|
-
stripped = SimpleEnglish::Markdown.strip(text)
|
|
21
|
-
(SimpleEnglish::Counts.check(stripped) +
|
|
22
|
-
SimpleEnglish::Client.check(stripped, base_url: base_url))
|
|
23
|
-
.sort_by { |finding| [finding.line, finding.rule] }
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def lint_json(text, base_url:, language: nil)
|
|
28
|
-
JSON.generate(lint(text, base_url: base_url, language: language).map(&:to_h))
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# The pinned LanguageTool distro: its version, its rules file, and the
|
|
4
|
-
# downloader. Everything that resolves "where it is on this machine and
|
|
5
|
-
# which java runs it" lives in Install. The engine runs as the daemon's
|
|
6
|
-
# inner HTTP server (lib/simple_english/server.rb).
|
|
7
|
-
|
|
8
|
-
module SimpleEnglish
|
|
9
|
-
module LanguageTool
|
|
10
|
-
RULES_FILE = File.expand_path("../../rules/simple-english.xml", __dir__)
|
|
11
|
-
# The only place a LanguageTool version number appears. `se
|
|
12
|
-
# setup` downloads this version, and every jar path derives from it. No
|
|
13
|
-
# env var can point at another one.
|
|
14
|
-
LT_VERSION = "6.6"
|
|
15
|
-
CACHE_DIR_ENV = "SE_CACHE_DIR"
|
|
16
|
-
JAVA_ENV = "SE_JAVA"
|
|
17
|
-
# macOS ships a /usr/bin/java stub that reports no runtime. Homebrew
|
|
18
|
-
# installs the real one here, outside PATH.
|
|
19
|
-
HOMEBREW_JAVA = "/opt/homebrew/opt/openjdk/bin/java"
|
|
20
|
-
TIMEOUT_SECONDS = 300
|
|
21
|
-
|
|
22
|
-
module_function
|
|
23
|
-
|
|
24
|
-
# Download and unpack the pinned LanguageTool into DIR. Idempotent:
|
|
25
|
-
# returns the install directory, or nil after warning why. Pure Ruby
|
|
26
|
-
# throughout: the download uses Net::HTTP and the unpack uses rubyzip,
|
|
27
|
-
# so setup needs no curl or unzip on the machine.
|
|
28
|
-
def install(dir)
|
|
29
|
-
require "fileutils"
|
|
30
|
-
dest = File.join(dir, "LanguageTool-#{LT_VERSION}")
|
|
31
|
-
if File.exist?(File.join(dest, "languagetool-commandline.jar"))
|
|
32
|
-
remove_stale_versions(dir, dest)
|
|
33
|
-
return dest
|
|
34
|
-
end
|
|
35
|
-
zip = File.join(dir, "LanguageTool-#{LT_VERSION}.zip")
|
|
36
|
-
FileUtils.mkdir_p(dir)
|
|
37
|
-
return nil unless download(download_url, zip)
|
|
38
|
-
return nil unless extract(zip, dir)
|
|
39
|
-
File.delete(zip)
|
|
40
|
-
remove_stale_versions(dir, dest)
|
|
41
|
-
dest
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def download_url
|
|
45
|
-
"https://languagetool.org/download/LanguageTool-#{LT_VERSION}.zip"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Streams URL to TO and follows redirects. Warns and returns false
|
|
49
|
-
# on any transport or HTTP failure, so callers never see a stack
|
|
50
|
-
# trace.
|
|
51
|
-
def download(url, to, redirects: 3)
|
|
52
|
-
require "net/http"
|
|
53
|
-
uri = URI(url)
|
|
54
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
|
|
55
|
-
open_timeout: 10, read_timeout: 60) do |http|
|
|
56
|
-
http.request(Net::HTTP::Get.new(uri)) do |response|
|
|
57
|
-
case response
|
|
58
|
-
when Net::HTTPRedirection
|
|
59
|
-
return false if redirects.zero?
|
|
60
|
-
return download(response["location"], to, redirects: redirects - 1)
|
|
61
|
-
when Net::HTTPSuccess
|
|
62
|
-
File.binwrite(to, "")
|
|
63
|
-
File.open(to, "wb") do |file|
|
|
64
|
-
response.read_body { |chunk| file.write(chunk) }
|
|
65
|
-
end
|
|
66
|
-
return true
|
|
67
|
-
else
|
|
68
|
-
warn "error: download failed: HTTP #{response.code} from #{url}"
|
|
69
|
-
return false
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
rescue SystemCallError, SocketError, Timeout::Error,
|
|
74
|
-
OpenSSL::SSL::SSLError, Net::ProtocolError => e
|
|
75
|
-
warn "error: download failed: #{e.class}: #{e.message}"
|
|
76
|
-
false
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Unpacks ZIP into DIR and refuses entries that try to escape it.
|
|
80
|
-
# Warns and returns false on any failure.
|
|
81
|
-
def extract(zip, dir)
|
|
82
|
-
require "zip"
|
|
83
|
-
Zip::File.open(zip) do |archive|
|
|
84
|
-
archive.each do |entry|
|
|
85
|
-
unless safe_entry_target(dir, entry.name)
|
|
86
|
-
warn "error: zip entry escapes the install dir: #{entry.name}"
|
|
87
|
-
return false
|
|
88
|
-
end
|
|
89
|
-
entry.extract(entry.name, destination_directory: dir,
|
|
90
|
-
create_parent_directories: true)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
true
|
|
94
|
-
rescue SystemCallError, Zip::Error => e
|
|
95
|
-
warn "error: unpack failed: #{e.class}: #{e.message}"
|
|
96
|
-
false
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# The path to write zip entry NAME into, or nil when NAME escapes
|
|
100
|
-
# DIR. A download is untrusted input: this is the zip-slip
|
|
101
|
-
# guard.
|
|
102
|
-
def safe_entry_target(dir, name)
|
|
103
|
-
target = File.expand_path(File.join(dir, name))
|
|
104
|
-
base = File.expand_path(dir) + File::SEPARATOR
|
|
105
|
-
target.start_with?(base) ? target : nil
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# One gem version pins one LanguageTool version. Older dirs are
|
|
109
|
-
# hundreds of MB each, so delete them on every setup run and keep
|
|
110
|
-
# only the current version (and any zips: they are re-deleted or
|
|
111
|
-
# re-extracted by their own flow).
|
|
112
|
-
def remove_stale_versions(dir, keep)
|
|
113
|
-
require "fileutils"
|
|
114
|
-
Dir.glob(File.join(dir, "LanguageTool-*")).each do |entry|
|
|
115
|
-
FileUtils.rm_rf(entry) if File.directory?(entry) && entry != keep
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Proves the installation works end to end: java runs the
|
|
120
|
-
# commandline jar and the custom rules load. The command exits
|
|
121
|
-
# nonzero on a broken java, a corrupt jar, or invalid rules XML,
|
|
122
|
-
# so setup can report a real verdict instead of "files exist".
|
|
123
|
-
# Returns a boolean, never raises or warns: the caller owns the
|
|
124
|
-
# message.
|
|
125
|
-
def smoke(install)
|
|
126
|
-
rule = rule_ids.first
|
|
127
|
-
return false if rule.nil?
|
|
128
|
-
require "tmpdir"
|
|
129
|
-
Dir.mktmpdir do |dir|
|
|
130
|
-
text = File.join(dir, "smoke.txt")
|
|
131
|
-
File.write(text, "Check this sentence.\n")
|
|
132
|
-
system(install.java!, "-jar", install.commandline_jar,
|
|
133
|
-
"--language", "en", "--rulefile", RULES_FILE,
|
|
134
|
-
"--enable", rule, "--enabledonly", text,
|
|
135
|
-
out: File::NULL, err: File::NULL) ? true : false
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def rule_ids
|
|
140
|
-
File.read(RULES_FILE).scan(/<rule(?:group)? id="(\w+)"/).flatten
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SimpleEnglish
|
|
4
|
-
module Client
|
|
5
|
-
# The plain-text payload for /v2/check, duck-compatible with
|
|
6
|
-
# AnnotatedText::Result: #lt_params and #locate.
|
|
7
|
-
PlainText = Struct.new(:text) do
|
|
8
|
-
def lt_params
|
|
9
|
-
{"text" => text}
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def locate(utf16_offset)
|
|
13
|
-
Client.offset_to_position(text, utf16_offset)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|