LCOV - code coverage report
Current view: top level - shared/util - string.go Coverage Total Hit
Test: coverage.lcov Lines: 0.0 % 162 0
Test Date: 2026-04-14 06:42:22 Functions: - 0 0

            Line data    Source code
       1              : // Package util は、共通の軽量ユーティリティ関数群を提供します。
       2              : // string.go は文字列関連の便利関数をまとめています
       3              : package util
       4              : 
       5              : import (
       6              :         "fmt"
       7              :         "strings"
       8              :         "unicode"
       9              : )
      10              : 
      11              : // Initialisms は Go の慣習に従って大文字で保持すべき単語のリストです。
      12              : var Initialisms = map[string]bool{
      13              :         "ID":   true,
      14              :         "JSON": true,
      15              :         "XML":  true,
      16              :         "HTTP": true,
      17              :         "URL":  true,
      18              :         "IP":   true,
      19              : }
      20              : 
      21              : // ToHalfWidth は全角数字・英字・スペースを半角に変換します。
      22            0 : func ToHalfWidth(s string) string {
      23            0 :         return toHalfWidth(s)
      24            0 : }
      25              : 
      26              : // toHalfWidth は全角数字・英字・スペースを半角に変換します。
      27            0 : func toHalfWidth(s string) string {
      28            0 :         var b strings.Builder
      29            0 :         for _, r := range s {
      30            0 :                 if r == 0x3000 { // 全角スペース
      31            0 :                         _, _ = b.WriteRune(0x0020)
      32            0 :                 } else if r >= 0xFF01 && r <= 0xFF5E { // 全角記号・英数字
      33            0 :                         _, _ = b.WriteRune(r - 0xFEE0)
      34            0 :                 } else {
      35            0 :                         _, _ = b.WriteRune(r)
      36            0 :                 }
      37              :         }
      38            0 :         return b.String()
      39              : }
      40              : 
      41              : // OptPtr は空文字列なら nil、非空なら *string を返します。
      42              : // JSONやDBにNULLを入れたいときなどに便利です。
      43            0 : func OptPtr(s string) *string {
      44            0 :         if s == "" {
      45            0 :                 return nil
      46            0 :         }
      47            0 :         return &s
      48              : }
      49              : 
      50              : // SafeStr は、将来のnil対応などを見越した「安全な文字列取得」用のラッパ。
      51              : // 現状はそのまま返しますが、nilガード付きのCloneなどと統一しておくと保守性が上がります。
      52            0 : func SafeStr(s string) string {
      53            0 :         return s
      54            0 : }
      55              : 
      56              : // FallbackStr は、最初に空でない文字列を返します。
      57              : // すべて空の場合は空文字列("")を返します。
      58              : //
      59              : // 例:
      60              : //
      61              : //      name := util.FallbackStr(p.DisplayName, user.DisplayName, "unknown")
      62            0 : func FallbackStr(ss ...string) string {
      63            0 :         for _, s := range ss {
      64            0 :                 if s != "" {
      65            0 :                         return s
      66            0 :                 }
      67              :         }
      68            0 :         return ""
      69              : }
      70              : 
      71              : // ToCamelLower は ToCamel へ統合したため削除(呼び出し側は ToCamel に置換
      72              : // ToCamelLower は、与えられた文字列の先頭文字を小文字に変換して返します。
      73              : // 例: "UserName" → "userName"。
      74              : // 空文字列を渡した場合はそのまま空文字を返します。
      75              : //func ToCamelLower(s string) string {
      76              : //      if s == "" {
      77              : //              return s
      78              : //      }
      79              : //      return strings.ToLower(s[:1]) + s[1:]
      80              : //}
      81              : 
      82              : // normalizeToSnake は、入力が snake/kebab/Pascal/camel/UPPER でも
      83              : // いったん snake_case(小文字)へ正規化する内部関数。
      84            0 : func normalizeToSnake(s string) string {
      85            0 :         if s == "" {
      86            0 :                 return s
      87            0 :         }
      88              :         // kebab → snake
      89            0 :         s = strings.ReplaceAll(s, "-", "_")
      90            0 :         // Pascal/camel → snake(大文字手前に "_" を挿入)
      91            0 :         var out []rune
      92            0 :         var prev rune
      93            0 :         for i, r := range s {
      94            0 :                 if r == '_' {
      95            0 :                         out = append(out, r)
      96            0 :                         prev = r
      97            0 :                         continue
      98              :                 }
      99              :                 // 前が 英小文字/数字、今が 英大文字 → 境界
     100            0 :                 if i > 0 && unicode.IsUpper(r) && (unicode.IsLower(prev) || unicode.IsDigit(prev)) {
     101            0 :                         out = append(out, '_')
     102            0 :                 }
     103              :                 // 連続大文字 → 次が小文字なら境界(HTTPServer → http_server)
     104            0 :                 if i > 0 && unicode.IsUpper(r) && unicode.IsUpper(prev) {
     105            0 :                         // 次を先読み
     106            0 :                         if i+1 < len(s) {
     107            0 :                                 n := rune(s[i+1])
     108            0 :                                 if unicode.IsLower(n) {
     109            0 :                                         out = append(out, '_')
     110            0 :                                 }
     111              :                         }
     112              :                 }
     113            0 :                 out = append(out, unicode.ToLower(r))
     114            0 :                 prev = r
     115              :         }
     116              :         // 連続区切りの正規化("__" 等): Split 時に無視されるのでここではそのままでもOK
     117            0 :         return string(out)
     118              : }
     119              : 
     120              : // ToSnake は、入力が何であっても snake_case に変換します。
     121            0 : func ToSnake(s string) string {
     122            0 :         return normalizeToSnake(s)
     123            0 : }
     124              : 
     125              : // ToKebab は、入力が何であっても kebab-case に変換します。
     126            0 : func ToKebab(s string) string {
     127            0 :         if s == "" {
     128            0 :                 return s
     129            0 :         }
     130            0 :         return strings.ReplaceAll(normalizeToSnake(s), "_", "-")
     131              : }
     132              : 
     133              : // ToPascal は、入力が何であっても PascalCase に変換します。
     134            0 : func ToPascal(s string) string {
     135            0 :         if s == "" {
     136            0 :                 return s
     137            0 :         }
     138            0 :         parts := strings.Split(normalizeToSnake(s), "_")
     139            0 :         for i := range parts {
     140            0 :                 if parts[i] == "" {
     141            0 :                         continue
     142              :                 }
     143            0 :                 upper := strings.ToUpper(parts[i])
     144            0 :                 if Initialisms[upper] {
     145            0 :                         parts[i] = upper
     146            0 :                 } else {
     147            0 :                         parts[i] = strings.ToUpper(parts[i][:1]) + strings.ToLower(parts[i][1:])
     148            0 :                 }
     149              :         }
     150            0 :         return strings.Join(parts, "")
     151              : }
     152              : 
     153              : // ToCamel は入力文字列を lowerCamelCase に変換します。
     154              : // snake_case, kebab-case, PascalCase, UPPER_CASE いずれにも対応。
     155              : // 例:
     156              : //
     157              : //      "jp_pref"        → "jpPref"
     158              : //      "postal-code"    → "postalCode"
     159              : //      "PostalCode"     → "postalCode"
     160              : //      "JP_PREF"        → "jpPref"
     161              : //      "jpPref"         → "jpPref" (そのまま)
     162            0 : func ToCamel(s string) string {
     163            0 :         if s == "" {
     164            0 :                 return s
     165            0 :         }
     166              : 
     167              :         // 1) まず snake に正規化
     168            0 :         parts := strings.Split(normalizeToSnake(s), "_")
     169            0 : 
     170            0 :         for i := range parts {
     171            0 :                 if parts[i] == "" {
     172            0 :                         continue
     173              :                 }
     174            0 :                 if i > 0 {
     175            0 :                         upper := strings.ToUpper(parts[i])
     176            0 :                         if Initialisms[upper] {
     177            0 :                                 parts[i] = upper
     178            0 :                         } else {
     179            0 :                                 parts[i] = strings.ToUpper(parts[i][:1]) + parts[i][1:]
     180            0 :                         }
     181            0 :                 } else {
     182            0 :                         parts[i] = strings.ToLower(parts[i])
     183            0 :                 }
     184              :         }
     185            0 :         return strings.Join(parts, "")
     186              : 
     187              : }
     188              : 
     189              : // Humanize は表記ゆれ(snake/kebab/camel/Pascal/UPPER)を問わず
     190              : // 「先頭だけ大文字 + スペース区切り」の人間可読表記に変換します。
     191              : // 例:
     192              : //   - "postalCode"     → "Postal code"
     193              : //   - "address_line1"  → "Address line1"
     194              : //   - "HTTPServerID"   → "Http server id"
     195              : //   - "jp-pref"        → "Jp pref"
     196              : //   - "JP_PREF"        → "Jp pref"
     197            0 : func Humanize(s string) string {
     198            0 :         if s == "" {
     199            0 :                 return s
     200            0 :         }
     201              :         // 1) まず snake_case に正規化(小文字化もされる)
     202            0 :         s = normalizeToSnake(s)
     203            0 : 
     204            0 :         // 2) "_" → " " に置換(連続区切りは自然に連続スペースになり得る)
     205            0 :         s = strings.ReplaceAll(s, "_", " ")
     206            0 : 
     207            0 :         // 3) 余分なスペースの整理(前後・連続)
     208            0 :         s = strings.TrimSpace(s)
     209            0 :         // 連続スペースを 1 個に
     210            0 :         var b strings.Builder
     211            0 :         prevSpace := false
     212            0 :         for _, r := range s {
     213            0 :                 if r == ' ' {
     214            0 :                         if !prevSpace {
     215            0 :                                 _, _ = b.WriteRune(' ')
     216            0 :                                 prevSpace = true
     217            0 :                         }
     218            0 :                         continue
     219              :                 }
     220            0 :                 _, _ = b.WriteRune(r)
     221            0 :                 prevSpace = false
     222              :         }
     223            0 :         s = b.String()
     224            0 : 
     225            0 :         if s == "" {
     226            0 :                 return s
     227            0 :         }
     228              : 
     229              :         // 4) 先頭だけ大文字に(他はすでに小文字化済みなのでそのまま)
     230              :         //    例: "http server id" → "Http server id"
     231            0 :         runes := []rune(s)
     232            0 :         runes[0] = unicode.ToUpper(runes[0])
     233            0 :         return string(runes)
     234              : }
     235              : 
     236              : // Fmt は fmt.Sprintf の簡略ラッパです。
     237              : // フォーマット文字列をより簡潔に記述したい場合に使用します。
     238              : //
     239              : // 例:
     240              : //
     241              : //      util.Fmt("ui.profile.age_group.%d", 20) → "ui.profile.age_group.20"
     242            0 : func Fmt(format string, args ...any) string {
     243            0 :         return fmt.Sprintf(format, args...)
     244            0 : }
     245              : 
     246              : // NullableString は、空文字列の場合 nil を返し、
     247              : // 非空文字列の場合はポインタを返すユーティリティです。
     248            0 : func NullableString(s string) *string {
     249            0 :         if s == "" {
     250            0 :                 return nil
     251            0 :         }
     252            0 :         return &s
     253              : }
     254              : 
     255              : // DerefString は、nil ポインタの場合は "" を返し、
     256              : // 値があればその内容を返す逆変換です。
     257            0 : func DerefString(s *string) string {
     258            0 :         if s == nil {
     259            0 :                 return ""
     260            0 :         }
     261            0 :         return *s
     262              : }
     263              : 
     264              : // ToCanonical は、名寄せや比較のために文字列を正規化します。
     265              : // 入力が camelCase, snake_case, PascalCase, kebab-case,
     266              : // あるいは不規則な空白混じりであっても、すべて「小文字の半角スペース区切り」に統一します。
     267            0 : func ToCanonical(s string) string {
     268            0 :         if s == "" {
     269            0 :                 return s
     270            0 :         }
     271              : 
     272              :         // 0) 全角英数字・記号を半角に
     273            0 :         s = toHalfWidth(s)
     274            0 : 
     275            0 :         // 1) 既存の normalizeToSnake(s) を利用
     276            0 :         // これにより以下の変換が一度に行われます:
     277            0 :         // - "AdobePhotoshop" (camel/Pascal) -> "adobe_photoshop"
     278            0 :         // - "ADOBE_PHOTOSHOP" (UPPER)        -> "adobe_photoshop"
     279            0 :         // - "adobe-photoshop" (kebab)        -> "adobe_photoshop"
     280            0 :         // - "Adobe"                          -> "adobe"
     281            0 :         snaked := normalizeToSnake(s)
     282            0 : 
     283            0 :         // 2) アンダースコアを半角スペースに置換
     284            0 :         spaced := strings.ReplaceAll(snaked, "_", " ")
     285            0 : 
     286            0 :         // 3) strings.Fields で「連続する空白」を完全に除去して分割
     287            0 :         // 4) strings.Join で「単一の半角スペース」で結合
     288            0 :         parts := strings.Fields(spaced)
     289            0 :         return strings.Join(parts, " ")
     290              : }
        

Generated by: LCOV version 2.3.1-1