Adicionando dependencias

Este tutorial cubre el uso de dependencias con fpm y cómo reutilizar proyectos fpm existentes.

Usando la biblioteca estándar

Iniciamos un nuevo proyecto con fpm, deseamos construir un programa de línea de comandos que lee un archivo, encuentre cierto patrón y lo reemplaze. Ya que no queremos escribir una función para reemplazar, usamos la biblioteca estándar de Fortran (stdlib) como dependencia. En el manifiesto del paquete definimos stdlib en la tabla de dependencias:

fpm.toml
name = "demo"
version = "0.1.0"

[dependencies]
stdlib = "*"

Ahora creamos un módulo con un procedimiento para realizar la substitución. Esto requiere tres pasos:

  1. leer una línea completa desde la unidad uno

  2. reemplazar el patrón en la cadena de caracteres

  3. escribir la nueva cadena de caracteres en un archivo de salida

Para esto, usaremos la función replace_all desde el módulo stdlib_strings. La implementación es mostrada aquí

src/demo.f90
module demo
  use stdlib_io, only : getline
  use stdlib_strings, only : replace_all
  implicit none
  private

  public :: substitute

contains

  !> Read all lines from input, replace pattern and print it to output
  subroutine substitute(input, output, pattern, replacement)
    !> Formatted input unit
    integer, intent(in) :: input
    !> Formatted output unit
    integer, intent(in) :: output
    !> Pattern to replace in input
    character(len=*), intent(in) :: pattern
    !> Replacement for pattern in output
    character(len=*), intent(in) :: replacement

    character(len=:), allocatable :: line
    integer :: stat

    do
      call getline(input, line, stat)
      if (stat /= 0) exit
      write(output, '(a)') replace_all(line, pattern, replacement)
    end do
  end subroutine substitute

end module demo

Finalmente, necesitamos un controlador de línea de comandos para usar la nueva función.

app/main.f90
program main
  use, intrinsic :: iso_fortran_env, only : output_unit
  use demo, only : substitute
  implicit none
  character(len=256) :: pattern, replacement, input_file
  integer :: input

  call get_command_argument(1, pattern)
  call get_command_argument(2, replacement)
  call get_command_argument(3, input_file)

  open(newunit=input, file=input_file, status='old')
  call substitute(input, output_unit, trim(pattern), trim(replacement))
  close(input)
end program main

Podemos verificar el controlador de línea de comandos ejecutando con fpm:

❯ fpm run -- demo substitute fpm.toml
name = "substitute"
version = "0.1.0"

[dependencies]
stdlib = "*"

Adicionando un marco de pruebas

Antes de adicionar nuevas características, deseamos realizar algunas pruebas para verificar si nuestra implementación sigue funcionando a medida que esta es modificada. Un marco de pruebas minimalista está disponible con [test-drive]. Dado que el marco de pruebas es solo requerido durante el desarrollo del paquete, pero no para otros paquetes que puedan ser usados en el futuro para nuestros módulos, adicionamos una dependencia local. El paquete test-drive es adicionado en la table dev-dependencies como se muestra abajo

fpm.toml
name = "demo"
version = "0.1.0"

[dependencies]
stdlib = "*"

[dev-dependencies]
test-drive.git = "https://github.com/fortran-lang/test-drive"
test-drive.tag = "v0.4.0"

Nota

Para una dependencia de desarrollo similar a un marco de prueba, elegimos un estricto pin de la versión especificando la tag que queremos usar.

Ahora podemos escribir una prueba unitária simple, dado que nuestra función trabaja con unidades, usaremos unidades borrador para definir las entradas y las salidas. Por ahora, adicionaremos una única substitución de línea como caso de prueba

test/main.f90
module test_demo
  use demo, only : substitute
  use stdlib_io, only : getline
  use testdrive, only : error_type, unittest_type, new_unittest, check
  implicit none
  private

  public :: collect_demo

