패키지

카테고리 없음 2026. 7. 13. 13:05
{
  "name": "crypto-verification-next",
  "version": "1.0.0",
  "private": true,
  "engines": {
    "node": "16.18.0",
    "npm": ">=8"
  },
  "scripts": {
    "dev": "next dev -p 1234",
    "build": "next build",
    "start": "next start -p 1234"
  },
  "dependencies": {
    "next": "13.5.11",
    "oracledb": "^7.0.0",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "@types/node": "16.18.126",
    "@types/oracledb": "^7.0.1",
    "@types/react": "18.2.79",
    "@types/react-dom": "18.2.25",
    "typescript": "^5.7.2"
  }
}
블로그 이미지

와사비망고

,

git

카테고리 없음 2026. 7. 13. 11:31
#!/usr/bin/env node

const fs = require('fs');
const https = require('https');
const path = require('path');

const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
const inputUrl = process.argv[2];
const outputFile = process.argv[3] || 'repo-urls.txt';

if (!inputUrl) {
  console.error('Usage: node list-github-repos.js <github-url-or-org-url> [output-file]');
  console.error('Example: node list-github-repos.js https://github.com/samsung');
  console.error('Example: node list-github-repos.js https://github.samsungds.net');
  console.error('Token: set GITHUB_TOKEN or GH_TOKEN environment variable.');
  process.exit(1);
}

if (!token) {
  console.error('Missing token. Set GITHUB_TOKEN or GH_TOKEN environment variable.');
  process.exit(1);
}

function parseGitHubOwner(rawUrl) {
  const url = new URL(rawUrl);
  const owner = url.pathname.split('/').filter(Boolean)[0] || null;

  const apiBase =
    url.hostname === 'github.com'
      : `${url.protocol}//${url.hostname}/api/v3`;

  return { apiBase, hostname: url.hostname, owner };
}

function requestJson(url) {
  return new Promise((resolve, reject) => {
    const options = {
      headers: {
        Accept: 'application/vnd.github+json',
        Authorization: `token ${token}`,
        'User-Agent': 'samsung-crypt-dynamic-verify-repo-list',
        'X-GitHub-Api-Version': '2022-11-28',
      },
    };

    https
      .get(url, options, (res) => {
        let body = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
          body += chunk;
        });
        res.on('end', () => {
          let json;
          try {
            json = body ? JSON.parse(body) : null;
          } catch (error) {
            reject(new Error(`Invalid JSON from ${url}: ${error.message}`));
            return;
          }

          if (res.statusCode < 200 || res.statusCode >= 300) {
            const message = json && json.message ? json.message : body;
            reject(new Error(`GitHub API ${res.statusCode}: ${message}`));
            return;
          }

          resolve({ json, link: res.headers.link || '' });
        });
      })
      .on('error', reject);
  });
}

function nextPageUrl(linkHeader) {
  const links = linkHeader.split(',').map((part) => part.trim());
  const next = links.find((part) => part.includes('rel="next"'));
  const match = next && next.match(/<([^>]+)>/);
  return match ? match[1] : null;
}

async function requestAllPages(firstUrl) {
  const items = [];
  let url = firstUrl;

  while (url) {
    const { json, link } = await requestJson(url);
    if (!Array.isArray(json)) {
      throw new Error(`Expected array response from ${url}`);
    }
    items.push(...json);
    url = nextPageUrl(link);
  }

  return items;
}

async function listRepos(apiBase, owner) {
  if (!owner) {
    const userReposUrl =
      `${apiBase}/user/repos?visibility=all&affiliation=owner,collaborator,organization_member&per_page=100&sort=full_name`;
    return requestAllPages(userReposUrl);
  }

  const encodedOwner = encodeURIComponent(owner);
  const orgUrl = `${apiBase}/orgs/${encodedOwner}/repos?type=all&per_page=100&sort=full_name`;

  try {
    return await requestAllPages(orgUrl);
  } catch (error) {
    if (!error.message.includes('GitHub API 404')) {
      throw error;
    }

    const userUrl = `${apiBase}/users/${encodedOwner}/repos?type=all&per_page=100&sort=full_name`;
    return requestAllPages(userUrl);
  }
}

