prettyrb 0.1.0 → 0.5.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/.github/workflows/ruby.yml +24 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/Gemfile.lock +23 -9
- data/README.md +17 -3
- data/exe/prettyrb +6 -0
- data/lib/prettyrb.rb +24 -186
- data/lib/prettyrb/builder.rb +20 -0
- data/lib/prettyrb/cli.rb +30 -0
- data/lib/prettyrb/document.rb +164 -0
- data/lib/prettyrb/formatter.rb +33 -0
- data/lib/prettyrb/nodes/and_node.rb +11 -0
- data/lib/prettyrb/nodes/base_node.rb +34 -0
- data/lib/prettyrb/nodes/def_node.rb +17 -0
- data/lib/prettyrb/nodes/dstr_node.rb +20 -0
- data/lib/prettyrb/nodes/if_node.rb +63 -0
- data/lib/prettyrb/nodes/logical_operator_helper.rb +13 -0
- data/lib/prettyrb/nodes/or_node.rb +11 -0
- data/lib/prettyrb/nodes/regexp_node.rb +41 -0
- data/lib/prettyrb/nodes/send_node.rb +63 -0
- data/lib/prettyrb/nodes/str_node.rb +18 -0
- data/lib/prettyrb/nodes/string_helper.rb +43 -0
- data/lib/prettyrb/version.rb +1 -1
- data/lib/prettyrb/visitor.rb +947 -0
- data/lib/prettyrb/writer.rb +157 -0
- data/prettyrb.gemspec +6 -3
- metadata +73 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3ca66d3c436cf92ecaeef327cd56e32380eb956c844cafcc605e471f80e5a9d
|
4
|
+
data.tar.gz: 6a88f83ea9a52c7cebcb0c70d72a8f599c8480e0a628c2eba016ce506ef99996
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3fb301879d8c8905aa82268ac2d04944fc02eb4a3e9d28466b80a70dd3e3b2e1ad93dba3a4916736e5c5533c067eb69b869a610f12ee6746d61ec5165e43d128
|
7
|
+
data.tar.gz: 3a2894fd21b9493aec14b1d85a08e3be929e356ad7dd2dee013409f2ec3dcd47f914ab15d6028146e769533812abb786bbce415d449edcaefd679093c06d5af5
|
@@ -0,0 +1,24 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v2
|
16
|
+
- name: Set up Ruby 2.6
|
17
|
+
uses: actions/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: 2.6.x
|
20
|
+
- name: Build and test with Rake
|
21
|
+
run: |
|
22
|
+
gem install bundler
|
23
|
+
bundle install --jobs 4 --retry 3
|
24
|
+
bundle exec rake
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.6.0
|
data/Gemfile.lock
CHANGED
@@ -1,26 +1,40 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
prettyrb (0.
|
5
|
-
|
4
|
+
prettyrb (0.5.0)
|
5
|
+
parser (~> 2.7.0.5)
|
6
|
+
thor
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
11
|
+
ast (2.4.0)
|
12
|
+
coderay (1.1.2)
|
13
|
+
docile (1.3.2)
|
14
|
+
method_source (1.0.0)
|
10
15
|
minitest (5.14.0)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
parser (2.7.0.5)
|
17
|
+
ast (~> 2.4.0)
|
18
|
+
pry (0.13.1)
|
19
|
+
coderay (~> 1.1)
|
20
|
+
method_source (~> 1.0)
|
21
|
+
rake (12.3.3)
|
22
|
+
simplecov (0.18.5)
|
23
|
+
docile (~> 1.1)
|
24
|
+
simplecov-html (~> 0.11)
|
25
|
+
simplecov-html (0.12.2)
|
26
|
+
thor (1.0.1)
|
15
27
|
|
16
28
|
PLATFORMS
|
17
29
|
ruby
|
18
30
|
|
19
31
|
DEPENDENCIES
|
20
|
-
bundler (~> 1.
|
32
|
+
bundler (~> 2.1.4)
|
21
33
|
minitest (~> 5.0)
|
22
34
|
prettyrb!
|
23
|
-
|
35
|
+
pry (~> 0.13)
|
36
|
+
rake (~> 12.3.3)
|
37
|
+
simplecov (~> 0.18)
|
24
38
|
|
25
39
|
BUNDLED WITH
|
26
|
-
1.
|
40
|
+
2.1.4
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Prettyrb
|
2
2
|
|
3
|
-
Super experimental gem for auto-formatting Ruby files.
|
3
|
+
Super experimental gem for auto-formatting Ruby files. AKA the code isn't great, but things are kind of working.
|
4
|
+
|
5
|
+
Pronounced "pretty-erb".
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -20,12 +22,24 @@ Or install it yourself as:
|
|
20
22
|
|
21
23
|
## Usage
|
22
24
|
|
25
|
+
CLI:
|
26
|
+
|
27
|
+
```
|
28
|
+
$ prettyrb format FILE
|
29
|
+
```
|
30
|
+
|
31
|
+
or to re-write the file:
|
32
|
+
|
33
|
+
```
|
34
|
+
prettyrb format FILE --write
|
35
|
+
```
|
36
|
+
|
37
|
+
In Ruby code:
|
38
|
+
|
23
39
|
```ruby
|
24
40
|
PrettyRb.new(source_code).format
|
25
41
|
```
|
26
42
|
|
27
|
-
CLI helper coming soon™
|
28
|
-
|
29
43
|
## License
|
30
44
|
|
31
45
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/exe/prettyrb
ADDED
data/lib/prettyrb.rb
CHANGED
@@ -1,191 +1,29 @@
|
|
1
|
-
require "
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
require "parser/current"
|
2
4
|
require "prettyrb/version"
|
3
5
|
|
6
|
+
require "prettyrb/nodes/base_node"
|
7
|
+
require "prettyrb/nodes/if_node"
|
8
|
+
require "prettyrb/nodes/string_helper"
|
9
|
+
require "prettyrb/nodes/logical_operator_helper"
|
10
|
+
require "prettyrb/nodes/str_node"
|
11
|
+
require "prettyrb/nodes/dstr_node"
|
12
|
+
require "prettyrb/nodes/def_node"
|
13
|
+
require "prettyrb/nodes/and_node"
|
14
|
+
require "prettyrb/nodes/or_node"
|
15
|
+
require "prettyrb/nodes/regexp_node"
|
16
|
+
require "prettyrb/nodes/send_node"
|
17
|
+
|
18
|
+
require "prettyrb/document"
|
19
|
+
require "prettyrb/builder"
|
20
|
+
require "prettyrb/formatter"
|
21
|
+
require "prettyrb/visitor"
|
22
|
+
require "prettyrb/writer"
|
23
|
+
|
24
|
+
require "prettyrb/cli"
|
25
|
+
|
4
26
|
module Prettyrb
|
27
|
+
MAX_LINE_LENGTH = 100
|
5
28
|
class Error < StandardError; end
|
6
|
-
|
7
|
-
class Formatter
|
8
|
-
def self.for(sexp)
|
9
|
-
case sexp.sexp_type
|
10
|
-
when :if
|
11
|
-
IfFormatter
|
12
|
-
when :str
|
13
|
-
StringFormatter
|
14
|
-
when :or
|
15
|
-
OrFormatter
|
16
|
-
when :and
|
17
|
-
AndFormatter
|
18
|
-
when :lit
|
19
|
-
LitFormatter
|
20
|
-
when :call
|
21
|
-
CallFormatter
|
22
|
-
when :class
|
23
|
-
ClassFormatter
|
24
|
-
when :cdecl
|
25
|
-
CdeclFormatter
|
26
|
-
when :array
|
27
|
-
ArrayFormatter
|
28
|
-
else
|
29
|
-
raise "can't handle #{sexp}"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def initialize(code)
|
34
|
-
@code = code
|
35
|
-
end
|
36
|
-
|
37
|
-
def format
|
38
|
-
sexp_tree = RubyParser.new.parse(@code)
|
39
|
-
|
40
|
-
self.class.for(sexp_tree).new(sexp_tree, 0, nil).format
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def format_type(sexp, indentation)
|
46
|
-
case sexp.sexp_type
|
47
|
-
when :if
|
48
|
-
IfFormatter.new(sexp, indentation).format
|
49
|
-
else
|
50
|
-
raise "can't handle #{sexp}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
attr_reader :code
|
55
|
-
end
|
56
|
-
|
57
|
-
class BaseFormatter
|
58
|
-
attr_reader :sexp, :indentation, :parent
|
59
|
-
|
60
|
-
def initialize(sexp, indentation, parent)
|
61
|
-
@sexp = sexp
|
62
|
-
@indentation = indentation
|
63
|
-
@parent = parent
|
64
|
-
end
|
65
|
-
|
66
|
-
def type
|
67
|
-
sexp.sexp_type
|
68
|
-
end
|
69
|
-
|
70
|
-
def indents
|
71
|
-
' ' * indentation
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class LitFormatter < BaseFormatter
|
76
|
-
def format
|
77
|
-
sexp[1]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
class StringFormatter < BaseFormatter
|
82
|
-
def format
|
83
|
-
if parent.type == :array
|
84
|
-
"\"#{sexp[1]}\""
|
85
|
-
else
|
86
|
-
"#{indents}\"#{sexp[1]}\""
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
class IfFormatter < BaseFormatter
|
92
|
-
def format
|
93
|
-
start = "#{' ' * @indentation}if "
|
94
|
-
condition = Formatter.for(sexp[1]).new(@sexp[1], @indentation + 2, self).format
|
95
|
-
body = Formatter.for(sexp[2]).new(@sexp[2], @indentation + 2, self).format
|
96
|
-
ending = "#{' ' * @indentation}end"
|
97
|
-
|
98
|
-
"#{start}#{condition}\n#{body}\n#{ending}"
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
class OrFormatter < BaseFormatter
|
103
|
-
def format
|
104
|
-
left = Formatter.for(sexp[1]).new(sexp[1], @indentation, self).format
|
105
|
-
right = Formatter.for(sexp[2]).new(sexp[2], @indentation, self).format
|
106
|
-
|
107
|
-
if needs_parens?
|
108
|
-
"(#{left} || #{right})"
|
109
|
-
else
|
110
|
-
"#{left} || #{right}"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def needs_parens?
|
117
|
-
parent&.type == :or || parent&.type == :and
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
class AndFormatter < BaseFormatter
|
122
|
-
def format
|
123
|
-
left = Formatter.for(sexp[1]).new(sexp[1], @indentation, self).format
|
124
|
-
right = Formatter.for(sexp[2]).new(sexp[2], @indentation, self).format
|
125
|
-
|
126
|
-
if needs_parens?
|
127
|
-
"(#{left} && #{right})"
|
128
|
-
else
|
129
|
-
"#{left} && #{right}"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
private
|
134
|
-
|
135
|
-
def needs_parens?
|
136
|
-
parent&.type == :or || parent&.type == :and
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
class CallFormatter < BaseFormatter
|
141
|
-
def format
|
142
|
-
if infix?
|
143
|
-
left = Formatter.for(sexp[1]).new(sexp[1], @indentation, self).format
|
144
|
-
right = Formatter.for(sexp[3]).new(sexp[3], @indentation, self).format
|
145
|
-
|
146
|
-
if needs_parens?
|
147
|
-
"(#{left} #{sexp[2]} #{right})"
|
148
|
-
else
|
149
|
-
"#{left} #{sexp[2]} #{right}"
|
150
|
-
end
|
151
|
-
else
|
152
|
-
content = Formatter.for(sexp[1]).new(sexp[1], @indentation, self).format
|
153
|
-
"#{content}.#{sexp[2]}"
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
private
|
158
|
-
|
159
|
-
def infix?
|
160
|
-
sexp.length == 4
|
161
|
-
end
|
162
|
-
|
163
|
-
def needs_parens?
|
164
|
-
parent&.type == :or || parent&.type == :and
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
class ClassFormatter < BaseFormatter
|
169
|
-
def format
|
170
|
-
content = Formatter.for(sexp[3]).new(sexp[3], indentation + 2, self).format
|
171
|
-
"#{indents}class #{sexp[1]}\n#{content}\n#{indents}end" # TODO handle inheritance
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
class CdeclFormatter < BaseFormatter
|
176
|
-
def format
|
177
|
-
content = Formatter.for(sexp[2]).new(sexp[2], indentation + 2, self).format
|
178
|
-
"#{indents}#{sexp[1]} = #{content}"
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
class ArrayFormatter < BaseFormatter
|
183
|
-
def format
|
184
|
-
elements = sexp[1..-1].map do |nested_sexp|
|
185
|
-
Formatter.for(nested_sexp).new(nested_sexp, indentation + 2, self).format
|
186
|
-
end
|
187
|
-
|
188
|
-
"[#{elements.join(", ")}]"
|
189
|
-
end
|
190
|
-
end
|
191
29
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Prettyrb
|
2
|
+
class Builder < Parser::Builders::Default
|
3
|
+
NODE_TYPES = {
|
4
|
+
and: Prettyrb::Nodes::AndNode,
|
5
|
+
dstr: Prettyrb::Nodes::DstrNode,
|
6
|
+
if: Prettyrb::Nodes::IfNode,
|
7
|
+
or: Prettyrb::Nodes::OrNode,
|
8
|
+
regexp: Prettyrb::Nodes::RegexpNode,
|
9
|
+
send: Prettyrb::Nodes::SendNode,
|
10
|
+
str: Prettyrb::Nodes::StrNode,
|
11
|
+
def: Prettyrb::Nodes::DefNode,
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def n(type, children, source_map)
|
15
|
+
node_class = NODE_TYPES.fetch(type, Prettyrb::Nodes::BaseNode)
|
16
|
+
|
17
|
+
node_class.new(type, children, location: source_map)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/prettyrb/cli.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
module Prettyrb
|
4
|
+
class CLI < Thor
|
5
|
+
desc "format [FILE]", "Ruby file to prettify"
|
6
|
+
def format(file)
|
7
|
+
content = File.read(file)
|
8
|
+
formatted_content = Prettyrb::Formatter.new(content).format
|
9
|
+
|
10
|
+
puts formatted_content
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "write [FILES]", "Write prettified Ruby files"
|
14
|
+
def write(*files)
|
15
|
+
files.each do |file|
|
16
|
+
content = File.read(file)
|
17
|
+
begin
|
18
|
+
formatted_content = Prettyrb::Formatter.new(content).format
|
19
|
+
rescue Exception => e
|
20
|
+
puts "Failed to write #{file}"
|
21
|
+
throw e
|
22
|
+
end
|
23
|
+
|
24
|
+
File.open(file, 'w') do |f|
|
25
|
+
f.write(formatted_content)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Prettyrb
|
2
|
+
module Document
|
3
|
+
module DSL
|
4
|
+
def concat(*args)
|
5
|
+
Document::Concat.new(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def join(*args)
|
9
|
+
Document::Join.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def group(*args)
|
13
|
+
Document::Group.new(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def if_break(*args)
|
17
|
+
Document::IfBreak.new(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def indent(*args)
|
21
|
+
Document::Indent.new(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def dedent(*args)
|
25
|
+
Document::Dedent.new(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def hardline(*args)
|
29
|
+
Document::Hardline.new(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def softline(*args)
|
33
|
+
Document::Softline.new(*args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Builder
|
38
|
+
attr_reader :parts
|
39
|
+
|
40
|
+
include Enumerable
|
41
|
+
|
42
|
+
def initialize(*args)
|
43
|
+
@parts = args
|
44
|
+
end
|
45
|
+
|
46
|
+
def each
|
47
|
+
@parts.each
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
inspect_children(self, indent_level: 0)
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_group_part?
|
55
|
+
parts.any? { |p| p.is_a?(Group) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def groups
|
59
|
+
parts.select { |p| p.is_a?(Group) } + parts.flat_map do |p|
|
60
|
+
p.groups if p.respond_to?(:groups)
|
61
|
+
end.compact
|
62
|
+
end
|
63
|
+
|
64
|
+
def max_group_depth
|
65
|
+
return 0 if !parts
|
66
|
+
return @max_group_depth if defined?(@max_group_depth)
|
67
|
+
|
68
|
+
has_groups = parts.any? { |p| p.is_a?(Group) }
|
69
|
+
|
70
|
+
total = if has_groups
|
71
|
+
1
|
72
|
+
else
|
73
|
+
0
|
74
|
+
end
|
75
|
+
|
76
|
+
# TODO swap filter/flat_map for single iteration
|
77
|
+
nested_total = parts.
|
78
|
+
filter { |p| p.respond_to?(:max_group_depth) }.
|
79
|
+
flat_map { |p| p.max_group_depth }.
|
80
|
+
max
|
81
|
+
|
82
|
+
@max_group_depth = total + (nested_total || 0)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def inspect_children(builder, indent_level:)
|
88
|
+
if builder.respond_to?(:parts)
|
89
|
+
children = if builder.parts
|
90
|
+
builder.parts.map do |p|
|
91
|
+
inspect_children(p, indent_level: indent_level + 1)
|
92
|
+
end.join("\n")
|
93
|
+
end
|
94
|
+
|
95
|
+
" " * indent_level + "(#{builder.class}\n#{" "*indent_level}#{children}\n #{" " * indent_level})"
|
96
|
+
else
|
97
|
+
" " * indent_level + builder.inspect
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Concat < Builder
|
103
|
+
end
|
104
|
+
|
105
|
+
class Group < Builder
|
106
|
+
attr_reader :joiner
|
107
|
+
|
108
|
+
def initialize(*args, joiner: "")
|
109
|
+
super(*args)
|
110
|
+
@joiner = joiner
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Join < Builder
|
115
|
+
attr_reader :separator
|
116
|
+
|
117
|
+
def initialize(separator: ",", parts:)
|
118
|
+
if parts.is_a?(Array)
|
119
|
+
super(*parts)
|
120
|
+
else
|
121
|
+
super(parts)
|
122
|
+
end
|
123
|
+
@separator = separator
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class IfBreak
|
128
|
+
attr_reader :without_break, :with_break
|
129
|
+
def initialize(without_break:, with_break: )
|
130
|
+
@without_break = without_break
|
131
|
+
@with_break = with_break
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Indent < Builder
|
136
|
+
attr_reader :only_when_break
|
137
|
+
|
138
|
+
def initialize(*args, only_when_break: false)
|
139
|
+
@only_when_break = only_when_break
|
140
|
+
super(*args)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class Dedent < Builder
|
145
|
+
end
|
146
|
+
|
147
|
+
class Hardline < Builder
|
148
|
+
attr_reader :count, :skip_indent
|
149
|
+
def initialize(count: 1, skip_indent: false)
|
150
|
+
@count = count
|
151
|
+
@skip_indent = skip_indent
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Softline < Builder
|
156
|
+
attr_reader :fallback
|
157
|
+
|
158
|
+
def initialize(*args, fallback: nil)
|
159
|
+
super(args)
|
160
|
+
@fallback = fallback
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|