dorian 2.1.0 → 2.2.1

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 +325 -12
  4. metadata +19 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 926123b36e9d821e68bec3a83818a70f7a39f6f52c1fa5d19ac777dc98b64da0
4
- data.tar.gz: ae5fda1a6fb16ae0ae1d3fd61a329608586329fde537e4c4430513f0d2e1eb25
3
+ metadata.gz: dc5cf78891ac8f7ed7f4ebe350d639c6e42bc7cfa2a8bd1fa6ad526b3e9785db
4
+ data.tar.gz: '018eee86347e85d6e02b12d23f0a8eadb34a1ba429e0881d8c14fe656a3fa956'
5
5
  SHA512:
6
- metadata.gz: 5c9bbdc2e3951bd84159719b1292dce233c4092937cdb18054301e438517030bf4bf3b2352a89ec1cb705751130f8f374b1bc40b7496951be21b7744be36cf5c
7
- data.tar.gz: 1e9210a0bf87c799539ac9ce4c39dd774c4483facdd326718c31bb3655a895a08b1aae273e80eb87a0f32ad6aa46491bbe73a4a2ec5645102ee4c59665019f50
6
+ metadata.gz: 98bfeb69db7cc33735780171db3a4f507e6d13f233bd18b2ae0c0e1d264999bff7827fabb5cbcd4590afdc3bf870d4a6d1c701f2257b502bb72f9ad3ea01f11a
7
+ data.tar.gz: a3351fdbc3b191402813581845e15fd049276510365a13417d461a9806c1f018073e6c65ab2974167269386cab2a569ec70a4c94302ce8740b3ae1ccb0df6654
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.0
1
+ 2.2.1
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:)
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,106 @@ 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.sum { |input| lines(reads(input)) })
268
+ end
269
+
270
+ def command_prepend
271
+ outputs(everything.reverse.sum { |input| lines(reads(input)) })
272
+ end
273
+
274
+ def command_select
275
+ each(stdin_files + files) do |input|
276
+ outputs(
277
+ select(lines(reads(File.read(input)))) { |element| match?(element) },
278
+ file: input
279
+ )
280
+ end
281
+
282
+ each(stdin_arguments + arguments) do |input|
283
+ outputs(select(lines(reads(input))) { |element| match?(element) })
284
+ end
285
+ end
286
+
287
+ def command_reject
288
+ each(stdin_files + files) do |input|
289
+ outputs(
290
+ reject(lines(reads(File.read(input)))) { |element| match?(element) },
291
+ file: input
292
+ )
293
+ end
294
+
295
+ each(stdin_arguments + arguments) do |input|
296
+ outputs(reject(lines(reads(input))) { |element| match?(element) })
297
+ end
298
+ end
299
+
300
+ def command_after
301
+ each(stdin_files + files) do |input|
302
+ outputs(after(lines(reads(File.read(input)))), file: input)
303
+ end
304
+
305
+ each(stdin_arguments + arguments) do |input|
306
+ outputs(after(lines(reads(input))))
307
+ end
308
+ end
309
+
310
+ def command_before
311
+ each(stdin_files + files) do |input|
312
+ outputs(before(reads(File.read(input))), file: input)
313
+ end
314
+
315
+ each(stdin_arguments + arguments) do |input|
316
+ outputs(before(lines(reads(input))))
317
+ end
318
+ end
319
+
320
+ def command_between
321
+ each(stdin_files + files) do |input|
322
+ outputs(between(lines(reads(File.read(input)))), file: input)
323
+ end
324
+
325
+ each(stdin_arguments + arguments) do |input|
326
+ outputs(between(lines(reads(input))))
327
+ end
328
+ end
329
+
330
+ def command_anonymize
331
+ each(stdin_files + files) do |input|
332
+ outputs(anonymize(reads(File.read(input))), file: input)
333
+ end
334
+
335
+ each(stdin_arguments + arguments) do |input|
336
+ outputs(anonymize(reads(input)))
162
337
  end
163
338
  end
164
339
 
@@ -400,6 +575,24 @@ class Dorian
400
575
  end
401
576
  end
402
577
 
578
+ def select(collection, progress: false, &)
579
+ collection = wrap(collection)
580
+ progress_bar = progress ? create_progress_bar(collection.size) : nil
581
+
582
+ collection.select do |element|
583
+ yield(element).tap { progress_bar&.increment }
584
+ end
585
+ end
586
+
587
+ def reject(collection, progress: false, &)
588
+ collection = wrap(collection)
589
+ progress_bar = progress ? create_progress_bar(collection.size) : nil
590
+
591
+ collection.reject do |element|
592
+ yield(element).tap { progress_bar&.increment }
593
+ end
594
+ end
595
+
403
596
  def lines(input)
404
597
  if input.is_a?(String)
405
598
  input.lines.map(&:rstrip)
@@ -441,8 +634,128 @@ class Dorian
441
634
  Dorian::Progress.create(total:, format: progress_format)
