階層におけるブロック文字を実装する。
成果物
前回まで
indent_pos 'namespace MyProj' 'class MyClass' 'static void Main()' "$(echo -e "int i;\ni = 5;")"
namespace MyProj class MyClass static void Main() int i; i = 5;
ブロック文字
インデントしたらその先頭と末尾にそれぞれブロック文字を付与したい。このとき、改行の付与も別途指定できるようにしたい。
namespace MyProj { class MyClass { static void Main() { int i; i = 5; } } }
namespace MyProj { class MyClass { static void Main() { int i; i = 5; } } }
前回までは以下。
result+="$(indent_part "$code" $indent)""\n"
今回は以下のようになるはず。
result+="$(indent_part "$code" $indent)""改行するか否か(すれば要インデント)""開始ブロック文字""改行するか否か"
開始ブロックが改行するときは以下。
result+="$(indent_part "$code" $indent)""\n""$(indent_part '{' $indent)""\n"
開始ブロックが改行しないときは以下。
result+="$(indent_part "$code" $indent)"" "'{'"\n"
- ブロック文字
- 開始:
{
- 終了:
}
- 開始:
- ブロック文字の前後における改行の是非
- する(要インデント。直前と同じインデント数)
- スペース等を挿入するか否か(同じ階層でも多数の引数を改行表示させたものを見やすくするためのスペースを入れたいときがある)
- しない
- スペース等を挿入するか否か
- する(要インデント。直前と同じインデント数)
今回は簡易化のため、ブロック開始文字の直前で改行+インデントする方式のみ採用する。
終了ブロック文字は、すべての出力が終わったあと、逆順で閉じていけばいい。インデントの数はデクリメントしていく。
コード
# stdin: インデントしたいテキスト $1: インデント用文字列(任意。省略時はタブ) indent() { echo "$(cat -)" | sed "s/^/${1:-\t}/g"; } # $1..9..{10}.. 位置引数の位置がそのままインデント階層である nested() { local result=""; local indent=0; local block_start={; local block_end=}; # $1: 繰り返したいテキスト(省略時"") $2: 回数(省略時1) repeat() { local target=${1:-}; local count=${2:-2}; let count--; [ 0 -lt $count ] && printf "$target"'%*s' $count; } # ソフトインデントを返す。$1: インデント数(省略時0) $2: 1インデントあたりのスペース数(省略時4) soft_indent() { repeat ' ' $((${1:-0} * ${2:-4})); } # $1を$2だけインデントする indent_part() { local indent=${2:-0} [ 0 -eq $indent ] && echo "$1" || \ echo "$1" | indent "$(soft_indent $indent)"; } # ブロックを開始する $1: 開始文字 $2: インデント数 blocking() { local indent=${1:-0}; local block_start=${2:-{}; echo -e "\n""$(indent_part "$block_start" $indent)""\n" # echo -e " ""$block_start""\n" } # ブロックを終了する $1: 開始文字 $2: インデント数 blocked() { local indent=${1:-0}; local block_end=${2:-}}; echo -e "$(indent_part "$block_end" $indent)" } #ブロックをすべて閉じる $1: index blockeds() { local indent=${1:-0} for ((i=$indent; 0<i; i--)); do echo -e "$(blocked $((i - 1)))"; done; } local header=; local footer=; for code in "$@"; do header+="$(indent_part "$code" $indent)""$(blocking $indent)""\n" let indent++ done footer="$(blockeds $indent)" result="$(echo "$header" | sed '$d')""\n""$(echo "$footer" | sed '1d')" echo -e "$result" | head -c -1 }
実行
nested 'namespace MyProj' 'class MyClass' 'static void Main()' "$(echo -e "int i;\ni = 5;")"
namespace MyProj { class MyClass { static void Main() { int i; i = 5; } } }
改善したい点
以下の箇所。
result="$(echo "$header" | sed '$d')""\n""$(echo "$footer" | sed '1d')"
header
の行末とfooter
の行頭を削除している。
やらないと、以下のようなコードになってしまう。
namespace MyProj { class MyClass { static void Main() { int i; i = 5; { } } } }
一番深い空ブロックは不要。でもループで実装すると最後に不要なものが余分についてしまう症状。いつものアレ。C#のstring.Join("\nインデント{\n", 位置引数n)
ならうまくいったかもしれない。
この症状、bashだと毎回苦しめられている。何とかしたい。
所感
シェルスクリプトの限界か。やはり50行くらいが適量。
C#での実装を検討すべきか。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13 ※
- bash 4.4.12(1)-release ※
- SQLite 3.29.0 ※
- C# dotnet 3.0.100 ※
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux