dorian 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/dorian/bin.rb +327 -12
  4. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 926123b36e9d821e68bec3a83818a70f7a39f6f52c1fa5d19ac777dc98b64da0
4
- data.tar.gz: ae5fda1a6fb16ae0ae1d3fd61a329608586329fde537e4c4430513f0d2e1eb25
3
+ metadata.gz: bd057371121d824ad1a6172b189916820b5e5544f9cabd95db4fd7f51a2bd5fa
4
+ data.tar.gz: 244af12257d30dc4c617b8443a4a02d2c8ecc0fb7209f7b27dee7525ee43b35f
5
5
  SHA512:
6
- metadata.gz: 5c9bbdc2e3951bd84159719b1292dce233c4092937cdb18054301e438517030bf4bf3b2352a89ec1cb705751130f8f374b1bc40b7496951be21b7744be36cf5c
7
- data.tar.gz: 1e9210a0bf87c799539ac9ce4c39dd774c4483facdd326718c31bb3655a895a08b1aae273e80eb87a0f32ad6aa46491bbe73a4a2ec5645102ee4c59665019f50
6
+ metadata.gz: a6e82fef1ff6f595dfecba39e6f14ad19bf502f38b8c4af6d5e261eb69cbd3461119fbbb526089cddea881a00cb26f3b11706b34969485d2e2ad61bce6829c27
7
+ data.tar.gz: a3f12ae1d9d88cb6f28b975f5b59ca11ed37baa612c3e8b6c58009bc639f59084ed1af3cb2f400ed94bfc38ed8a51b7ef0c6b93aa7a1ad8118a52c8e7b7cec41
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.0
1
+ 2.2.0
data/lib/dorian/bin.rb CHANGED
@@ -5,8 +5,11 @@ require "dorian/arguments"
5
5
  require "dorian/eval"
6
6
  require "dorian/progress"
7
7
  require "dorian/to_struct"
8
+ require "git"
8
9
  require "json"
10
+ require "net/http"
9
11
  require "parallel"
12
+ require "uri"
10
13
  require "yaml"
11
14
 
12
15
  class Dorian
@@ -26,7 +29,7 @@ class Dorian
26
29
  "ymll" => :yamll
27
30
  }.freeze
28
31
 
29
- attr_reader :parsed, :command, :arguments, :ruby
32
+ attr_reader :parsed, :command, :arguments, :ruby, :ruby_before, :ruby_after
30
33
 
31
34
  def initialize
32
35
  @parsed =
@@ -116,15 +119,64 @@ class Dorian
116
119
  when :each
117
120
  arguments.delete("each")
118
121
  @command = :each
119
- @ruby = arguments.join(" ")
120
- @arguments = []
122
+ @ruby = arguments.delete_at(0)
121
123
  command_each
122
124
  when :all
123
125
  arguments.delete("all")
124
126
  @command = :all
125
- @ruby = arguments.join(" ")
126
- @arguments = []
127
+ @ruby = arguments.delete_at(0)
127
128
  command_all
129
+ when :before
130
+ arguments.delete("before")
131
+ @command = :before
132
+ @ruby = arguments.delete_at(0)
133
+ command_before
134
+ when :after
135
+ arguments.delete("after")
136
+ @command = :after
137
+ @ruby = arguments.delete_at(0)
138
+ command_after
139
+ when :between
140
+ arguments.delete("between")
141
+ @command = :between
142
+ @ruby_after = arguments.delete_at(0)
143
+ @ruby_before = arguments.delete_at(0)
144
+ command_between
145
+ when :select
146
+ arguments.delete("select")
147
+ @command = :select
148
+ @ruby = arguments.delete_at(0)
149
+ command_select
150
+ when :reject
151
+ arguments.delete("reject")
152
+ @command = :reject
153
+ @ruby = arguments.delete_at(0)
154
+ command_reject
155
+ when :tally
156
+ arguments.delete("tally")
157
+ @command = :tally
158
+ @ruby = arguments.delete_at(0)
159
+ command_tally
160
+ when :anonymize
161
+ arguments.delete("anonymize")
162
+ @command = :anonymize
163
+ command_anonymize
164
+ when :append
165
+ arguments.delete("append")
166
+ @command = :append
167
+ command_append
168
+ when :prepend
169
+ arguments.delete("prepend")
170
+ @command = :prepend
171
+ command_prepend
172
+ when :chat
173
+ arguments.delete("chat")
174
+ @command = :chat
175
+ command_chat
176
+ when :commit
177
+ arguments.delete("commit")
178
+ @command = :commit
179
+ command_commit
128
180
  else
129
181
  arguments.delete("read")
130
182
  @command = :read
@@ -136,6 +188,43 @@ class Dorian
136
188
  parsed.files
137
189
  end
138
190
 
191
+ def command_chat
192
+ puts completion(
193
+ token: token(".chat"),
194
+ model: "gpt-4o",
195
+ messages: [{ role: :user, content: everything.join("\n") }]
196
+ )
197
+ end
198
+
199
+ def command_commit
200
+ system_prompt = "simple, clear, short, lowercase commit message"
201
+ prompt_1 = "for the following diff:"
202
+ prompt_2 = "for the following git status:"
203
+ prompt_3 = "for the following comment:"
204
+
205
+ content_1 = short(`git diff --staged`)
206
+ content_2 = short(`git status`)
207
+ content_3 = short(arguments.join("\n"))
208
+
209
+ abort "no staged files" if content_1.empty?
210
+
211
+ messages = [
212
+ { role: :system, content: system_prompt },
213
+ { role: :system, content: prompt_1 },
214
+ { role: :user, content: content_1 },
215
+ { role: :system, content: prompt_2 },
216
+ { role: :user, content: content_2 },
217
+ { role: :system, content: prompt_3 },
218
+ { role: :user, content: content_3 }
219
+ ]
220
+
221
+ message = completion(token: token(".commit"), model: "gpt-4o", messages: messages)
222
+
223
+ Git.open(".").commit(message)
224
+
225
+ puts message
226
+ end
227
+
139
228
  def command_read
140
229
  each(stdin_files + files) do |input|
141
230
  outputs(reads(File.read(input)), file: input)
@@ -145,20 +234,108 @@ class Dorian
145
234
  end
146
235
 
147
236
  def everything
148
- read_stdin_files + read_files + stdin_arguments + arguments
237
+ read_stdin_files + stdin_arguments + read_files + arguments
149
238
  end
150
239
 
151
240
  def command_each
152
241
  each(everything) do |input|
153
- each(lines(reads(input)), progress: true) do |line|
154
- evaluates(ruby, it: line)
155
- end
242
+ each(lines(reads(input)), progress: true) { |line| evaluates(it: line) }
243
+ end
244
+ end
245
+
246
+ def command_tally
247
+ each(everything) do |input|
248
+ outputs(
249
+ JSON.pretty_generate(
250
+ map(lines(reads(input)), progress: true) do |element|
251
+ if ruby.to_s.empty?
252
+ element
253
+ else
254
+ evaluates(it: element, returns: true, stdout: false).returned
255
+ end
256
+ end.tally
257
+ )
258
+ )
156
259
  end
157
260
  end
158
261
 
159
262
  def command_all
160
- each(everything, progress: true) do |input|
161
- evaluates(ruby, it: reads(input))
263
+ each(everything, progress: true) { |input| evaluates(it: reads(input)) }
264
+ end
265
+
266
+ def command_append
267
+ outputs(everything.map { |input| lines(reads(input)) }.inject(&:+))
268
+ end
269
+
270
+ def command_prepend
271
+ outputs(
272
+ everything.reverse.map { |input| lines(reads(input)) }.inject(&:+)
273
+ )
274
+ end
275
+
276
+ def command_select
277
+ each(stdin_files + files) do |input|
278
+ outputs(
279
+ select(lines(reads(File.read(input)))) { |element| match?(element) },
280
+ file: input
281
+ )
282
+ end
283
+
284
+ each(stdin_arguments + arguments) do |input|
285
+ outputs(select(lines(reads(input))) { |element| match?(element) })
286
+ end
287
+ end
288
+
289
+ def command_reject
290
+ each(stdin_files + files) do |input|
291
+ outputs(
292
+ reject(lines(reads(File.read(input)))) { |element| match?(element) },
293
+ file: input
294
+ )
295
+ end
296
+
297
+ each(stdin_arguments + arguments) do |input|
298
+ outputs(reject(lines(reads(input))) { |element| match?(element) })
299
+ end
300
+ end
301
+
302
+ def command_after
303
+ each(stdin_files + files) do |input|
304
+ outputs(after(lines(reads(File.read(input)))), file: input)
305
+ end
306
+
307
+ each(stdin_arguments + arguments) do |input|
308
+ outputs(after(lines(reads(input))))
309
+ end
310
+ end
311
+
312
+ def command_before
313
+ each(stdin_files + files) do |input|
314
+ outputs(before(reads(File.read(input))), file: input)
315
+ end
316
+
317
+ each(stdin_arguments + arguments) do |input|
318
+ outputs(before(lines(reads(input))))
319
+ end
320
+ end
321
+
322
+ def command_between
323
+ each(stdin_files + files) do |input|
324
+ outputs(between(lines(reads(File.read(input)))), file: input)
325
+ end
326
+
327
+ each(stdin_arguments + arguments) do |input|
328
+ outputs(between(lines(reads(input))))
329
+ end
330
+ end
331
+
332
+ def command_anonymize
333
+ each(stdin_files + files) do |input|
334
+ outputs(anonymize(reads(File.read(input))), file: input)
335
+ end
336
+
337
+ each(stdin_arguments + arguments) do |input|
338
+ outputs(anonymize(reads(input)))
162
339
  end
163
340
  end
164
341
 
@@ -400,6 +577,24 @@ class Dorian
400
577
  end
401
578
  end
402
579
 
580
+ def select(collection, progress: false, &)
581
+ collection = wrap(collection)
582
+ progress_bar = progress ? create_progress_bar(collection.size) : nil
583
+
584
+ collection.select do |element|
585
+ yield(element).tap { progress_bar&.increment }
586
+ end
587
+ end
588
+
589
+ def reject(collection, progress: false, &)
590
+ collection = wrap(collection)
591
+ progress_bar = progress ? create_progress_bar(collection.size) : nil
592
+
593
+ collection.reject do |element|
594
+ yield(element).tap { progress_bar&.increment }
595
+ end
596
+ end
597
+
403
598
  def lines(input)
404
599
  if input.is_a?(String)
405
600
  input.lines.map(&:rstrip)
@@ -441,8 +636,128 @@ class Dorian
441
636
  Dorian::Progress.create(total:, format: progress_format)
442
637
  end
443
638
 
639
+ def after(input, ruby: @ruby_after || @ruby)
640
+ if ruby.to_i.to_s == ruby
641
+ input[(ruby.to_i)..]
642
+ else
643
+ selected = false
644
+
645
+ input.select do |element|
646
+ selected = true if match?(element, ruby:)
647
+ selected
648
+ end
649
+ end
650
+ end
651
+
652
+ def before(input, ruby: @ruby_before || @ruby)
653
+ if ruby.to_i.to_s == ruby
654
+ input[..(ruby.to_i)]
655
+ else
656
+ selected = true
657
+
658
+ input.select do |element|
659
+ selected.tap { selected = false if match?(element, ruby:) }
660
+ end
661
+ end
662
+ end
663
+
664
+ def between(input, ruby_before: @ruby_before, ruby_after: @ruby_after)
665
+ if ruby_before.to_i.to_s == ruby_before &&
666
+ ruby_after.to_i.to_s == ruby_after
667
+ input[(ruby_after.to_i)..(ruby_before.to_i)]
668
+ else
669
+ selected = false
670
+
671
+ input.select do |element|
672
+ selected = true if match?(element, ruby: ruby_after)
673
+ selected.tap do
674
+ selected = false if match?(element, ruby: ruby_before)
675
+ end
676
+ end
677
+ end
678
+ end
679
+
680
+ def anonymize(input)
681
+ if input.is_a?(String)
682
+ input.gsub(/[a-z]/, "a").gsub(/[A-Z]/, "A").gsub(/[0-9]/, "0")
683
+ elsif input.is_a?(Integer)
684
+ 0
685
+ elsif input.is_a?(Float)
686
+ 0.0
687
+ elsif input.is_a?(TrueClass) || input.is_a?(FalseClass)
688
+ false
689
+ elsif input.nil?
690
+ nil
691
+ elsif input.is_a?(Hash)
692
+ input.transform_values { |value| anonymize(value) }
693
+ elsif input.is_a?(Array)
694
+ input.map { |element| anonymize(element) }
695
+ elsif input.is_a?(Struct)
696
+ anonymize(input.from_deep_struct).to_deep_struct
697
+ else
698
+ raise "#{input.class.inspect} not supported"
699
+ end
700
+ end
701
+
702
+ def match?(element, ruby: @ruby)
703
+ !!evaluates(ruby:, it: element, stdout: false, returns: true).returned
704
+ end
705
+
706
+ def token(file)
707
+ token_file = File.join(Dir.home, file)
708
+
709
+ if File.exist?(token_file)
710
+ token = File.read(token_file).strip
711
+ else
712
+ print "token: "
713
+ token = gets.strip
714
+ File.write(token_file, token)
715
+ puts "token written to #{token_file}"
716
+ end
717
+
718
+ token
719
+ end
720
+
721
+ def completion(token:, model:, messages:)
722
+ body =
723
+ post(
724
+ "https://api.openai.com/v1/chat/completions",
725
+ headers: {
726
+ "Content-Type" => "application/json",
727
+ "Authorization" => "Bearer #{token}"
728
+ },
729
+ body: { model:, messages: }.to_json
730
+ )
731
+
732
+ json = JSON.parse(body)
733
+ output = json.dig("choices", 0, "message", "content")
734
+
735
+ if output
736
+ output.strip
737
+ else
738
+ abort JSON.pretty_generate(json)
739
+ end
740
+ end
741
+
742
+ def post(url, headers: {}, body: {})
743
+ uri = URI.parse(url)
744
+ http = Net::HTTP.new(uri.host, uri.port)
745
+ http.use_ssl = true
746
+ request = Net::HTTP::Post.new(uri.path, headers)
747
+ request.body = body
748
+ http.request(request).body
749
+ end
750
+
751
+ def short(string)
752
+ string[0..5000]
753
+ end
754
+
755
+ def encoder
756
+ Tiktoken.encoding_for_model("gpt-4o")
757
+ end
758
+
444
759
  def evaluates(
445
- ruby,
760
+ ruby: @ruby,
446
761
  it: nil,
447
762
  debug: debug?,
448
763
  stdout: stdout?,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dorian
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Marié
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-02 00:00:00.000000000 Z
11
+ date: 2024-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: csv
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: git
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: json
85
99
  requirement: !ruby/object:Gem::Requirement