dorian 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +15 -0
  3. data/.github/workflows/ci.yml +38 -0
  4. data/.gitignore +30 -0
  5. data/.node-version +1 -0
  6. data/.prettierignore +2 -0
  7. data/.rubocop.yml +129 -0
  8. data/.ruby-version +1 -0
  9. data/.tool-versions +3 -0
  10. data/Gemfile +20 -0
  11. data/Gemfile.lock +186 -0
  12. data/LICENSE +19 -0
  13. data/README.md +24 -0
  14. data/VERSION +1 -1
  15. data/bin/bundle +123 -0
  16. data/bin/bundle-audit +31 -0
  17. data/bin/bundler-audit +31 -0
  18. data/bin/rspec +31 -0
  19. data/bin/rubocop +31 -0
  20. data/dorian.gemspec +35 -0
  21. data/lib/dorian/bin.rb +739 -81
  22. data/package-lock.json +39 -0
  23. data/package.json +11 -0
  24. data/samples/books.json +33 -0
  25. data/samples/books.jsonl +3 -0
  26. data/samples/config.yml +27 -0
  27. data/samples/config_2.yml +22 -0
  28. data/samples/maths.js +1 -0
  29. data/samples/numbers.raw +10 -0
  30. data/samples/people.csv +6 -0
  31. data/samples/people.yml +28 -0
  32. data/samples/query.sql +7 -0
  33. data/samples/user.json +31 -0
  34. data/spec/dorian_spec.rb +276 -0
  35. data/spec/spec_helper.rb +3 -0
  36. data/vendor/groovy-beautify/LICENSE.MD +21 -0
  37. data/vendor/groovy-beautify/README.md +65 -0
  38. data/vendor/groovy-beautify/dist/__tests__/index.test.d.ts +1 -0
  39. data/vendor/groovy-beautify/dist/cjs/index.js +573 -0
  40. data/vendor/groovy-beautify/dist/cjs/package.json +3 -0
  41. data/vendor/groovy-beautify/dist/es/index.js +682 -0
  42. data/vendor/groovy-beautify/dist/es/package.json +3 -0
  43. data/vendor/groovy-beautify/dist/formatter/FormatRule.d.ts +14 -0
  44. data/vendor/groovy-beautify/dist/formatter/Formatter.d.ts +11 -0
  45. data/vendor/groovy-beautify/dist/formatter/GroovyFormatRules.d.ts +3 -0
  46. data/vendor/groovy-beautify/dist/formatter/__tests__/formatter.test.d.ts +1 -0
  47. data/vendor/groovy-beautify/dist/index.d.ts +5 -0
  48. data/vendor/groovy-beautify/dist/parser/GroovyParseRules.d.ts +3 -0
  49. data/vendor/groovy-beautify/dist/parser/ParseRule.d.ts +23 -0
  50. data/vendor/groovy-beautify/dist/parser/Parser.d.ts +29 -0
  51. data/vendor/groovy-beautify/dist/parser/__tests__/parser.test.d.ts +1 -0
  52. data/vendor/groovy-beautify/dist/parser/types.d.ts +6 -0
  53. data/vendor/groovy-beautify/dist/utils/text.d.ts +4 -0
  54. data/vendor/groovy-beautify/package.json +55 -0
  55. data/vendor/prettier/LICENSE +4379 -0
  56. data/vendor/prettier/README.md +109 -0
  57. data/vendor/prettier/bin/prettier.cjs +71 -0
  58. data/vendor/prettier/doc.d.ts +243 -0
  59. data/vendor/prettier/doc.js +1545 -0
  60. data/vendor/prettier/doc.mjs +1503 -0
  61. data/vendor/prettier/index.cjs +825 -0
  62. data/vendor/prettier/index.d.ts +941 -0
  63. data/vendor/prettier/index.mjs +25899 -0
  64. data/vendor/prettier/internal/cli.mjs +4366 -0
  65. data/vendor/prettier/package.json +198 -0
  66. data/vendor/prettier/plugins/acorn.d.ts +6 -0
  67. data/vendor/prettier/plugins/acorn.js +6618 -0
  68. data/vendor/prettier/plugins/acorn.mjs +6456 -0
  69. data/vendor/prettier/plugins/angular.d.ts +8 -0
  70. data/vendor/prettier/plugins/angular.js +2435 -0
  71. data/vendor/prettier/plugins/angular.mjs +2375 -0
  72. data/vendor/prettier/plugins/babel.d.ts +18 -0
  73. data/vendor/prettier/plugins/babel.js +14271 -0
  74. data/vendor/prettier/plugins/babel.mjs +13986 -0
  75. data/vendor/prettier/plugins/estree.d.ts +1 -0
  76. data/vendor/prettier/plugins/estree.js +9487 -0
  77. data/vendor/prettier/plugins/estree.mjs +9279 -0
  78. data/vendor/prettier/plugins/flow.d.ts +5 -0
  79. data/vendor/prettier/plugins/flow.js +51477 -0
  80. data/vendor/prettier/plugins/flow.mjs +51219 -0
  81. data/vendor/prettier/plugins/glimmer.d.ts +5 -0
  82. data/vendor/prettier/plugins/glimmer.js +8534 -0
  83. data/vendor/prettier/plugins/glimmer.mjs +8421 -0
  84. data/vendor/prettier/plugins/graphql.d.ts +5 -0
  85. data/vendor/prettier/plugins/graphql.js +2421 -0
  86. data/vendor/prettier/plugins/graphql.mjs +2347 -0
  87. data/vendor/prettier/plugins/html.d.ts +8 -0
  88. data/vendor/prettier/plugins/html.js +8182 -0
  89. data/vendor/prettier/plugins/html.mjs +8077 -0
  90. data/vendor/prettier/plugins/markdown.d.ts +7 -0
  91. data/vendor/prettier/plugins/markdown.js +9068 -0
  92. data/vendor/prettier/plugins/markdown.mjs +8977 -0
  93. data/vendor/prettier/plugins/meriyah.d.ts +5 -0
  94. data/vendor/prettier/plugins/meriyah.js +5953 -0
  95. data/vendor/prettier/plugins/meriyah.mjs +5800 -0
  96. data/vendor/prettier/plugins/postcss.d.ts +7 -0
  97. data/vendor/prettier/plugins/postcss.js +9244 -0
  98. data/vendor/prettier/plugins/postcss.mjs +9046 -0
  99. data/vendor/prettier/plugins/typescript.d.ts +5 -0
  100. data/vendor/prettier/plugins/typescript.js +38058 -0
  101. data/vendor/prettier/plugins/typescript.mjs +37732 -0
  102. data/vendor/prettier/plugins/yaml.d.ts +5 -0
  103. data/vendor/prettier/plugins/yaml.js +7425 -0
  104. data/vendor/prettier/plugins/yaml.mjs +7329 -0
  105. data/vendor/prettier/standalone.d.ts +33 -0
  106. data/vendor/prettier/standalone.js +3984 -0
  107. data/vendor/prettier/standalone.mjs +3938 -0
  108. data/vendor/sql-formatter.js +18762 -0
  109. metadata +205 -2
