#!/usr/bin/perl # -*- coding: utf-8-unix -*- #--------------------------------- プラグマ類 --------------------------------- use strict vars; #----------------------------- パラメータの初期値 ----------------------------- our $TARGET = 'translation_list'; our %CONST = ( # 設定ファイル関係 'iniFile' => './jmlint.ini', 'iniSection' => $TARGET, # 標準のターゲット名をセクション名にする # スクリプト内で使用するURL類 '://JM' => 'https://linuxjm.osdn.jp/', '://CC4' => 'https://creativecommons.org/licenses/by/4.0/deed.ja', ); our %CONFIG = ( # データの入っているフォルダ '/orig' => 'original', '/draft' => 'draft', '/rel' => 'release', '/po4a' => 'po4a', # コマンドラインオプション '--debug' => 'mono', # ''→出力なし, 'mono'→色不使用, 他→その色で '--yes' => 0, '--prompt' => 0, 'check.fix' => 0, # check/check+ の際に自動的に修正する ); our %INI_CONTENTS = ( # ターゲットファイルに入れるべき文字列 'user.name' => '', 'user.email' => '', ); our $COMMAND = ''; our $nChanged = 0; #--------------------------------- 各種初期化 --------------------------------- open(KBD, '< /dev/tty') or open(KBD, '< /con') or open(KBD, '<&STDIN'); &parseArgv(); &parseIniFile(); #print join("\n", @{$INI_CONTENTS{'legalCondition.description[]'}}); exit 0; #******************************** @ARGVのパース ******************************* sub parseArgv() { my $s; foreach $s ( @ARGV ) { if ( $s =~ /^-+([\w]+)(=(.+))?$/ ) { &parseArgvOption( $1, $2 ); next; } if ( $s =~ /^(create|update|check)$/ ) { if ( $COMMAND eq '' ) { $COMMAND = $s; next; } } if ( $s =~ /^(check\+|and so on...)$/ ) { die "Sorry, '$s' is not implemented yet.\n"; } if ( $s eq 'fix' ) { if ( $COMMAND =~ /^check\+?$/ ) { $CONFIG{'check.fix'} = 1;} else { die &doesNotApply($s); } } die "Unknown command-line parameter: '$s'\n"; } } sub doesNotApply() { return "'$_[0]' does not apply to '$COMMAND' command.\n"; } sub parseArgvOption() { my ( $k, $v ) = @_; if ( $k eq 'version' ) { &version(); exit 0; } if ( $k eq 'help' ) { &usage(); exit 0; } if ( $k =~ /^(yes|prompt)$/ ) { $CONFIG{"--$k"} = ( $v !~ /^(0|-|off|no.+)$/ ); return; } if ( $k eq 'debug' ) { my $isDisabled = ( $v =~ /^(0|-|off|no.+)$/ ); my $isAutoColor = ( $v eq '' || $v =~ /^(1|\+|on|yes)$/ ); if ( $isDisabled ) { $v = ''; } if ( $isAutoColor ) { $v = ( defined $ENV{CLICOLOR} ? 'bright_red' # この色は飯嶋の個人的嗜好 : 'mono' ); } $CONFIG{'--debug'} = $v; return; } &warning("Unknown option: --$k='$v'. Just ignored.\n"); } #**************************** iniファイル関係の処理 *************************** # キーの値を参照するときは直接ハッシュを索かず、このメソッドを経由する。 # キー名のミスタイプでundefと判定されないようにするため。 sub getIniData() { my $key = $_[0]; if ( !&isValidIniKey($key) ) { die "getIniData('$key'): data required with undefined key.\n"; } return $INI_CONTENTS{$key}; } sub isValidIniKey() { my $names = '('. join('|', 'common\.(controlCodes|variationSelectors)', 'user\.(name|email)', 'progressStatus\.(choices|forLinkPages|correction\[\]|' . 'description\[\]|now(Untouched|Working|Released))', 'legalCondition\.(choices|description\[\])', 'sectionNames\.(validNames|knownExceptions)' ) . ')'; return $_[0] =~ /^$names$/; } sub parseIniFile() { my $fname = $CONST{iniFile}; if ( !-e $fname ) { &debugLog("'$fname' does not exist. Going anyway....\n"); return; } &debugLog("Starting to read '$fname'.\n"); my @DATA = &readWholeFile($fname); my $i; my $inOurSection = 0; for ( $i=1 ; $i<=@DATA ; $i++ ) { my $s = $DATA[$i]; my $tag = "$fname($i): "; $s =~ s/^\s+//; $s =~ s/^;*$//; next if $s eq ''; if ( $s =~ /^\[\s*(.+)\s*\]/ ) { my $section = $1; &debugLog("${tag}New Section: '$section'\n"); $inOurSection = ( $section eq $CONST{iniSection} ); next; } next unless $inOurSection; if ( $s =~ /^['"]?(.+?)['"]?\s*=\s*['"]?(.+)['"]?$/ ) { my ( $key, $value ) = ( $1, $2 ); if ( !&isValidIniKey($key) ) { &warning("${tag}Invalid key name: '$key'. " . "Skipping."); next; } &debugLog("'$key' => '$value'\n"); if ( substr($key,-2) eq '[]' ) { push @{$INI_CONTENTS{$key}}, $value; } else { $INI_CONTENTS{$key} = $value; } } } } #**************************** 一覧表ファイルの処理 **************************** #------------------- 各行のコロン区切りデータををセルに分割 ------------------- # split() では末尾の空要素は切り捨てられることに注意。 # つまり、split(':','a:b:::') は ('a','b','','','') ではなく ('a','b') を返す。 #------------------------------------------------------------------------------ sub splitTargetLine() { my @cells = split(':', $_[0], 10); # https://linuxjm.osdn.jp/guide/translation_list.html # [For Real Bodies] # 0)Status, 1)Package Name, 2)Manpage Version, 3)Original Date, # 4)Basename, 5)Section, 6)Working Date, 7)License, # 8)Translator Email, 9)Translator Name, 10)Memo # [For Links] # 0)Status, 1)Package Name, 2)Manpage Version, 3)Original Date, # 4)Basename, 5)Section, 6)Body Basename, 7)Body Section return @cells; } #******************************* バナー、ヘルプ ******************************* sub version() { print << "__END_VERSION__"; JM TranslationList Lint, version 0.0.0 Copyright (c) 2022 Linux JM Project <$CONST{'://JM'}>. Licensed under CC BY 4.0 clauses <$CONST{'://CC4'}> Originally written by IIJIMA 'Delmonta' Hiromitsu. __END_VERSION__ } # とりあえずSTDOUTで sub usage() { # &print_Disclaimer(); my $basename = $0; $basename =~ s#^.+[/\\]##; print << "__END_USAGE__"; Usage: $basename [...] create Creates a new '$TARGET' file. update Updates '$TARGET' file according to the replsitory check Detects common errors in '$TARGET'. check+ Analyses '$TARGET' in detail, and prints one by one. --yes Assume YES for all confirmations. --debug[=param] Switch on/off debug output. --prompt Prompts for an Enter key for every action. For check/check+: fix Fixes errors and creates a new '$TARGET' file. __END_USAGE__ } #***************************** 雑多なサブルーチン ***************************** #------------------------------ ファイルアクセス ------------------------------ sub readWholeFile() { my $fname = $_[0]; my $fd; if ( !open($fd, "< $fname") ) { die "Cannot open 'fname': $!\n"; } my $all = join('', <$fd>); close $fd; if ( $all eq '') { &warning("'$fname' is empty. Going anyway...\n"); return undef; } $all =~ s/\x0D\x0A?/\x0A/g; # CR, CRLF => LF my @data = split("\x0A", $all); # split() の仕様上、ファイル最後尾に限ってはいくつ空行があっても # 丸ごと切り捨てられることに注意。今回はそれで実害なしと判断。 unshift @data, undef; # 行番号を1から始めるため $data[1] = &trimBOM($data[1]); # 保険として入れておく。 &debugLog("'$fname' has $#data lines.\n"); return @data; } #------------------------------- 文字コード関係 ------------------------------- sub chompAll() { $_[0] =~ s/[\x0D\x0A]+$//; return $_[0]; } sub trimBOM() { $_[0] =~ s/^\xEF\xBB\xBF//; return $_[0]; } sub addBOM() { my $text = join('', @_); my $bom = "\xEF\xBB\xBF"; if ( $text !~ /^$bom/ ) { return $bom . $text } else { return $text; } } sub tromControlCodes() { my $text = join('', @_); my $re = &getIniData('common.controlCodes'); return $text unless defined $re; $text =~ s/$re//g; return $text; } sub tromVariationSelectors() { my $text = join('', @_); my $vs = &getIniData('common.variationSelectors'); return $text unless defined $re; $text =~ s/$re//g; return $text; } #------------------------------------ 確認 ------------------------------------ sub confirm() { return 1 if $CONFIG{'--yes'}; my $prompt = join('', @_); while ( 1 ) { print $prompt; my $key = ; if ( $key =~ /^[yY]/ ) { return 1; } elsif ( $key =~ /^[nN]/ ) { return 0; } } } sub promptIfFlagged() { &promptAlways() if $CONFIG{'--prompt'}; } sub promptAlways() { print "Hit Enter to continue..."; my $dummy = ; } #-------------------------------- デバッグ表示 -------------------------------- sub __debugLog() { ($CONFIG{'--debug'} ne '' ) && print join('', @_); } eval 'use Win32::Console::ANSI'; eval 'use Term::ANSIColor'; sub debugLog() { my $flag = $CONFIG{'--debug'}; return if $flag eq ''; if ( $flag ne 'mono' ) { eval "print color('$flag')"; } print join('', @_); if ( $flag ne 'mono' ) { eval "print color('reset')"; } &promptIfFlagged(); } #---------------------------------- 警告表示 ---------------------------------- sub __warning() { print STDERR join('', @_); return -1;} # いろいろ盛り込みたいけど、とりあえずこれで済ます sub warning() { print STDERR "\cG"; # beep print STDERR join('', @_); return -1; } #************************************* EOF ************************************