add gray-convert tool
This is a command line tool to convert between different GRAY configuration file formats.
This commit is contained in:
parent
44e957f8d6
commit
a596b0dff2
11
Makefile
11
Makefile
@ -47,9 +47,10 @@ MODULES = $(filter-out $(OBJDIR)/main%,$(OBJECTS))
|
|||||||
# Build outputs
|
# Build outputs
|
||||||
GRAY = $(BINDIR)/gray
|
GRAY = $(BINDIR)/gray
|
||||||
LIBGRAY = $(LIBDIR)/libgray.so
|
LIBGRAY = $(LIBDIR)/libgray.so
|
||||||
BINARIES = $(GRAY)
|
BINARIES = $(GRAY) $(GRAY)-convert
|
||||||
LIBRARIES = $(LIBGRAY)
|
LIBRARIES = $(LIBGRAY)
|
||||||
MANPAGES = $(addprefix $(SHAREDIR)/,gray.1 gray.ini.5 profiles.txt.5 beamdata.txt.5 magneticdata.txt.5)
|
MANPAGES = $(addprefix $(SHAREDIR)/,gray.1 gray-convert.1 gray.ini.5 \
|
||||||
|
profiles.txt.5 beamdata.txt.5 magneticdata.txt.5)
|
||||||
|
|
||||||
##
|
##
|
||||||
## Git information (used in the version string)
|
## Git information (used in the version string)
|
||||||
@ -149,6 +150,10 @@ $(OBJDIR)/%.o: $(OBJDIR)/%.d
|
|||||||
$(GRAY): $(OBJDIR)/main.o $(LIBGRAY) | $(BINDIR)
|
$(GRAY): $(OBJDIR)/main.o $(LIBGRAY) | $(BINDIR)
|
||||||
$(LD) $(LDFLAGS) -o '$@' $< -lgray
|
$(LD) $(LDFLAGS) -o '$@' $< -lgray
|
||||||
|
|
||||||
|
# gray-convert binary
|
||||||
|
$(GRAY)-convert: $(OBJDIR)/main_convert.o $(LIBGRAY) | $(BINDIR)
|
||||||
|
$(LD) $(LDFLAGS) -o '$@' $< -lgray
|
||||||
|
|
||||||
# GRAY shared library
|
# GRAY shared library
|
||||||
$(LIBDIR)/libgray.so: $(MODULES) | $(LIBDIR)
|
$(LIBDIR)/libgray.so: $(MODULES) | $(LIBDIR)
|
||||||
$(LD) -shared $(LDFLAGS) -o '$@' $^
|
$(LD) -shared $(LDFLAGS) -o '$@' $^
|
||||||
@ -173,6 +178,8 @@ $(SHAREDIR)/doc: | $(SHAREDIR)
|
|||||||
# Generated man pages
|
# Generated man pages
|
||||||
$(SHAREDIR)/gray.1: $(GRAY) doc/man/gray.1 | $(SHAREDIR)
|
$(SHAREDIR)/gray.1: $(GRAY) doc/man/gray.1 | $(SHAREDIR)
|
||||||
help2man '$<' -i doc/man/gray.1 -N -n 'beam-tracing code for EC waves' > '$@'
|
help2man '$<' -i doc/man/gray.1 -N -n 'beam-tracing code for EC waves' > '$@'
|
||||||
|
$(SHAREDIR)/gray-convert.1: $(GRAY)-convert | $(SHAREDIR)
|
||||||
|
help2man '$<' -N -n 'configuration format converter' > '$@'
|
||||||
|
|
||||||
$(SHAREDIR)/%: doc/man/%.md $(GRAY) | $(SHAREDIR)
|
$(SHAREDIR)/%: doc/man/%.md $(GRAY) | $(SHAREDIR)
|
||||||
pandoc -s '$<' -t man \
|
pandoc -s '$<' -t man \
|
||||||
|
@ -13,7 +13,8 @@ GRAY main configuration file
|
|||||||
Legacy configuration file
|
Legacy configuration file
|
||||||
|
|
||||||
This file allows configuring all the parameters of a GRAY simulation, including
|
This file allows configuring all the parameters of a GRAY simulation, including
|
||||||
all other input files.
|
all other input files. Existing files can be upgraded to the gray.ini format
|
||||||
|
using the \fBgray-convert\fR(1) tool.
|
||||||
.TP
|
.TP
|
||||||
.I beamdata.txt
|
.I beamdata.txt
|
||||||
Beam description file
|
Beam description file
|
||||||
@ -41,4 +42,5 @@ location can be changed with the \fB-o\fR option.
|
|||||||
For a list of the unit numbers and their descriptions see the GRAY user manual.
|
For a list of the unit numbers and their descriptions see the GRAY user manual.
|
||||||
|
|
||||||
[SEE ALSO]
|
[SEE ALSO]
|
||||||
\fBgray.ini\fR(5), \fBgray_params.data\fR(5), \fBbeamdata.txt\fR(5), \fBprofiles.txt\fR(5)
|
\fBgray-convert\fR(1), \fBgray.ini\fR(5), \fBgray_params.data\fR(5),
|
||||||
|
\fBbeamdata.txt\fR(5), \fBprofiles.txt\fR(5)
|
||||||
|
@ -24,7 +24,8 @@ module gray_cli
|
|||||||
|
|
||||||
private
|
private
|
||||||
public :: cli_options, print_cli_options, parse_cli_options, &
|
public :: cli_options, print_cli_options, parse_cli_options, &
|
||||||
deinit_cli_options, parse_param_overrides
|
deinit_cli_options, parse_param_overrides, &
|
||||||
|
print_version, get_next_command
|
||||||
|
|
||||||
contains
|
contains
|
||||||
|
|
||||||
|
340
src/main_convert.f90
Normal file
340
src/main_convert.f90
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
program gray_convert
|
||||||
|
|
||||||
|
use gray_cli, only: print_version, get_next_command
|
||||||
|
use gray_params, only: gray_parameters, read_gray_params, read_gray_config, &
|
||||||
|
step_enum, absorption_enum, current_drive_enum, &
|
||||||
|
polar_enum, beam_enum, equil_enum, x_point_enum, &
|
||||||
|
scaling_enum, rho_enum, profiles_enum
|
||||||
|
implicit none
|
||||||
|
|
||||||
|
! Configuration formats
|
||||||
|
enum, bind(c)
|
||||||
|
enumerator :: config_format = -1
|
||||||
|
enumerator :: FORMAT_PARAMS = 1 ! the legacy gray_params.data format
|
||||||
|
enumerator :: FORMAT_INI = 2 ! the current INI format
|
||||||
|
end enum
|
||||||
|
|
||||||
|
! Command line options
|
||||||
|
! See print_help() for a description.
|
||||||
|
type cli_options
|
||||||
|
character(len=:), allocatable :: input_file ! file to read
|
||||||
|
character(len=:), allocatable :: output_file ! file to write
|
||||||
|
integer(kind(config_format)) :: format ! output file format
|
||||||
|
end type
|
||||||
|
|
||||||
|
type(cli_options) :: opts
|
||||||
|
type(gray_parameters) :: params
|
||||||
|
integer :: err
|
||||||
|
|
||||||
|
call parse_cli_options(opts)
|
||||||
|
|
||||||
|
! Reading
|
||||||
|
if (index(opts%input_file, ".data") /= 0) &
|
||||||
|
call read_gray_params(opts%input_file, params, err)
|
||||||
|
if (index(opts%input_file, ".ini") /= 0) &
|
||||||
|
call read_gray_config(opts%input_file, params, err)
|
||||||
|
|
||||||
|
if (err /= 0) call exit(2)
|
||||||
|
|
||||||
|
! Writing
|
||||||
|
select case (opts%format)
|
||||||
|
case (FORMAT_PARAMS)
|
||||||
|
print '(a)', 'gray_params.data output not implemented!'
|
||||||
|
call exit(1)
|
||||||
|
case (FORMAT_INI)
|
||||||
|
call write_gray_config(opts%output_file, params)
|
||||||
|
end select
|
||||||
|
|
||||||
|
call exit(0)
|
||||||
|
|
||||||
|
contains
|
||||||
|
|
||||||
|
subroutine print_help()
|
||||||
|
! Prints the usage screen
|
||||||
|
|
||||||
|
print '(a)', 'Usage: gray-convert [options] INFILE OUTFILE'
|
||||||
|
print '(a)', ''
|
||||||
|
print '(a)', 'Converts between different GRAY configuration formats.'
|
||||||
|
print '(a)', ''
|
||||||
|
print '(a)', 'Options:'
|
||||||
|
print '(a)', ' -h, --help display this help and exit'
|
||||||
|
print '(a)', ' -V, --version display version information and exit'
|
||||||
|
print '(a)', ' -f, --format set the output file format (default: ini)'
|
||||||
|
print '(a)', ' valid formats are: `ini`, `params`.'
|
||||||
|
print '(a)', ''
|
||||||
|
print '(a)', '*Exit status*'
|
||||||
|
print '(a)', ' 0 if OK,'
|
||||||
|
print '(a)', ' 1 if technical problems (e.g., invalid arguments, missing input files),'
|
||||||
|
print '(a)', ' 2 if serious trouble (e.g., invalid input file).'
|
||||||
|
end subroutine
|
||||||
|
|
||||||
|
subroutine parse_cli_options(opts)
|
||||||
|
! Parses the CLI arguments and initialise the options
|
||||||
|
|
||||||
|
! subroutine arguments
|
||||||
|
type(cli_options), intent(out) :: opts
|
||||||
|
|
||||||
|
! local variables
|
||||||
|
character(len=:), allocatable :: argument
|
||||||
|
integer :: i, nargs
|
||||||
|
|
||||||
|
! Default option values
|
||||||
|
opts%format = FORMAT_INI
|
||||||
|
|
||||||
|
nargs = command_argument_count()
|
||||||
|
i = 1
|
||||||
|
|
||||||
|
do
|
||||||
|
if (i > nargs) exit
|
||||||
|
call get_next_command(i, argument)
|
||||||
|
|
||||||
|
! parse an argument (and possibly a value)
|
||||||
|
select case (argument)
|
||||||
|
! parse optional arguments
|
||||||
|
case ('-h', '--help')
|
||||||
|
call print_help()
|
||||||
|
deallocate(argument)
|
||||||
|
call exit(0)
|
||||||
|
|
||||||
|
case ('-V', '--version')
|
||||||
|
call print_version()
|
||||||
|
deallocate(argument)
|
||||||
|
call exit(0)
|
||||||
|
|
||||||
|
case ('-f', '--format')
|
||||||
|
call get_next_command(i, argument)
|
||||||
|
select case (argument)
|
||||||
|
case ('params')
|
||||||
|
opts%format = FORMAT_PARAMS
|
||||||
|
case ('ini')
|
||||||
|
opts%format = FORMAT_INI
|
||||||
|
case default
|
||||||
|
print '(a)', 'unknown format: ', argument
|
||||||
|
end select
|
||||||
|
|
||||||
|
case default
|
||||||
|
! read positional arguments
|
||||||
|
if (i - nargs == 0) then
|
||||||
|
opts%input_file = argument
|
||||||
|
cycle
|
||||||
|
else if (i - nargs == 1) then
|
||||||
|
opts%output_file = argument
|
||||||
|
cycle
|
||||||
|
end if
|
||||||
|
|
||||||
|
print '(a,a,/)', 'Unknown option: ', argument
|
||||||
|
call print_help()
|
||||||
|
deallocate(argument)
|
||||||
|
call exit(1)
|
||||||
|
|
||||||
|
end select
|
||||||
|
end do
|
||||||
|
|
||||||
|
! free temporary string
|
||||||
|
if (nargs > 0) deallocate(argument)
|
||||||
|
end subroutine
|
||||||
|
|
||||||
|
subroutine write_gray_config(filename, params)
|
||||||
|
! Generates a gray.ini configuration file
|
||||||
|
|
||||||
|
! subroutine arguments
|
||||||
|
character(len=*), intent(in) :: filename
|
||||||
|
type(gray_parameters), intent(in) :: params
|
||||||
|
|
||||||
|
! local variables
|
||||||
|
type(gray_parameters) :: defaults
|
||||||
|
character(*), parameter :: fmt = '(a, " = ", g0.6)'
|
||||||
|
integer :: err, u
|
||||||
|
|
||||||
|
open(u, file=filename, action='write', iostat=err)
|
||||||
|
if (err /= 0) then
|
||||||
|
print '("opening output file (",a,") failed!")', filename
|
||||||
|
return
|
||||||
|
end if
|
||||||
|
|
||||||
|
write(u, '(a)') "[raytracing]"
|
||||||
|
write(u, fmt) "nrayr", params%raytracing%nrayr
|
||||||
|
write(u, fmt) "nrayth", params%raytracing%nrayth
|
||||||
|
if (params%raytracing%rwmax /= defaults%raytracing%rwmax) &
|
||||||
|
write(u, fmt) "rwmax", params%raytracing%rwmax
|
||||||
|
if (params%raytracing%igrad /= defaults%raytracing%igrad) &
|
||||||
|
write(u, fmt) "igrad", params%raytracing%igrad
|
||||||
|
if (params%raytracing%ipass /= defaults%raytracing%ipass) &
|
||||||
|
write(u, fmt) "ipass", params%raytracing%ipass
|
||||||
|
if (params%raytracing%ipol .neqv. defaults%raytracing%ipol) &
|
||||||
|
write(u, fmt) "ipol", format_bool(params%raytracing%ipol)
|
||||||
|
write(u, fmt) "dst", params%raytracing%dst
|
||||||
|
if (params%raytracing%nstep /= defaults%raytracing%nstep) &
|
||||||
|
write(u, fmt) "nstep", params%raytracing%nstep
|
||||||
|
if (params%raytracing%idst /= defaults%raytracing%idst) &
|
||||||
|
write(u, fmt) "idst", format_step(params%raytracing%idst)
|
||||||
|
|
||||||
|
write(u, '(/,a)') "[ecrh_cd]"
|
||||||
|
if (params%ecrh_cd%iwarm /= defaults%ecrh_cd%iwarm) &
|
||||||
|
write(u, fmt) "iwarm", format_absorp(params%ecrh_cd%iwarm)
|
||||||
|
if (params%ecrh_cd%ieccd /= defaults%ecrh_cd%ieccd) &
|
||||||
|
write(u, fmt) "ieccd", format_current(params%ecrh_cd%ieccd)
|
||||||
|
if (params%ecrh_cd%ilarm /= defaults%ecrh_cd%ilarm) &
|
||||||
|
write(u, fmt) "ilarm", params%ecrh_cd%ilarm
|
||||||
|
if (params%ecrh_cd%imx /= defaults%ecrh_cd%imx) &
|
||||||
|
write(u, fmt) "imx", params%ecrh_cd%imx
|
||||||
|
|
||||||
|
write(u, '(/,a)') "[antenna]"
|
||||||
|
write(u, fmt) "alpha", params%antenna%alpha
|
||||||
|
write(u, fmt) "beta", params%antenna%beta
|
||||||
|
if (params%antenna%power /= defaults%antenna%power) &
|
||||||
|
write(u, fmt) "power", params%antenna%power
|
||||||
|
write(u, fmt) "iox", format_polar(params%antenna%iox)
|
||||||
|
write(u, fmt) "ibeam", format_beam(params%antenna%ibeam)
|
||||||
|
if (params%raytracing%ipol) then
|
||||||
|
write(u, fmt) "psi", params%antenna%psi
|
||||||
|
write(u, fmt) "chi", params%antenna%chi
|
||||||
|
end if
|
||||||
|
write(u, fmt) "filenm", quote(params%antenna%filenm)
|
||||||
|
|
||||||
|
write(u, '(/,a)') "[equilibrium]"
|
||||||
|
if (params%equilibrium%iequil /= defaults%equilibrium%iequil) &
|
||||||
|
write(u, fmt) "iequil", format_equil(params%equilibrium%iequil)
|
||||||
|
write(u, fmt) "filenm", quote(params%equilibrium%filenm)
|
||||||
|
if (params%equilibrium%icocos /= defaults%equilibrium%icocos) &
|
||||||
|
write(u, fmt) "icocos", params%equilibrium%icocos
|
||||||
|
if (params%equilibrium%ipsinorm .neqv. defaults%equilibrium%ipsinorm) &
|
||||||
|
write(u, fmt) "ipsinorm", format_bool(params%equilibrium%ipsinorm)
|
||||||
|
if (params%equilibrium%idesc .neqv. defaults%equilibrium%idesc) &
|
||||||
|
write(u, fmt) "idesc", format_bool(params%equilibrium%idesc)
|
||||||
|
if (params%equilibrium%ifreefmt .neqv. defaults%equilibrium%ifreefmt) &
|
||||||
|
write(u, fmt) "ifreefmt", format_bool(params%equilibrium%ifreefmt)
|
||||||
|
if (params%equilibrium%ixp /= defaults%equilibrium%ixp) &
|
||||||
|
write(u, fmt) "ixp", format_x_point(params%equilibrium%ixp)
|
||||||
|
if (params%equilibrium%ssplps /= defaults%equilibrium%ssplps) &
|
||||||
|
write(u, fmt) "ssplps", params%equilibrium%ssplps
|
||||||
|
if (params%equilibrium%ssplf /= defaults%equilibrium%ssplf) &
|
||||||
|
write(u, fmt) "ssplf", params%equilibrium%ssplf
|
||||||
|
if (params%equilibrium%sgnb /= defaults%equilibrium%sgnb) &
|
||||||
|
write(u, fmt) "sgnb", params%equilibrium%sgnb
|
||||||
|
if (params%equilibrium%sgni /= defaults%equilibrium%sgni) &
|
||||||
|
write(u, fmt) "sgni", params%equilibrium%sgni
|
||||||
|
if (params%equilibrium%factb /= defaults%equilibrium%factb) &
|
||||||
|
write(u, fmt) "factb", params%equilibrium%factb
|
||||||
|
|
||||||
|
write(u, '(/,a)') "[profiles]"
|
||||||
|
write(u, fmt) "iprof", format_profiles(params%profiles%iprof)
|
||||||
|
write(u, fmt) "irho", format_rho(params%profiles%irho)
|
||||||
|
write(u, fmt) "filenm", quote(params%profiles%filenm)
|
||||||
|
if (params%profiles%sspld /= defaults%profiles%sspld) &
|
||||||
|
write(u, fmt) "sspld", params%profiles%sspld
|
||||||
|
if (params%profiles%factne /= defaults%profiles%factne) &
|
||||||
|
write(u, fmt) "factne", params%profiles%factne
|
||||||
|
if (params%profiles%factte /= defaults%profiles%factte) &
|
||||||
|
write(u, fmt) "factte", params%profiles%factte
|
||||||
|
if (params%profiles%iscal /= defaults%profiles%iscal) &
|
||||||
|
write(u, fmt) "iscal", format_scaling(params%profiles%iscal)
|
||||||
|
if (params%profiles%psnbnd /= defaults%profiles%psnbnd) &
|
||||||
|
write(u, fmt) "psnbnd", params%profiles%psnbnd
|
||||||
|
|
||||||
|
write(u, '(/,a)') "[output]"
|
||||||
|
if (params%output%ipec /= defaults%output%ipec) &
|
||||||
|
write(u, fmt) "ipec", format_rho(params%output%ipec)
|
||||||
|
if (params%output%nrho /= defaults%output%nrho) &
|
||||||
|
write(u, fmt) "nrho", params%output%nrho
|
||||||
|
if (params%output%istpr /= defaults%output%istpr) &
|
||||||
|
write(u, fmt) "istpr", params%output%istpr
|
||||||
|
if (params%output%istpl /= defaults%output%istpl) &
|
||||||
|
write(u, fmt) "istpl", params%output%istpl
|
||||||
|
|
||||||
|
write(u, '(/,a)') "[misc]"
|
||||||
|
write(u, fmt) "rwall", params%misc%rwall
|
||||||
|
|
||||||
|
end subroutine write_gray_config
|
||||||
|
|
||||||
|
|
||||||
|
! Helper functions for formatting values
|
||||||
|
|
||||||
|
pure function format_bool(p) result(str)
|
||||||
|
logical, intent(in) :: p
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
str = trim(merge("true ", "false", p))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_step(val) result(str)
|
||||||
|
integer(kind(step_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(0:2) = &
|
||||||
|
[character(len=11) :: "ARCLEN", "TIME", "PHASE"]
|
||||||
|
str = "STEP_" // trim(choices(val))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_absorp(val) result(str)
|
||||||
|
integer(kind(absorption_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(0:3) = &
|
||||||
|
[character(len=8) :: "OFF", "WEAK", "FULL", "FULL_ALT"]
|
||||||
|
str = "ABSORP_" // trim(choices(val))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_current(val) result(str)
|
||||||
|
integer(kind(current_drive_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(0:3) = &
|
||||||
|
[character(len=10) :: "OFF", "COHEN", "NO_TRAP", "NEOCLASSIC"]
|
||||||
|
str = "CD_" // trim(choices(val))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_polar(val) result(str)
|
||||||
|
integer(kind(polar_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
str = merge("MODE_O", "MODE_X", val == 1)
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_beam(val) result(str)
|
||||||
|
integer(kind(beam_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(0:2) = ["0D", "1D", "2D"]
|
||||||
|
str = "BEAM_" // choices(val)
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_equil(val) result(str)
|
||||||
|
integer(kind(equil_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(0:3) = &
|
||||||
|
[character(len=13) :: "VACUUM", "ANALYTICAL", &
|
||||||
|
"EQDSK_FULL", "EQDSK_PARTIAL"]
|
||||||
|
str = "EQ_" // trim(choices(val))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_x_point(val) result(str)
|
||||||
|
integer(kind(x_point_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(-1:+1) = &
|
||||||
|
[character(len=10) :: "AT_BOTTOM", "IS_MISSING", "AT_TOP"]
|
||||||
|
str = "X_" // trim(choices(val))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_scaling(val) result(str)
|
||||||
|
integer(kind(scaling_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(0:2) = &
|
||||||
|
[character(len=9) :: "COLLISION", "GREENWALD", "OFF"]
|
||||||
|
str = "SCALE_" // trim(choices(val))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_rho(val) result(str)
|
||||||
|
integer(kind(rho_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
character(len=*), parameter :: choices(0:2) = ["TOR", "POL", "PSI"]
|
||||||
|
str = "RHO_" // trim(choices(val))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function format_profiles(val) result(str)
|
||||||
|
integer(kind(profiles_enum)), intent(in) :: val
|
||||||
|
character(len=:), allocatable :: str
|
||||||
|
str = "PROF_" // trim(merge("ANALYTIC", "NUMERIC ", val == 0))
|
||||||
|
end function
|
||||||
|
|
||||||
|
pure function quote(str) result(quoted)
|
||||||
|
character(*), intent(in) :: str
|
||||||
|
character(len=:), allocatable :: quoted
|
||||||
|
quoted = '"' // trim(str) // '"'
|
||||||
|
end function
|
||||||
|
|
||||||
|
end program
|
Loading…
Reference in New Issue
Block a user