data/lib/dorian/bin.rb CHANGED
@@ -6,14 +6,82 @@ require "dorian/eval"
6
6
  require "dorian/progress"
7
7
  require "dorian/to_struct"
8
8
  require "git"
9
+ require "hexapdf"
9
10
  require "json"
11
+ require "mini_racer"
10
12
  require "net/http"
11
13
  require "parallel"
14
+ require "shellwords"
15
+ require "syntax_tree"
16
+ require "syntax_tree/erb"
17
+ require "syntax_tree/haml"
18
+ require "syntax_tree/xml"
19
+ require "syntax_tree/json"
20
+ require "tempfile"
21
+ require "terminal-table"
12
22
  require "uri"
13
23
  require "yaml"
14
24
 
15
25
  class Dorian
16
26
  class Bin
27
+ RUBY_EXTENSIONS = %w[
28
+ .rb
29
+ .arb
30
+ .axlsx
31
+ .builder
32
+ .fcgi
33
+ .gemfile
34
+ .gemspec
35
+ .god
36
+ .jb
37
+ .jbuilder
38
+ .mspec
39
+ .opal
40
+ .pluginspec
41
+ .podspec
42
+ .rabl
43
+ .rake
44
+ .rbuild
45
+ .rbw
46
+ .rbx
47
+ .ru
48
+ .ruby
49
+ .schema
50
+ .spec
51
+ .thor
52
+ .watchr
53
+ ].freeze
54
+
55
+ RUBY_FILENAMES = %w[
56
+ .irbrc
57
+ .pryrc
58
+ .simplecov
59
+ Appraisals
60
+ Berksfile
61
+ Brewfile
62
+ Buildfile
63
+ Capfile
64
+ Cheffile
65
+ Dangerfile
66
+ Deliverfile
67
+ Fastfile
68
+ Gemfile
69
+ Guardfile
70
+ Jarfile
71
+ Mavenfile
72
+ Podfile
73
+ Puppetfile
74
+ Rakefile
75
+ rakefile
76
+ Schemafile
77
+ Snapfile
78
+ Steepfile
79
+ Thorfile
80
+ Vagabondfile
81
+ Vagrantfile
82
+ buildfile
83
+ ].freeze
84
+
17
85
  VERSION = File.read(File.expand_path("../../VERSION", __dir__))
18
86
 
19
87
  DEFAULT_IO = :raw
@@ -51,8 +119,7 @@ class Dorian
51
119
  alias: :o
52
120
  },
53
121
  parallel: {
54
- alias: :p,
55
- default: true
122
+ alias: :p
56
123
  },
