66module Homebrew
77 module Bundle
88 class Uv < Extension
9- WithOptions = T . type_alias { T ::Hash [ Symbol , T ::Array [ String ] ] }
10- Tool = T . type_alias { { name : String , with : T ::Array [ String ] } }
9+ WithOptions = T . type_alias { T ::Hash [ Symbol , T . any ( String , T ::Array [ String ] ) ] }
10+ Tool = T . type_alias { { name : String , with : T ::Array [ String ] , source : T . nilable ( String ) } }
1111 Checkable = T . type_alias { { name : String , options : WithOptions } }
1212 ToolEntry = T . type_alias { T . any ( Tool , Checkable ) }
1313
14+ SOURCE_REQUIREMENT_REGEX = T . let ( %r{\A (?:git\+ |https?://|file://|\. {0,2}/)|\. git\z } , Regexp )
15+
1416 class << self
1517 sig { override . returns ( Symbol ) }
1618 def type = :uv
@@ -23,17 +25,24 @@ def banner_name = "uv tools"
2325
2426 sig { override . params ( name : String , options : Homebrew ::Bundle ::EntryInputOptions ) . returns ( Dsl ::Entry ) }
2527 def entry ( name , options = { } )
26- unknown_options = options . keys - [ :with ]
28+ unknown_options = options . keys - [ :with , :source ]
2729 raise "unknown options(#{ unknown_options . inspect } ) for uv" if unknown_options . present?
2830
2931 with = options [ :with ]
3032 if !with . nil? && ( !with . is_a? ( Array ) || with . any? { |requirement | !requirement . is_a? ( String ) } )
3133 raise "options[:with](#{ with . inspect } ) should be an Array of String objects"
3234 end
3335
36+ source = options . fetch ( :source , nil )
37+ if !source . nil? && !source . is_a? ( String )
38+ raise "options[:source](#{ source . inspect } ) should be a String object"
39+ end
40+
3441 normalized_options = { }
3542 normalized_with = normalize_with ( with || [ ] )
3643 normalized_options [ :with ] = normalized_with if normalized_with . present?
44+ normalized_source = normalize_source ( source )
45+ normalized_options [ :source ] = normalized_source if normalized_source . present?
3746
3847 Dsl ::Entry . new ( :uv , name , normalized_options )
3948 end
@@ -55,7 +64,7 @@ def packages
5564 return packages if packages
5665
5766 @packages = if ( uv = package_manager_executable )
58- output = `#{ uv } tool list --show-with --show-extras 2>/dev/null`
67+ output = `#{ uv } tool list --show-with --show-extras --show-version-specifiers 2>/dev/null`
5968 parse_tool_list ( output )
6069 end
6170 return [ ] if @packages . nil?
@@ -73,17 +82,23 @@ def dump_with(package)
7382 package_with ( T . cast ( package , ToolEntry ) )
7483 end
7584
85+ sig { params ( package : Object ) . returns ( T . nilable ( String ) ) }
86+ def dump_source ( package )
87+ package_source ( T . cast ( package , ToolEntry ) )
88+ end
89+
7690 sig {
7791 override . params (
7892 name : String ,
7993 with : T . nilable ( T ::Array [ String ] ) ,
94+ source : T . nilable ( String ) ,
8095 verbose : T ::Boolean ,
8196 ) . returns ( T ::Boolean )
8297 }
83- def install_package! ( name , with : nil , verbose : false )
98+ def install_package! ( name , with : nil , source : nil , verbose : false )
8499 uv = package_manager_executable!
85100
86- args = [ "tool" , "install" , name ]
101+ args = [ "tool" , "install" , source . presence || name ]
87102 normalize_with ( with || [ ] ) . each do |requirement |
88103 args << "--with"
89104 args << requirement
@@ -114,17 +129,29 @@ def parse_tool_list(output)
114129 extras_raw = line [ /\[ extras:\s *([^\] ]+)\] / , 1 ]
115130 name = name_with_extras ( name , extras_raw )
116131 with_raw = line [ /\[ with:\s *([^\] ]+)\] / , 1 ]
132+ required_raw = line [ /\[ required:\s *([^\] ]+)\] / , 1 ]
117133
118134 entries << {
119- name : name ,
120- with : parse_with_requirements ( with_raw ) ,
135+ name : name ,
136+ with : parse_with_requirements ( with_raw ) ,
137+ source : parse_source ( required_raw ) ,
121138 }
122139 end
123140
124141 entries . sort_by { |entry | entry [ :name ] . to_s }
125142 end
126143 private :parse_tool_list
127144
145+ sig { params ( required_raw : T . nilable ( String ) ) . returns ( T . nilable ( String ) ) }
146+ def parse_source ( required_raw )
147+ source = normalize_source ( required_raw )
148+ return if source . nil?
149+ return source if source . match? ( SOURCE_REQUIREMENT_REGEX )
150+
151+ nil
152+ end
153+ private :parse_source
154+
128155 sig { params ( name : String , extras_raw : T . nilable ( String ) ) . returns ( String ) }
129156 def name_with_extras ( name , extras_raw )
130157 return name if extras_raw . blank?
@@ -175,6 +202,12 @@ def normalize_with(with)
175202 end
176203 private :normalize_with
177204
205+ sig { params ( source : T . nilable ( String ) ) . returns ( T . nilable ( String ) ) }
206+ def normalize_source ( source )
207+ source . presence &.strip
208+ end
209+ private :normalize_source
210+
178211 sig { params ( name : String ) . returns ( String ) }
179212 def normalize_name ( name )
180213 match = name . strip . match ( /\A (?<base>[^\[ \] ]+)(?:\[ (?<extras>[^\] ]+)\] )?\z / )
@@ -193,16 +226,23 @@ def normalize_name(name)
193226 end
194227 private :normalize_name
195228
196- sig { override . params ( name : String , with : T . nilable ( T ::Array [ String ] ) ) . returns ( Object ) }
197- def package_record ( name , with : nil )
198- normalized_options ( name , with : with || [ ] )
229+ sig {
230+ override . params (
231+ name : String ,
232+ with : T . nilable ( T ::Array [ String ] ) ,
233+ source : T . nilable ( String ) ,
234+ ) . returns ( Object )
235+ }
236+ def package_record ( name , with : nil , source : nil )
237+ normalized_options ( name , with : with || [ ] , source :)
199238 end
200239
201- sig { params ( name : String , with : T ::Array [ String ] ) . returns ( Tool ) }
202- def normalized_options ( name , with :)
240+ sig { params ( name : String , with : T ::Array [ String ] , source : T . nilable ( String ) ) . returns ( Tool ) }
241+ def normalized_options ( name , with :, source : nil )
203242 {
204- name : normalize_name ( name ) ,
205- with : normalize_with ( with ) ,
243+ name : normalize_name ( name ) ,
244+ with : normalize_with ( with ) ,
245+ source : normalize_source ( source ) ,
206246 }
207247 end
208248 private :normalized_options
@@ -223,6 +263,85 @@ def package_with(package)
223263 end
224264 private :package_with
225265
266+ sig { params ( package : ToolEntry ) . returns ( T . nilable ( String ) ) }
267+ def package_source ( package )
268+ return package [ :source ] if package . key? ( :source )
269+
270+ T . cast ( package [ :options ] . fetch ( :source , nil ) , T . nilable ( String ) )
271+ end
272+ private :package_source
273+
274+ sig { override . params ( package : Object ) . returns ( String ) }
275+ def dump_entry ( package )
276+ line = super
277+ source = dump_source ( package )
278+ line = "#{ line } , source: #{ quote ( source ) } " if source . present?
279+
280+ line
281+ end
282+
283+ sig {
284+ override . params (
285+ name : String ,
286+ with : T . nilable ( T ::Array [ String ] ) ,
287+ source : T . nilable ( String ) ,
288+ no_upgrade : T ::Boolean ,
289+ verbose : T ::Boolean ,
290+ _options : Homebrew ::Bundle ::EntryOption ,
291+ ) . returns ( T ::Boolean )
292+ }
293+ def preinstall! ( name , with : nil , source : nil , no_upgrade : false , verbose : false , **_options )
294+ _ = no_upgrade
295+
296+ ensure_package_manager_installed! ( name , verbose :)
297+
298+ if package_installed? ( name , with :, source :)
299+ puts "Skipping install of #{ name } #{ package_description } . It is already installed." if verbose
300+ return false
301+ end
302+
303+ true
304+ end
305+
306+ sig {
307+ override . params (
308+ name : String ,
309+ with : T . nilable ( T ::Array [ String ] ) ,
310+ source : T . nilable ( String ) ,
311+ preinstall : T ::Boolean ,
312+ no_upgrade : T ::Boolean ,
313+ verbose : T ::Boolean ,
314+ force : T ::Boolean ,
315+ _options : Homebrew ::Bundle ::EntryOption ,
316+ ) . returns ( T ::Boolean )
317+ }
318+ def install! ( name , with : nil , source : nil , preinstall : true , no_upgrade : false , verbose : false , force : false ,
319+ **_options )
320+ _ = no_upgrade
321+ _ = force
322+
323+ return true unless preinstall
324+
325+ puts "Installing #{ name } #{ package_description } . It is not currently installed." if verbose
326+ return false unless install_package! ( name , with :, source :, verbose :)
327+
328+ package = normalized_options ( name , with : with || [ ] , source :)
329+ installed_packages << package unless installed_packages . include? ( package )
330+ packages << package unless packages . include? ( package )
331+ true
332+ end
333+
334+ sig {
335+ override . params (
336+ name : String ,
337+ with : T . nilable ( T ::Array [ String ] ) ,
338+ source : T . nilable ( String ) ,
339+ ) . returns ( T ::Boolean )
340+ }
341+ def package_installed? ( name , with : nil , source : nil )
342+ installed_packages . include? ( package_record ( name , with :, source :) )
343+ end
344+
226345 sig { override . params ( name : String , executable : Pathname ) . void }
227346 def uninstall_package! ( name , executable : Pathname . new ( "" ) )
228347 Bundle . system ( executable . to_s , "tool" , "uninstall" , name , verbose : false )
@@ -232,16 +351,18 @@ def uninstall_package!(name, executable: Pathname.new(""))
232351 sig { override . params ( entries : T ::Array [ Dsl ::Entry ] ) . returns ( T ::Array [ Object ] ) }
233352 def format_checkable ( entries )
234353 checkable_entries ( entries ) . map do |entry |
235- with = if entry . options . is_a? ( Hash )
236- value = entry . options [ :with ]
237- value . is_a? ( Array ) ? value : [ ]
238- else
239- [ ]
240- end
241-
242- self . class . package_record ( entry . name , with :)
354+ { name : entry . name , options : entry . options }
243355 end
244356 end
357+
358+ sig { override . params ( package : Object , no_upgrade : T ::Boolean ) . returns ( T ::Boolean ) }
359+ def installed_and_up_to_date? ( package , no_upgrade : false )
360+ self . class . package_installed? (
361+ self . class . dump_name ( package ) ,
362+ with : self . class . dump_with ( package ) ,
363+ source : self . class . dump_source ( package ) ,
364+ )
365+ end
245366 end
246367 end
247368end
0 commit comments