442
635
  end
443
636
 
637
+ def after(input, ruby: @ruby_after || @ruby)
638
+ if ruby.to_i.to_s == ruby
639
+ input[(ruby.to_i)..]
640
+ else
641
+ selected = false
642
+
643
+ input.select do |element|
644
+ selected = true if match?(element, ruby:)
645
+ selected
646
+ end
647
+ end
648
+ end
649
+
650
+ def before(input, ruby: @ruby_before || @ruby)
651
+ if ruby.to_i.to_s == ruby
652
+ input[..(ruby.to_i)]
653
+ else
654
+ selected = true
655
+
656
+ input.select do |element|
657
+ selected.tap { selected = false if match?(element, ruby:) }
658
+ end
659
+ end
660
+ end
661
+
662
+ def between(input, ruby_before: @ruby_before, ruby_after: @ruby_after)
663
+ if ruby_before.to_i.to_s == ruby_before &&
664
+ ruby_after.to_i.to_s == ruby_after
665
+ input[(ruby_after.to_i)..(ruby_before.to_i)]
666
+ else
667
+ selected = false
668
+
669
+ input.select do |element|
670
+ selected = true if match?(element, ruby: ruby_after)
671
+ selected.tap do
672
+ selected = false if match?(element, ruby: ruby_before)
673
+ end
674
+ end
675
+ end
676
+ end
677
+
678
+ def anonymize(input)
679
+ if input.is_a?(String)
680
+ input.gsub(/[a-z]/, "a").gsub(/[A-Z]/, "A").gsub(/[0-9]/, "0")
681
+ elsif input.is_a?(Integer)
682
+ 0
683
+ elsif input.is_a?(Float)
684
+ 0.0
685
+ elsif input.is_a?(TrueClass) || input.is_a?(FalseClass)
686
+ false
687
+ elsif input.nil?
688
+ nil
689
+ elsif input.is_a?(Hash)
690
+ input.transform_values { |value| anonymize(value) }
691
+ elsif input.is_a?(Array)
692
+ input.map { |element| anonymize(element) }
693
+ elsif input.is_a?(Struct)
694
+ anonymize(input.from_deep_struct).to_deep_struct
695
+ else
696
+ raise "#{input.class.inspect} not supported"
697
+ end
698
+ end
699
+
700
+ def match?(element, ruby: @ruby)
701
+ !!evaluates(ruby:, it: element, stdout: false, returns: true).returned
702
+ end
703
+
704
+ def token(file)
705
+ token_file = File.join(Dir.home, file)
706
+
707
+ if File.exist?(token_file)
708
+ token = File.read(token_file).strip
709
+ else
710
+ print "token: "
711
+ token = gets.strip
712
+ File.write(token_file, token)
713
+ puts "token written to #{token_file}"
714
+ end
715
+
716
+ token
717
+ end
718
+
719
+ def completion(token:, model:, messages:)
720
+ body =
721
+ post(
722
+ "https://api.openai.com/v1/chat/completions",
723
+ headers: {
724
+ "Content-Type" => "application/json",
725
+ "Authorization" => "Bearer #{token}"
726
+ },
727
+ body: { model:, messages: }.to_json
728
+ )
729
+
730
+ json = JSON.parse(body)
731
+ output = json.dig("choices", 0, "message", "content")
732
+
733
+ if output
734
+ output.strip
735
+ else
736
+ abort JSON.pretty_generate(json)
737
+ end
738
+ end
739
+
740
+ def post(url, headers: {}, body: {})
741
+ uri = URI.parse(url)
742
+ http = Net::HTTP.new(uri.host, uri.port)
743
+ http.use_ssl = true
744
+ request = Net::HTTP::Post.new(uri.path, headers)
745
+ request.body = body
746
+ http.request(request).body
747
+ end
748
+
749
+ def short(string)
750
+ string[0..5000]
751
+ end
752
+
753
+ def encoder
754
+ Tiktoken.encoding_for_model("gpt-4o")
755
+ end
756
+
444
757
  def evaluates(
445
- ruby,
758
+ ruby: @ruby,
446
759
  it: nil,
447
760
  debug: debug?,
448
761
  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.1
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-05 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
@@ -143,16 +157,16 @@ require_paths:
143
157
  - lib
144
158
  required_ruby_version: !ruby/object:Gem::Requirement
145
159
  requirements:
146
- - - '='
160
+ - - ">="
147
161
  - !ruby/object:Gem::Version
148
- version: 3.3.4
162
+ version: '3'
149
163
  required_rubygems_version: !ruby/object:Gem::Requirement
150
164
  requirements:
151
165
  - - ">="
152
166
  - !ruby/object:Gem::Version
153
167
  version: '0'
154
168
  requirements: []
155
- rubygems_version: 3.5.11
169
+ rubygems_version: 3.5.16
156
170
  signing_key:
157
171
  specification_version: 4
158
172
  summary: a collection of gems