From bd85cd5cdf631ad85aef3dd06f89aafb4a1aa0af Mon Sep 17 00:00:00 2001 From: Mo Lawson Date: Mon, 1 Aug 2022 13:30:05 -0500 Subject: [PATCH] Add support for a simple config file As syntax_tree gains wider usage, it'll be very helpful to allow projects to commit their particular configs within their repo for use by any tool that uses `stree` instead of having to create wrapper scripts, rely on rake tasks that come with their own overhead, or manually keep settings up to date among all contributors. This set of changes takes inspiration from sorbet to provide users with a simple config file that's also easy to parse and use within the gem. It's a text file that has the exact options you'd pass on the command line, each on their own line. These are parsed and prepended to the arguments array within `CLI.run`, still allowing for other options to passed from the command line. I decided to restrict this only to the command line options and avoid the source files argument, opting to let other tools pass their own source file from the command line, which is preferable for tools like editor integrations that might interact with a single file at a time. If users want to interact with all of their Ruby files at once, the rake tasks are perfect for providing larger, static patterns of files. And since they use `CLI.run` as well, they'll pick up options from a .streerc file, if present. I also opted for only supporting a single .streerc file at the project root. If there's a need for multiple configs or config locations, this can be easily extended to look up through a directory structure or accept an option for a specific config file location (or even a different filename). Those felt out of scope for this initial support. --- README.md | 14 +++++++++ lib/syntax_tree/cli.rb | 7 +++++ test/cli_test.rb | 71 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/README.md b/README.md index 3a3803ec..fb1a49cd 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ It is built with only standard library dependencies. It additionally ships with - [json](#json) - [match](#match) - [write](#write) + - [Configuration](#configuration) - [Library](#library) - [SyntaxTree.read(filepath)](#syntaxtreereadfilepath) - [SyntaxTree.parse(source)](#syntaxtreeparsesource) @@ -231,6 +232,19 @@ To change the print width that you are writing with, specify the `--print-width` stree write --print-width=100 path/to/file.rb ``` +### Configuration + +Any of the above CLI commands can also read configuration options from a `.streerc` file in the directory where the commands are executed. + +This should be a text file with each argument on a separate line. + +```txt +--print-width=100 +--plugins=plugin/trailing_comma +``` + +If this file is present, it will _always_ be used for CLI commands. You can also pass options from the command line as in the examples above. The options in the `.streerc` file are passed to the CLI first, then the arguments from the command line. In the case of exclusive options (e.g. `--print-width`), this means that the command line options override what's in the config file. In the case of options that can take multiple inputs (e.g. `--plugins`), the effect is additive. That is, the plugins passed from the command line will be loaded _in addition to_ the plugins in the config file. + ## Library Syntax Tree can be used as a library to access the syntax tree underlying Ruby source code. diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index cad4fc35..fb2e4554 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -4,6 +4,8 @@ module SyntaxTree # Syntax Tree ships with the `stree` CLI, which can be used to inspect and # manipulate Ruby code. This module is responsible for powering that CLI. module CLI + CONFIG_FILE = ".streerc" + # A utility wrapper around colored strings in the output. class Color attr_reader :value, :code @@ -269,6 +271,11 @@ def run(argv) name, *arguments = argv print_width = DEFAULT_PRINT_WIDTH + config_file = File.join(Dir.pwd, CONFIG_FILE) + if File.readable?(config_file) + arguments.unshift(*File.readlines(config_file, chomp: true)) + end + while arguments.first&.start_with?("--") case (argument = arguments.shift) when /^--plugins=(.+)$/ diff --git a/test/cli_test.rb b/test/cli_test.rb index 31e4b7e2..9bc237fb 100644 --- a/test/cli_test.rb +++ b/test/cli_test.rb @@ -180,6 +180,77 @@ def test_language_server $stdout = prev_stdout end + def test_config_file + config_file = File.join(Dir.pwd, SyntaxTree::CLI::CONFIG_FILE) + config = <<~TXT + --print-width=100 + --plugins=plugin + TXT + File.write(config_file, config) + + Dir.mktmpdir do |directory| + Dir.mkdir(File.join(directory, "syntax_tree")) + $:.unshift(directory) + + File.write( + File.join(directory, "syntax_tree", "plugin.rb"), + "puts 'Hello, world!'" + ) + + file = Tempfile.new(%w[test- .rb]) + contents = "#{"a" * 40} + #{"b" * 40}\n" + file.write(contents) + + result = run_cli("format", file: file) + assert_equal("Hello, world!\n#{contents}", result.stdio) + end + ensure + FileUtils.rm(config_file) + end + + def test_print_width_args_with_config_file + config_file = File.join(Dir.pwd, SyntaxTree::CLI::CONFIG_FILE) + File.write(config_file, "--print-width=100") + + contents = "#{"a" * 40} + #{"b" * 40}\n" + + file = Tempfile.new(%w[test- .rb]) + file.write(contents) + result = run_cli("check", file: file) + assert_includes(result.stdio, "match") + + file = Tempfile.new(%w[test- .rb]) + file.write(contents) + result = run_cli("check", "--print-width=82", file: file) + assert_includes(result.stderr, "expected") + ensure + FileUtils.rm(config_file) + end + + def test_plugin_args_with_config_file + config_file = File.join(Dir.pwd, SyntaxTree::CLI::CONFIG_FILE) + File.write(config_file, "--plugins=hello_plugin") + + Dir.mktmpdir do |directory| + Dir.mkdir(File.join(directory, "syntax_tree")) + $:.unshift(directory) + + File.write( + File.join(directory, "syntax_tree", "hello_plugin.rb"), + "puts 'Hello, world!'" + ) + File.write( + File.join(directory, "syntax_tree", "bye_plugin.rb"), + "puts 'Bye, world!'" + ) + + result = run_cli("format", "--plugins=bye_plugin") + assert_equal("Hello, world!\nBye, world!\ntest\n", result.stdio) + end + ensure + FileUtils.rm(config_file) + end + private Result = Struct.new(:status, :stdio, :stderr, keyword_init: true)