async function main() {
  const { apiBase, hostname, owner } = parseGitHubOwner(inputUrl);
  const repos = await listRepos(apiBase, owner);
  const urls = repos
    .map((repo) => repo.clone_url || repo.html_url)
    .filter(Boolean)
    .sort((a, b) => a.localeCompare(b));

  const outputPath = path.resolve(outputFile);
  fs.writeFileSync(outputPath, `${urls.join('\n')}${urls.length ? '\n' : ''}`, 'utf8');

  console.log(`GitHub host: ${hostname}`);
  console.log(`Scope: ${owner || 'all repositories visible to token'}`);
  console.log(`Repositories: ${urls.length}`);
  console.log(`Output: ${outputPath}`);
  console.log('');
  console.log(urls.join('\n'));
}

main().catch((error) => {
  console.error(error.message);
  process.exit(1);
});
블로그 이미지

와사비망고

,

CryptoInfoGen Source Copy

이 파일은 프로젝트 소스 파일을 한곳에 모아둔 복사본입니다. bin/obj 산출물과 xlsx 바이너리는 제외했습니다.

CryptoInfoGen.csproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{3FA4E432-1BE5-48DE-997E-41A1D5DF12B1}</ProjectGuid>
    <OutputType>WinExe</OutputType>
    <RootNamespace>CryptoInfoGen</RootNamespace>
    <AssemblyName>CryptoInfoGen</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.IO.Compression" />
    <Reference Include="System.IO.Compression.FileSystem" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Deployment" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Net.Http" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Form1.cs">
      <SubType>Form</SubType>
    </Compile>
    <Compile Include="Form1.Designer.cs">
      <DependentUpon>Form1.cs</DependentUpon>
    </Compile>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <EmbeddedResource Include="Properties\Resources.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
      <SubType>Designer</SubType>
    </EmbeddedResource>
    <Compile Include="Properties\Resources.Designer.cs">
      <AutoGen>True</AutoGen>
      <DependentUpon>Resources.resx</DependentUpon>
    </Compile>
    <None Include="Properties\Settings.settings">
      <Generator>SettingsSingleFileGenerator</Generator>
      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
    </None>
    <Compile Include="Properties\Settings.Designer.cs">
      <AutoGen>True</AutoGen>
      <DependentUpon>Settings.settings</DependentUpon>
      <DesignTimeSharedInput>True</DesignTimeSharedInput>
    </Compile>
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
</configuration>

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CryptoInfoGen
{
    internal static class Program
    {
        /// <summary>
        /// 해당 애플리케이션의 주 진입점입니다.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.cs

using System;
using System.Collections.Generic;
using System.Data.Odbc;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.Linq;

namespace CryptoInfoGen
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            var samplePath = Path.Combine(Application.StartupPath, "..", "..", "통합 문서.xlsx");
            samplePath = Path.GetFullPath(samplePath);
            if (File.Exists(samplePath))
            {
                txtExcelPath.Text = samplePath;
            }
        }

        private void btnBrowse_Click(object sender, EventArgs e)
        {
            if (openFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                txtExcelPath.Text = openFileDialog.FileName;
            }
        }

        private void btnGenerate_Click(object sender, EventArgs e)
        {
            try
            {
                labelStatus.Text = "생성 중...";
                txtOutput.Clear();

                var excelPath = txtExcelPath.Text.Trim();
                var connectionString = txtConnectionString.Text.Trim();

                if (string.IsNullOrWhiteSpace(excelPath) || !File.Exists(excelPath))
                {
                    MessageBox.Show(this, "엑셀 파일 경로를 확인하세요.", "입력 오류", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    labelStatus.Text = "엑셀 파일 경로가 올바르지 않습니다.";
                    return;
                }

                if (string.IsNullOrWhiteSpace(connectionString))
                {
                    MessageBox.Show(this, "ODBC 연결 문자열을 입력하세요.", "입력 오류", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    labelStatus.Text = "ODBC 연결 문자열이 비어 있습니다.";
                    return;
                }

                var tables = ExcelTableReader.Read(excelPath);
                if (tables.Count == 0)
                {
                    MessageBox.Show(this, "엑셀에서 테이블/컬럼 정보를 찾지 못했습니다.", "데이터 없음", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    labelStatus.Text = "생성할 데이터가 없습니다.";
                    return;
                }

                var primaryKeys = OraclePrimaryKeyReader.Read(connectionString, tables.Select(t => t.Name));
                txtOutput.Text = SourceCodeBuilder.Build(tables, primaryKeys);
                labelStatus.Text = string.Format("{0}개 테이블의 소스 코드를 생성했습니다.", tables.Count);
            }
            catch (Exception ex)
            {
                labelStatus.Text = "생성 실패: " + ex.Message;
                MessageBox.Show(this, ex.ToString(), "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private sealed class TableInfo
        {
            public TableInfo(string name)
            {
                Name = name;
                Columns = new List<string>();
                HandlerTypes = new List<string>();
            }

            public string Name { get; private set; }
            public List<string> Columns { get; private set; }
            public List<string> HandlerTypes { get; private set; }
        }

        private static class ExcelTableReader
        {
            private static readonly XNamespace SpreadsheetNamespace = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
            private static readonly XNamespace RelationshipsNamespace = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
            private static readonly XNamespace PackageRelationshipsNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";

            public static List<TableInfo> Read(string excelPath)
            {
                using (var archive = ZipFile.OpenRead(excelPath))
                {
                    var sharedStrings = ReadSharedStrings(archive);
                    var worksheetEntry = GetFirstWorksheetEntry(archive);
                    if (worksheetEntry == null)
                    {
                        throw new InvalidOperationException("엑셀 파일에서 첫 번째 워크시트를 찾지 못했습니다.");
                    }

                    var rows = ReadRows(worksheetEntry, sharedStrings);
                    return ConvertRows(rows);
                }
            }

            private static List<string> ReadSharedStrings(ZipArchive archive)
            {
                var entry = archive.GetEntry("xl/sharedStrings.xml");
                if (entry == null)
                {
                    return new List<string>();
                }

                var document = LoadXml(entry);
                return document.Descendants(SpreadsheetNamespace + "si")
                    .Select(si => string.Concat(si.Descendants(SpreadsheetNamespace + "t").Select(t => t.Value)))
                    .ToList();
            }

            private static ZipArchiveEntry GetFirstWorksheetEntry(ZipArchive archive)
            {
                var workbookEntry = archive.GetEntry("xl/workbook.xml");
                var relsEntry = archive.GetEntry("xl/_rels/workbook.xml.rels");
                if (workbookEntry == null || relsEntry == null)
                {
                    return archive.GetEntry("xl/worksheets/sheet1.xml");
                }

                var workbook = LoadXml(workbookEntry);
                var rels = LoadXml(relsEntry);
                var firstSheet = workbook.Descendants(SpreadsheetNamespace + "sheet").FirstOrDefault();
                if (firstSheet == null)
                {
                    return archive.GetEntry("xl/worksheets/sheet1.xml");
                }

                var relId = (string)firstSheet.Attribute(RelationshipsNamespace + "id");
                var relationship = rels.Descendants(PackageRelationshipsNamespace + "Relationship")
                    .FirstOrDefault(r => string.Equals((string)r.Attribute("Id"), relId, StringComparison.Ordinal));
                var target = relationship == null ? "worksheets/sheet1.xml" : (string)relationship.Attribute("Target");
                var entryName = "xl/" + target.Replace('\\', '/').TrimStart('/');
                return archive.GetEntry(entryName);
            }

            private static List<List<string>> ReadRows(ZipArchiveEntry worksheetEntry, List<string> sharedStrings)
            {
                var document = LoadXml(worksheetEntry);
                var result = new List<List<string>>();

                foreach (var row in document.Descendants(SpreadsheetNamespace + "row"))
                {
                    var values = new SortedDictionary<int, string>();
                    foreach (var cell in row.Elements(SpreadsheetNamespace + "c"))
                    {
                        var reference = (string)cell.Attribute("r");
                        var columnIndex = GetColumnIndex(reference);
                        values[columnIndex] = ReadCellValue(cell, sharedStrings);
                    }

                    if (values.Count == 0)
                    {
                        continue;
                    }

                    var maxIndex = values.Keys.Max();
                    var rowValues = new List<string>();
                    for (var i = 0; i <= maxIndex; i++)
                    {
                        string value;
                        rowValues.Add(values.TryGetValue(i, out value) ? value.Trim() : string.Empty);
                    }

                    result.Add(rowValues);
                }

                return result;
            }

            private static string ReadCellValue(XElement cell, List<string> sharedStrings)
            {
                var type = (string)cell.Attribute("t");
                if (string.Equals(type, "inlineStr", StringComparison.OrdinalIgnoreCase))
                {
                    return string.Concat(cell.Descendants(SpreadsheetNamespace + "t").Select(t => t.Value));
                }

                var value = (string)cell.Element(SpreadsheetNamespace + "v") ?? string.Empty;
                if (string.Equals(type, "s", StringComparison.OrdinalIgnoreCase))
                {
                    int sharedIndex;
                    if (int.TryParse(value, out sharedIndex) && sharedIndex >= 0 && sharedIndex < sharedStrings.Count)
                    {
                        return sharedStrings[sharedIndex];
                    }
                }

                return value;
            }

            private static List<TableInfo> ConvertRows(List<List<string>> rows)
            {
                var headerIndex = FindHeaderIndex(rows);
                var tableColumnIndex = headerIndex >= 0 ? FindColumnIndex(rows[headerIndex], "테이블", "table") : 0;
                var columnColumnIndex = headerIndex >= 0 ? FindColumnIndex(rows[headerIndex], "컬럼", "column", "col") : 1;
                var handlerColumnIndex = headerIndex >= 0 ? FindColumnIndex(rows[headerIndex], "핸들러", "handler", "handtype", "type") : 2;

                if (tableColumnIndex < 0)
                {
                    tableColumnIndex = 0;
                }

                if (columnColumnIndex < 0)
                {
                    columnColumnIndex = 1;
                }

                if (handlerColumnIndex < 0)
                {
                    handlerColumnIndex = 2;
                }

                var tableMap = new Dictionary<string, TableInfo>(StringComparer.OrdinalIgnoreCase);
                var tableOrder = new List<TableInfo>();

                foreach (var row in rows.Skip(headerIndex >= 0 ? headerIndex + 1 : 0))
                {
                    var tableName = GetValue(row, tableColumnIndex);
                    var columnName = GetValue(row, columnColumnIndex);
                    var handlerType = GetValue(row, handlerColumnIndex);

                    if (string.IsNullOrWhiteSpace(tableName) || string.IsNullOrWhiteSpace(columnName))
                    {
                        continue;
                    }

                    TableInfo table;
                    if (!tableMap.TryGetValue(tableName, out table))
                    {
                        table = new TableInfo(tableName);
                        tableMap.Add(tableName, table);
                        tableOrder.Add(table);
                    }

                    table.Columns.Add(columnName);
                    table.HandlerTypes.Add(handlerType);
                }

                return tableOrder;
            }

            private static int FindHeaderIndex(List<List<string>> rows)
            {
                for (var i = 0; i < rows.Count; i++)
                {
                    var joined = string.Join("|", rows[i]).ToLowerInvariant();
                    if ((joined.Contains("테이블") || joined.Contains("table")) &&
                        (joined.Contains("컬럼") || joined.Contains("column") || joined.Contains("col")))
                    {
                        return i;
                    }
                }

                return -1;
            }

            private static int FindColumnIndex(List<string> row, params string[] keys)
            {
                for (var i = 0; i < row.Count; i++)
                {
                    var value = row[i].Trim().ToLowerInvariant();
                    if (keys.Any(key => value.Contains(key.ToLowerInvariant())))
                    {
                        return i;
                    }
                }

                return -1;
            }

            private static string GetValue(List<string> row, int index)
            {
                return index >= 0 && index < row.Count ? row[index].Trim() : string.Empty;
            }

            private static int GetColumnIndex(string cellReference)
            {
                if (string.IsNullOrEmpty(cellReference))
                {
                    return 0;
                }

                var column = 0;
                foreach (var ch in cellReference)
                {
                    if (!char.IsLetter(ch))
                    {
                        break;
                    }

                    column = column * 26 + (char.ToUpperInvariant(ch) - 'A' + 1);
                }

                return Math.Max(0, column - 1);
            }

            private static XDocument LoadXml(ZipArchiveEntry entry)
            {
                using (var stream = entry.Open())
                {
                    return XDocument.Load(stream);
                }
            }
        }

        private static class OraclePrimaryKeyReader
        {
            public static Dictionary<string, List<string>> Read(string connectionString, IEnumerable<string> tableNames)
            {
                var normalizedNames = tableNames
                    .Where(name => !string.IsNullOrWhiteSpace(name))
                    .Select(name => name.Trim())
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToList();
                var result = normalizedNames.ToDictionary(name => name, name => new List<string>(), StringComparer.OrdinalIgnoreCase);

                if (normalizedNames.Count == 0)
                {
                    return result;
                }

                using (var connection = new OdbcConnection(connectionString))
                {
                    connection.Open();

                    foreach (var batch in Split(normalizedNames, 900))
                    {
                        var placeholders = string.Join(",", batch.Select(_ => "?"));
                        var sql = @"
SELECT acc.table_name, acc.column_name
  FROM all_constraints ac
       JOIN all_cons_columns acc
         ON ac.owner = acc.owner
        AND ac.constraint_name = acc.constraint_name
        AND ac.table_name = acc.table_name
 WHERE ac.constraint_type = 'P'
   AND ac.owner = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')
   AND UPPER(acc.table_name) IN (" + placeholders + @")
 ORDER BY acc.table_name, acc.position";

                        using (var command = new OdbcCommand(sql, connection))
                        {
                            foreach (var tableName in batch)
                            {
                                command.Parameters.Add("?", OdbcType.VarChar).Value = tableName.ToUpperInvariant();
                            }

                            using (var reader = command.ExecuteReader())
                            {
                                while (reader.Read())
                                {
                                    var tableName = reader.GetString(0);
                                    var columnName = reader.GetString(1);
                                    List<string> keys;
                                    if (result.TryGetValue(tableName, out keys) && !keys.Contains(columnName, StringComparer.OrdinalIgnoreCase))
                                    {
                                        keys.Add(columnName);
                                    }
                                }
                            }
                        }
                    }
                }

                return result;
            }

            private static IEnumerable<List<string>> Split(List<string> values, int size)
            {
                for (var i = 0; i < values.Count; i += size)
                {
                    yield return values.Skip(i).Take(size).ToList();
                }
            }
        }

        private static class SourceCodeBuilder
        {
            public static string Build(List<TableInfo> tables, Dictionary<string, List<string>> primaryKeys)
            {
                var builder = new StringBuilder();
                builder.AppendLine("string[] arr = { " + JoinQuoted(tables.Select(t => t.Name)) + " };");
                builder.AppendLine();
                builder.AppendLine("String[] cols = { " + JoinQuoted(tables.Select(t => string.Join(",", t.Columns))) + " };");
                builder.AppendLine();
                builder.AppendLine("String[] handtype = { " + JoinQuoted(tables.Select(t => string.Join(",", t.HandlerTypes))) + " };");
                builder.AppendLine();
                builder.AppendLine("String[] pks = { " + JoinQuoted(tables.Select(t => GetPrimaryKeyText(primaryKeys, t.Name))) + " };");
                return builder.ToString();
            }

            private static string GetPrimaryKeyText(Dictionary<string, List<string>> primaryKeys, string tableName)
            {
                List<string> keys;
                return primaryKeys.TryGetValue(tableName, out keys) && keys.Count > 0
                    ? string.Join(",", keys)
                    : "rowid";
            }

            private static string JoinQuoted(IEnumerable<string> values)
            {
                return string.Join(", ", values.Select(value => "\"" + Escape(value) + "\""));
            }

            private static string Escape(string value)
            {
                return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"");
            }
        }
    }
}

Form1.Designer.cs

namespace CryptoInfoGen
{
    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;
        private System.Windows.Forms.Label labelExcelPath;
        private System.Windows.Forms.TextBox txtExcelPath;
        private System.Windows.Forms.Button btnBrowse;
        private System.Windows.Forms.Label labelConnectionString;
        private System.Windows.Forms.TextBox txtConnectionString;
        private System.Windows.Forms.Button btnGenerate;
        private System.Windows.Forms.TextBox txtOutput;
        private System.Windows.Forms.Label labelStatus;
        private System.Windows.Forms.OpenFileDialog openFileDialog;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.labelExcelPath = new System.Windows.Forms.Label();
            this.txtExcelPath = new System.Windows.Forms.TextBox();
            this.btnBrowse = new System.Windows.Forms.Button();
            this.labelConnectionString = new System.Windows.Forms.Label();
            this.txtConnectionString = new System.Windows.Forms.TextBox();
            this.btnGenerate = new System.Windows.Forms.Button();
            this.txtOutput = new System.Windows.Forms.TextBox();
            this.labelStatus = new System.Windows.Forms.Label();
            this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
            this.SuspendLayout();
            // 
            // labelExcelPath
            // 
            this.labelExcelPath.AutoSize = true;
            this.labelExcelPath.Location = new System.Drawing.Point(16, 18);
            this.labelExcelPath.Name = "labelExcelPath";
            this.labelExcelPath.Size = new System.Drawing.Size(81, 12);
            this.labelExcelPath.TabIndex = 0;
            this.labelExcelPath.Text = "엑셀 파일 경로";
            // 
            // txtExcelPath
            // 
            this.txtExcelPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.txtExcelPath.Location = new System.Drawing.Point(16, 38);
            this.txtExcelPath.Name = "txtExcelPath";
            this.txtExcelPath.Size = new System.Drawing.Size(653, 21);
            this.txtExcelPath.TabIndex = 1;
            // 
            // btnBrowse
            // 
            this.btnBrowse.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
            this.btnBrowse.Location = new System.Drawing.Point(675, 36);
            this.btnBrowse.Name = "btnBrowse";
            this.btnBrowse.Size = new System.Drawing.Size(83, 25);
            this.btnBrowse.TabIndex = 2;
            this.btnBrowse.Text = "찾기";
            this.btnBrowse.UseVisualStyleBackColor = true;
            this.btnBrowse.Click += new System.EventHandler(this.btnBrowse_Click);
            // 
            // labelConnectionString
            // 
            this.labelConnectionString.AutoSize = true;
            this.labelConnectionString.Location = new System.Drawing.Point(16, 76);
            this.labelConnectionString.Name = "labelConnectionString";
            this.labelConnectionString.Size = new System.Drawing.Size(100, 12);
            this.labelConnectionString.TabIndex = 3;
            this.labelConnectionString.Text = "ODBC 연결 문자열";
            // 
            // txtConnectionString
            // 
            this.txtConnectionString.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.txtConnectionString.Location = new System.Drawing.Point(16, 96);
            this.txtConnectionString.Name = "txtConnectionString";
            this.txtConnectionString.Size = new System.Drawing.Size(653, 21);
            this.txtConnectionString.TabIndex = 4;
            // 
            // btnGenerate
            // 
            this.btnGenerate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
            this.btnGenerate.Location = new System.Drawing.Point(675, 94);
            this.btnGenerate.Name = "btnGenerate";
            this.btnGenerate.Size = new System.Drawing.Size(83, 25);
            this.btnGenerate.TabIndex = 5;
            this.btnGenerate.Text = "생성";
            this.btnGenerate.UseVisualStyleBackColor = true;
            this.btnGenerate.Click += new System.EventHandler(this.btnGenerate_Click);
            // 
            // txtOutput
            // 
            this.txtOutput.AcceptsReturn = true;
            this.txtOutput.AcceptsTab = true;
            this.txtOutput.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
            | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.txtOutput.Font = new System.Drawing.Font("Consolas", 10F);
            this.txtOutput.Location = new System.Drawing.Point(16, 138);
            this.txtOutput.Multiline = true;
            this.txtOutput.Name = "txtOutput";
            this.txtOutput.ScrollBars = System.Windows.Forms.ScrollBars.Both;
            this.txtOutput.Size = new System.Drawing.Size(742, 354);
            this.txtOutput.TabIndex = 6;
            this.txtOutput.WordWrap = false;
            // 
            // labelStatus
            // 
            this.labelStatus.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.labelStatus.AutoEllipsis = true;
            this.labelStatus.Location = new System.Drawing.Point(16, 503);
            this.labelStatus.Name = "labelStatus";
            this.labelStatus.Size = new System.Drawing.Size(742, 20);
            this.labelStatus.TabIndex = 7;
            this.labelStatus.Text = "엑셀 파일과 ODBC 연결 문자열을 입력한 뒤 생성하세요.";
            // 
            // openFileDialog
            // 
            this.openFileDialog.Filter = "Excel Workbook (*.xlsx)|*.xlsx|All files (*.*)|*.*";
            this.openFileDialog.Title = "엑셀 파일 선택";
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(774, 535);
            this.Controls.Add(this.labelStatus);
            this.Controls.Add(this.txtOutput);
            this.Controls.Add(this.btnGenerate);
            this.Controls.Add(this.txtConnectionString);
            this.Controls.Add(this.labelConnectionString);
            this.Controls.Add(this.btnBrowse);
            this.Controls.Add(this.txtExcelPath);
            this.Controls.Add(this.labelExcelPath);
            this.MinimumSize = new System.Drawing.Size(640, 420);
            this.Name = "Form1";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "CryptoInfoGen";
            this.ResumeLayout(false);
            this.PerformLayout();
        }
    }
}

Form1.resx

<?xml version="1.0" encoding="utf-8"?>
<root>
  <!-- 
    Microsoft ResX Schema 

    Version 2.0

    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.

    Example:

    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>

    There are any number of "resheader" rows that contain simple 
    name/value pairs.

    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.

    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:

    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.

    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
</root>

Properties\AssemblyInfo.cs

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 
// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
// 이러한 특성 값을 변경하세요.
[assembly: AssemblyTitle("CryptoInfoGen")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CryptoInfoGen")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 
// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
[assembly: ComVisible(false)]

// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
[assembly: Guid("3fa4e432-1be5-48de-997e-41a1d5df12b1")]

// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
//
//      주 버전
//      부 버전 
//      빌드 번호
//      수정 버전
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Properties\Resources.resx

<?xml version="1.0" encoding="utf-8"?>
<root>
  <!-- 
    Microsoft ResX Schema 

    Version 2.0

    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.

    Example:

    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>

    There are any number of "resheader" rows that contain simple 
    name/value pairs.

    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.

    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:

    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.

    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
</root>

Properties\Resources.Designer.cs

//------------------------------------------------------------------------------
// <auto-generated>
//     이 코드는 도구를 사용하여 생성되었습니다.
//     런타임 버전:4.0.30319.42000
//
//     파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
//     이러한 변경 내용이 손실됩니다.
// </auto-generated>
//------------------------------------------------------------------------------

namespace CryptoInfoGen.Properties
{


    /// <summary>
    ///   지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다.
    /// </summary>
    // 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder
    // 클래스에서 자동으로 생성되었습니다.
    // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여
    // ResGen을 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오.
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    internal class Resources
    {

        private static global::System.Resources.ResourceManager resourceMan;

        private static global::System.Globalization.CultureInfo resourceCulture;

        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal Resources()
        {
        }

        /// <summary>
        ///   이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Resources.ResourceManager ResourceManager
        {
            get
            {
                if ((resourceMan == null))
                {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CryptoInfoGen.Properties.Resources", typeof(Resources).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }

        /// <summary>
        ///   이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을
        ///   재정의합니다.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Globalization.CultureInfo Culture
        {
            get
            {
                return resourceCulture;
            }
            set
            {
                resourceCulture = value;
            }
        }
    }
}

Properties\Settings.settings

<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
  <Profiles>
    <Profile Name="(Default)" />
  </Profiles>
  <Settings />
</SettingsFile>

Properties\Settings.Designer.cs

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace CryptoInfoGen.Properties
{


    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
    {

        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

        public static Settings Default
        {
            get
            {
                return defaultInstance;
            }
        }
    }
}
블로그 이미지

와사비망고

,