diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9d525b0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,46 @@ +name: Test & deploy documentation + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + + runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: ['2.6', '2.7', '3.0', '3.1'] + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + - name: Run tests + run: bundle exec rake + + deploy: + runs-on: ubuntu-latest + needs: test + name: Update gh-pages to docs + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 18 + - name: Install required npm dependencies + run: npm install -g markdown-to-html + - name: Create docs + run: mkdir docs + - name: Generate docs + run: github-markdown README.md -s https://cdn.simplecss.org/simple-v1.css > docs/index.html + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} + publish_dir: ./docs diff --git a/.gitignore b/.gitignore index 76c9de5..bffcd4b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ .bundle .config .yardoc -Gemfile.lock InstalledFiles _yardoc coverage diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d273337..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: ruby -rvm: - - 2.3 - - 2.4 - - 2.5 - - 2.6 - - 2.7 - - 3.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3ca78..db6cfa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,147 @@ # CHANGELOG +## 1.4.1 (2023-02-10) + +* Make sure `tab_width` is an Integer +* Update `synvert-core` to 1.21.1 + +## 1.4.0 (2023-02-08) + +* Add `--tab-width` flag +* Update `synvert-core` to 1.21.0 + +## 1.3.0 (2023-02-08) + +* Add `--double-quote` flag +* Update `synvert-core` to 1.20.0 + +## 1.2.6 (2023-02-02) + +* Output error message + +## 1.2.5 (2023-02-01) + +* Remove `rewriter.todo` +* Update ``synvert-core`` to 1.18.0 + +## 1.2.4 (2023-01-21) + +* Update ``synvert-core`` to 1.17.0 + +## 1.2.3 (2022-12-29) + +* Do not install synvert-core after syncing snippet +* Update `synvert-core` to 1.16.0 + +## 1.2.2 (2022-11-13) + +* Fix `default_snippets_home` in windows + +## 1.2.1 (2022-11-13) + +* Concat multiple git commands with && + +## 1.2.0 (2022-10-11) + +* Add `--number-of-workers` option + +## 1.1.3 (2022-10-09) + +* No need to read rewriters before run or test a snippet +* Reuse `Synvert::Core::Utils.eval_snippet` +* `rewriter.test` already returns all results + +## 1.1.2 (2022-10-06) + +* Convert github url to github raw url + +## 1.1.1 (2022-10-05) + +* Fix get_last_snippet_name, eval snippet to get rewriter + +## 1.1.0 (2022-10-03)) + +* Fix sub snippet not found +* Support sub snippets in `test` + +## 1.0.1 (2022-09-23) + +* Execute with EXECUTE_COMMAND + +## 1.0.0 (2022-09-17) + +* Test a snippet +* Require single rewriter instead of loading all rewriters +* Execute command can be either run or test +* Add `skip_path` and `only_paths` options + +## 0.20.0 (2022-08-20) + +* Rename `load_rewriters` to `read_rewriters` +* Run a snippet from remote url or local file path + +## 0.19.3 (2022-07-18) + +* Require json +* Update `synvert-core` to 1.5.0 + +## 0.19.2 (2021-12-15) + +* List sub_snippets group and name + +## 0.19.1 (2021-10-23) + +* Make URI.open work in ruby 2.4 + +## 0.19.0 (2021-09-10) + +* Add `--show-run-process` option +* Deprecate `synvert`, use `synvert-ruby` instead +* Update `synvert-snippets` url +* Fix `affected_files` is Set + +## 0.18.0 (2021-07-14) + +* Execute a snippet + +## 0.17.0 (2021-04-19) + +* Run `git checkout .` before `git pull --rebase` + +## 0.16.0 (2021-03-24) + +* Add `ruby_version` and `gem_spec` to json output + +## 0.15.0 (2021-03-23) + +* Fix reduce on empty array +* Update synvert-core when syncing snippets + +## 0.14.0 (2021-03-13) + +* Add CLI option `--format` +* Run one snippet once + +## 0.13.0 (2021-03-02) + +* Use `ENV['SYNVERT_SNIPPETS_HOME']` to change default snippets home +* Display snippet source code for showing a snippet + +## 0.12.0 (2021-03-01) + +* Display `synvert-core` and `parser` version +* Generate a new snippet + +## 0.11.1 (2021-02-20) + +* Use `Synvert::VERSION` instead of `Synvert::Core::VERSION` + +## 0.11.0 (2021-02-15) + +* Add `--list-all` option +* Add post install message +* Fix `Synvert::Snippet.fetch_core_version` + ## 0.10.0 (2021-02-07) * Use new `Core::Confiruation` diff --git a/Gemfile b/Gemfile index 0690ae0..a64a457 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in synvert.gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..37b65ec --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,99 @@ +PATH + remote: . + specs: + synvert (1.4.1) + synvert-core (>= 1.21.1) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (6.1.7.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + ast (2.4.2) + concurrent-ruby (1.2.2) + coveralls (0.8.23) + json (>= 1.8, < 3) + simplecov (~> 0.16.1) + term-ansicolor (~> 1.3) + thor (>= 0.19.4, < 2.0) + tins (~> 1.6) + crack (0.4.5) + rexml + diff-lcs (1.4.4) + docile (1.4.0) + erubis (2.7.0) + hashdiff (1.0.1) + i18n (1.12.0) + concurrent-ruby (~> 1.0) + json (2.5.1) + minitest (5.18.0) + node_mutation (1.9.1) + erubis + node_query (1.12.0) + parallel (1.22.1) + parser (3.2.1.0) + ast (~> 2.4.1) + parser_node_ext (0.10.0) + parser + public_suffix (4.0.6) + rake (13.0.6) + rexml (3.2.5) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.2) + simplecov (0.16.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + sync (0.5.0) + synvert-core (1.21.1) + activesupport (< 7.0.0) + erubis + node_mutation (>= 1.9.0) + node_query (>= 1.12.0) + parallel + parser + parser_node_ext (>= 0.9.0) + term-ansicolor (1.7.1) + tins (~> 1.0) + thor (1.1.0) + tins (1.29.1) + sync + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + webmock (3.14.0) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + zeitwerk (2.6.7) + +PLATFORMS + ruby + +DEPENDENCIES + bundler + coveralls + rake + rspec + synvert! + webmock + +BUNDLED WITH + 2.3.7 diff --git a/README.md b/README.md index e20ed4c..a92999e 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,144 @@ -# Synvert +# synvert-ruby -[![Build Status](https://secure.travis-ci.org/xinminlabs/synvert.svg)](http://travis-ci.org/xinminlabs/synvert) -[![Coverage Status](https://coveralls.io/repos/xinminlabs/synvert/badge.svg?branch=master)](https://coveralls.io/r/xinminlabs/synvert) -[![Gem Version](https://badge.fury.io/rb/synvert.svg)](http://badge.fury.io/rb/synvert) - -Synvert = syntax + convert, makes it easy to convert ruby code -automatically. +logo -Synvert is tested against MRI 1.9.3, 2.0.0, 2.1.7 and 2.2.3. +[![AwesomeCode Status for xinminlabs/synvert-ruby](https://awesomecode.io/projects/47cd9805-171c-4c61-b927-baa46cd4020a/status)](https://awesomecode.io/repos/xinminlabs/synvert-ruby) +![Main workflow](https://github.com/xinminlabs/synvert-ruby/actions/workflows/main.yml/badge.svg) +[![Gem Version](https://badge.fury.io/rb/synvert.svg)](http://badge.fury.io/rb/synvert) -Synvert is composed by synvert-core and synvert-snippets. +`synvert-ruby` is a command tool to rewrite ruby code automatically, it depends on `synvert-core-ruby` and `synvert-snippets-ruby`. -[synvert-core][1] provides a dsl to convert ruby code. +[synvert-core-ruby](https://github.com/xinminlabs/synvert-core-ruby) provides a set of DSLs to rewrite ruby code. -[synvert-snippets][2] lists all snippets to convert ruby code based on -synvert-core. +[synvert-snippets-ruby](https://github.com/xinminlabs/synvert-snippets-ruby) provides official snippets to rewrite ruby code. ## Installation -Install it using rubygems +To install the latest version, run + ``` $ gem install synvert ``` +This will also install `synvert-core-ruby`. + +Synvert is completely working with remote snippets on github, +but you can sync all official snippets locally to make it run faster. + +``` +$ synvert-ruby --sync +``` + +Then you can use synvert to rewrite your ruby code, e.g. + +``` +$ synvert-ruby -r factory_bot/use_short_syntax +``` + ## Usage ``` -$ synvert -h -Usage: synvert [project_path] - -d, --load SNIPPET_PATHS load custom snippets, snippet paths can be local file path or remote http url +$ synvert-ruby -h +Usage: synvert-ruby [project_path] -l, --list list all available snippets -q, --query QUERY query specified snippets - --skip FILE_PATTERNS skip specified files or directories, separated by comma, e.g. app/models/post.rb,vendor/plugins/**/*.rb - -s, --show SNIPPET_NAME show specified snippet description + -s, --show SNIPPET_NAME show specified snippet description, SNIPPET_NAME is combined by group and name, e.g. ruby/new_hash_syntax + -o, --open SNIPPET_NAME Open a snippet + -g, --generate NEW_SNIPPET_NAME generate a new snippet --sync sync snippets - -r, --run SNIPPET_NAMES run specified snippets + --execute EXECUTE_COMMAND execute snippet + -r, --run SNIPPET_NAME run a snippet with snippet name, e.g. ruby/new_hash_syntax, or remote url, or local file path + -t, --test SNIPPET_NAME test a snippet with snippet name, e.g. ruby/new_hash_syntax, or remote url, or local file path + --show-run-process show processing files when running a snippet + --only-paths DIRECTORIES only specified files or directories, separated by comma, e.g. app/models,app/controllers + --skip-paths FILE_PATTERNS skip specified files or directories, separated by comma, e.g. vendor/,lib/**/*.rb + -f, --format FORMAT output format + --number-of-workers NUMBER_OF_WORKERS + set the number of workers, if it is greater than 1, it tests snippet in parallel + --double-quote prefer double quote, it uses single quote by default + --tab-width TAB_WIDTH prefer tab width, it uses 2 by default -v, --version show this version ``` -e.g. +### Sync snippets + +[Official Snippets](https://github.com/xinminlabs/synvert-snippets-ruby) are available on github, +you can sync them any time you want. + +``` +$ synvert-ruby --sync +``` + +### List snippets + +List all available snippets + +``` +$ synvert-ruby -l + +$ synvert-ruby --list --format json +``` + +### Show a snippet + +Describe what a snippet does. + +``` +$ synvert-ruby -s factory_bot/use_short_syntax +``` + +### Open a snippet + +Open a snippet in your editor, editor is defined in +`ENV['SNIPPET_EDITOR']` or `ENV['EDITOR']` + +``` +$ synvert-ruby -o factory_bot/use_short_syntax +``` + +### Run a snippet + +Run a snippet, analyze and then rewrite code. + +``` +$ synvert-ruby -r factory_bot/use_short_syntax ~/Sites/xinminlabs/synvert-core-ruby +``` + +Run a snippet from remote url ``` -$ synvert --sync +$ synvert-ruby -r https://raw.githubusercontent.com/xinminlabs/synvert-snippets-ruby/master/lib/factory_bot/use_short_syntax.rb ~/sites/xinminlabs/synvert-core-ruby ``` +Run a snippet from local path + ``` -$ synvert -r factory_girl/use_short_syntax,rails/upgrade_3_2_to_4_0 ~/Sites/railsbp/rails-bestpractices.com +$ synvert-ruby -r ~/.synvert-ruby/lib/factory_bot/use_short_syntax.rb ~/sites/xinminlabs/synvert-core-ruby ``` -## Documentation +Skip paths -[http://xinminlabs.github.io/synvert/][3] +``` +$ synvert-ruby -r factory_bot/use_short_syntax --skip-paths vendor/ ~/sites/xinminlabs/synvert-core-ruby +``` -## Contributing +Only paths -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request +``` +$ synvert-ruby -r factory_bot/use_short_syntax --only-paths app/models/ ~/sites/xinminlabs/synvert-core-ruby +``` + +Show processing files when running a snippet. -[1]: https://github.com/xinminlabs/synvert-core/ -[2]: https://github.com/xinminlabs/synvert-snippets/ -[3]: http://xinminlabs.github.io/synvert/ +``` +$ synvert-ruby -r factory_bot/use_short_syntax --show-run-process ~/Sites/xinminlabs/synvert-core-ruby +``` + +### Generate a snippet + +Generate a new snippet + +``` +$ synvert-ruby -g ruby/convert_foo_to_bar +``` diff --git a/Rakefile b/Rakefile index 93cb943..d96571f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'bundler/gem_tasks' require 'rspec/core/rake_task' diff --git a/bin/synvert b/bin/synvert index 7de7aad..e3b2e4a 100755 --- a/bin/synvert +++ b/bin/synvert @@ -1,6 +1,10 @@ #!/usr/bin/env ruby -$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) +# frozen_string_literal: true + +$LOAD_PATH.unshift(File.expand_path('../lib', __dir__)) require 'synvert' +puts "synvert is deprecated, use synvert-ruby instead." +puts result = Synvert::CLI.run exit(result ? 0 : 1) diff --git a/bin/synvert-ruby b/bin/synvert-ruby new file mode 100755 index 0000000..72958fc --- /dev/null +++ b/bin/synvert-ruby @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +$LOAD_PATH.unshift(File.expand_path('../lib', __dir__)) +require 'synvert' + +result = Synvert::CLI.run +exit(result ? 0 : 1) diff --git a/lib/synvert.rb b/lib/synvert.rb index ea975b4..b93898a 100644 --- a/lib/synvert.rb +++ b/lib/synvert.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require 'synvert/version' require 'bundler' require 'synvert/core' module Synvert autoload :CLI, 'synvert/cli' + autoload :Utils, 'synvert/utils' autoload :Snippet, 'synvert/snippet' end diff --git a/lib/synvert/cli.rb b/lib/synvert/cli.rb index 6d57585..25f548f 100644 --- a/lib/synvert/cli.rb +++ b/lib/synvert/cli.rb @@ -1,4 +1,8 @@ +# frozen_string_literal: true + require 'optparse' +require 'json' +require 'fileutils' module Synvert # Synvert command line interface. @@ -13,7 +17,7 @@ def self.run(args = ARGV) # Initialize a CLI. def initialize - @options = {command: 'run', custom_snippet_paths: [], snippet_names: []} + @options = { command: 'run', format: 'plain' } end # Run the CLI. @@ -24,30 +28,29 @@ def run(args) case @options[:command] when 'list' - load_rewriters + read_rewriters list_available_rewriters when 'open' open_rewriter when 'query' - load_rewriters + read_rewriters query_available_rewriters when 'show' - load_rewriters show_rewriter when 'sync' sync_snippets - else # run - load_rewriters - @options[:snippet_names].each do |snippet_name| - puts "===== #{snippet_name} started =====" - group, name = snippet_name.split('/') - rewriter = Core::Rewriter.call group, name - rewriter.warnings.each do |warning| - puts '[Warn] ' + warning.message - end - puts rewriter.todo if rewriter.todo - puts "===== #{snippet_name} done =====" - end + when 'generate' + generate_snippet + when 'execute' + execute_snippet(@options[:execute_command]) + when 'test' + rewriter = Synvert::Core::Utils.eval_snippet(@options[:snippet_name]) + test_snippet(rewriter) + when 'run' + rewriter = Synvert::Core::Utils.eval_snippet(@options[:snippet_name]) + run_snippet(rewriter) + else + # nothing to do end true rescue SystemExit @@ -57,8 +60,7 @@ def run(args) puts "file #{e.diagnostic.location.source_buffer.name}" puts "line #{e.diagnostic.location.line}" false - rescue Synvert::Core::RewriterNotFound => e - puts e.message + rescue StandardError false end @@ -66,80 +68,125 @@ def run(args) # Run OptionParser to parse arguments. def run_option_parser(args) - optparse = OptionParser.new do |opts| - opts.banner = 'Usage: synvert [project_path]' - opts.on '-d', '--load SNIPPET_PATHS', 'load custom snippets, snippet paths can be local file path or remote http url' do |snippet_paths| - @options[:custom_snippet_paths] = snippet_paths.split(',').map(&:strip) - end - opts.on '-l', '--list', 'list all available snippets' do - @options[:command] = 'list' - end - opts.on '-o', '--open SNIPPET_NAME', 'Open a snippet' do |snippet_name| - @options[:command] = 'open' - @options[:snippet_name] = snippet_name - end - opts.on '-q', '--query QUERY', 'query specified snippets' do |query| - @options[:command] = 'query' - @options[:query] = query - end - opts.on '--skip FILE_PATTERNS', 'skip specified files or directories, separated by comma, e.g. app/models/post.rb,vendor/plugins/**/*.rb' do |file_patterns| - @options[:skip_file_patterns] = file_patterns.split(',') - end - opts.on '-s', '--show SNIPPET_NAME', 'show specified snippet description, SNIPPET_NAME is combined by group and name, e.g. ruby/new_hash_syntax' do |snippet_name| - @options[:command] = 'show' - @options[:snippet_name] = snippet_name - end - opts.on '--sync', 'sync snippets' do - @options[:command] = 'sync' - end - opts.on '-r', '--run SNIPPET_NAMES', 'run specified snippets, each SNIPPET_NAME is combined by group and name, e.g. ruby/new_hash_syntax,ruby/new_lambda_syntax' do |snippet_names| - @options[:snippet_names] = snippet_names.split(',').map(&:strip) - end - opts.on '-v', '--version', 'show this version' do - puts Core::VERSION - exit + optparse = + OptionParser.new do |opts| + opts.banner = 'Usage: synvert-ruby [project_path]' + opts.on '-l', '--list', 'list all available snippets' do + @options[:command] = 'list' + end + opts.on '-q', '--query QUERY', 'query specified snippets' do |query| + @options[:command] = 'query' + @options[:query] = query + end + opts.on '-s', + '--show SNIPPET_NAME', + 'show specified snippet description, SNIPPET_NAME is combined by group and name, e.g. ruby/new_hash_syntax' do |snippet_name| + @options[:command] = 'show' + @options[:snippet_name] = snippet_name + end + opts.on '-o', '--open SNIPPET_NAME', 'Open a snippet' do |snippet_name| + @options[:command] = 'open' + @options[:snippet_name] = snippet_name + end + opts.on '-g', '--generate NEW_SNIPPET_NAME', 'generate a new snippet' do |name| + @options[:command] = 'generate' + @options[:snippet_name] = name + end + opts.on '--sync', 'sync snippets' do + @options[:command] = 'sync' + end + opts.on '--execute EXECUTE_COMMAND', 'execute snippet' do |execute_command| + @options[:command] = 'execute' + @options[:execute_command] = execute_command + end + opts.on '-r', + '--run SNIPPET_NAME', + 'run a snippet with snippet name, e.g. ruby/new_hash_syntax, or remote url, or local file path' do |snippet_name| + @options[:command] = 'run' + @options[:snippet_name] = snippet_name + end + opts.on '-t', + '--test SNIPPET_NAME', + 'test a snippet with snippet name, e.g. ruby/new_hash_syntax, or remote url, or local file path' do |snippet_name| + @options[:command] = 'test' + @options[:snippet_name] = snippet_name + end + opts.on '--show-run-process', 'show processing files when running a snippet' do + Core::Configuration.show_run_process = true + end + opts.on '--only-paths DIRECTORIES', + 'only specified files or directories, separated by comma, e.g. app/models,app/controllers' do |directories| + @options[:only_paths] = directories + end + opts.on '--skip-paths FILE_PATTERNS', + 'skip specified files or directories, separated by comma, e.g. vendor/,lib/**/*.rb' do |file_patterns| + @options[:skip_paths] = file_patterns + end + opts.on '-f', '--format FORMAT', 'output format' do |format| + @options[:format] = format + end + opts.on '--number-of-workers NUMBER_OF_WORKERS', + 'set the number of workers, if it is greater than 1, it tests snippet in parallel' do |number_of_workers| + Core::Configuration.number_of_workers = number_of_workers.to_i + end + opts.on '--double-quote', 'prefer double quote, it uses single quote by default' do |_double_quote| + Core::Configuration.single_quote = false + end + opts.on '--tab-width TAB_WIDTH', 'prefer tab width, it uses 2 by default' do |tab_width| + Core::Configuration.tab_width = tab_width.to_i + end + opts.on '-v', '--version', 'show this version' do + puts "#{VERSION} (with synvert-core #{Core::VERSION} and parser #{Parser::VERSION})" + exit + end end - end paths = optparse.parse(args) - Core::Configuration.path = paths.first || Dir.pwd - if @options[:skip_file_patterns] && !@options[:skip_file_patterns].empty? - skip_files = @options[:skip_file_patterns].map do |file_pattern| - full_file_pattern = File.join(Core::Configuration.path, file_pattern) - Dir.glob(full_file_pattern) - end.flatten - Core::Configuration.skip_files = skip_files + Core::Configuration.root_path = paths.first || Dir.pwd + if @options[:only_paths] && !@options[:only_paths].empty? + Core::Configuration.only_paths = @options[:only_paths].split(",").map { |only_path| only_path.strip } + end + if @options[:skip_paths] && !@options[:skip_paths].empty? + Core::Configuration.skip_paths = @options[:skip_paths].split(",").map { |skip_path| skip_path.strip } end end - # Load all rewriters. - def load_rewriters - Dir.glob(File.join(default_snippets_path, 'lib/**/*.rb')).each { |file| require file } - - @options[:custom_snippet_paths].each do |snippet_path| - if snippet_path =~ /^http/ - uri = URI.parse snippet_path - eval(uri.read) - else - require snippet_path - end - end - rescue - FileUtils.rm_rf default_snippets_path - retry + # read all rewriters. + def read_rewriters + Dir.glob(File.join(default_snippets_home, 'lib/**/*.rb')).each { |file| require file } end # List and print all available rewriters. def list_available_rewriters if Core::Rewriter.availables.empty? - puts 'There is no snippet under ~/.synvert, please run `synvert --sync` to fetch snippets.' - else + puts "There is no snippet under #{default_snippets_home}, please run `synvert-ruby --sync` to fetch snippets." + return + end + + if plain_output? Core::Rewriter.availables.each do |group, rewriters| puts group - rewriters.each do |name, rewriter| + rewriters.each do |name, _rewriter| puts ' ' + name end end puts + elsif json_output? + output = [] + Core::Rewriter.availables.each do |group, rewriters| + rewriters.each do |name, rewriter| + rewriter.process_with_sandbox + sub_snippets = + rewriter.sub_snippets.map { |sub_snippet| + { group: sub_snippet.group, name: sub_snippet.name } + } + item = { group: group, name: name, description: rewriter.description, sub_snippets: sub_snippets } + item[:ruby_version] = rewriter.ruby_version.version if rewriter.ruby_version + item[:gem_spec] = { name: rewriter.gem_spec.name, version: rewriter.gem_spec.version } if rewriter.gem_spec + output << item + end + end + + puts JSON.generate(output) end end @@ -148,8 +195,8 @@ def open_rewriter editor = [ENV['SYNVERT_EDITOR'], ENV['EDITOR']].find { |e| !e.nil? && !e.empty? } return puts 'To open a synvert snippet, set $EDITOR or $SYNVERT_EDITOR' unless editor - path = File.expand_path(File.join(default_snippets_path, "lib/#{@options[:snippet_name]}.rb")) - if File.exist? path + path = File.expand_path(File.join(default_snippets_home, "lib/#{@options[:snippet_name]}.rb")) + if File.exist?(path) system editor, path else puts "Can't run #{editor} #{path}" @@ -161,12 +208,12 @@ def query_available_rewriters Core::Rewriter.availables.each do |group, rewriters| if group.include? @options[:query] puts group - rewriters.each do |name, rewriter| + rewriters.each do |name, _rewriter| puts ' ' + name end elsif rewriters.keys.any? { |name| name.include? @options[:query] } puts group - rewriters.each do |name, rewriter| + rewriters.each do |name, _rewriter| puts ' ' + name if name.include?(@options[:query]) end end @@ -176,18 +223,9 @@ def query_available_rewriters # Show and print one rewriter. def show_rewriter - group, name = @options[:snippet_name].split('/') - rewriter = Core::Rewriter.fetch(group, name) - if rewriter - rewriter.process_with_sandbox - puts rewriter.description - rewriter.sub_snippets.each do |sub_rewriter| - puts - puts '=' * 80 - puts "snippet: #{sub_rewriter.name}" - puts '=' * 80 - puts sub_rewriter.description - end + path = File.expand_path(File.join(default_snippets_home, "lib/#{@options[:snippet_name]}.rb")) + if File.exist?(path) + puts File.read(path) else puts "snippet #{@options[:snippet_name]} not found" end @@ -195,16 +233,120 @@ def show_rewriter # sync snippets def sync_snippets - Snippet.new(default_snippets_path).sync + Snippet.new(default_snippets_home).sync puts 'synvert snippets are synced' - core_version = Snippet.fetch_core_version - if Gem::Version.new(core_version) > Gem::Version.new(Synvert::Core::VERSION) - puts "synvert-core is updated, please install synvert-core #{core_version}" + end + + # eval snippet name by user input + def eval_snippet_name_by_input(input) + eval(input) + end + + # run a snippet + def run_snippet(rewriter) + if plain_output? + puts "===== #{rewriter.group}/#{rewriter.name} started =====" + rewriter.process + rewriter.warnings.each do |warning| + puts '[Warn] ' + warning.message + end + puts "===== #{rewriter.group}/#{rewriter.name} done =====" + elsif json_output? + rewriter.process + output = { + affected_files: rewriter.affected_files.union(rewriter.sub_snippets.sum(Set.new, &:affected_files)).to_a, + warnings: rewriter.warnings.union(rewriter.sub_snippets.sum([], &:warnings)) + } + puts JSON.generate(output) + end + rescue StandardError => e + if plain_output? + puts "Error: #{e.message}" + else + puts JSON.generate(error: e.message) + end + raise + end + + # test a snippet + def test_snippet(rewriter) + results = rewriter.test + puts JSON.generate(results) + rescue StandardError => e + puts JSON.generate(error: e.message) + raise + end + + # execute snippet + def execute_snippet(execute_command) + rewriter = eval_snippet_name_by_input(STDIN.read) + if execute_command == 'test' + test_snippet(rewriter) + else + run_snippet(rewriter) end end - def default_snippets_path - File.join(ENV['HOME'], '.synvert') + # generate a new snippet + def generate_snippet + group, name = @options[:snippet_name].split('/') + FileUtils.mkdir_p("lib/#{group}") + FileUtils.mkdir_p("spec/#{group}") + lib_content = <<~EOF + # frozen_string_literal: true + + Synvert::Rewriter.new '#{group}', '#{name}' do + description <<~EOS + It converts Foo to Bar + + ```ruby + Foo + ``` + + => + + ```ruby + Bar + ``` + EOS + + within_files '**/*.rb' do + with_node type: 'const', to_source: 'Foo' do + replace_with 'Bar' + end + end + end + EOF + spec_content = <<~EOF + # frozen_string_literal: true + + require 'spec_helper' + + RSpec.describe 'Convert Foo to Bar' do + let(:rewriter_name) { '#{group}/#{name}' } + let(:fake_file_path) { 'foobar.rb' } + let(:test_content) { 'Foo' } + let(:test_rewritten_content) { 'Bar' } + + include_examples 'convertable' + end + EOF + File.write("lib/#{group}/#{name}.rb", lib_content) + File.write("spec/#{group}/#{name}_spec.rb", spec_content) + end + + def default_snippets_home + # ENV['HOME'] may use \ as file separator, + # but File.join always uses / as file separator. + ENV['SYNVERT_SNIPPETS_HOME'] || File.join(ENV['HOME'].gsub("\\", "/"), '.synvert-ruby') + end + + def plain_output? + @options[:format] == 'plain' + end + + def json_output? + @options[:format] == 'json' end end end diff --git a/lib/synvert/snippet.rb b/lib/synvert/snippet.rb index b49be1f..e96bf11 100644 --- a/lib/synvert/snippet.rb +++ b/lib/synvert/snippet.rb @@ -1,5 +1,4 @@ -require 'open-uri' -require 'json' +# frozen_string_literal: true module Synvert # Manage synvert snippets. @@ -11,16 +10,12 @@ def initialize(snippets_path) # synchronize snippets from github. def sync if File.exist?(@snippets_path) - FileUtils.cd @snippets_path - Kernel.system('git pull --rebase') + Dir.chdir(@snippets_path) do + Kernel.system('git checkout . && git pull --rebase') + end else - Kernel.system("git clone https://github.com/xinminlabs/synvert-snippets.git #{@snippets_path}") + Kernel.system("git clone https://github.com/xinminlabs/synvert-snippets-ruby.git #{@snippets_path}") end end - - def fetch_core_version - content = URI.open('https://rubygems.org/api/v1/versions/synvert-core.json').read - JSON.parse(content).first['number'] - end end end diff --git a/lib/synvert/utils.rb b/lib/synvert/utils.rb new file mode 100644 index 0000000..65a214e --- /dev/null +++ b/lib/synvert/utils.rb @@ -0,0 +1,18 @@ +module Synvert + module Utils + class << self + def format_url(url) + convert_to_github_raw_url(url) + end + + private + + def convert_to_github_raw_url(url) + if url.include?('//github.com/') + url = url.sub('//github.com/', '//raw.githubusercontent.com/').sub('/blob/', '/') + end + url + end + end + end +end diff --git a/lib/synvert/version.rb b/lib/synvert/version.rb index 2ca08cc..317cb00 100644 --- a/lib/synvert/version.rb +++ b/lib/synvert/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Synvert - VERSION = '0.10.0' + VERSION = '1.4.1' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 43e7b39..d0c5392 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'synvert' diff --git a/spec/synvert/snippet_spec.rb b/spec/synvert/snippet_spec.rb index 6b2006e..dbcc550 100644 --- a/spec/synvert/snippet_spec.rb +++ b/spec/synvert/snippet_spec.rb @@ -1,31 +1,27 @@ +# frozen_string_literal: true + require 'spec_helper' module Synvert describe Snippet do - let(:snippets_path) { File.join(File.dirname(__FILE__), '.synvert') } + let(:snippets_path) { File.join(File.dirname(__FILE__), '.synvert-ruby') } let(:snippet) { Snippet.new(snippets_path) } after { FileUtils.rmdir(snippets_path) if File.exist?(snippets_path) } describe 'sync' do it 'git clones snippets' do - expect(Kernel).to receive(:system).with("git clone https://github.com/xinminlabs/synvert-snippets.git #{snippets_path}") + expect(Kernel).to receive(:system).with( + "git clone https://github.com/xinminlabs/synvert-snippets-ruby.git #{snippets_path}" + ) snippet.sync end it 'git pull snippets' do FileUtils.mkdir snippets_path - expect(Kernel).to receive(:system).with('git pull --rebase') + expect(Kernel).to receive(:system).with('git checkout . && git pull --rebase') snippet.sync FileUtils.cd File.dirname(__FILE__) end end - - describe 'fetch_core_version' do - it 'gets remote version' do - stub_request(:get, 'https://rubygems.org/api/v1/versions/synvert-core.json'). - to_return(:body => '[{"number":"0.4.2"}]') - expect(snippet.fetch_core_version).to eq '0.4.2' - end - end end end diff --git a/spec/synvert/utils_spec.rb b/spec/synvert/utils_spec.rb new file mode 100644 index 0000000..f84ab94 --- /dev/null +++ b/spec/synvert/utils_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Synvert + RSpec.describe Utils do + describe '.format_url' do + it 'converts github url to github raw url' do + url = 'https://github.com/xinminlabs/synvert-snippets-ruby/blob/main/lib/ruby/map_and_flatten_to_flat_map.rb' + raw_url = 'https://raw.githubusercontent.com/xinminlabs/synvert-snippets-ruby/main/lib/ruby/map_and_flatten_to_flat_map.rb' + expect(described_class.format_url(url)).to eq raw_url + end + end + end +end diff --git a/synvert.gemspec b/synvert-ruby.gemspec similarity index 74% rename from synvert.gemspec rename to synvert-ruby.gemspec index 9bc5d38..48abd86 100644 --- a/synvert.gemspec +++ b/synvert-ruby.gemspec @@ -1,4 +1,6 @@ -lib = File.expand_path('../lib', __FILE__) +# frozen_string_literal: true + +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'synvert/version' @@ -9,16 +11,16 @@ Gem::Specification.new do |spec| spec.email = ['flyerhzm@gmail.com'] spec.description = 'synvert is used to convert ruby code to better syntax.' spec.summary = 'synvert = syntax + convert.' - spec.homepage = 'https://github.com/xinminlabs/synvert' + spec.homepage = 'https://github.com/xinminlabs/synvert-ruby' spec.license = 'MIT' spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] - spec.post_install_message = 'Please run `synvert --sync` first to sync snippets remotely.' + spec.post_install_message = 'Please run `synvert-ruby --sync` first to sync snippets remotely.' - spec.add_runtime_dependency 'synvert-core', '>= 0.19.0' + spec.add_runtime_dependency 'synvert-core', '>= 1.21.1' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake'