dorian 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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