@@ -354,10 +354,8 @@ defmodule Mix.Utils do
354354 end
355355
356356 @ doc """
357- Opens and reads content from either a URL or a local filesystem path.
358-
359- Used by tasks like `archive.install` and `local.rebar` that support
360- installation either from a URL or a local file.
357+ Opens and reads content from either a URL or a local filesystem path
358+ and returns the contents as a binary.
361359
362360 Raises if the given path is not a URL, nor a file or if the
363361 file or URL are invalid.
@@ -370,21 +368,74 @@ defmodule Mix.Utils do
370368 def read_path! ( path , opts \\ [ ] ) do
371369 cond do
372370 url? ( path ) && opts [ :shell ] ->
373- read_shell ( path )
371+ read_shell ( path , [ ] )
374372 url? ( path ) ->
375- read_httpc ( path )
373+ read_httpc ( path , [ ] )
376374 file? ( path ) ->
377375 read_file ( path )
378376 true ->
379377 Mix . raise "Expected #{ path } to be a url or a local file path"
380378 end
381379 end
382380
381+ @ doc """
382+ Copies content from either a URL or a local filesystem path to
383+ target path.
384+
385+ Used by tasks like `archive.install` and `local.rebar` that support
386+ installation either from a URL or a local file.
387+
388+ Raises if the given path is not a URL, nor a file or if the
389+ file or URL are invalid.
390+
391+ ## Options
392+
393+ * `:shell` - Forces the use of `wget` or `curl` to fetch the file if the
394+ given path is a URL.
395+
396+ * `:force` - Forces overwriting target file without a shell prompt.
397+ """
398+ def copy_path! ( source , target , opts \\ [ ] ) do
399+ if opts [ :force ] || overwriting? ( target ) do
400+ cond do
401+ url? ( source ) && opts [ :shell ] ->
402+ read_shell ( source , file: target )
403+ url? ( source ) ->
404+ read_httpc ( source , file: target )
405+ file? ( source ) ->
406+ copy_file ( source , target )
407+ true ->
408+ Mix . raise "Expected #{ source } to be a url or a local file path"
409+ end
410+
411+ put_creating_file ( target )
412+ end
413+
414+ :ok
415+ end
416+
417+ @ doc """
418+ Prompts the user to overwrite the file if it exists. Returns
419+ the user input.
420+ """
421+ def overwriting? ( path ) do
422+ if File . exists? ( path ) do
423+ full = Path . expand ( path )
424+ Mix . shell . yes? ( Path . relative_to_cwd ( full ) <> " already exists, overwrite?" )
425+ else
426+ true
427+ end
428+ end
429+
383430 defp read_file ( path ) do
384431 File . read! ( path )
385432 end
386433
387- defp read_httpc ( path ) do
434+ defp copy_file ( source , target ) do
435+ File . cp! ( source , target )
436+ end
437+
438+ defp read_httpc ( path , opts ) do
388439 { :ok , _ } = Application . ensure_all_started ( :ssl )
389440 { :ok , _ } = Application . ensure_all_started ( :inets )
390441
@@ -401,11 +452,20 @@ defmodule Mix.Utils do
401452 if http_proxy , do: proxy ( http_proxy )
402453 if https_proxy , do: proxy ( https_proxy )
403454
404- # We are using relaxed: true because some clients is returning a Location
455+ if out_path = opts [ :file ] do
456+ File . rm ( out_path )
457+ req_opts = [ stream: String . to_char_list ( out_path ) ]
458+ else
459+ req_opts = [ body_format: :binary ]
460+ end
461+
462+ # We are using relaxed: true because some servers is returning a Location
405463 # header with relative paths, which does not follow the spec. This would
406464 # cause the request to fail with {:error, :no_scheme} unless :relaxed
407465 # is given.
408- case :httpc . request ( :get , request , [ relaxed: true ] , [ body_format: :binary ] , :mix ) do
466+ case :httpc . request ( :get , request , [ relaxed: true ] , req_opts , :mix ) do
467+ { :ok , :saved_to_file } ->
468+ :ok
409469 { :ok , { { _ , status , _ } , _ , body } } when status in 200 .. 299 ->
410470 body
411471 { :ok , { { _ , status , _ } , _ , _ } } ->
@@ -430,9 +490,10 @@ defmodule Mix.Utils do
430490 end
431491 end
432492
433- defp read_shell ( path ) do
493+ defp read_shell ( path , opts ) do
434494 filename = URI . parse ( path ) . path |> Path . basename
435- out_path = Path . join ( System . tmp_dir! , filename )
495+ out_path = opts [ :file ] || Path . join ( System . tmp_dir! , filename )
496+
436497 File . rm ( out_path )
437498
438499 status = cond do
@@ -450,14 +511,20 @@ defmodule Mix.Utils do
450511 1
451512 end
452513
453- check_command! ( status , path , out_path )
514+ check_command! ( status , path , opts [ :file ] )
454515
455- data = File . read! ( out_path )
456- File . rm! ( out_path )
457- data
516+ unless opts [ :file ] do
517+ data = File . read! ( out_path )
518+ File . rm! ( out_path )
519+ data
520+ end
458521 end
459522
460523 defp check_command! ( 0 , _path , _out_path ) , do: :ok
524+ defp check_command! ( _status , path , nil ) do
525+ Mix . raise "Could not fetch data, please download manually from " <>
526+ "#{ inspect path } "
527+ end
461528 defp check_command! ( _status , path , out_path ) do
462529 Mix . raise "Could not fetch data, please download manually from " <>
463530 "#{ inspect path } and copy it to #{ inspect out_path } "
@@ -467,6 +534,10 @@ defmodule Mix.Utils do
467534 match? ( { :win32 , _ } , :os . type )
468535 end
469536
537+ defp put_creating_file ( path ) do
538+ Mix . shell . info [ :green , "* creating " , :reset , Path . relative_to_cwd ( path ) ]
539+ end
540+
470541 defp file? ( path ) do
471542 File . regular? ( path )
472543 end
0 commit comments