contains

  !> Collect all exported unit tests
  subroutine collect_demo(testsuite)
    !> Collection of tests
    type(unittest_type), allocatable, intent(out) :: testsuite(:)

    testsuite = [new_unittest("substitute", test_substitute)]
  end subroutine collect_demo

  !> Check substitution of a single line
  subroutine test_substitute(error)
    !> Error handling
    type(error_type), allocatable, intent(out) :: error
    integer :: input, output, stat
    character(len=:), allocatable :: line
    open(newunit=input, status="scratch")
    write(input, '(a)') "This is a valid test"
    rewind(input)

    open(newunit=output, status="scratch")
    call substitute(input, output, "test", "example")
    close(input)

    rewind(output)
    call getline(output, line, stat)
    close(output)

    call check(error, line, "This is a valid example")
  end subroutine test_substitute
end module test_demo

program tester
  use, intrinsic :: iso_fortran_env, only : error_unit
  use testdrive, only : run_testsuite
  use test_demo, only : collect_demo
  implicit none
  integer :: stat

  stat = 0
  call run_testsuite(collect_demo, error_unit, stat)

  if (stat > 0) then
    write(error_unit, '(i0, 1x, a)') stat, "test(s) failed!"
    error stop
  end if

end program tester

Ejecutamos nuestra nueva prueba usando fpm

❯ fpm test
  Starting substitute ... (1/1)
       ... substitute [PASSED]

La creación de unidades borrador para múltiples pruebas unitárias será repetitiva, este tipo de tareas normalmente se pueden realizar en un procedimiento separado y reutilizarse en varias pruebas.

Dependencias de objetivo específico

Las dependencias también se pueden ser usadas, solamente, para objetivos específicos. Esto se puede usar para adicionar un paquete de interfaz de línea de comandos, que solo se utiliza para el ejecutable pero que no forma parte de las dependencias de la biblioteca.

fpm.toml
name = "demo"
version = "0.1.0"

[dependencies]
stdlib = "*"

[dev-dependencies]
test-drive.git = "https://github.com/fortran-lang/test-drive"
test-drive.tag = "v0.4.0"

[[executable]]
name = "demo"
[executable.dependencies]
M_CLI2.git = "https://github.com/urbanjost/M_CLI2"

Reestructuramos un poco nuestro programa principal para usar [M \ _ CLI2] para manejar la entrada de línea de comandos. La matriz unname contiene todos los comandos posicionales de argumentos de línea, todavía usamos los dos primeros como patrón y reemplazo, y usamos todos los argumentos restantes como entrada. También agregamos una opción a redirigir la salida. Nuestro programa principal final parece

app/main.f90
program main
  use, intrinsic :: iso_fortran_env, only : output_unit
  use demo, only : substitute
  use m_cli2, only : set_args, unnamed, sget
  implicit none
  character(len=:), allocatable :: input_file, output_file, pattern, replacement
  integer :: input, output, i

  call set_args("--output:o ''")

  output_file = trim(sget("output"))
  if (len(output_file) > 0) then
    open(file=output_file, newunit=output)
  else
    output = output_unit
  end if

  pattern = trim(unnamed(1))
  replacement = trim(unnamed(2))

  do i = 3, size(unnamed)
    input_file = trim(unnamed(i))
    open(file=input_file, newunit=input, status='old')
    call substitute(input, output_unit, trim(pattern), trim(replacement))
    close(input)
  end do

  if (output /= output_unit) close(output)
end program main

Nuevamente ejecutamos y rápidamente verificamos con fpm

❯ fpm run -- demo substitute fpm.toml
name = "substitute"
version = "0.1.0"

[dependencies]
stdlib = "*"

[dev-dependencies]
test-drive.git = "https://github.com/fortran-lang/test-drive"
test-drive.tag = "v0.4.0"

[[executable]]
name = "substitute"
[executable.dependencies]
M_CLI2.git = "https://github.com/urbanjost/M_CLI2"

La salida luce como se espera con dos substituciones.

Resumen

En este tutorial aprenderas cómo

  • depender de otro proyecto fpm en el manifiesto del paquete

  • añadir dependencias de desarrollo para pruebas

  • usar dependencias para ejecutables