dorian 2.1.0 → 2.2.1
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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/dorian/bin.rb +325 -12
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc5cf78891ac8f7ed7f4ebe350d639c6e42bc7cfa2a8bd1fa6ad526b3e9785db
|
4
|
+
data.tar.gz: '018eee86347e85d6e02b12d23f0a8eadb34a1ba429e0881d8c14fe656a3fa956'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98bfeb69db7cc33735780171db3a4f507e6d13f233bd18b2ae0c0e1d264999bff7827fabb5cbcd4590afdc3bf870d4a6d1c701f2257b502bb72f9ad3ea01f11a
|
7
|
+
data.tar.gz: a3351fdbc3b191402813581845e15fd049276510365a13417d461a9806c1f018073e6c65ab2974167269386cab2a569ec70a4c94302ce8740b3ae1ccb0df6654
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1
|
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.
|
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.
|
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 +
|
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)
|
154
|
-
|
155
|
-
|
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)
|
161
|
-
|
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
|
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-
|
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
|
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.
|
169
|
+
rubygems_version: 3.5.16
|
156
170
|
signing_key:
|
157
171
|
specification_version: 4
|
158
172
|
summary: a collection of gems
|