Abhängigkeiten hinzufügen

Dieses Tutorial behandelt die Benutzung von Abhängigkeiten mit fpm und wie ein existierendes fpm-Projekt wiederverwenden werden kann.

Benutzung der Standardbibliothek

Wir beginnen mit einem neuen Projekt mit fpm, wir möchten eine Kommandozeilenanwendung bauen, die eine Datei liest, einebestimmtes Suchmuster findet und ersetzt. Da wir nicht die Funktion zum Ersetzen selbst schreiben wollen, werden wir die Standardbibliothek (stdlib) als Abhängigkeit benutzen. Im Paketmanifest definieren wir stdlib in der depedencies Tabelle:

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

[dependencies]
stdlib = "*"

Zuerst müssen wir ein Modul mit einer Prozedur erstellen, das die Substitution durchführt. Diese benötigt drei Schritte:

  1. eine ganze Zeile von einer Adresse lesen

  2. ein Muster in einem String ersetzen

  3. einen neuen String in der Ausgabe schreiben

Wir werden die replace_all-Funktion aus dem stdlib_strings-Modul für diesen Zweck verwenden. Die Implementierung ist hier angegeben

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

Zuletzt brauchen wir einen Kommandozeilen-Treiber, um unsere neue Funktion zu nutzen.

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

Wir können unseren Kommandozeilen-Treiber über fpm testen:

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

[dependencies]
stdlib = "*"

Hinzufügen eines Test-Frameworks

Bevor wir weitermachen, um neue Funktionen zu implementieren, möchten wir einige Tests hinzufügen, um zu verifizieren, dass unsere Implementierung weiterhin funktioniert, wenn wir es ändern. Ein minimalistes Test-Framework ist verfügbar mit [test-drive]. Da das Test-Framework nur benötigt wird, wenn wir das Paket selbst entwickeln, aber nicht für andere Pakete, die in der Zukunft unsere Module nutzen, fügen wir eine lokale Abhängigkeit hinzu. Das test-drive-Paket wird in der dev-dependencies-Tabelle wie unten angegeben hinzugefügt

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"

Bemerkung

Für eine Entwicklungsabhängigkeit wie das Test-Framework wählen wir eine strikte Versions Einschränkung, indem wir den tag angeben, den wir benutzen wollen.

Wir können nun einen einfachen Test schreiben, da unsere Funktion mit Units arbeitet, werden wir temporäre Units erstellen, um den Eingang und die Ausgabe zu erfassen. Für den Moment werden wir eine einfache Ersetzung in einer Zeile als Test hinzufügen

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

Wir führen unseren neuen Test mit fpm aus

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

Die Erstellung der temporären Units für mehrere Unit-Tests wird repetitiv sein, diese Aufgaben können in einer separatem Prozedur erledigt werden und in mehreren Tests wiederverwendet werden.

Ziel-spezifische Abhängigkeiten

Abhängigkeiten können auch nur für bestimmte Ziele verwendet werden. Dies kann verwendet werden, um ein Kommandozeilen-Interface-Paket hinzuzufügen, das nur für die Ausführungsdatei benutzt wird, aber nicht Teil der Bibliotheksabhängigkeiten ist.

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"

Wir restrukturieren unsere Hauptprogramm etwas, um [M_CLI2] zu benutzen was die Kommandozeileneingabe verarbeitet. Das unnamed-Feld enthält alle positionsbezogenen Kommandozeilenargumente, wir benutzen die ersten zwei als Muster und Ersetzung, und verwenden alle weiteren Argumente als Eingabe. Wir fügen auch eine Option hinzu, um die Ausgabe zu umleiten. Unser finales Hauptprogramm sieht wie folgt aus

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

Wir führen einen kurzen Test mit fpm aus

❯ 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"

Die Ausgabe sieht wie erwartet aus, mit zwei Ersetzungen.

Zusammenfassung

In diesem Kurs haben wir gelernt, wie man

  • von einem anderen fpm-Projekt im Paketmanifest abhängen

  • Hinzufügen von Entwicklungsabhängigkeiten zum Testen

  • Verwenden von Abhängigkeiten für ausführbare Dateien