textlint-ruby 0.1.1 → 2.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 +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +4 -5
- data/bin/textlint-ruby +1 -18
- data/lib/textlint/cli.rb +92 -0
- data/lib/textlint/parser.rb +104 -26
- data/lib/textlint/server.rb +115 -0
- data/lib/textlint/version.rb +7 -1
- data/lib/textlint.rb +5 -1
- data/textlint.gemspec +1 -1
- metadata +5 -5
- data/bin/textlint-ruby-optimized +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5bcefd07ee04b3840f45267770a1faa99fe9ed9f7f5161b11df94fa0839bcb62
|
4
|
+
data.tar.gz: d2dd891a4a2ce1866d13dd6963b3944494f2ca19ad15aec67859743edb6c41b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9658421db72d38e73718eae4325d64b15d9b871976e8db31dda7ae2e3630348f66fbf30c30cdd18744f980e3cbdc5c34e89f02d15f455ed687a1176a377756fc
|
7
|
+
data.tar.gz: 979b738b6230d5d717a0d6e534128867274d641a003603f6ea8050b6e9462c5cd18dc2294e348d25f31078f3ea7dd42f4d3187ca8bef2ec6bcf78db0509e86ea
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -4,8 +4,6 @@ Ruby AST parser for [textlint-ruby-plugin](https://github.com/alpaca-tc/textlint
|
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
|
-
**TODO: release gems**
|
8
|
-
|
9
7
|
Add this line to your application's Gemfile:
|
10
8
|
|
11
9
|
```ruby
|
@@ -25,11 +23,12 @@ Or install it yourself as:
|
|
25
23
|
Parse ruby to textlint AST
|
26
24
|
|
27
25
|
```sh
|
26
|
+
# Parse specific file
|
28
27
|
$ textlint-ruby ./path/to/file.rb
|
29
28
|
{"type":"Document","raw":"...","range":[0,465],"loc":{"start":{"line":1,"column":0},"end":{"line":26,"column":0}},"children":[...]}
|
30
29
|
|
31
|
-
# textlint-ruby
|
32
|
-
$ textlint-ruby
|
30
|
+
# Boot textlint-ruby server for textlint-plugin-ruby
|
31
|
+
$ textlint-ruby --stdio
|
33
32
|
```
|
34
33
|
|
35
34
|
### Supported nodes
|
@@ -69,7 +68,7 @@ Supported node types are only `Document` and `Str` because this plugin is used t
|
|
69
68
|
| ASTNodeTypes.ImageExit | TxtNode | |
|
70
69
|
| ASTNodeTypes.HorizontalRule | TxtNode | |
|
71
70
|
| ASTNodeTypes.HorizontalRuleExit | TxtNode | |
|
72
|
-
| ASTNodeTypes.Comment | TxtTextNode |
|
71
|
+
| ASTNodeTypes.Comment | TxtTextNode | yes |
|
73
72
|
| ASTNodeTypes.CommentExit | TxtTextNode | |
|
74
73
|
| ASTNodeTypes.Str | TxtTextNode | yes |
|
75
74
|
| ASTNodeTypes.StrExit | TxtTextNode | |
|
data/bin/textlint-ruby
CHANGED
@@ -5,21 +5,4 @@ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
|
5
5
|
|
6
6
|
require 'textlint'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
unless File.exist?(path.to_s)
|
11
|
-
warn("Error: No such file or directory: #{path}")
|
12
|
-
exit(1)
|
13
|
-
end
|
14
|
-
|
15
|
-
content = File.read(path)
|
16
|
-
|
17
|
-
begin
|
18
|
-
ast = Textlint::Parser.parse(content)
|
19
|
-
puts(ast.as_textlint_json.to_json)
|
20
|
-
rescue Textlint::SyntaxError => error
|
21
|
-
warn("Failed to compile: #{path}")
|
22
|
-
warn('')
|
23
|
-
warn(error)
|
24
|
-
exit(1)
|
25
|
-
end
|
8
|
+
Textlint::Cli.run(ARGV.clone)
|
data/lib/textlint/cli.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module Textlint
|
6
|
+
class Cli
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
stdio: false,
|
11
|
+
paths: []
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
# @param argv [Array] ARGV
|
15
|
+
def self.run(argv)
|
16
|
+
new(argv).run
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param argv [Array] ARGV
|
20
|
+
def initialize(argv)
|
21
|
+
@options = DEFAULT_OPTIONS.dup
|
22
|
+
parse_options(argv)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Run textlint-ruby
|
26
|
+
#
|
27
|
+
# @return [void]
|
28
|
+
def run
|
29
|
+
if @options[:stdio]
|
30
|
+
run_stdio_server
|
31
|
+
else
|
32
|
+
run_file_parser
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def run_stdio_server
|
39
|
+
Textlint::Server.new.start
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_file_parser
|
43
|
+
path = @options[:path]
|
44
|
+
|
45
|
+
unless File.exist?(path.to_s)
|
46
|
+
warn("Error: No such file or directory: #{path}")
|
47
|
+
exit(1)
|
48
|
+
end
|
49
|
+
|
50
|
+
content = File.read(path)
|
51
|
+
|
52
|
+
begin
|
53
|
+
ast = Textlint::Parser.parse(content)
|
54
|
+
puts(ast.as_textlint_json.to_json)
|
55
|
+
rescue Textlint::SyntaxError => error
|
56
|
+
warn("Failed to compile: #{path}")
|
57
|
+
warn('')
|
58
|
+
warn(error)
|
59
|
+
exit(1)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_options(argv)
|
64
|
+
option_parser = ::OptionParser.new do |parser|
|
65
|
+
parser.banner = 'Usage: textlint-ruby [rubyfile_path] [options]'
|
66
|
+
parser.program_name = 'textlint-ruby'
|
67
|
+
|
68
|
+
parser.version = [
|
69
|
+
Textlint::VERSION::MAJOR,
|
70
|
+
Textlint::VERSION::MINOR,
|
71
|
+
Textlint::VERSION::TINY
|
72
|
+
]
|
73
|
+
|
74
|
+
parser.on('--stdio', 'use stdio') do
|
75
|
+
@options[:stdio] = true
|
76
|
+
end
|
77
|
+
|
78
|
+
parser.on_tail('-h', '--help') do
|
79
|
+
puts option_parser.help
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@options[:path] = option_parser.parse(argv).first
|
85
|
+
|
86
|
+
if !@options[:stdio] && !@options[:path]
|
87
|
+
puts option_parser.help
|
88
|
+
exit(1)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/textlint/parser.rb
CHANGED
@@ -5,6 +5,8 @@ require 'ripper'
|
|
5
5
|
module Textlint
|
6
6
|
class Parser
|
7
7
|
class RubyToTextlintAST < ::Ripper::Filter
|
8
|
+
EVENT_RE = /\Aon_(?<name>\w*)_(?:beg|end)\z/.freeze
|
9
|
+
|
8
10
|
# @param src [String]
|
9
11
|
# @param lines [Array<String>]
|
10
12
|
def initialize(src)
|
@@ -12,20 +14,23 @@ module Textlint
|
|
12
14
|
@src = src
|
13
15
|
@pos = 0
|
14
16
|
@lines = @src.lines
|
17
|
+
@events = []
|
15
18
|
end
|
16
19
|
|
17
20
|
private
|
18
21
|
|
22
|
+
# All events call this method
|
19
23
|
# NOTE: Instance variables are allowed to assign only here to readable code.
|
20
24
|
def on_default(event, token, node)
|
21
25
|
@token = token
|
26
|
+
@event = event
|
22
27
|
|
23
28
|
method_name = :"custom_#{event}"
|
24
29
|
|
25
30
|
if respond_to?(method_name, true)
|
26
31
|
@range = @pos...(@pos + @token.size)
|
27
32
|
@raw = @src[@range]
|
28
|
-
|
33
|
+
send(method_name, node)
|
29
34
|
end
|
30
35
|
|
31
36
|
@pos += @token.size
|
@@ -34,6 +39,14 @@ module Textlint
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def default_node_attributes(type:, **attributes)
|
42
|
+
break_count = @token.scan(Textlint::BREAK_RE).size
|
43
|
+
|
44
|
+
last_column = if break_count == 0
|
45
|
+
column + @token.size
|
46
|
+
else
|
47
|
+
@token.match(LAST_LINE_RE).to_s.size
|
48
|
+
end
|
49
|
+
|
37
50
|
{
|
38
51
|
type: type,
|
39
52
|
raw: @raw,
|
@@ -43,12 +56,21 @@ module Textlint
|
|
43
56
|
line: lineno,
|
44
57
|
column: column
|
45
58
|
),
|
46
|
-
end:
|
59
|
+
end: Textlint::Nodes::TxtNodePosition.new(
|
60
|
+
line: lineno + break_count,
|
61
|
+
column: last_column
|
62
|
+
)
|
47
63
|
)
|
48
64
|
}.merge(attributes)
|
49
65
|
end
|
50
66
|
|
67
|
+
# "hello world"
|
68
|
+
# 'hello world'
|
69
|
+
# %q{hello world}
|
51
70
|
def custom_on_tstring_content(parentNode)
|
71
|
+
begin_event_name, _begin_node = @events.last
|
72
|
+
return unless %w[tstring qwords].include?(begin_event_name)
|
73
|
+
|
52
74
|
node = Textlint::Nodes::TxtTextNode.new(
|
53
75
|
**default_node_attributes(
|
54
76
|
type: Textlint::Nodes::STR,
|
@@ -61,20 +83,68 @@ module Textlint
|
|
61
83
|
parentNode
|
62
84
|
end
|
63
85
|
|
64
|
-
|
65
|
-
|
86
|
+
# # hello world
|
87
|
+
def custom_on_comment(parentNode)
|
88
|
+
node = Textlint::Nodes::TxtTextNode.new(
|
89
|
+
**default_node_attributes(
|
90
|
+
type: Textlint::Nodes::COMMENT,
|
91
|
+
value: @token.gsub(/\A#/, '')
|
92
|
+
)
|
93
|
+
)
|
66
94
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
95
|
+
parentNode.children.push(node)
|
96
|
+
end
|
97
|
+
|
98
|
+
# =begin
|
99
|
+
# hello world
|
100
|
+
# =end
|
101
|
+
def custom_on_embdoc(parentNode)
|
102
|
+
_begin_event_name, begin_node = @events.last
|
103
|
+
|
104
|
+
node = Textlint::Nodes::TxtTextNode.new(
|
105
|
+
type: Textlint::Nodes::COMMENT,
|
106
|
+
loc: begin_node.loc,
|
107
|
+
range: begin_node.range,
|
108
|
+
raw: begin_node.raw,
|
109
|
+
value: @token
|
110
|
+
)
|
111
|
+
|
112
|
+
parentNode.children.push(node)
|
113
|
+
end
|
72
114
|
|
73
|
-
|
74
|
-
|
75
|
-
|
115
|
+
def event_name
|
116
|
+
matched = EVENT_RE.match(@event)
|
117
|
+
matched[:name] if matched
|
118
|
+
end
|
119
|
+
|
120
|
+
def on_beg_event(*)
|
121
|
+
@events.push(
|
122
|
+
[
|
123
|
+
event_name,
|
124
|
+
Textlint::Nodes::TxtTextNode.new(
|
125
|
+
**default_node_attributes(
|
126
|
+
type: nil, # NOTE: beg event has no type
|
127
|
+
value: @token
|
128
|
+
)
|
129
|
+
)
|
130
|
+
]
|
76
131
|
)
|
77
132
|
end
|
133
|
+
|
134
|
+
def on_end_event(*)
|
135
|
+
@events.pop
|
136
|
+
end
|
137
|
+
|
138
|
+
alias custom_on_tstring_beg on_beg_event
|
139
|
+
alias custom_on_regexp_beg on_beg_event
|
140
|
+
alias custom_on_embexpr_beg on_beg_event
|
141
|
+
alias custom_on_qwords_beg on_beg_event
|
142
|
+
alias custom_on_embdoc_beg on_beg_event
|
143
|
+
|
144
|
+
alias custom_on_tstring_end on_end_event
|
145
|
+
alias custom_on_regexp_end on_end_event
|
146
|
+
alias custom_on_embexpr_end on_end_event
|
147
|
+
alias custom_on_embdoc_end on_end_event
|
78
148
|
end
|
79
149
|
|
80
150
|
# Parse ruby code to AST for textlint
|
@@ -86,33 +156,41 @@ module Textlint
|
|
86
156
|
new(src).call
|
87
157
|
end
|
88
158
|
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
# Parse ruby code to AST for textlint
|
159
|
+
# Parse ruby code to TxtParentNode
|
160
|
+
#
|
161
|
+
# @param src [String]
|
95
162
|
#
|
96
163
|
# @return [Textlint::Nodes::TxtParentNode]
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
document = Textlint::Nodes::TxtParentNode.new(
|
164
|
+
def self.build_document(src)
|
165
|
+
Textlint::Nodes::TxtParentNode.new(
|
101
166
|
type: Textlint::Nodes::DOCUMENT,
|
102
|
-
raw:
|
103
|
-
range: 0
|
167
|
+
raw: src,
|
168
|
+
range: 0...src.size,
|
104
169
|
loc: Textlint::Nodes::TxtNodeLineLocation.new(
|
105
170
|
start: Textlint::Nodes::TxtNodePosition.new(
|
106
171
|
line: 1,
|
107
172
|
column: 0
|
108
173
|
),
|
109
174
|
end: Textlint::Nodes::TxtNodePosition.new(
|
110
|
-
line:
|
111
|
-
column:
|
175
|
+
line: src.split(Textlint::BREAK_RE).size + 1,
|
176
|
+
column: src.match(LAST_LINE_RE).to_s.size # extract last line
|
112
177
|
)
|
113
178
|
)
|
114
179
|
)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @param src [String] ruby source code
|
183
|
+
def initialize(src)
|
184
|
+
@src = src
|
185
|
+
end
|
186
|
+
|
187
|
+
# Parse ruby code to AST for textlint
|
188
|
+
#
|
189
|
+
# @return [Textlint::Nodes::TxtParentNode]
|
190
|
+
def call
|
191
|
+
check_syntax!
|
115
192
|
|
193
|
+
document = self.class.build_document(@src)
|
116
194
|
RubyToTextlintAST.new(@src).parse(document)
|
117
195
|
end
|
118
196
|
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Textlint
|
6
|
+
class Server
|
7
|
+
REQUIRED_VERSION = '2.0.0'
|
8
|
+
|
9
|
+
AVAILABLE_ACTIONS = %w[
|
10
|
+
parse
|
11
|
+
info
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
# @param stdin [IO]
|
15
|
+
# @param stdout [IO]
|
16
|
+
# @param stderr [IO]
|
17
|
+
def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr)
|
18
|
+
@stdin = stdin
|
19
|
+
@stdout = stdout
|
20
|
+
@stderr = stderr
|
21
|
+
end
|
22
|
+
|
23
|
+
# Start stdio server
|
24
|
+
#
|
25
|
+
# @return [void]
|
26
|
+
def start
|
27
|
+
@stdout.sync = true
|
28
|
+
trap_signals
|
29
|
+
start_server
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def start_server
|
35
|
+
receive_stdin do |json|
|
36
|
+
action = json['action']
|
37
|
+
result = send(:"do_#{action}", json)
|
38
|
+
request_seq = json['seq']
|
39
|
+
response = { request_seq: request_seq, result: result }
|
40
|
+
|
41
|
+
@stdout.puts(JSON.dump(response))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_request(json)
|
46
|
+
if Gem::Version.create(json['version'].to_s) < Gem::Version.create(REQUIRED_VERSION)
|
47
|
+
raise(Textlint::RequestError, "textlint-ruby requires textlin-plugin-ruby version >= #{REQUIRED_VERSION}")
|
48
|
+
elsif !AVAILABLE_ACTIONS.include?(json['action'])
|
49
|
+
raise(Textlint::RequestError, "Unknown action(#{json['action']}) given. Available actions are #{AVAILABLE_ACTIONS}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# request spec
|
54
|
+
# { "seq": number, "action": string, "version": string, [key: string]: any }
|
55
|
+
#
|
56
|
+
# response spec
|
57
|
+
# action "info"
|
58
|
+
# { "request_seq": number, "version": string, result: string(version) }
|
59
|
+
#
|
60
|
+
# action "parse"
|
61
|
+
# { "request_seq": number, "version": string, result: string(AST for textlint) }
|
62
|
+
def receive_stdin
|
63
|
+
loop do
|
64
|
+
return if @stdin.eof?
|
65
|
+
|
66
|
+
line = @stdin.readline
|
67
|
+
|
68
|
+
begin
|
69
|
+
json = JSON.parse(line)
|
70
|
+
validate_request(json)
|
71
|
+
yield(json)
|
72
|
+
rescue JSON::JSONError
|
73
|
+
warn("Can't parse request to JSON")
|
74
|
+
rescue StandardError => error
|
75
|
+
warn(error.message)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def warn(message)
|
81
|
+
@stderr.puts(message)
|
82
|
+
end
|
83
|
+
|
84
|
+
def trap_signals
|
85
|
+
::Signal.trap(:SIGINT) do
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def do_info(_json)
|
91
|
+
Textlint::VERSION::STRING
|
92
|
+
end
|
93
|
+
|
94
|
+
def do_parse(json)
|
95
|
+
path = json['path'].to_s
|
96
|
+
|
97
|
+
unless File.exist?(path)
|
98
|
+
raise(Textlint::RequestError, "Error: No such file or directory: #{path}")
|
99
|
+
end
|
100
|
+
|
101
|
+
content = File.read(path)
|
102
|
+
|
103
|
+
ast = begin
|
104
|
+
Textlint::Parser.parse(content)
|
105
|
+
rescue Textlint::SyntaxError
|
106
|
+
error = Textlint::RequestError.new("Failed to parse: #{path}. syntax error or the file is incompatible with the ruby(#{RUBY_VERSION}) running textlint-ruby")
|
107
|
+
warn(error)
|
108
|
+
|
109
|
+
Textlint::Parser.build_document(content)
|
110
|
+
end
|
111
|
+
|
112
|
+
ast.as_textlint_json
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/textlint/version.rb
CHANGED
data/lib/textlint.rb
CHANGED
@@ -4,10 +4,14 @@ require 'json'
|
|
4
4
|
require 'textlint/version'
|
5
5
|
require 'textlint/nodes'
|
6
6
|
require 'textlint/parser'
|
7
|
+
require 'textlint/cli'
|
8
|
+
require 'textlint/server'
|
7
9
|
|
8
10
|
module Textlint
|
9
11
|
BREAK_RE = /\r?\n/.freeze
|
10
12
|
LAST_LINE_RE = /(?!\r?\n).*\z/.freeze
|
11
13
|
|
12
|
-
class
|
14
|
+
class Error < StandardError; end
|
15
|
+
class SyntaxError < Error; end
|
16
|
+
class RequestError < Error; end
|
13
17
|
end
|
data/textlint.gemspec
CHANGED
metadata
CHANGED
@@ -1,21 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: textlint-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alpaca-tc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: ''
|
14
14
|
email:
|
15
15
|
- alpaca-tc@alpaca.tc
|
16
16
|
executables:
|
17
17
|
- textlint-ruby
|
18
|
-
- textlint-ruby-optimized
|
19
18
|
extensions: []
|
20
19
|
extra_rdoc_files: []
|
21
20
|
files:
|
@@ -31,11 +30,12 @@ files:
|
|
31
30
|
- README.md
|
32
31
|
- Rakefile
|
33
32
|
- bin/textlint-ruby
|
34
|
-
- bin/textlint-ruby-optimized
|
35
33
|
- lib/textlint-ruby.rb
|
36
34
|
- lib/textlint.rb
|
35
|
+
- lib/textlint/cli.rb
|
37
36
|
- lib/textlint/nodes.rb
|
38
37
|
- lib/textlint/parser.rb
|
38
|
+
- lib/textlint/server.rb
|
39
39
|
- lib/textlint/version.rb
|
40
40
|
- textlint.gemspec
|
41
41
|
homepage: https://github.com/alpaca-tc/textlint-ruby
|
@@ -60,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
requirements: []
|
63
|
-
rubygems_version: 3.2.
|
63
|
+
rubygems_version: 3.2.22
|
64
64
|
signing_key:
|
65
65
|
specification_version: 4
|
66
66
|
summary: ruby source code parser for textlint
|
data/bin/textlint-ruby-optimized
DELETED