ruby_crystal_codemod 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ module RubyCrystalCodemod
2
+ class Logger
3
+ LEVELS = {
4
+ silent: 0,
5
+ error: 1,
6
+ warn: 2,
7
+ log: 3,
8
+ debug: 4,
9
+ }
10
+
11
+ def initialize(level)
12
+ @level = LEVELS.fetch(level)
13
+ end
14
+
15
+ def debug(*args)
16
+ $stdout.puts(*args) if should_output?(:debug)
17
+ end
18
+
19
+ def log(*args)
20
+ $stdout.puts(*args) if should_output?(:log)
21
+ end
22
+
23
+ def warn(*args)
24
+ $stderr.puts(*args) if should_output?(:warn)
25
+ end
26
+
27
+ def error(*args)
28
+ $stderr.puts(*args) if should_output?(:error)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :level
34
+
35
+ def should_output?(level_to_check)
36
+ LEVELS.fetch(level_to_check) <= level
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ module RubyCrystalCodemod::Settings
2
+ OPTIONS = {
3
+ parens_in_def: [:yes, :dynamic],
4
+ align_case_when: [false, true],
5
+ align_chained_calls: [false, true],
6
+ trailing_commas: [true, false],
7
+ quote_style: [:double, :single],
8
+ store_logs: [false, true],
9
+ }
10
+
11
+ attr_accessor(*OPTIONS.keys)
12
+
13
+ def init_settings(options)
14
+ OPTIONS.each do |name, valid_options|
15
+ default = valid_options.first
16
+ value = options.fetch(name, default)
17
+ unless valid_options.include?(value)
18
+ $stderr.puts "Invalid value for #{name}: #{value.inspect}. Valid " \
19
+ "values are: #{valid_options.map(&:inspect).join(", ")}"
20
+ value = default
21
+ end
22
+ self.public_send("#{name}=", value)
23
+ end
24
+ diff = options.keys - OPTIONS.keys
25
+ diff.each do |key|
26
+ $stderr.puts "Invalid config option=#{key}"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyCrystalCodemod
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,22 @@
1
+ # desc "Alias for `rake rufo:run`"
2
+ # task :rufo => ["rufo:run"]
3
+
4
+ # namespace :rufo do
5
+ # require "rufo"
6
+
7
+ # def rufo_command(*switches, rake_args)
8
+ # files_or_dirs = rake_args[:files_or_dirs] || "."
9
+ # args = switches + files_or_dirs.split(" ")
10
+ # RubyCrystalCodemod::Command.run(args)
11
+ # end
12
+
13
+ # desc "Format Ruby code in current directory"
14
+ # task :run, [:files_or_dirs] do |_task, rake_args|
15
+ # rufo_command(rake_args)
16
+ # end
17
+
18
+ # desc "Check that no formatting changes are produced"
19
+ # task :check, [:files_or_dirs] do |_task, rake_args|
20
+ # rufo_command("--check", rake_args)
21
+ # end
22
+ # end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ruby_crystal_codemod/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ruby_crystal_codemod"
8
+ spec.version = RubyCrystalCodemod::VERSION
9
+ spec.authors = ["Ary Borenszweig", "Nathan Broadbent"]
10
+ spec.email = ["asterite@gmail.com", "nathan@docspring.com"]
11
+
12
+ spec.summary = %q{Ruby => Crystal codemod}
13
+ spec.description = %q{Attempts to transpile Ruby code into Crystal code}
14
+ spec.homepage = "https://github.com/docspring/ruby_crystal_codemod"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+ spec.required_ruby_version = ">= 2.4.5"
24
+
25
+ spec.add_development_dependency "awesome_print", "~> 1.8.0"
26
+ spec.add_development_dependency "bundler", ">= 1.15"
27
+ spec.add_development_dependency "byebug", "~> 10.0.2"
28
+ spec.add_development_dependency "guard-rspec", "~> 4.0"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ spec.add_development_dependency "rspec_junit_formatter", "~> 0.4.1"
32
+ spec.add_development_dependency "rubocop", "~> 0.63.1"
33
+ spec.add_development_dependency "rufo", "~> 0.7.0"
34
+ end
data/run_test ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -eo pipefail
3
+
4
+ # For testing things
5
+ ./exe/ruby_crystal_codemod --simple-exit test.rb && crystal run test.cr
@@ -0,0 +1,14 @@
1
+ version: 1.0
2
+ shards:
3
+ mocks:
4
+ github: DocSpring/mocks.cr
5
+ commit: b098c6ae8a34509df88baf4d3664471cd223bdfe
6
+
7
+ singleton:
8
+ github: waterlink/singleton.cr
9
+ version: 1.0.0
10
+
11
+ spectator:
12
+ gitlab: arctic-fox/spectator
13
+ version: 0.8.3
14
+
@@ -0,0 +1,11 @@
1
+ name: process_comments
2
+ version: 0.1.0
3
+
4
+ development_dependencies:
5
+ spectator:
6
+ gitlab: arctic-fox/spectator
7
+ mocks:
8
+ github: DocSpring/mocks.cr
9
+ branch: updates_for_0.31.1
10
+
11
+ license: MIT
@@ -0,0 +1,13 @@
1
+ class Foo
2
+ attr_accessor :foo
3
+
4
+ #~# BEGIN ruby
5
+ def initialize(foo)
6
+ @foo = foo
7
+ end
8
+ #~# END ruby
9
+ #~# BEGIN crystal
10
+ # @foo : Int32
11
+ # def initialize(@foo: Int32); end
12
+ #~# END crystal
13
+ end
@@ -0,0 +1,124 @@
1
+ require "./spec_helper"
2
+
3
+ Mocks.create_mock PostProcessCrystal do
4
+ mock self.file_read_lines(filename)
5
+ end
6
+
7
+ Spectator.describe PostProcessCrystal do
8
+ it "mocks the call to File.read_lines" do
9
+ allow(PostProcessCrystal).to receive(self.file_read_lines("example_file")).and_return([
10
+ "hey, world!",
11
+ "how's it hanging"
12
+ ])
13
+
14
+ ppc = PostProcessCrystal.new("example_file")
15
+ ppc.post_process_crystal
16
+ expect(ppc.contents).to eq("hey, world!\nhow's it hanging\n")
17
+ end
18
+
19
+ it "removes any content between #~# BEGIN ruby and #~# END ruby" do
20
+ allow(PostProcessCrystal).to receive(self.file_read_lines("example_file")).and_return(
21
+ <<-CODE.lines
22
+ def foo
23
+ #~# BEGIN ruby
24
+ return 234
25
+ #~# END ruby
26
+ 123
27
+ end
28
+ #~# BEGIN ruby
29
+ puts "Code called from Ruby!"
30
+ # comment should be removed
31
+ #~# END ruby
32
+
33
+ puts foo
34
+ CODE
35
+ )
36
+
37
+ ppc = PostProcessCrystal.new("example_file")
38
+ ppc.post_process_crystal
39
+ expect(ppc.contents).to eq(
40
+ <<-CODE
41
+ def foo
42
+ 123
43
+ end
44
+
45
+ puts foo
46
+
47
+ CODE
48
+ )
49
+ end
50
+
51
+ it "uncomments any content between #~# BEGIN crystal and #~# END crystal" do
52
+ allow(PostProcessCrystal).to receive(self.file_read_lines("example_file")).and_return(
53
+ <<-CODE.lines
54
+ def foo
55
+ 123
56
+ #~# BEGIN crystal
57
+ # return 456
58
+ #~# END crystal
59
+ end
60
+ #~# BEGIN crystal
61
+ # puts "Code called from Crystal!"
62
+ # # comment should be present
63
+ #~# END crystal
64
+ puts foo
65
+ CODE
66
+ )
67
+
68
+ ppc = PostProcessCrystal.new("example_file")
69
+ ppc.post_process_crystal
70
+ expect(ppc.contents).to eq(
71
+ <<-CODE
72
+ def foo
73
+ 123
74
+ return 456
75
+ end
76
+ puts "Code called from Crystal!"
77
+ # comment should be present
78
+ puts foo
79
+
80
+ CODE
81
+ )
82
+ end
83
+
84
+ it "removes all Ruby lines and uncomments all Crystal lines" do
85
+ allow(PostProcessCrystal).to receive(self.file_read_lines("example_file")).and_return(
86
+ <<-CODE.lines
87
+ class Foo
88
+ property :foo, :bar
89
+
90
+ #~# BEGIN ruby
91
+ def initialize(foo, bar)
92
+ @foo = foo
93
+ @bar = bar
94
+ end
95
+ #~# END ruby
96
+ #~# BEGIN crystal
97
+ # @foo : Int32
98
+ # @bar : Int32
99
+ # def initialize(@foo: Int32, @bar : Int32)
100
+ # end
101
+ #~# END crystal
102
+ end
103
+ CODE
104
+ )
105
+
106
+ ppc = PostProcessCrystal.new("example_file")
107
+ ppc.post_process_crystal
108
+ # NOTE: This is a post-processing step. The rufo formatting code
109
+ # must run before this, and will translate attr_accessor into property, etc.
110
+ expect(ppc.contents).to eq(
111
+ <<-CODE
112
+ class Foo
113
+ property :foo, :bar
114
+
115
+ @foo : Int32
116
+ @bar : Int32
117
+ def initialize(@foo: Int32, @bar : Int32)
118
+ end
119
+ end
120
+
121
+ CODE
122
+ )
123
+ end
124
+ end
@@ -0,0 +1,4 @@
1
+ require "../src/post_process_crystal"
2
+ require "spec"
3
+ require "spectator"
4
+ require "mocks/spec"
@@ -0,0 +1,16 @@
1
+ require "./post_process_crystal"
2
+
3
+ if ARGV.size == 0
4
+ STDERR.puts "Usage: #{PROGRAM_NAME} <files>"
5
+ exit 1
6
+ end
7
+
8
+ ppc = PostProcessCrystal.new
9
+ puts "Processing #~# BEGIN and #~# END comments..."
10
+
11
+ ARGV.each_with_index do |arg, i|
12
+ puts "=> Processing file: #{arg}"
13
+ ppc.filename = arg
14
+ ppc.post_process_crystal
15
+ File.write(arg, ppc.contents)
16
+ end
@@ -0,0 +1,48 @@
1
+ class PostProcessCrystal
2
+ getter :filename, :contents
3
+ @filename : String
4
+ @contents : String
5
+
6
+ def self.file_read_lines(path)
7
+ File.read_lines(path)
8
+ end
9
+
10
+ def initialize(filename : String = "")
11
+ @filename = filename
12
+ @contents = ""
13
+ end
14
+
15
+ def filename=(filename)
16
+ self.initialize(filename)
17
+ end
18
+
19
+ def post_process_crystal
20
+ @contents = String.build do |io|
21
+ lines = self.class.file_read_lines(@filename)
22
+
23
+ current_lang = nil
24
+ regex = /^\s*# ?~# (?<action>(BEGIN|END)) (?<lang>(ruby|crystal))/
25
+ uncomment_regex = /^(?<indent>\s*)# ?/
26
+ lines.each do |line|
27
+ matches = regex.match(line)
28
+ if matches
29
+ case matches["action"]
30
+ when "BEGIN"
31
+ current_lang = matches["lang"]
32
+ when "END"
33
+ current_lang = nil
34
+ end
35
+ next
36
+ end
37
+ case current_lang
38
+ when "ruby"
39
+ next
40
+ when "crystal"
41
+ line = line.sub(uncomment_regex, "\\k<indent>")
42
+ end
43
+
44
+ io << line << "\n"
45
+ end
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_crystal_codemod
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ary Borenszweig
8
+ - Nathan Broadbent
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2019-12-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: awesome_print
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 1.8.0
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 1.8.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '1.15'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '1.15'
42
+ - !ruby/object:Gem::Dependency
43
+ name: byebug
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 10.0.2
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 10.0.2
56
+ - !ruby/object:Gem::Dependency
57
+ name: guard-rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '4.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '4.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '10.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '10.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rspec
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '3.0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rspec_junit_formatter
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: 0.4.1
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: 0.4.1
112
+ - !ruby/object:Gem::Dependency
113
+ name: rubocop
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: 0.63.1
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: 0.63.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: rufo
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: 0.7.0
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: 0.7.0
140
+ description: Attempts to transpile Ruby code into Crystal code
141
+ email:
142
+ - asterite@gmail.com
143
+ - nathan@docspring.com
144
+ executables:
145
+ - ruby_crystal_codemod
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - ".circleci/config.yml"
150
+ - ".github/PULL_REQUEST_TEMPLATE.md"
151
+ - ".gitignore"
152
+ - ".rspec"
153
+ - ".rubocop.yml"
154
+ - ".rufo"
155
+ - ".vscode/settings.json"
156
+ - Gemfile
157
+ - Guardfile
158
+ - LICENSE.txt
159
+ - README.md
160
+ - Rakefile
161
+ - bin/compile_post_process
162
+ - bin/console
163
+ - bin/setup
164
+ - docs/developing-ruby-crystal-codemod.md
165
+ - docs/releasing-a-gem.md
166
+ - exe/ruby_crystal_codemod
167
+ - lib/ruby_crystal_codemod.rb
168
+ - lib/ruby_crystal_codemod/command.rb
169
+ - lib/ruby_crystal_codemod/dot_file.rb
170
+ - lib/ruby_crystal_codemod/file_finder.rb
171
+ - lib/ruby_crystal_codemod/formatter.rb
172
+ - lib/ruby_crystal_codemod/logger.rb
173
+ - lib/ruby_crystal_codemod/settings.rb
174
+ - lib/ruby_crystal_codemod/version.rb
175
+ - rakelib/ruby_crystal_codemod.rake
176
+ - ruby_crystal_codemod.gemspec
177
+ - run_test
178
+ - util/post_process_crystal/shard.lock
179
+ - util/post_process_crystal/shard.yml
180
+ - util/post_process_crystal/spec/fixtures/example.rb
181
+ - util/post_process_crystal/spec/post_process_crystal_spec.cr
182
+ - util/post_process_crystal/spec/spec_helper.cr
183
+ - util/post_process_crystal/src/command.cr
184
+ - util/post_process_crystal/src/post_process_crystal.cr
185
+ homepage: https://github.com/docspring/ruby_crystal_codemod
186
+ licenses:
187
+ - MIT
188
+ metadata: {}
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: 2.4.5
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubygems_version: 3.0.3
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: Ruby => Crystal codemod
208
+ test_files: []