diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8b92e40..daeea91 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,4 @@ --- -version: 2 updates: - directory: "/" package-ecosystem: github-actions @@ -13,3 +12,4 @@ updates: package-ecosystem: bundler schedule: interval: daily +version: 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32c19a4..8332c26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,4 @@ -name: CI -on: push +--- jobs: bundler-audit: name: Bundler Audit @@ -10,6 +9,13 @@ jobs: with: bundler-cache: true - run: bin/bundler-audit check --update + npm-audit: + name: npm Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - run: npm audit rspec: name: Test runs-on: ubuntu-latest @@ -28,10 +34,5 @@ jobs: with: bundler-cache: true - run: bin/rubocop - npm-audit: - name: npm Audit - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - run: npm audit +name: CI +"on": push diff --git a/Gemfile.lock b/Gemfile.lock index 7a9dd52..a5ad123 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,6 +9,7 @@ PATH dorian-to_struct git json + mini_racer ostruct parallel terminal-table @@ -57,7 +58,12 @@ GEM concurrent-ruby (~> 1.0) json (2.7.2) language_server-protocol (3.17.0.3) + libv8-node (18.19.0.0) + libv8-node (18.19.0.0-arm64-darwin) + libv8-node (18.19.0.0-x86_64-linux) logger (1.6.1) + mini_racer (0.16.0) + libv8-node (~> 18.19.0.0) minitest (5.25.1) ostruct (0.6.0) parallel (1.26.3) diff --git a/a.js b/a.js new file mode 100644 index 0000000..090c024 --- /dev/null +++ b/a.js @@ -0,0 +1,3 @@ +import prettier from "prettier"; + +console.log(prettier.format("1 + 1", { parser: "babel" })); diff --git a/dorian.gemspec b/dorian.gemspec index 33aba23..11acecb 100644 --- a/dorian.gemspec +++ b/dorian.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |s| s.add_dependency "dorian-to_struct" s.add_dependency "git" s.add_dependency "json" + s.add_dependency "mini_racer" s.add_dependency "ostruct" s.add_dependency "parallel" s.add_dependency "terminal-table" diff --git a/lib/dorian/bin.rb b/lib/dorian/bin.rb index 8e1cc54..f200544 100644 --- a/lib/dorian/bin.rb +++ b/lib/dorian/bin.rb @@ -7,14 +7,74 @@ require "dorian/to_struct" require "git" require "json" +require "mini_racer" require "net/http" require "parallel" +require "syntax_tree" require "terminal-table" require "uri" require "yaml" class Dorian class Bin + RUBY_EXTENSIONS = %w[ + .rb + .arb + .axlsx + .builder + .fcgi + .gemfile + .gemspec + .god + .jb + .jbuilder + .mspec + .opal + .pluginspec + .podspec + .rabl + .rake + .rbuild + .rbw + .rbx + .ru + .ruby + .schema + .spec + .thor + .watchr + ].freeze + + RUBY_FILENAMES = %w[ + .irbrc + .pryrc + .simplecov + Appraisals + Berksfile + Brewfile + Buildfile + Capfile + Cheffile + Dangerfile + Deliverfile + Fastfile + Gemfile + Guardfile + Jarfile + Mavenfile + Podfile + Puppetfile + Rakefile + rakefile + Schemafile + Snapfile + Steepfile + Thorfile + Vagabondfile + Vagrantfile + buildfile + ].freeze + VERSION = File.read(File.expand_path("../../VERSION", __dir__)) DEFAULT_IO = :raw @@ -275,6 +335,14 @@ def run arguments.delete("tree") @command = :tree command_tree + when :format + arguments.delete("format") + @command = :format + command_format + when :pretty + arguments.delete("pretty") + @command = :pretty + command_pretty else arguments.delete("read") @command = :read @@ -282,6 +350,23 @@ def run end end + def command_pretty + command_format + end + + def command_format + root = File.expand_path("../../../", __dir__) + prettier_path = File.join(root, "node_modules/prettier/standalone.js") + prettier_js = File.read(prettier_path) + parser_path = File.join(root, "node_modules/prettier/plugins/babel.js") + parser_js = File.read(parser_path) + context = MiniRacer::Context.new + context.eval(prettier_js) + context.eval(parser_js) + + Git.open(".").ls_files.map(&:first).each { |file| format(file, context:) } + end + def command_release File.delete(*Dir["*.gem"]) system("gem build") @@ -323,7 +408,7 @@ def command_top :count => command_count, :percent => "#{(command_count * 100 / history.size.to_f).round(3)}%", - :command => + :command => command } end .first(limit) @@ -336,7 +421,7 @@ def command_tree down = "│   " down_and_right = "├── " - git_ls_files = lambda { |path| Git.open(".").ls_files(path).map(&:first) } + git_ls_files = ->(path) { Git.open(".").ls_files(path).map(&:first) } group = lambda do |files| @@ -1233,77 +1318,61 @@ def encoder Tiktoken.encoding_for_model("gpt-4o") end + def filetype(path) + ext = File.extname(path).to_s.downcase + return :directory if Dir.exist?(path) + return :symlink if File.symlink?(path) + return :ruby if RUBY_FILENAMES.include?(path) + return :ruby if RUBY_EXTENSIONS.include?(ext) + return :json if ext == ".json" + return :jsonl if ext == ".jsonl" + return :yaml if ext == ".yaml" + return :yaml if ext == ".yml" + return :csv if ext == ".csv" + return :js if ext == ".js" + return :css if ext == ".css" + return :html if ext == ".html" + return :html if ext == ".htm" + return :haml if ext == ".haml" + return :slim if ext == ".slim" + return :erb if ext == ".erb" + return :fish if ext == ".fish" + return :sql if ext == ".sql" + return :tex if ext == ".tex" + return :md if ext == ".md" + return :md if ext == ".markdown" + return :png if ext == ".png" + return :jpeg if ext == ".jpg" + return :jpeg if ext == ".jpeg" + return :ico if ext == ".ico" + return :webp if ext == ".webp" + return :heic if ext == ".heic" + return :pdf if ext == ".pdf" + return :env if path == ".env" + return :env if path.start_with?(".env.") + return unless File.exist?(path) + + first_line = File.open(path, &:gets).to_s + first_line = first_line.encode("UTF-8", invalid: :replace) + return :ruby if /\A#!.*ruby\z/.match?(first_line) + + false + end + def match_filetypes?(path, filetypes: arguments) return true if filetypes.none? return true unless filetypes.intersect?(%w[rb ruby]) - - ruby_extensions = %w[ - .rb - .arb - .axlsx - .builder - .fcgi - .gemfile - .gemspec - .god - .jb - .jbuilder - .mspec - .opal - .pluginspec - .podspec - .rabl - .rake - .rbuild - .rbw - .rbx - .ru - .ruby - .schema - .spec - .thor - .watchr - ] - - ruby_filenames = %w[ - .irbrc - .pryrc - .simplecov - Appraisals - Berksfile - Brewfile - Buildfile - Capfile - Cheffile - Dangerfile - Deliverfile - Fastfile - Gemfile - Guardfile - Jarfile - Mavenfile - Podfile - Puppetfile - Rakefile - rakefile - Schemafile - Snapfile - Steepfile - Thorfile - Vagabondfile - Vagrantfile - buildfile - ] - return false if Dir.exist?(path) - return true if ruby_filenames.include?(path) - return true if ruby_extensions.include?(File.extname(path)) + return true if RUBY_FILENAMES.include?(path) + return true if RUBY_EXTENSIONS.include?(File.extname(path)) return false unless File.exist?(path) - first_line = - File.open(path, &:gets).to_s.encode("UTF-8", invalid: :replace) + first_line = File.open(path, &:gets).to_s + first_line = first_line.encode("UTF-8", invalid: :replace) - /\A#!.*ruby\z/.match?(first_line) + return true if /\A#!.*ruby\z/.match?(first_line) + + false end def pluck(element) @@ -1374,5 +1443,66 @@ def evaluates( returns: ).returned end + + def sort(object) + object = object.from_deep_struct + + if object.is_a?(Hash) + object + .to_a + .sort_by(&:first) + .to_h + .transform_values { |value| sort(value) } + elsif object.is_a?(Array) + object.map { |element| sort(element) } + else + object + end + end + + def format(path, context:) + return if File.symlink?(path) + return unless File.exist?(path) + + before = File.read(path) + + case filetype(path) + when :directory + when :ruby + after = SyntaxTree.format(before) + when :json + after = JSON.pretty_generate(sort(JSON.parse(before))) + when :jsonl + after = before.lines.map { |line| JSON.parse(line).to_json }.join("\n") + when :csv + after = + CSV.generate { |csv| CSV.parse(before).each { |row| csv << row } } + when :yaml + after = sort(YAML.safe_load(before)).to_yaml + when :js + promise = context.eval(<<~JS) + prettier.format(#{before.to_json}, { "parser": "babel" }) + JS + p promise + p promise.class + after = before + loop do + if promise.fulfilled? + after = promise.value + elsif promise.rejected? + raise promise.reason + else + sleep(0.01) + end + end + end + + if after && before != after + puts path + File.write(path, after) + end + rescue StandardError => e + warn "failed to parse #{path}: #{e.message}" + end end end diff --git a/package-lock.json b/package-lock.json index 7c5026c..fb956b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,10 +5,29 @@ "packages": { "": { "license": "MIT", + "devDependencies": { + "prettier": "*" + }, "engines": { "node": "22.5.1", "npm": "10.8.2" } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } } } } diff --git a/package.json b/package.json index 9a182e7..1254e67 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { - "license": "MIT", + "devDependencies": { + "prettier": "*" + }, "engines": { "node": "22.5.1", "npm": "10.8.2" - } + }, + "license": "MIT" } diff --git a/samples/books.json b/samples/books.json index d8fa2c5..9b7afdb 100644 --- a/samples/books.json +++ b/samples/books.json @@ -1,23 +1,23 @@ [ { - "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", - "published_year": 1925, + "available": true, "genres": ["Novel", "Historical"], - "available": true + "published_year": 1925, + "title": "The Great Gatsby" }, { - "title": "To Kill a Mockingbird", "author": "Harper Lee", - "published_year": 1960, + "available": false, "genres": ["Novel", "Southern Gothic", "Bildungsroman"], - "available": false + "published_year": 1960, + "title": "To Kill a Mockingbird" }, { - "title": "1984", "author": "George Orwell", - "published_year": 1949, + "available": true, "genres": ["Dystopian", "Political Fiction"], - "available": true + "published_year": 1949, + "title": "1984" } ] diff --git a/samples/books.jsonl b/samples/books.jsonl index fe68405..191ab2d 100644 --- a/samples/books.jsonl +++ b/samples/books.jsonl @@ -1,3 +1,3 @@ -{"title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "published_year": 1925, "genres": ["Novel", "Historical"], "available": true} -{"title": "To Kill a Mockingbird", "author": "Harper Lee", "published_year": 1960, "genres": ["Novel", "Southern Gothic", "Bildungsroman"], "available": false} -{"title": "1984", "author": "George Orwell", "published_year": 1949, "genres": ["Dystopian", "Political Fiction"], "available": true} \ No newline at end of file +{"title":"The Great Gatsby","author":"F. Scott Fitzgerald","published_year":1925,"genres":["Novel","Historical"],"available":true} +{"title":"To Kill a Mockingbird","author":"Harper Lee","published_year":1960,"genres":["Novel","Southern Gothic","Bildungsroman"],"available":false} +{"title":"1984","author":"George Orwell","published_year":1949,"genres":["Dystopian","Political Fiction"],"available":true} \ No newline at end of file diff --git a/samples/config.yml b/samples/config.yml index b85e06a..b789d62 100644 --- a/samples/config.yml +++ b/samples/config.yml @@ -1,27 +1,27 @@ --- +allowed_hosts: + - localhost + - example.com + - api.example.com app_config: + debug_mode: false + environment: production name: My Web App version: 1.0.0 - environment: production - debug_mode: false database: host: localhost + name: my_web_app_db + password: securepassword port: 5432 username: db_user - password: securepassword - name: my_web_app_db -server: - host: 0.0.0.0 - port: 8080 -logging: - level: info - log_to_file: true - file_path: "/var/log/my_web_app.log" features: - authentication: true api: true + authentication: true web_sockets: false -allowed_hosts: - - localhost - - example.com - - api.example.com +logging: + file_path: "/var/log/my_web_app.log" + level: info + log_to_file: true +server: + host: 0.0.0.0 + port: 8080 diff --git a/samples/config_2.yml b/samples/config_2.yml index faefade..6be9acb 100644 --- a/samples/config_2.yml +++ b/samples/config_2.yml @@ -1,22 +1,22 @@ --- +allowed_hosts: + - example.com + - api.example.com app_config: + debug_mode: false + environment: production name: My Web App version: 1.0.0 - environment: production - debug_mode: false database: host: localhost + name: my_web_app_db port: 5432 username: db_user - name: my_web_app_db -logging: - level: info - log_to_file: true - file_path: "/var/log/my_web_app.log" features: - authentication: true api: true + authentication: true web_sockets: false -allowed_hosts: - - example.com - - api.example.com +logging: + file_path: "/var/log/my_web_app.log" + level: info + log_to_file: true diff --git a/samples/maths.js b/samples/maths.js new file mode 100644 index 0000000..839ca11 --- /dev/null +++ b/samples/maths.js @@ -0,0 +1 @@ +1 + 1; diff --git a/samples/people.yml b/samples/people.yml index 1200737..cc1a14e 100644 --- a/samples/people.yml +++ b/samples/people.yml @@ -1,27 +1,27 @@ --- -- name: Alice Johnson +- department: Engineering + full_time: true id: 101 - department: Engineering + name: Alice Johnson role: Software Engineer - full_time: true skills: - Python - Ruby - JavaScript -- name: Bob Smith +- department: Marketing + full_time: false id: 102 - department: Marketing + name: Bob Smith role: Marketing Specialist - full_time: false skills: - SEO - Content Writing - Social Media -- name: Charlie Brown +- department: Human Resources + full_time: true id: 103 - department: Human Resources + name: Charlie Brown role: HR Manager - full_time: true skills: - Recruitment - Employee Relations diff --git a/samples/user.json b/samples/user.json index e5f4e00..aefd8c9 100644 --- a/samples/user.json +++ b/samples/user.json @@ -1,27 +1,27 @@ { "user": { - "id": 12345, - "name": "John Doe", - "email": "john.doe@example.com", - "is_active": true, - "profile": { - "age": 30, - "gender": "male", - "interests": ["reading", "coding", "hiking"] - }, + "account_created": "2021-05-20T14:30:00Z", "address": { - "street": "123 Main St", "city": "Anytown", "state": "CA", + "street": "123 Main St", "zip_code": "12345" }, - "account_created": "2021-05-20T14:30:00Z", + "email": "john.doe@example.com", + "id": 12345, + "is_active": true, + "name": "John Doe", "preferences": { "newsletter": true, "notifications": { "email": true, "sms": false } + }, + "profile": { + "age": 30, + "gender": "male", + "interests": ["reading", "coding", "hiking"] } } }