57
124
  parallel_type: {
58
125
  alias: :pt,
@@ -206,6 +273,83 @@ class Dorian
206
273
  arguments.delete("ls")
207
274
  @command = :ls
208
275
  command_ls
276
+ when :strip
277
+ arguments.delete("strip")
278
+ @command = :strip
279
+ command_strip
280
+ when :rstrip
281
+ arguments.delete("rstrip")
282
+ @command = :rstrip
283
+ command_rstrip
284
+ when :lstrip
285
+ arguments.delete("lstrip")
286
+ @command = :lstrip
287
+ command_lstrip
288
+ when :merge
289
+ arguments.delete("merge")
290
+ @command = :merge
291
+ command_merge
292
+ when :pluck
293
+ arguments.delete("pluck")
294
+ @command = :pluck
295
+ command_pluck
296
+ when :shuffle
297
+ arguments.delete("shuffle")
298
+ @command = :shuffle
299
+ command_shuffle
300
+ when :rename
301
+ arguments.delete("rename")
302
+ @command = :rename
303
+ command_rename
304
+ when :replace
305
+ arguments.delete("replace")
306
+ @command = :replace
307
+ command_replace
308
+ when :sort
309
+ arguments.delete("sort")
310
+ @command = :sort
311
+ command_sort
312
+ when :table
313
+ arguments.delete("table")
314
+ @command = :table
315
+ command_table
316
+ when :then
317
+ arguments.delete("then")
318
+ @command = :then
319
+ @ruby = arguments.delete_at(0)
320
+ command_then
321
+ when :times
322
+ arguments.delete("times")
323
+ @command = :times
324
+ command_times
325
+ when :uniq
326
+ arguments.delete("uniq")
327
+ @command = :uniq
328
+ command_uniq
329
+ when :write
330
+ arguments.delete("write")
331
+ @command = :write
332
+ command_write
333
+ when :release
334
+ arguments.delete("release")
335
+ @command = :release
336
+ command_release
337
+ when :top
338
+ arguments.delete("top")
339
+ @command = :top
340
+ command_top
341
+ when :tree
342
+ arguments.delete("tree")
343
+ @command = :tree
344
+ command_tree
345
+ when :format
346
+ arguments.delete("format")
347
+ @command = :format
348
+ command_format
349
+ when :pretty
350
+ arguments.delete("pretty")
351
+ @command = :pretty
352
+ command_pretty
209
353
  else
210
354
  arguments.delete("read")
211
355
  @command = :read
@@ -213,12 +357,217 @@ class Dorian
213
357
  end
214
358
  end
215
359
 
360
+ def command_pretty
361
+ command_format
362
+ end
363
+
364
+ def command_format
365
+ context = MiniRacer::Context.new
366
+ context.attach("puts", proc { |string| puts(string) })
367
+ context.attach("warn", proc { |string| warn(string) })
368
+ context.attach("read", proc { |path| File.read(path) })
369
+ context.attach(
370
+ "write",
371
+ proc { |path, content| File.write(path, content) }
372
+ )
373
+
374
+ root = File.expand_path("../../", __dir__)
375
+
376
+ prettier_path = File.join(root, "vendor/prettier/standalone.js")
377
+ prettier_js = File.read(prettier_path)
378
+ context.eval(prettier_js)
379
+
380
+ sql_path = File.join(root, "vendor/sql-formatter.js")
381
+ sql_js = File.read(sql_path)
382
+ context.eval("self = {};")
383
+ context.eval(sql_js)
384
+ context.eval("sqlFormatter = self.sqlFormatter;")
385
+
386
+ groovy_path = File.join(root, "vendor/groovy-beautify/dist/cjs/index.js")
387
+ groovy_js = File.read(groovy_path)
388
+ context.eval("module = { exports: {} };")
389
+ context.eval("exports = module.exports;")
390
+ context.eval(groovy_js)
391
+ context.eval("groovyBeautify = module.exports;")
392
+
393
+ plugins = %w[babel estree typescript html postcss markdown]
394
+
395
+ plugins.each do |plugin|
396
+ path = File.join(root, "vendor/prettier/plugins/#{plugin}.js")
397
+ js = File.read(path)
398
+ context.eval("module = { exports: {} };")
399
+ context.eval("exports = module.exports;")
400
+ context.eval(js)
401
+ context.eval("#{plugin} = module.exports;")
402
+ end
403
+
404
+ context.eval("plugins = [#{plugins.join(", ")}];")
405
+
406
+ context.eval(<<~JS)
407
+ format = async (path, parser) => {
408
+ try {
409
+ const before = read(path);
410
+ let after;
411
+
412
+ if (parser === "sql") {
413
+ after = sqlFormatter.format(before);
414
+ } else if (parser === "groovy") {
415
+ after = groovyBeautify(before);
416
+ } else {
417
+ after = await prettier.format(before, { parser, plugins });
418
+ }
419
+
420
+ if (before != after) {
421
+ puts(path);
422
+ write(path, after);
423
+ }
424
+ } catch (e) {
425
+ warn(`failed to parse ${path}: ${e.message.split("\\n")[0]}`);
426
+ }
427
+ };
428
+ JS
429
+
430
+ if files.any?
431
+ each(files) { |file| format(file, context:) }
432
+ else
433
+ each(
434
+ Git.open(".").ls_files.map(&:first)
435
+ ) { |file| format(file, context:) }
436
+ end
437
+ end
438
+
439
+ def command_release
440
+ File.delete(*Dir["*.gem"])
441
+ system("gem build")
442
+ system("gem push *.gem")
443
+ File.delete(*Dir["*.gem"])
444
+ end
445
+
446
+ def command_top
447
+ shell = arguments[0] || File.basename(ENV.fetch("SHELL", nil)) || "bash"
448
+ limit = arguments[1] || 10
449
+
450
+ history =
451
+ case shell.to_s.downcase
452
+ when "fish"
453
+ File
454
+ .read("#{Dir.home}/.local/share/fish/fish_history")
455
+ .lines
456
+ .grep(/^- cmd: /)
457
+ .map { |line| line.split("- cmd: ", 2).last.strip }
458
+ when "bash"
459
+ File.read("#{Dir.home}/.bash_history").lines.map(&:strip)
460
+ when "zsh"
461
+ File.read("#{Dir.home}/.zsh_history").lines.map(&:strip)
462
+ else
463
+ raise NotImplementedError, shell
464
+ end
465
+
466
+ table(
467
+ history
468
+ .map { |line| line.split.first }
469
+ .tally
470
+ .to_a
471
+ .sort_by(&:last)
472
+ .reverse
473
+ .map
474
+ .with_index do |(command, command_count), index|
475
+ {
476
+ "#" => index + 1,
477
+ :count => command_count,
478
+ :percent =>
479
+ "#{(command_count * 100 / history.size.to_f).round(3)}%",
480
+ :command => command
481
+ }
482
+ end
483
+ .first(limit)
484
+ )
485
+ end
486
+
487
+ def command_tree
488
+ space = " "
489
+ right = "└── "
490
+ down = "│   "
491
+ down_and_right = "├── "
492
+
493
+ git_ls_files = ->(path) { Git.open(".").ls_files(path).map(&:first) }
494
+
495
+ group =
496
+ lambda do |files|
497
+ files
498
+ .group_by { |file| file.split("/").first }
499
+ .transform_values do |values|
500
+ group.call(
501
+ values
502
+ .map { |value| value.split("/")[1..].join("/") }
503
+ .reject(&:empty?)
504
+ )
505
+ end
506
+ end
507
+
508
+ print =
509
+ lambda do |key:, values:, index: 0, size: 1, prefix: ""|
510
+ key = "#{key}/" if values.any?
511
+ last = index + 1 == size
512
+ right_prefix = last ? right : down_and_right
513
+ puts prefix + right_prefix + key
514
+ values.each.with_index do |(value_key, value_values), value_index|
515
+ print.call(
516
+ key: value_key,
517
+ values: value_values,
518
+ index: value_index,
519
+ size: values.size,
520
+ prefix: prefix + (last ? space : down)
521
+ )
522
+ end
523
+ end
524
+
525
+ keys = (arguments + files)
526
+ keys = ["."] unless keys.any?
527
+
528
+ keys.each do |key|
529
+ files =
530
+ git_ls_files
531
+ .call(key)
532
+ .map { |file| parsed.arguments.any? ? file.sub(key, "") : file }
533
+ values = group.call(files)
534
+ key = "#{key}/" if values.any? && key != "." && key[-1] != "/"
535
+ puts key
536
+ values.each.with_index do |(value_key, value_values), value_index|
537
+ print.call(
538
+ key: value_key,
539
+ values: value_values,
540
+ index: value_index,
541
+ size: values.size
542
+ )
543
+ end
544
+ end
545
+ end
546
+
216
547
  def files
217
548
  parsed.files
218
549
  end
219
550
 
551
+ def command_times
552
+ map(everything, &:to_i).sum.times { |index| puts index + 1 }
553
+ end
554
+
220
555
  def command_eval
221
- each(everything) { |thing| outputs(evaluates(ruby: thing).returned) }
556
+ each(everything) { |thing| outputs(evaluates(ruby: thing)) }
557
+ end
558
+
559
+ def command_then
560
+ each(stdin_files + files) do |input|
561
+ outputs(evaluates(it: reads(File.read(input))), file: input)
562
+ end
563
+
564
+ each(stdin_arguments + arguments) do |input|
565
+ outputs(evaluates(it: reads(input)))
566
+ end
567
+ end
568
+
569
+ def command_table
570
+ table(map(everything) { |thing| lines(reads(thing)) }.inject(&:+))
222
571
  end
223
572
 
224
573
  def command_chat
@@ -258,6 +607,39 @@ class Dorian
258
607
  puts message
259
608
  end
260
609
 
610
+ def command_replace
611
+ from, to = arguments
612
+
613
+ each(stdin_files + stdin_arguments + files) do |file|
614
+ next if File.directory?(file)
615
+
616
+ File.write(file, File.read(file).gsub(from, to))
617
+ end
618
+ end
619
+
620
+ def command_rename
621
+ from, to = arguments
622
+ files = stdin_files + stdin_arguments + self.files
623
+ (files - directories).each { |file| rename(file, file.gsub(from, to)) }
624
+ directories.each { |dir| rename(dir, dir.gsub(from, to)) }
625
+ end
626
+
627
+ def directories
628
+ (stdin_files + files).select { |file| File.directory?(file) }
629
+ end
630
+
631
+ def rename(old, new)
632
+ return if old == new
633
+
634
+ puts "#{old} -> #{new}"
635
+ File.rename(old, new)
636
+ end
637
+
638
+ def command_write
639
+ content = read_stdin.join
640
+ each(files + arguments) { |file| File.write(file, content) }
641
+ end
642
+
261
643
  def command_read
262
644
  each(stdin_files + files) do |input|
263
645
  outputs(reads(File.read(input)), file: input)
@@ -266,6 +648,36 @@ class Dorian
266
648
  each(stdin_arguments + arguments) { |input| outputs(reads(input)) }
267
649
  end
268
650
 
651
+ def command_strip
652
+ each(stdin_files + files) do |input|
653
+ outputs(lines(reads(File.read(input)), strip: :strip), file: input)
654
+ end
655
+
656
+ each(stdin_arguments + arguments) do |input|
657
+ outputs(lines(reads(input), strip: :strip))
658
+ end
659
+ end
660
+
661
+ def command_rstrip
662
+ each(stdin_files + files) do |input|
663
+ outputs(lines(reads(File.read(input)), strip: :rstrip), file: input)
664
+ end
665
+
666
+ each(stdin_arguments + arguments) do |input|
667
+ outputs(lines(reads(input), strip: :rstrip))
668
+ end
669
+ end
670
+
671
+ def command_lstrip
672
+ each(stdin_files + files) do |input|
673
+ outputs(lines(reads(File.read(input)), strip: :lstrip), file: input)
674
+ end
675
+
676
+ each(stdin_arguments + arguments) do |input|
677
+ outputs(lines(reads(input), strip: :lstrip))
678
+ end
679
+ end
680
+
269
681
  def everything
270
682
  read_stdin_files + stdin_arguments + read_files + arguments
271
683
  end
@@ -335,7 +747,9 @@ class Dorian
335
747
 
336
748
  homefile = "#{Dir.home}/#{file}"
337
749
  dotfile = File.expand_path("#{dir.chomp("/")}/#{file}")
338
- File.delete(homefile) if File.exist?(homefile) || File.symlink?(homefile)
750
+ if File.exist?(homefile) || File.symlink?(homefile)
751
+ File.delete(homefile)
752
+ end
339
753
  FileUtils.mkdir_p(File.dirname(homefile))
340
754
  FileUtils.ln_s(dotfile, homefile, verbose: true)
341
755
  end
@@ -370,6 +784,43 @@ class Dorian
370
784
  end
371
785
  end
372
786
 
787
+ def command_merge
788
+ outputs(map(everything) { |thing| lines(reads(thing)) }.inject(&:+))
789
+ end
790
+
791
+ def command_sort
792
+ outputs(
793
+ map(everything) { |thing| lines(reads(thing)) }
794
+ .inject(&:+)
795
+ .sort_by do |line|
796
+ result = pluck(line).from_deep_struct
797
+ result.is_a?(Hash) ? result.values : result
798
+ end
799
+ )
800
+ end
801
+
802
+ def command_uniq
803
+ outputs(
804
+ map(everything) { |thing| lines(reads(thing)) }
805
+ .inject(&:+)
806
+ .uniq { |line| pluck(line) }
807
+ )
808
+ end
809
+
810
+ def command_pluck
811
+ outputs(
812
+ map(everything) do |thing|
813
+ map(lines(reads(thing))) { |line| pluck(line) }
814
+ end.inject(&:+)
815
+ )
816
+ end
817
+
818
+ def command_shuffle
819
+ outputs(
820
+ map(everything) { |thing| lines(reads(thing)) }.inject(&:+).shuffle
821
+ )
822
+ end
823
+
373
824
  def command_tally
374
825
  each(everything) do |input|
375
826
  outputs(
@@ -378,7 +829,7 @@ class Dorian
378
829
  if ruby.to_s.empty?
379
830
  element
380
831
  else
381
- evaluates(it: element, returns: true, stdout: false).returned
832
+ evaluates(it: element, returns: true, stdout: false)
382
833
  end
383
834
  end.tally
384
835
  )
@@ -393,11 +844,13 @@ class Dorian
393
844
  end
394
845
 
395
846
  def command_append
396
- outputs(everything.sum { |input| lines(reads(input)) })
847
+ outputs(map(everything) { |input| lines(reads(input)) }.inject(&:+))
397
848
  end
398
849
 
399
850
  def command_prepend
400
- outputs(everything.reverse.sum { |input| lines(reads(input)) })
851
+ outputs(
852
+ map(everything.reverse) { |input| lines(reads(input)) }.inject(&:+)
853
+ )
401
854
  end
402
855
 
403
856
  def command_select
@@ -487,12 +940,14 @@ class Dorian
487
940
  end.join
488
941
  when :json
489
942
  pretty? ? JSON.pretty_generate(content) : content.to_json
490
- when :jsonl, :yamll
491
- map(content, &:to_json).join("\n")
943
+ when :jsonl
944
+ "#{map(content, &:to_json).join("\n")}\n"
492
945
  when :raw
493
946
  content
494
947
  when :yaml
495
948
  content.to_yaml
949
+ when :yamll
950
+ "#{map(map(content, &:to_yaml), &:to_json).join("\n")}\n"
496
951
  else
497
952
  abort "#{output.inspect} not supported"
498
953
  end
@@ -508,12 +963,16 @@ class Dorian
508
963
  end
509
964
  when :json
510
965
  JSON.parse(content).to_deep_struct
511
- when :jsonl, :yamll
966
+ when :jsonl
512
967
  map(content.lines) { |line| JSON.parse(line) }.to_deep_struct
513
968
  when :raw
514
969
  content
515
970
  when :yaml
516
971
  YAML.safe_load(content).to_deep_struct
972
+ when :yamll
973
+ map(content.lines) do |line|
974
+ YAML.safe_load(JSON.parse(line))
975
+ end.to_deep_struct
517
976
  else
518
977
  abort "#{input.inspect} not supported"
519
978
  end
@@ -722,9 +1181,9 @@ class Dorian
722
1181
  end
723
1182
  end
724
1183
 
725
- def lines(input)
1184
+ def lines(input, strip: :rstrip)
726
1185
  if input.is_a?(String)
727
- input.lines.map(&:rstrip)
1186
+ input.lines.map(&strip)
728
1187
  elsif deep?
729
1188
  deep_lines(input)
730
1189
  else
@@ -827,7 +1286,7 @@ class Dorian
827
1286
  end
828
1287
 
829
1288
  def match?(element, ruby: @ruby)
830
- !!evaluates(ruby:, it: element, stdout: false, returns: true).returned
1289
+ !!evaluates(ruby:, it: element, stdout: false, returns: true)
831
1290
  end
832
1291
 
833
1292
  def token(file)
@@ -934,77 +1393,65 @@ class Dorian
934
1393
  Tiktoken.encoding_for_model("gpt-4o")
935
1394
  end
936
1395
 
937
- def match_filetypes?(path)
938
- return true unless arguments.any?
939
- return true unless arguments.intersect?(["rb", "ruby"])
940
-
941
- ruby_extensions = %w[
942
- .rb
943
- .arb
944
- .axlsx
945
- .builder
946
- .fcgi
947
- .gemfile
948
- .gemspec
949
- .god
950
- .jb
951
- .jbuilder
952
- .mspec
953
- .opal
954
- .pluginspec
955
- .podspec
956
- .rabl
957
- .rake
958
- .rbuild
959
- .rbw
960
- .rbx
961
- .ru
962
- .ruby
963
- .schema
964
- .spec
965
- .thor
966
- .watchr
967
- ]
968
-
969
- ruby_filenames = %w[
970
- .irbrc
971
- .pryrc
972
- .simplecov
973
- Appraisals
974
- Berksfile
975
- Brewfile
976
- Buildfile
977
- Capfile
978
- Cheffile
979
- Dangerfile
980
- Deliverfile
981
- Fastfile
982
- Gemfile
983
- Guardfile
984
- Jarfile
985
- Mavenfile
986
- Podfile
987
- Puppetfile
988
- Rakefile
989
- rakefile
990
- Schemafile
991
- Snapfile
992
- Steepfile
993
- Thorfile
994
- Vagabondfile
995
- Vagrantfile
996
- buildfile
997
- ]
998
-
1396
+ def match_filetypes?(path, filetypes: arguments)
1397
+ return true if filetypes.none?
1398
+ return true unless filetypes.intersect?(%w[rb ruby])
999
1399
  return false if Dir.exist?(path)
1000
- return true if ruby_filenames.include?(path)
1001
- return true if ruby_extensions.include?(File.extname(path))
1400
+ return true if RUBY_FILENAMES.include?(path)
1401
+ return true if RUBY_EXTENSIONS.include?(File.extname(path))
1002
1402
  return false unless File.exist?(path)
1003
1403
 
1004
- first_line =
1005
- File.open(path, &:gets).to_s.encode("UTF-8", invalid: :replace)
1404
+ first_line = File.open(path, &:gets).to_s
1405
+ first_line = first_line.encode("UTF-8", invalid: :replace)
1006
1406
 
1007
- /\A#!.*ruby\z/.match?(first_line)
1407
+ return true if /\A#!.*ruby\z/.match?(first_line)
1408
+
1409
+ false
1410
+ end
1411
+
1412
+ def pluck(element)
1413
+ element = element.from_deep_struct
1414
+
1415
+ keys =
1416
+ if arguments.any?
1417
+ arguments
1418
+ elsif element.is_a?(Hash)
1419
+ element.keys
1420
+ elsif element.is_a?(Array)
1421
+ (0...(element.size)).map(&:to_s)
1422
+ else
1423
+ "it"
1424
+ end
1425
+
1426
+ results =
1427
+ keys.map do |argument|
1428
+ if element.is_a?(Array) && argument.to_i.to_s == argument
1429
+ element[argument.to_i]
1430
+ elsif element.is_a?(Hash) && element.key?(argument)
1431
+ { argument => element[argument] }
1432
+ else
1433
+ evaluates(ruby: argument, it: element.to_deep_struct)
1434
+ end
1435
+ end
1436
+
1437
+ if results.all?(Hash)
1438
+ results.inject(&:merge).to_deep_struct
1439
+ else
1440
+ results
1441
+ .map { |result| result.is_a?(Hash) ? result.values.first : result }
1442
+ .to_deep_struct
1443
+ end
1444
+ end
1445
+
1446
+ def table(data)
1447
+ is_hashes = data.first.from_deep_struct.is_a?(Hash)
1448
+ headings = is_hashes ? data.first.to_h.keys : nil
1449
+ rows = is_hashes ? data.map(&:values) : data.map { |row| wrap(row) }
1450
+ if headings
1451
+ puts Terminal::Table.new(headings:, rows:)
1452
+ else
1453
+ puts Terminal::Table.new(rows:)
1454
+ end
1008
1455
  end
1009
1456
 
1010
1457
  def evaluates(
@@ -1028,7 +1475,218 @@ class Dorian
1028
1475
  rails:,
1029
1476
  fast:,
1030
1477
  returns:
1031
- )
1478
+ ).returned
1479
+ end
1480
+
1481
+ def sort(object)
1482
+ object = object.from_deep_struct
1483
+
1484
+ if object.is_a?(Hash)
1485
+ object
1486
+ .to_a
1487
+ .sort_by(&:first)
1488
+ .to_h
1489
+ .transform_values { |value| sort(value) }
1490
+ elsif object.is_a?(Array)
1491
+ object.map { |element| sort(element) }
1492
+ else
1493
+ object
1494
+ end
1495
+ end
1496
+
1497
+ def filetype(path)
1498
+ ext = File.extname(path).to_s.downcase
1499
+ return :directory if Dir.exist?(path)
1500
+ return :symlink if File.symlink?(path)
1501
+ return :ruby if RUBY_FILENAMES.include?(File.basename(path))
1502
+ return :ruby if RUBY_EXTENSIONS.include?(ext)
1503
+ return :json if ext == ".json"
1504
+ return :jsonl if ext == ".jsonl"
1505
+ return :yaml if ext == ".yaml"
1506
+ return :yaml if ext == ".yml"
1507
+ return :yamll if ext == ".yamll"
1508
+ return :yamll if ext == ".ymll"
1509
+ return :csv if ext == ".csv"
1510
+ return :js if ext == ".js"
1511
+ return :js if ext == ".mjs"
1512
+ return :js if ext == ".cjs"
1513
+ return :ts if ext == ".ts"
1514
+ return :css if ext == ".css"
1515
+ return :html if ext == ".html"
1516
+ return :html if ext == ".htm"
1517
+ return :haml if ext == ".haml"
1518
+ return :slim if ext == ".slim"
1519
+ return :erb if ext == ".erb"
1520
+ return :fish if ext == ".fish"
1521
+ return :sql if ext == ".sql"
1522
+ return :tex if ext == ".tex"
1523
+ return :md if ext == ".md"
1524
+ return :md if ext == ".markdown"
1525
+ return :png if ext == ".png"
1526
+ return :jpeg if ext == ".jpg"
1527
+ return :jpeg if ext == ".jpeg"
1528
+ return :ico if ext == ".ico"
1529
+ return :webp if ext == ".webp"
1530
+ return :heic if ext == ".heic"
1531
+ return :pdf if ext == ".pdf"
1532
+ return :raw if ext == ".raw"
1533
+ return :env if path == ".env"
1534
+ return :env if path.start_with?(".env.")
1535
+ return :sh if path == "Dockerfile"
1536
+ return :sh if ext == ".sh"
1537
+ return :enc if ext == ".enc"
1538
+ return :enc if ext == ".keystore"
1539
+ return :pro if ext == ".pro"
1540
+ return :txt if ext == ".txt"
1541
+ return :bat if ext == ".bat"
1542
+ return :xcconfig if ext == ".xcconfig"
1543
+ return :pbxproj if ext == ".pbxproj"
1544
+ return :xml if ext == ".xml"
1545
+ return :xml if ext == ".plist"
1546
+ return :xml if ext == ".storyboard"
1547
+ return :xml if ext == ".xcscheme"
1548
+ return :xml if ext == ".xcworkspacedata"
1549
+ return :xml if ext == ".xcprivacy"
1550
+ return :xml if ext == ".entitlements"
1551
+ return :kotlin if ext == ".kt"
1552
+ return :groovy if ext == ".gradle"
1553
+ return :groovy if ext == ".properties"
1554
+ return :binary if ext == ".jar"
1555
+ return :objectivec if ext == ".h"
1556
+ return :objectivec if ext == ".mm"
1557
+ return :objectivec if ext == ".m"
1558
+ return unless File.exist?(path)
1559
+
1560
+ first_line = File.open(path, &:gets).to_s
1561
+ first_line = first_line.encode("UTF-8", invalid: :replace).strip
1562
+ return :ruby if first_line == "#!/usr/bin/env ruby"
1563
+ return :sh if first_line == "#!/bin/bash"
1564
+ return :sh if first_line == "#!/bin/sh"
1565
+ return :sh if first_line == "#!/bin/bash -e"
1566
+ return :sh if first_line == "#!/usr/bin/env sh"
1567
+
1568
+ nil
1569
+ end
1570
+
1571
+ def format(path, context:)
1572
+ return if File.symlink?(path)
1573
+ return if File.directory?(path)
1574
+ return unless File.exist?(path)
1575
+
1576
+ before = File.read(path)
1577
+
1578
+ case filetype(path)
1579
+ when :directory
1580
+ when :ruby
1581
+ after = SyntaxTree.format(before)
1582
+ when :haml
1583
+ after = SyntaxTree::Haml.format(before)
1584
+ when :erb
1585
+ after = SyntaxTree::ERB.format(before)
1586
+ when :xml
1587
+ after = SyntaxTree::XML.format(before)
1588
+ when :json
1589
+ after = JSON.pretty_generate(JSON.parse(before))
1590
+ when :jsonl
1591
+ after = before.lines.map { |line| JSON.parse(line).to_json }.join("\n")
1592
+ when :csv
1593
+ after =
1594
+ CSV.generate { |csv| CSV.parse(before).each { |row| csv << row } }
1595
+ when :yaml
1596
+ after = sort(YAML.safe_load(before)).to_yaml
1597
+ when :yamll
1598
+ after =
1599
+ before
1600
+ .lines
1601
+ .map do |line|
1602
+ sort(YAML.safe_load(JSON.parse(line))).to_yaml.to_json
1603
+ end
1604
+ .join("\n") + "\n"
1605
+ when :js
1606
+ context.eval("format(#{path.to_json}, 'babel')")
1607
+ when :ts
1608
+ context.eval("format(#{path.to_json}, 'typescript')")
1609
+ when :html
1610
+ context.eval("format(#{path.to_json}, 'html')")
1611
+ when :md
1612
+ context.eval("format(#{path.to_json}, 'markdown')")
1613
+ when :sql
1614
+ context.eval("format(#{path.to_json}, 'sql')")
1615
+ when :groovy
1616
+ context.eval("format(#{path.to_json}, 'groovy')")
1617
+ when :css
1618
+ context.eval("format(#{path.to_json}, 'css')")
1619
+ when :sh
1620
+ if system("command -v shfmt > /dev/null 2>&1")
1621
+ command = ["shfmt", "--indent", "4", path].shelljoin
1622
+ stdout, stderr, status = Open3.capture3(command)
1623
+ raise stderr unless stderr.empty? && status.success?
1624
+
1625
+ after = stdout
1626
+ else
1627
+ warn "run: `brew install shfmt` for #{path}"
1628
+ end
1629
+ when :pdf
1630
+ doc = HexaPDF::Document.open(path)
1631
+ doc.trailer.info.each_key { |key| doc.trailer.info.delete(key) }
1632
+ doc.write(path, update_fields: false)
1633
+ after = File.read(path)
1634
+ when :tex
1635
+ if system("command -v latexindent > /dev/null 2>&1")
1636
+ command = ["latexindent", path, "--logfile", "/dev/null"].shelljoin
1637
+ stdout, stderr, status = Open3.capture3(command)
1638
+ raise stderr unless stderr.empty? && status.success?
1639
+
1640
+ after = stdout.gsub("\t", " ")
1641
+ else
1642
+ warn "run: `brew install latexindent` for #{path}"
1643
+ end
1644
+ when :objectivec
1645
+ if system("command -v clang-format > /dev/null 2>&1")
1646
+ command = ["clang-format", path].shelljoin
1647
+ stdout, stderr, status = Open3.capture3(command)
1648
+ raise stderr unless stderr.empty? && status.success?
1649
+
1650
+ after = stdout.gsub("\t", " ")
1651
+ else
1652
+ warn "run: `brew install clang-format` for #{path}"
1653
+ end
1654
+ when :kotlin
1655
+ if system("command -v ktlint > /dev/null 2>&1")
1656
+ command = ["ktlint", "-F", path].shelljoin
1657
+ stdout, stderr, status = Open3.capture3(command)
1658
+ raise stderr unless stderr.empty? && status.success?
1659
+
1660
+ after = File.read(path)
1661
+ else
1662
+ warn "run: `brew install ktlint` for #{path}"
1663
+ end
1664
+ when :raw, :directory, :symlink, :env, :enc, :txt, :pro, :binary, :slim,
1665
+ :fish, :bat, :xcconfig, :pbxproj, :jpeg, :png, :webp, :heic, :ico
1666
+ # nothing to do
1667
+ else
1668
+ case File.basename(path)
1669
+ when ".gitignore", ".node-version", ".prettierignore", ".ruby-version",
1670
+ ".tool-versions", "Gemfile.lock", "LICENSE", "VERSION", ".rspec",
1671
+ "Procfile", "Procfile.dev", "Podfile.lock", ".xcode.env", "CNAME",
1672
+ "TODO", ".gitmodules", ".asdfrc", "config", ".dotignore", ".gemrc",
1673
+ ".gitconfig", ".gitmessage", ".hushlogin", ".psqlrc", ".vimrc",
1674
+ "DIRECTORIES"
1675
+ # nothing to do
1676
+ when ".keep"
1677
+ File.write(path, "")
1678
+ else
1679
+ puts "unhandled: #{path}"
1680
+ end
1681
+ end
1682
+
1683
+ if after && before != after
1684
+ puts path
1685
+ File.write(path, after)
1686
+ end
1687
+ rescue StandardError => e
1688
+ warn "failed to parse #{path}: #{e.message}"
1689
+ binding.irb
1032
1690
  end
1033
1691
  end
1034
1692
  end