2254 lines
64 KiB
Python
2254 lines
64 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
generate.py - acquire-core-full 레거시 C 코퍼스 결정론적 생성기
|
||
================================================================
|
||
|
||
카드 매입·정산 도메인의 TxCore(사내 TP 프레임워크) 기반 레거시 C 시스템을
|
||
아키타입별 템플릿 × 파라미터(module, program-id, difficulty tier)로 생성한다.
|
||
|
||
- 각 프로그램은 self-contained: framework/txcore(공유 라이브러리)만 의존하며
|
||
독립적으로 .o 로 컴파일된다.
|
||
- 산출: framework/, app/{online,batch,dbio,common,include,shared}, fml/, scripts/,
|
||
mk/, config/, db/, Makefile, inventory.json
|
||
|
||
사용법:
|
||
python3 generate.py [--fraction F] # F=1.0 full(~2050 files), 0.05 quick test
|
||
"""
|
||
import argparse
|
||
import json
|
||
import os
|
||
import shutil
|
||
|
||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||
|
||
# 11개 업무 모듈: code -> (한글명, 영문 transform 태그)
|
||
MODULES = [
|
||
("mg", "전문게이트웨이", "msg-gateway"),
|
||
("au", "승인한도", "authorization"),
|
||
("ac", "매입", "acquire"),
|
||
("rc", "대사", "reconciliation"),
|
||
("vl", "정합성", "validation"),
|
||
("st", "정산수수료", "settlement"),
|
||
("py", "지급", "payment"),
|
||
("lg", "원장", "ledger"),
|
||
("mm", "마스터", "master"),
|
||
("cm", "공통", "common"),
|
||
("cl", "마감", "closing"),
|
||
]
|
||
MOD_CODES = [m[0] for m in MODULES]
|
||
MOD_KNAME = {m[0]: m[1] for m in MODULES}
|
||
MOD_XFORM = {m[0]: m[2] for m in MODULES}
|
||
|
||
# 파일 타입별 목표 건수 (fraction=1.0 기준)
|
||
TARGETS = {
|
||
"online": 350,
|
||
"batch": 180,
|
||
"common": 120,
|
||
"dbio": 300, # 이 중 11개는 모듈 표준 dbio_main
|
||
"dbio_sql": 50,
|
||
"headers": 600, # core(11)+io헤더+cu헤더+standalone
|
||
"fml": 120,
|
||
"sh": 180,
|
||
"mk": 90,
|
||
"config": 60,
|
||
}
|
||
|
||
# 데모 링크 대상 (생성 산출물로 링크 검증)
|
||
ONLINE_DEMOS = ["mg", "ac", "rc", "st"]
|
||
BATCH_DEMOS = ["rc", "cl"]
|
||
|
||
|
||
def tier_for(i):
|
||
"""~55% easy, ~30% medium, ~15% hard (결정론적)."""
|
||
r = i % 20
|
||
if r < 11:
|
||
return "easy"
|
||
if r < 17:
|
||
return "medium"
|
||
return "hard"
|
||
|
||
|
||
def render(tpl, **kw):
|
||
out = tpl
|
||
for k, v in kw.items():
|
||
out = out.replace("@" + k + "@", str(v))
|
||
return out
|
||
|
||
|
||
# ======================================================================
|
||
# 공유 프레임워크 소스 (검증된 슬라이스에서 그대로 이식) - raw 문자열
|
||
# ======================================================================
|
||
TXCORE_H = r'''/*
|
||
* txcore.h - TxCore 공통 프레임워크 API
|
||
*
|
||
* TxCore 는 사내 표준 공통 프레임워크로, Tuxedo/ProFrame 계열 TP 모니터의
|
||
* ATMI(tpcall/tpreturn/tpbegin) 및 FML/UBF(Fchg/Fget) 관용구를 얇게 감싼
|
||
* 자체 구현 계층이다. 외부 TP 미들웨어 설치 없이 단독 빌드/링크된다.
|
||
*/
|
||
#ifndef TXCORE_H
|
||
#define TXCORE_H
|
||
|
||
#include <stddef.h>
|
||
#include <stdarg.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
#define TX_OK 0
|
||
#define TX_FAIL (-1)
|
||
#define TX_ENOENT (-2)
|
||
#define TX_EINVAL (-3)
|
||
#define TX_EDB (-4)
|
||
#define TX_ENOMEM (-5)
|
||
#define TX_ENOKEY (-6)
|
||
|
||
#define TX_LOG_DEBUG 0
|
||
#define TX_LOG_INFO 1
|
||
#define TX_LOG_WARN 2
|
||
#define TX_LOG_ERROR 3
|
||
|
||
#define TX_MAX_SLOTS 64
|
||
#define TX_KEY_LEN 32
|
||
#define TX_VAL_LEN 256
|
||
|
||
typedef struct {
|
||
char key[TX_KEY_LEN];
|
||
char val[TX_VAL_LEN];
|
||
int len;
|
||
int used;
|
||
} TXFIELD;
|
||
|
||
typedef struct {
|
||
int count;
|
||
TXFIELD slots[TX_MAX_SLOTS];
|
||
} TXBUF;
|
||
|
||
typedef struct {
|
||
char name[TX_KEY_LEN];
|
||
TXBUF *in;
|
||
TXBUF *out;
|
||
int rcode;
|
||
long xid;
|
||
} TXSVCINFO;
|
||
|
||
typedef void (*tx_service_fn)(TXSVCINFO *ctx);
|
||
|
||
#define TX_SERVICE(name, ctx) void name(TXSVCINFO *ctx)
|
||
|
||
TXBUF *tx_buf_alloc(void);
|
||
void tx_buf_free(TXBUF *buf);
|
||
void tx_buf_reset(TXBUF *buf);
|
||
int tx_buf_set(TXBUF *buf, const char *key, const char *val, int len);
|
||
int tx_buf_sets(TXBUF *buf, const char *key, const char *val);
|
||
int tx_buf_setlong(TXBUF *buf, const char *key, long val);
|
||
int tx_buf_get(TXBUF *buf, const char *key, char *out, int outlen);
|
||
int tx_buf_getlong(TXBUF *buf, const char *key, long *out);
|
||
|
||
int tx_register(const char *name, tx_service_fn fn);
|
||
int tx_call(const char *svc, TXBUF *in, TXBUF *out);
|
||
int tx_return(TXSVCINFO *ctx, int rc, TXBUF *out);
|
||
int tx_service_count(void);
|
||
|
||
int tx_begin(void);
|
||
int tx_commit(void);
|
||
int tx_abort(void);
|
||
long tx_current_xid(void);
|
||
|
||
void tx_log(int level, const char *fmt, ...);
|
||
void tx_set_loglevel(int level);
|
||
const char *tx_strerror(int rc);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* TXCORE_H */
|
||
'''
|
||
|
||
TXCORE_DBIO_H = r'''/*
|
||
* txcore_dbio.h - TxCore DBIO 규약 (ECPG/Pro*C 공통)
|
||
*/
|
||
#ifndef TXCORE_DBIO_H
|
||
#define TXCORE_DBIO_H
|
||
|
||
#include "txcore.h"
|
||
|
||
#define TX_SQL_OK 0
|
||
#define TX_SQL_NOTFOUND 100
|
||
|
||
#define TX_DBIO_RESULT(sc) \
|
||
((sc) == TX_SQL_OK ? TX_OK : \
|
||
(sc) == TX_SQL_NOTFOUND ? TX_ENOENT : TX_EDB)
|
||
|
||
#define TX_DBIO_LOG_ERR(tag) \
|
||
tx_log(TX_LOG_ERROR, "DBIO 오류 [%s] sqlcode=%ld msg=%.*s", \
|
||
(tag), (long)sqlca.sqlcode, \
|
||
(int)sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc)
|
||
|
||
#endif /* TXCORE_DBIO_H */
|
||
'''
|
||
|
||
TXCORE_C = r'''/*
|
||
* txcore.c - TxCore 공통 프레임워크 구현
|
||
*/
|
||
#include "txcore.h"
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <time.h>
|
||
|
||
#define TX_MAX_SERVICES 512
|
||
|
||
typedef struct {
|
||
char name[TX_KEY_LEN];
|
||
tx_service_fn fn;
|
||
} tx_entry_t;
|
||
|
||
static tx_entry_t g_registry[TX_MAX_SERVICES];
|
||
static int g_registry_count = 0;
|
||
|
||
static int g_loglevel = TX_LOG_INFO;
|
||
static long g_xid_seq = 0;
|
||
static long g_cur_xid = 0;
|
||
|
||
static const char *level_name(int level)
|
||
{
|
||
switch (level) {
|
||
case TX_LOG_DEBUG: return "DEBUG";
|
||
case TX_LOG_INFO: return "INFO ";
|
||
case TX_LOG_WARN: return "WARN ";
|
||
case TX_LOG_ERROR: return "ERROR";
|
||
default: return "?????";
|
||
}
|
||
}
|
||
|
||
void tx_set_loglevel(int level) { g_loglevel = level; }
|
||
|
||
void tx_log(int level, const char *fmt, ...)
|
||
{
|
||
va_list ap;
|
||
time_t now;
|
||
struct tm tmv;
|
||
char ts[20];
|
||
|
||
if (level < g_loglevel)
|
||
return;
|
||
|
||
now = time(NULL);
|
||
localtime_r(&now, &tmv);
|
||
strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tmv);
|
||
|
||
fprintf(stderr, "[%s] %s ", ts, level_name(level));
|
||
va_start(ap, fmt);
|
||
vfprintf(stderr, fmt, ap);
|
||
va_end(ap);
|
||
fputc('\n', stderr);
|
||
}
|
||
|
||
const char *tx_strerror(int rc)
|
||
{
|
||
switch (rc) {
|
||
case TX_OK: return "정상";
|
||
case TX_FAIL: return "일반 실패";
|
||
case TX_ENOENT: return "서비스 미등록";
|
||
case TX_EINVAL: return "파라미터 오류";
|
||
case TX_EDB: return "DB 오류";
|
||
case TX_ENOMEM: return "자원 부족";
|
||
case TX_ENOKEY: return "버퍼 키 없음";
|
||
default: return "알 수 없는 오류";
|
||
}
|
||
}
|
||
|
||
TXBUF *tx_buf_alloc(void) { return (TXBUF *)calloc(1, sizeof(TXBUF)); }
|
||
void tx_buf_free(TXBUF *buf) { if (buf) free(buf); }
|
||
void tx_buf_reset(TXBUF *buf) { if (buf) memset(buf, 0, sizeof(*buf)); }
|
||
|
||
static TXFIELD *find_slot(TXBUF *buf, const char *key)
|
||
{
|
||
int i;
|
||
for (i = 0; i < TX_MAX_SLOTS; i++) {
|
||
if (buf->slots[i].used &&
|
||
strncmp(buf->slots[i].key, key, TX_KEY_LEN) == 0)
|
||
return &buf->slots[i];
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static TXFIELD *alloc_slot(TXBUF *buf, const char *key)
|
||
{
|
||
int i;
|
||
TXFIELD *f = find_slot(buf, key);
|
||
if (f)
|
||
return f;
|
||
for (i = 0; i < TX_MAX_SLOTS; i++) {
|
||
if (!buf->slots[i].used) {
|
||
f = &buf->slots[i];
|
||
memset(f, 0, sizeof(*f));
|
||
strncpy(f->key, key, TX_KEY_LEN - 1);
|
||
f->used = 1;
|
||
buf->count++;
|
||
return f;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
int tx_buf_set(TXBUF *buf, const char *key, const char *val, int len)
|
||
{
|
||
TXFIELD *f;
|
||
if (!buf || !key || !val)
|
||
return TX_EINVAL;
|
||
if (len < 0 || len >= TX_VAL_LEN)
|
||
return TX_ENOMEM;
|
||
f = alloc_slot(buf, key);
|
||
if (!f)
|
||
return TX_ENOMEM;
|
||
memcpy(f->val, val, len);
|
||
f->val[len] = '\0';
|
||
f->len = len;
|
||
return TX_OK;
|
||
}
|
||
|
||
int tx_buf_sets(TXBUF *buf, const char *key, const char *val)
|
||
{
|
||
if (!val)
|
||
return TX_EINVAL;
|
||
return tx_buf_set(buf, key, val, (int)strlen(val));
|
||
}
|
||
|
||
int tx_buf_setlong(TXBUF *buf, const char *key, long val)
|
||
{
|
||
char tmp[32];
|
||
snprintf(tmp, sizeof(tmp), "%ld", val);
|
||
return tx_buf_set(buf, key, tmp, (int)strlen(tmp));
|
||
}
|
||
|
||
int tx_buf_get(TXBUF *buf, const char *key, char *out, int outlen)
|
||
{
|
||
TXFIELD *f;
|
||
if (!buf || !key || !out || outlen <= 0)
|
||
return TX_EINVAL;
|
||
f = find_slot(buf, key);
|
||
if (!f)
|
||
return TX_ENOKEY;
|
||
if (f->len >= outlen)
|
||
return TX_ENOMEM;
|
||
memcpy(out, f->val, f->len);
|
||
out[f->len] = '\0';
|
||
return TX_OK;
|
||
}
|
||
|
||
int tx_buf_getlong(TXBUF *buf, const char *key, long *out)
|
||
{
|
||
TXFIELD *f;
|
||
char *end;
|
||
long v;
|
||
if (!buf || !key || !out)
|
||
return TX_EINVAL;
|
||
f = find_slot(buf, key);
|
||
if (!f)
|
||
return TX_ENOKEY;
|
||
v = strtol(f->val, &end, 10);
|
||
if (end == f->val)
|
||
return TX_EINVAL;
|
||
*out = v;
|
||
return TX_OK;
|
||
}
|
||
|
||
int tx_register(const char *name, tx_service_fn fn)
|
||
{
|
||
int i;
|
||
if (!name || !fn)
|
||
return TX_EINVAL;
|
||
if (g_registry_count >= TX_MAX_SERVICES)
|
||
return TX_ENOMEM;
|
||
for (i = 0; i < g_registry_count; i++) {
|
||
if (strncmp(g_registry[i].name, name, TX_KEY_LEN) == 0) {
|
||
g_registry[i].fn = fn;
|
||
return TX_OK;
|
||
}
|
||
}
|
||
strncpy(g_registry[g_registry_count].name, name, TX_KEY_LEN - 1);
|
||
g_registry[g_registry_count].fn = fn;
|
||
g_registry_count++;
|
||
tx_log(TX_LOG_DEBUG, "서비스 등록: %s", name);
|
||
return TX_OK;
|
||
}
|
||
|
||
int tx_service_count(void) { return g_registry_count; }
|
||
|
||
static tx_service_fn lookup(const char *name)
|
||
{
|
||
int i;
|
||
for (i = 0; i < g_registry_count; i++) {
|
||
if (strncmp(g_registry[i].name, name, TX_KEY_LEN) == 0)
|
||
return g_registry[i].fn;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
int tx_call(const char *svc, TXBUF *in, TXBUF *out)
|
||
{
|
||
tx_service_fn fn;
|
||
TXSVCINFO ctx;
|
||
if (!svc || !in || !out)
|
||
return TX_EINVAL;
|
||
fn = lookup(svc);
|
||
if (!fn) {
|
||
tx_log(TX_LOG_ERROR, "tx_call: 서비스 미등록 svc=%s", svc);
|
||
return TX_ENOENT;
|
||
}
|
||
memset(&ctx, 0, sizeof(ctx));
|
||
strncpy(ctx.name, svc, TX_KEY_LEN - 1);
|
||
ctx.in = in;
|
||
ctx.out = out;
|
||
ctx.rcode = TX_OK;
|
||
ctx.xid = g_cur_xid;
|
||
fn(&ctx);
|
||
return ctx.rcode;
|
||
}
|
||
|
||
int tx_return(TXSVCINFO *ctx, int rc, TXBUF *out)
|
||
{
|
||
if (!ctx)
|
||
return TX_EINVAL;
|
||
ctx->rcode = rc;
|
||
if (out && ctx->out && out != ctx->out)
|
||
memcpy(ctx->out, out, sizeof(TXBUF));
|
||
return rc;
|
||
}
|
||
|
||
int tx_begin(void)
|
||
{
|
||
if (g_cur_xid != 0) {
|
||
tx_log(TX_LOG_WARN, "tx_begin: 이미 진행중 xid=%ld", g_cur_xid);
|
||
return TX_FAIL;
|
||
}
|
||
g_cur_xid = ++g_xid_seq;
|
||
tx_log(TX_LOG_INFO, "tx_begin xid=%ld", g_cur_xid);
|
||
return TX_OK;
|
||
}
|
||
|
||
int tx_commit(void)
|
||
{
|
||
if (g_cur_xid == 0) {
|
||
tx_log(TX_LOG_WARN, "tx_commit: 트랜잭션 없음");
|
||
return TX_FAIL;
|
||
}
|
||
tx_log(TX_LOG_INFO, "tx_commit xid=%ld", g_cur_xid);
|
||
g_cur_xid = 0;
|
||
return TX_OK;
|
||
}
|
||
|
||
int tx_abort(void)
|
||
{
|
||
if (g_cur_xid == 0) {
|
||
tx_log(TX_LOG_WARN, "tx_abort: 트랜잭션 없음");
|
||
return TX_FAIL;
|
||
}
|
||
tx_log(TX_LOG_WARN, "tx_abort xid=%ld", g_cur_xid);
|
||
g_cur_xid = 0;
|
||
return TX_OK;
|
||
}
|
||
|
||
long tx_current_xid(void) { return g_cur_xid; }
|
||
'''
|
||
|
||
MSG_LAYOUT_H = r'''/*
|
||
* msg_layout.h - 카드 매입 전문(電文) 고정길이 레이아웃 (공유)
|
||
*/
|
||
#ifndef MSG_LAYOUT_H
|
||
#define MSG_LAYOUT_H
|
||
|
||
#define MSG_TYPE_RECV "0200"
|
||
#define MSG_TYPE_RESP "0210"
|
||
#define MSG_TYPE_RECON "0500"
|
||
|
||
#define RESP_OK "0000"
|
||
#define RESP_INVALID "9001"
|
||
#define RESP_NOMERCH "9002"
|
||
#define RESP_SYSERR "9999"
|
||
|
||
typedef struct {
|
||
char msg_type[4];
|
||
char trans_code[6];
|
||
char merch_id[15];
|
||
char card_no[16];
|
||
char approval_no[9];
|
||
char amount[12];
|
||
char txn_date[8];
|
||
char txn_time[6];
|
||
char inst_month[2];
|
||
char resp_code[4];
|
||
char filler[18];
|
||
} acq_msg_t;
|
||
|
||
#define MSG_ACQ_LEN ((int)sizeof(acq_msg_t))
|
||
|
||
#define FLD_MSG_TYPE_LEN 4
|
||
#define FLD_TRANS_CODE_LEN 6
|
||
#define FLD_MERCH_ID_LEN 15
|
||
#define FLD_CARD_NO_LEN 16
|
||
#define FLD_APPROVAL_LEN 9
|
||
#define FLD_AMOUNT_LEN 12
|
||
#define FLD_TXN_DATE_LEN 8
|
||
#define FLD_TXN_TIME_LEN 6
|
||
#define FLD_INST_LEN 2
|
||
#define FLD_RESP_LEN 4
|
||
|
||
#endif /* MSG_LAYOUT_H */
|
||
'''
|
||
|
||
ACQ_UTIL_H = r'''/*
|
||
* acq_util.h - 공통 유틸리티 (일자/금액/전문) 선언 (공유)
|
||
*/
|
||
#ifndef ACQ_UTIL_H
|
||
#define ACQ_UTIL_H
|
||
|
||
#include "msg_layout.h"
|
||
|
||
int date_is_valid(const char *yyyymmdd);
|
||
int date_weekday(const char *yyyymmdd);
|
||
int date_is_weekend(const char *yyyymmdd);
|
||
int date_next_business(const char *yyyymmdd, char *out);
|
||
void date_today(char *out);
|
||
|
||
long amount_parse(const char *field, int len);
|
||
int amount_format(long amount, char *out, int len);
|
||
void amount_format_won(long amount, char *out, int outlen);
|
||
int amount_is_valid(long amount);
|
||
|
||
void msg_field_copy(char *out, const char *field, int len);
|
||
void msg_field_set(char *field, const char *src, int len);
|
||
void msg_field_set_num(char *field, const char *src, int len);
|
||
int msg_unpack(acq_msg_t *msg, const char *raw, int rawlen);
|
||
int msg_pack(const acq_msg_t *msg, char *raw, int rawlen);
|
||
int msg_validate(const acq_msg_t *msg);
|
||
|
||
#endif /* ACQ_UTIL_H */
|
||
'''
|
||
|
||
UTIL_DATE_C = r'''/*
|
||
* util_date.c - 일자/영업일 유틸리티 (plain C, 공유)
|
||
*/
|
||
#include "acq_util.h"
|
||
|
||
#include <string.h>
|
||
#include <ctype.h>
|
||
#include <stdio.h>
|
||
#include <time.h>
|
||
|
||
static int is_all_digit(const char *s, int len)
|
||
{
|
||
int i;
|
||
for (i = 0; i < len; i++)
|
||
if (!isdigit((unsigned char)s[i]))
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
static int to_int(const char *s, int len)
|
||
{
|
||
int i, v = 0;
|
||
for (i = 0; i < len; i++)
|
||
v = v * 10 + (s[i] - '0');
|
||
return v;
|
||
}
|
||
|
||
static int days_in_month(int y, int m)
|
||
{
|
||
static const int d[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||
if (m < 1 || m > 12)
|
||
return 0;
|
||
if (m == 2) {
|
||
int leap = (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
|
||
return leap ? 29 : 28;
|
||
}
|
||
return d[m - 1];
|
||
}
|
||
|
||
int date_is_valid(const char *yyyymmdd)
|
||
{
|
||
int y, m, d;
|
||
if (!yyyymmdd || strlen(yyyymmdd) < 8)
|
||
return 0;
|
||
if (!is_all_digit(yyyymmdd, 8))
|
||
return 0;
|
||
y = to_int(yyyymmdd, 4);
|
||
m = to_int(yyyymmdd + 4, 2);
|
||
d = to_int(yyyymmdd + 6, 2);
|
||
if (y < 1900 || y > 2999) return 0;
|
||
if (m < 1 || m > 12) return 0;
|
||
if (d < 1 || d > days_in_month(y, m)) return 0;
|
||
return 1;
|
||
}
|
||
|
||
int date_weekday(const char *yyyymmdd)
|
||
{
|
||
static const int t[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
|
||
int y, m, d;
|
||
if (!date_is_valid(yyyymmdd))
|
||
return -1;
|
||
y = to_int(yyyymmdd, 4);
|
||
m = to_int(yyyymmdd + 4, 2);
|
||
d = to_int(yyyymmdd + 6, 2);
|
||
if (m < 3)
|
||
y -= 1;
|
||
return (y + y / 4 - y / 100 + y / 400 + t[m - 1] + d) % 7;
|
||
}
|
||
|
||
int date_is_weekend(const char *yyyymmdd)
|
||
{
|
||
int w = date_weekday(yyyymmdd);
|
||
return (w == 0 || w == 6) ? 1 : 0;
|
||
}
|
||
|
||
static void add_one_day(const char *in, char *out)
|
||
{
|
||
int y = to_int(in, 4);
|
||
int m = to_int(in + 4, 2);
|
||
int d = to_int(in + 6, 2);
|
||
d++;
|
||
if (d > days_in_month(y, m)) {
|
||
d = 1; m++;
|
||
if (m > 12) { m = 1; y++; }
|
||
}
|
||
snprintf(out, 9, "%04d%02d%02d", y, m, d);
|
||
}
|
||
|
||
int date_next_business(const char *yyyymmdd, char *out)
|
||
{
|
||
char cur[9];
|
||
int guard = 0;
|
||
if (!date_is_valid(yyyymmdd) || !out)
|
||
return -1;
|
||
memcpy(cur, yyyymmdd, 8);
|
||
cur[8] = '\0';
|
||
do {
|
||
add_one_day(cur, cur);
|
||
if (++guard > 14)
|
||
return -1;
|
||
} while (date_is_weekend(cur));
|
||
memcpy(out, cur, 8);
|
||
out[8] = '\0';
|
||
return 0;
|
||
}
|
||
|
||
void date_today(char *out)
|
||
{
|
||
time_t now = time(NULL);
|
||
struct tm tmv;
|
||
localtime_r(&now, &tmv);
|
||
strftime(out, 9, "%Y%m%d", &tmv);
|
||
}
|
||
'''
|
||
|
||
UTIL_AMOUNT_C = r'''/*
|
||
* util_amount.c - 금액/통화 유틸리티 (plain C, 공유)
|
||
*/
|
||
#include "acq_util.h"
|
||
|
||
#include <string.h>
|
||
#include <ctype.h>
|
||
#include <stdio.h>
|
||
|
||
#define AMOUNT_MAX 100000000L
|
||
|
||
long amount_parse(const char *field, int len)
|
||
{
|
||
long v = 0;
|
||
int i;
|
||
if (!field || len <= 0)
|
||
return -1;
|
||
for (i = 0; i < len; i++) {
|
||
char c = field[i];
|
||
if (c == ' ')
|
||
continue;
|
||
if (!isdigit((unsigned char)c))
|
||
return -1;
|
||
v = v * 10 + (c - '0');
|
||
}
|
||
return v;
|
||
}
|
||
|
||
int amount_format(long amount, char *out, int len)
|
||
{
|
||
char tmp[32];
|
||
int n;
|
||
if (!out || len <= 0 || amount < 0)
|
||
return -1;
|
||
n = snprintf(tmp, sizeof(tmp), "%0*ld", len, amount);
|
||
if (n < 0 || n > len)
|
||
return -1;
|
||
memcpy(out, tmp, len);
|
||
return 0;
|
||
}
|
||
|
||
void amount_format_won(long amount, char *out, int outlen)
|
||
{
|
||
char raw[32];
|
||
int n, i, j, digits, first;
|
||
if (!out || outlen <= 0)
|
||
return;
|
||
n = snprintf(raw, sizeof(raw), "%ld", amount);
|
||
if (n < 0) { out[0] = '\0'; return; }
|
||
first = (raw[0] == '-') ? 1 : 0;
|
||
digits = n - first;
|
||
j = 0;
|
||
if (first && j < outlen - 1)
|
||
out[j++] = '-';
|
||
for (i = first; i < n && j < outlen - 1; i++) {
|
||
int pos = i - first;
|
||
if (pos > 0 && (digits - pos) % 3 == 0)
|
||
if (j < outlen - 1)
|
||
out[j++] = ',';
|
||
out[j++] = raw[i];
|
||
}
|
||
out[j] = '\0';
|
||
}
|
||
|
||
int amount_is_valid(long amount)
|
||
{
|
||
return (amount >= 1 && amount <= AMOUNT_MAX) ? 1 : 0;
|
||
}
|
||
'''
|
||
|
||
UTIL_MSG_C = r'''/*
|
||
* util_msg.c - 고정길이 전문 pack/unpack 유틸리티 (plain C, 공유)
|
||
*/
|
||
#include "acq_util.h"
|
||
|
||
#include <string.h>
|
||
#include <ctype.h>
|
||
|
||
void msg_field_copy(char *out, const char *field, int len)
|
||
{
|
||
int end;
|
||
if (!out || !field || len < 0)
|
||
return;
|
||
end = len;
|
||
while (end > 0 && (field[end - 1] == ' ' || field[end - 1] == '\0'))
|
||
end--;
|
||
memcpy(out, field, end);
|
||
out[end] = '\0';
|
||
}
|
||
|
||
void msg_field_set(char *field, const char *src, int len)
|
||
{
|
||
int slen, i;
|
||
if (!field || len < 0)
|
||
return;
|
||
slen = src ? (int)strlen(src) : 0;
|
||
if (slen > len)
|
||
slen = len;
|
||
memcpy(field, src, slen);
|
||
for (i = slen; i < len; i++)
|
||
field[i] = ' ';
|
||
}
|
||
|
||
void msg_field_set_num(char *field, const char *src, int len)
|
||
{
|
||
int slen, pad, i;
|
||
if (!field || len < 0)
|
||
return;
|
||
slen = src ? (int)strlen(src) : 0;
|
||
if (slen > len)
|
||
slen = len;
|
||
pad = len - slen;
|
||
for (i = 0; i < pad; i++)
|
||
field[i] = '0';
|
||
if (src)
|
||
memcpy(field + pad, src, slen);
|
||
}
|
||
|
||
int msg_unpack(acq_msg_t *msg, const char *raw, int rawlen)
|
||
{
|
||
if (!msg || !raw)
|
||
return -1;
|
||
if (rawlen < MSG_ACQ_LEN)
|
||
return -1;
|
||
memcpy(msg, raw, MSG_ACQ_LEN);
|
||
return 0;
|
||
}
|
||
|
||
int msg_pack(const acq_msg_t *msg, char *raw, int rawlen)
|
||
{
|
||
if (!msg || !raw)
|
||
return -1;
|
||
if (rawlen < MSG_ACQ_LEN)
|
||
return -1;
|
||
memcpy(raw, msg, MSG_ACQ_LEN);
|
||
return 0;
|
||
}
|
||
|
||
static int field_blank(const char *field, int len)
|
||
{
|
||
int i;
|
||
for (i = 0; i < len; i++)
|
||
if (field[i] != ' ' && field[i] != '\0')
|
||
return 0;
|
||
return 1;
|
||
}
|
||
|
||
int msg_validate(const acq_msg_t *msg)
|
||
{
|
||
if (!msg)
|
||
return -1;
|
||
if (field_blank(msg->msg_type, FLD_MSG_TYPE_LEN)) return -1;
|
||
if (field_blank(msg->merch_id, FLD_MERCH_ID_LEN)) return -1;
|
||
if (field_blank(msg->card_no, FLD_CARD_NO_LEN)) return -1;
|
||
if (field_blank(msg->amount, FLD_AMOUNT_LEN)) return -1;
|
||
if (field_blank(msg->txn_date, FLD_TXN_DATE_LEN)) return -1;
|
||
return 0;
|
||
}
|
||
'''
|
||
|
||
# ======================================================================
|
||
# 모듈 core 헤더 (표준 DBIO API + 레코드 구조 + 상태코드)
|
||
# ======================================================================
|
||
CORE_H_TPL = r'''/*
|
||
* @mod@_core.h - @KMOD@ 모듈 핵심 계약 (레코드/상태/표준 DBIO API)
|
||
*
|
||
* 이 모듈의 온라인/배치 프로그램은 SQL 을 직접 다루지 않고 아래 표준
|
||
* DBIO API 만 호출한다. 구현은 app/dbio/@mod@_dbio_main.pgc (ECPG).
|
||
*/
|
||
#ifndef @MOD@_CORE_H
|
||
#define @MOD@_CORE_H
|
||
|
||
#include "txcore.h"
|
||
|
||
/* @KMOD@ 처리 상태코드 */
|
||
#define @MOD@_ST_RECV "R" /* 접수/수신 */
|
||
#define @MOD@_ST_DONE "M" /* 처리완료 */
|
||
#define @MOD@_ST_FAIL "U" /* 불일치/실패 */
|
||
#define @MOD@_ST_SETTLED "S" /* 정산완료 */
|
||
|
||
/* @KMOD@ 원장 레코드 (1행) */
|
||
typedef struct {
|
||
char key[16]; /* 처리키(승인/거래 번호) PK */
|
||
char merch_id[16]; /* 가맹점번호 */
|
||
char card_no[20]; /* 카드번호(마스킹) */
|
||
long amount; /* 거래금액(원) */
|
||
char biz_date[9]; /* 영업일 YYYYMMDD */
|
||
char status[2]; /* 상태코드 */
|
||
long fee; /* 수수료(원) */
|
||
} @mod@_rec_t;
|
||
|
||
/* 표준 DBIO API (구현: @mod@_dbio_main.pgc) */
|
||
int @mod@_dbio_connect(const char *target);
|
||
int @mod@_dbio_disconnect(void);
|
||
int @mod@_rec_insert(const @mod@_rec_t *rec);
|
||
int @mod@_rec_select(const char *key, @mod@_rec_t *out);
|
||
int @mod@_rec_update_status(const char *key, const char *status);
|
||
long @mod@_rec_count(const char *biz_date, const char *status);
|
||
int @mod@_rec_sum(const char *biz_date, long *out_sum);
|
||
|
||
#endif /* @MOD@_CORE_H */
|
||
'''
|
||
|
||
DBIO_MAIN_TPL = r'''/* @tier medium @module @mod@ @transform @XFORM@-dbio */
|
||
/*
|
||
* @mod@_dbio_main.pgc - @KMOD@ 표준 DBIO (ECPG 임베디드 SQL)
|
||
*
|
||
* @mod@_core.h 의 표준 API 를 @mod@_ledger 테이블에 대해 구현한다.
|
||
* 온라인/배치 프로그램은 이 함수들만 호출한다 (self-contained).
|
||
*/
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "txcore.h"
|
||
#include "txcore_dbio.h"
|
||
#include "@mod@_core.h"
|
||
|
||
EXEC SQL INCLUDE sqlca;
|
||
|
||
int @mod@_dbio_connect(const char *target)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
const char *h_target;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
h_target = (target && target[0]) ? target : "acquire@localhost";
|
||
|
||
EXEC SQL CONNECT TO :h_target;
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@mod@ CONNECT");
|
||
return TX_EDB;
|
||
}
|
||
tx_log(TX_LOG_INFO, "[@mod@] DB 접속 완료 target=%s", h_target);
|
||
return TX_OK;
|
||
}
|
||
|
||
int @mod@_dbio_disconnect(void)
|
||
{
|
||
EXEC SQL DISCONNECT CURRENT;
|
||
return TX_OK;
|
||
}
|
||
|
||
int @mod@_rec_insert(const @mod@_rec_t *rec)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_key[16];
|
||
char h_merch_id[16];
|
||
char h_card_no[20];
|
||
long h_amount;
|
||
char h_biz_date[9];
|
||
char h_status[2];
|
||
long h_fee;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!rec)
|
||
return TX_EINVAL;
|
||
|
||
strncpy(h_key, rec->key, sizeof(h_key) - 1);
|
||
h_key[sizeof(h_key) - 1] = '\0';
|
||
strncpy(h_merch_id, rec->merch_id, sizeof(h_merch_id) - 1);
|
||
h_merch_id[sizeof(h_merch_id) - 1] = '\0';
|
||
strncpy(h_card_no, rec->card_no, sizeof(h_card_no) - 1);
|
||
h_card_no[sizeof(h_card_no) - 1] = '\0';
|
||
strncpy(h_biz_date, rec->biz_date, sizeof(h_biz_date) - 1);
|
||
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
|
||
strncpy(h_status, rec->status, sizeof(h_status) - 1);
|
||
h_status[sizeof(h_status) - 1] = '\0';
|
||
h_amount = rec->amount;
|
||
h_fee = rec->fee;
|
||
|
||
EXEC SQL INSERT INTO @mod@_ledger
|
||
(key, merch_id, card_no, amount, biz_date, status, fee)
|
||
VALUES
|
||
(:h_key, :h_merch_id, :h_card_no, :h_amount, :h_biz_date, :h_status, :h_fee);
|
||
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@mod@_rec_insert");
|
||
return TX_EDB;
|
||
}
|
||
return TX_OK;
|
||
}
|
||
|
||
int @mod@_rec_select(const char *key, @mod@_rec_t *out)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_key[16];
|
||
char h_merch_id[16];
|
||
char h_card_no[20];
|
||
long h_amount;
|
||
char h_biz_date[9];
|
||
char h_status[2];
|
||
long h_fee;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!key || !out)
|
||
return TX_EINVAL;
|
||
|
||
strncpy(h_key, key, sizeof(h_key) - 1);
|
||
h_key[sizeof(h_key) - 1] = '\0';
|
||
|
||
EXEC SQL SELECT merch_id, card_no, amount, biz_date, status, fee
|
||
INTO :h_merch_id, :h_card_no, :h_amount, :h_biz_date, :h_status, :h_fee
|
||
FROM @mod@_ledger
|
||
WHERE key = :h_key;
|
||
|
||
if (sqlca.sqlcode == TX_SQL_NOTFOUND)
|
||
return TX_ENOENT;
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@mod@_rec_select");
|
||
return TX_EDB;
|
||
}
|
||
|
||
memset(out, 0, sizeof(*out));
|
||
strncpy(out->key, h_key, sizeof(out->key) - 1);
|
||
strncpy(out->merch_id, h_merch_id, sizeof(out->merch_id) - 1);
|
||
strncpy(out->card_no, h_card_no, sizeof(out->card_no) - 1);
|
||
strncpy(out->biz_date, h_biz_date, sizeof(out->biz_date) - 1);
|
||
strncpy(out->status, h_status, sizeof(out->status) - 1);
|
||
out->amount = h_amount;
|
||
out->fee = h_fee;
|
||
return TX_OK;
|
||
}
|
||
|
||
int @mod@_rec_update_status(const char *key, const char *status)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_key[16];
|
||
char h_status[2];
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!key || !status)
|
||
return TX_EINVAL;
|
||
|
||
strncpy(h_key, key, sizeof(h_key) - 1);
|
||
h_key[sizeof(h_key) - 1] = '\0';
|
||
strncpy(h_status, status, sizeof(h_status) - 1);
|
||
h_status[sizeof(h_status) - 1] = '\0';
|
||
|
||
EXEC SQL UPDATE @mod@_ledger
|
||
SET status = :h_status, upd_ts = now()
|
||
WHERE key = :h_key;
|
||
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@mod@_rec_update_status");
|
||
return TX_EDB;
|
||
}
|
||
if (sqlca.sqlerrd[2] == 0)
|
||
return TX_ENOENT;
|
||
return TX_OK;
|
||
}
|
||
|
||
long @mod@_rec_count(const char *biz_date, const char *status)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_biz_date[9];
|
||
char h_status[2];
|
||
int h_cnt;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!biz_date || !status)
|
||
return -1;
|
||
|
||
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
|
||
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
|
||
strncpy(h_status, status, sizeof(h_status) - 1);
|
||
h_status[sizeof(h_status) - 1] = '\0';
|
||
|
||
EXEC SQL SELECT count(*)
|
||
INTO :h_cnt
|
||
FROM @mod@_ledger
|
||
WHERE biz_date = :h_biz_date
|
||
AND status = :h_status;
|
||
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@mod@_rec_count");
|
||
return -1;
|
||
}
|
||
return (long)h_cnt;
|
||
}
|
||
|
||
int @mod@_rec_sum(const char *biz_date, long *out_sum)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_biz_date[9];
|
||
long h_sum;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!biz_date || !out_sum)
|
||
return TX_EINVAL;
|
||
|
||
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
|
||
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
|
||
|
||
EXEC SQL SELECT coalesce(sum(amount), 0)
|
||
INTO :h_sum
|
||
FROM @mod@_ledger
|
||
WHERE biz_date = :h_biz_date;
|
||
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@mod@_rec_sum");
|
||
return TX_EDB;
|
||
}
|
||
*out_sum = h_sum;
|
||
return TX_OK;
|
||
}
|
||
'''
|
||
|
||
# ======================================================================
|
||
# 온라인 서비스 (.pgc) - easy / medium / hard
|
||
# ======================================================================
|
||
ONLINE_HEAD = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-online */
|
||
/*
|
||
* @ID@.pgc - @KMOD@ 온라인 서비스 (@SVC@) [tier=@TIER@]
|
||
*
|
||
* TxCore TX_SERVICE 엔트리. 요청 TXBUF 를 검증하고 @mod@ 표준 DBIO 를
|
||
* 통해 처리한 뒤 응답 TXBUF 를 구성한다. (ATMI void SVC(TPSVCINFO*) 대응)
|
||
*/
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "txcore.h"
|
||
#include "acq_util.h"
|
||
#include "@mod@_core.h"
|
||
|
||
EXEC SQL INCLUDE sqlca;
|
||
|
||
'''
|
||
|
||
ONLINE_EASY = r'''TX_SERVICE(@SVC@, ctx)
|
||
{
|
||
@mod@_rec_t rec;
|
||
char key[16];
|
||
int rc;
|
||
|
||
tx_log(TX_LOG_INFO, "[@SVC@] @KMOD@ 단건조회 서비스 진입");
|
||
|
||
if (tx_buf_get(ctx->in, "KEY", key, sizeof(key)) != TX_OK) {
|
||
tx_log(TX_LOG_ERROR, "[@SVC@] KEY 누락");
|
||
tx_return(ctx, TX_EINVAL, ctx->out);
|
||
return;
|
||
}
|
||
|
||
memset(&rec, 0, sizeof(rec));
|
||
rc = @mod@_rec_select(key, &rec);
|
||
if (rc != TX_OK) {
|
||
tx_log(TX_LOG_WARN, "[@SVC@] 조회 실패 key=%s rc=%d(%s)",
|
||
key, rc, tx_strerror(rc));
|
||
tx_return(ctx, rc, ctx->out);
|
||
return;
|
||
}
|
||
|
||
tx_buf_reset(ctx->out);
|
||
tx_buf_sets(ctx->out, "KEY", rec.key);
|
||
tx_buf_sets(ctx->out, "MERCHID", rec.merch_id);
|
||
tx_buf_setlong(ctx->out, "AMOUNT", rec.amount);
|
||
tx_buf_sets(ctx->out, "STATUS", rec.status);
|
||
tx_log(TX_LOG_INFO, "[@SVC@] 조회완료 key=%s amount=%ld", key, rec.amount);
|
||
tx_return(ctx, TX_OK, ctx->out);
|
||
}
|
||
'''
|
||
|
||
ONLINE_MEDIUM = r'''TX_SERVICE(@SVC@, ctx)
|
||
{
|
||
@mod@_rec_t rec;
|
||
char key[16], merch[16], bizdate[16];
|
||
long amount = 0, cnt;
|
||
int rc;
|
||
|
||
tx_log(TX_LOG_INFO, "[@SVC@] @KMOD@ 등록 서비스 진입");
|
||
|
||
if (tx_buf_get(ctx->in, "KEY", key, sizeof(key)) != TX_OK ||
|
||
tx_buf_get(ctx->in, "MERCHID", merch, sizeof(merch)) != TX_OK ||
|
||
tx_buf_get(ctx->in, "BIZDATE", bizdate, sizeof(bizdate)) != TX_OK) {
|
||
tx_log(TX_LOG_ERROR, "[@SVC@] 필수 필드 누락");
|
||
tx_return(ctx, TX_EINVAL, ctx->out);
|
||
return;
|
||
}
|
||
tx_buf_getlong(ctx->in, "AMOUNT", &amount);
|
||
|
||
/* 업무검증: 금액 범위 + 영업일 형식 */
|
||
if (!amount_is_valid(amount)) {
|
||
tx_log(TX_LOG_WARN, "[@SVC@] 금액 범위 오류 amount=%ld", amount);
|
||
tx_return(ctx, TX_EINVAL, ctx->out);
|
||
return;
|
||
}
|
||
if (!date_is_valid(bizdate)) {
|
||
tx_log(TX_LOG_WARN, "[@SVC@] 영업일 오류 date=%s", bizdate);
|
||
tx_return(ctx, TX_EINVAL, ctx->out);
|
||
return;
|
||
}
|
||
|
||
memset(&rec, 0, sizeof(rec));
|
||
strncpy(rec.key, key, sizeof(rec.key) - 1);
|
||
strncpy(rec.merch_id, merch, sizeof(rec.merch_id) - 1);
|
||
strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date) - 1);
|
||
strncpy(rec.status, @MOD@_ST_RECV, sizeof(rec.status) - 1);
|
||
rec.amount = amount;
|
||
rec.fee = amount / 100; /* 수수료 1% 가정 */
|
||
|
||
rc = @mod@_rec_insert(&rec);
|
||
if (rc != TX_OK) {
|
||
tx_log(TX_LOG_ERROR, "[@SVC@] 등록 실패 rc=%d(%s)", rc, tx_strerror(rc));
|
||
tx_return(ctx, rc, ctx->out);
|
||
return;
|
||
}
|
||
|
||
cnt = @mod@_rec_count(bizdate, @MOD@_ST_RECV);
|
||
|
||
tx_buf_reset(ctx->out);
|
||
tx_buf_sets(ctx->out, "RESPCODE", RESP_OK);
|
||
tx_buf_sets(ctx->out, "KEY", key);
|
||
tx_buf_setlong(ctx->out, "DAYCNT", (cnt < 0) ? 0 : cnt);
|
||
tx_log(TX_LOG_INFO, "[@SVC@] 등록완료 key=%s 당일건수=%ld", key, cnt);
|
||
tx_return(ctx, TX_OK, ctx->out);
|
||
}
|
||
'''
|
||
|
||
ONLINE_HARD = r'''TX_SERVICE(@SVC@, ctx)
|
||
{
|
||
@mod@_rec_t rec;
|
||
char key[16], merch[16], bizdate[16], nextbiz[9];
|
||
long amount = 0, daysum = 0;
|
||
int rc;
|
||
TXBUF *sub_in;
|
||
TXBUF *sub_out;
|
||
|
||
tx_log(TX_LOG_INFO, "[@SVC@] @KMOD@ 트랜잭션 오케스트레이션 진입");
|
||
|
||
if (tx_buf_get(ctx->in, "KEY", key, sizeof(key)) != TX_OK ||
|
||
tx_buf_get(ctx->in, "MERCHID", merch, sizeof(merch)) != TX_OK ||
|
||
tx_buf_get(ctx->in, "BIZDATE", bizdate, sizeof(bizdate)) != TX_OK) {
|
||
tx_log(TX_LOG_ERROR, "[@SVC@] 필수 필드 누락");
|
||
tx_return(ctx, TX_EINVAL, ctx->out);
|
||
return;
|
||
}
|
||
tx_buf_getlong(ctx->in, "AMOUNT", &amount);
|
||
|
||
if (!amount_is_valid(amount) || !date_is_valid(bizdate)) {
|
||
tx_log(TX_LOG_WARN, "[@SVC@] 입력 검증 실패 amount=%ld date=%s",
|
||
amount, bizdate);
|
||
tx_return(ctx, TX_EINVAL, ctx->out);
|
||
return;
|
||
}
|
||
|
||
/* 트랜잭션 시작 → 원장 반영 → 후속 서비스 연계 → 커밋 */
|
||
if (tx_begin() != TX_OK) {
|
||
tx_return(ctx, TX_FAIL, ctx->out);
|
||
return;
|
||
}
|
||
|
||
memset(&rec, 0, sizeof(rec));
|
||
strncpy(rec.key, key, sizeof(rec.key) - 1);
|
||
strncpy(rec.merch_id, merch, sizeof(rec.merch_id) - 1);
|
||
strncpy(rec.biz_date, bizdate, sizeof(rec.biz_date) - 1);
|
||
strncpy(rec.status, @MOD@_ST_RECV, sizeof(rec.status) - 1);
|
||
rec.amount = amount;
|
||
rec.fee = amount / 100;
|
||
|
||
rc = @mod@_rec_insert(&rec);
|
||
if (rc != TX_OK) {
|
||
tx_log(TX_LOG_ERROR, "[@SVC@] 원장반영 실패 rc=%d", rc);
|
||
tx_abort();
|
||
tx_return(ctx, rc, ctx->out);
|
||
return;
|
||
}
|
||
|
||
/* 정산 영업일 = 거래일의 다음 영업일 */
|
||
if (date_next_business(bizdate, nextbiz) != 0) {
|
||
memcpy(nextbiz, bizdate, 8);
|
||
nextbiz[8] = '\0';
|
||
}
|
||
|
||
/* 후속 정산집계 서비스 연계 (레지스트리 문자열 디스패치) */
|
||
sub_in = tx_buf_alloc();
|
||
sub_out = tx_buf_alloc();
|
||
if (sub_in && sub_out) {
|
||
tx_buf_sets(sub_in, "MERCHID", merch);
|
||
tx_buf_sets(sub_in, "BIZDATE", nextbiz);
|
||
tx_buf_setlong(sub_in, "AMOUNT", amount);
|
||
rc = tx_call("@NEXTSVC@", sub_in, sub_out);
|
||
if (rc != TX_OK)
|
||
tx_log(TX_LOG_WARN, "[@SVC@] 후속연계 경고 rc=%d", rc);
|
||
}
|
||
tx_buf_free(sub_in);
|
||
tx_buf_free(sub_out);
|
||
|
||
/* 교차 검증: 당일 합계 재계산 */
|
||
if (@mod@_rec_sum(bizdate, &daysum) != TX_OK)
|
||
daysum = 0;
|
||
|
||
tx_commit();
|
||
|
||
tx_buf_reset(ctx->out);
|
||
tx_buf_sets(ctx->out, "RESPCODE", RESP_OK);
|
||
tx_buf_sets(ctx->out, "KEY", key);
|
||
tx_buf_setlong(ctx->out, "DAYSUM", daysum);
|
||
tx_log(TX_LOG_INFO, "[@SVC@] 완료 key=%s 당일합계=%ld", key, daysum);
|
||
tx_return(ctx, TX_OK, ctx->out);
|
||
}
|
||
'''
|
||
|
||
# ======================================================================
|
||
# 배치 (.pgc) - 커서 DECLARE/OPEN/FETCH 루프 + main()
|
||
# ======================================================================
|
||
BATCH_TPL = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-batch */
|
||
/*
|
||
* @ID@.pgc - @KMOD@ 배치 (커서 순회 + 상태전이) [tier=@TIER@]
|
||
*
|
||
* 지정 영업일의 접수(status='R') 건을 커서로 순회하며 업무규칙에 따라
|
||
* 완료('M')/불일치('U') 로 상태를 전이한다. 배치 main 엔트리 포함.
|
||
*
|
||
* 실행: @ID@ <YYYYMMDD> [db@host]
|
||
*/
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include "txcore.h"
|
||
#include "txcore_dbio.h"
|
||
#include "acq_util.h"
|
||
#include "@mod@_core.h"
|
||
|
||
EXEC SQL INCLUDE sqlca;
|
||
|
||
static int run_@ID@(const char *biz_date)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_biz_date[9];
|
||
char h_key[16];
|
||
char h_merch_id[16];
|
||
long h_amount;
|
||
char h_new_status[2];
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
long total = 0, done = 0, unmatch = 0, errcnt = 0;
|
||
int rc;
|
||
|
||
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
|
||
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
|
||
|
||
EXEC SQL DECLARE cur_@ID@ CURSOR FOR
|
||
SELECT key, merch_id, amount
|
||
FROM @mod@_ledger
|
||
WHERE biz_date = :h_biz_date
|
||
AND status = 'R'
|
||
ORDER BY key;
|
||
|
||
EXEC SQL OPEN cur_@ID@;
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("OPEN cur_@ID@");
|
||
return TX_EDB;
|
||
}
|
||
|
||
if (tx_begin() != TX_OK)
|
||
tx_log(TX_LOG_WARN, "[@ID@] tx_begin 경고");
|
||
|
||
for (;;) {
|
||
EXEC SQL FETCH cur_@ID@ INTO :h_key, :h_merch_id, :h_amount;
|
||
|
||
if (sqlca.sqlcode == TX_SQL_NOTFOUND)
|
||
break;
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("FETCH cur_@ID@");
|
||
errcnt++;
|
||
break;
|
||
}
|
||
|
||
total++;
|
||
|
||
/* 업무규칙: 금액 유효성으로 완료/불일치 판정 */
|
||
if (amount_is_valid(h_amount)) {
|
||
strncpy(h_new_status, @MOD@_ST_DONE, sizeof(h_new_status));
|
||
done++;
|
||
} else {
|
||
strncpy(h_new_status, @MOD@_ST_FAIL, sizeof(h_new_status));
|
||
unmatch++;
|
||
}
|
||
|
||
rc = @mod@_rec_update_status(h_key, h_new_status);
|
||
if (rc != TX_OK) {
|
||
tx_log(TX_LOG_ERROR, "[@ID@] 상태전이 실패 key=%s rc=%d", h_key, rc);
|
||
errcnt++;
|
||
}
|
||
}
|
||
|
||
EXEC SQL CLOSE cur_@ID@;
|
||
|
||
if (errcnt == 0)
|
||
tx_commit();
|
||
else
|
||
tx_abort();
|
||
|
||
tx_log(TX_LOG_INFO,
|
||
"[@ID@] 요약 date=%s 대상=%ld 완료=%ld 불일치=%ld 오류=%ld",
|
||
biz_date, total, done, unmatch, errcnt);
|
||
|
||
printf("========== @KMOD@ 배치 결과 [@ID@] ==========\n");
|
||
printf(" 영업일 : %s\n", biz_date);
|
||
printf(" 대상 건수 : %ld\n", total);
|
||
printf(" 완료(M) : %ld\n", done);
|
||
printf(" 불일치(U) : %ld\n", unmatch);
|
||
printf(" 오류 건수 : %ld\n", errcnt);
|
||
printf("=============================================\n");
|
||
|
||
return (errcnt == 0) ? TX_OK : TX_FAIL;
|
||
}
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
const char *biz_date;
|
||
const char *target;
|
||
int rc;
|
||
|
||
tx_set_loglevel(TX_LOG_INFO);
|
||
|
||
if (argc < 2) {
|
||
fprintf(stderr, "사용법: %s <YYYYMMDD> [db@host]\n", argv[0]);
|
||
return 2;
|
||
}
|
||
biz_date = argv[1];
|
||
target = (argc >= 3) ? argv[2] : NULL;
|
||
|
||
if (!date_is_valid(biz_date)) {
|
||
fprintf(stderr, "오류: 유효하지 않은 영업일 '%s'\n", biz_date);
|
||
return 2;
|
||
}
|
||
|
||
tx_log(TX_LOG_INFO, "[@ID@] 배치 시작 date=%s", biz_date);
|
||
|
||
rc = @mod@_dbio_connect(target);
|
||
if (rc != TX_OK) {
|
||
tx_log(TX_LOG_ERROR, "[@ID@] DB 접속 실패 rc=%d", rc);
|
||
return 1;
|
||
}
|
||
|
||
rc = run_@ID@(biz_date);
|
||
|
||
@mod@_dbio_disconnect();
|
||
|
||
tx_log(TX_LOG_INFO, "[@ID@] 배치 종료 rc=%d(%s)", rc, tx_strerror(rc));
|
||
return (rc == TX_OK) ? 0 : 1;
|
||
}
|
||
'''
|
||
|
||
# ======================================================================
|
||
# 추가 DBIO (.pgc) - 자체 헤더에 선언된 함수 구현 (self-contained)
|
||
# ======================================================================
|
||
DBIO_EXTRA_H = r'''/*
|
||
* @ID@.h - @KMOD@ 보조 DBIO 선언 (@ID@.pgc 구현)
|
||
*/
|
||
#ifndef @IDU@_H
|
||
#define @IDU@_H
|
||
|
||
#include "txcore.h"
|
||
|
||
/* @ID@ 전용 조회/갱신 (테이블 @ID@_t) */
|
||
int @ID@_fetch(const char *key, long *out_amount);
|
||
int @ID@_touch(const char *key, long amount);
|
||
long @ID@_total(const char *biz_date);
|
||
|
||
#endif /* @IDU@_H */
|
||
'''
|
||
|
||
DBIO_EXTRA_TPL = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-dbio */
|
||
/*
|
||
* @ID@.pgc - @KMOD@ 보조 DBIO (ECPG) [tier=@TIER@]
|
||
*/
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
#include "txcore.h"
|
||
#include "txcore_dbio.h"
|
||
#include "@ID@.h"
|
||
|
||
EXEC SQL INCLUDE sqlca;
|
||
|
||
int @ID@_fetch(const char *key, long *out_amount)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_key[16];
|
||
long h_amount;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!key || !out_amount)
|
||
return TX_EINVAL;
|
||
|
||
strncpy(h_key, key, sizeof(h_key) - 1);
|
||
h_key[sizeof(h_key) - 1] = '\0';
|
||
|
||
EXEC SQL SELECT amount
|
||
INTO :h_amount
|
||
FROM @ID@_t
|
||
WHERE key = :h_key;
|
||
|
||
if (sqlca.sqlcode == TX_SQL_NOTFOUND)
|
||
return TX_ENOENT;
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@ID@_fetch");
|
||
return TX_EDB;
|
||
}
|
||
*out_amount = h_amount;
|
||
return TX_OK;
|
||
}
|
||
|
||
int @ID@_touch(const char *key, long amount)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_key[16];
|
||
long h_amount;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!key)
|
||
return TX_EINVAL;
|
||
|
||
strncpy(h_key, key, sizeof(h_key) - 1);
|
||
h_key[sizeof(h_key) - 1] = '\0';
|
||
h_amount = amount;
|
||
|
||
EXEC SQL UPDATE @ID@_t
|
||
SET amount = :h_amount, upd_ts = now()
|
||
WHERE key = :h_key;
|
||
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@ID@_touch");
|
||
return TX_EDB;
|
||
}
|
||
if (sqlca.sqlerrd[2] == 0)
|
||
return TX_ENOENT;
|
||
return TX_OK;
|
||
}
|
||
|
||
long @ID@_total(const char *biz_date)
|
||
{
|
||
EXEC SQL BEGIN DECLARE SECTION;
|
||
char h_biz_date[9];
|
||
long h_sum;
|
||
EXEC SQL END DECLARE SECTION;
|
||
|
||
if (!biz_date)
|
||
return -1;
|
||
|
||
strncpy(h_biz_date, biz_date, sizeof(h_biz_date) - 1);
|
||
h_biz_date[sizeof(h_biz_date) - 1] = '\0';
|
||
|
||
EXEC SQL SELECT coalesce(sum(amount), 0)
|
||
INTO :h_sum
|
||
FROM @ID@_t
|
||
WHERE biz_date = :h_biz_date;
|
||
|
||
if (sqlca.sqlcode != TX_SQL_OK) {
|
||
TX_DBIO_LOG_ERR("@ID@_total");
|
||
return -1;
|
||
}
|
||
return h_sum;
|
||
}
|
||
'''
|
||
|
||
# ======================================================================
|
||
# 공통 유틸 (.c) - plain C
|
||
# ======================================================================
|
||
COMMON_H = r'''/*
|
||
* @ID@.h - @KMOD@ 공통 유틸 선언 (@ID@.c 구현)
|
||
*/
|
||
#ifndef @IDU@_H
|
||
#define @IDU@_H
|
||
|
||
long @ID@_fee(long amount, int rate_bp);
|
||
long @ID@_round_unit(long amount, long unit);
|
||
int @ID@_classify(long amount);
|
||
|
||
#endif /* @IDU@_H */
|
||
'''
|
||
|
||
COMMON_TPL = r'''/* @tier @TIER@ @module @mod@ @transform @XFORM@-util */
|
||
/*
|
||
* @ID@.c - @KMOD@ 공통 유틸리티 (plain C) [tier=@TIER@]
|
||
*/
|
||
#include "@ID@.h"
|
||
|
||
#include <string.h>
|
||
|
||
/* 수수료 계산: rate_bp 는 basis point(1bp=0.01%) */
|
||
long @ID@_fee(long amount, int rate_bp)
|
||
{
|
||
long fee;
|
||
if (amount <= 0 || rate_bp < 0)
|
||
return 0;
|
||
fee = (amount * (long)rate_bp) / 10000L;
|
||
if (fee < 0)
|
||
fee = 0;
|
||
return fee;
|
||
}
|
||
|
||
/* 절사(내림) 단위 정규화 */
|
||
long @ID@_round_unit(long amount, long unit)
|
||
{
|
||
if (unit <= 0)
|
||
return amount;
|
||
return (amount / unit) * unit;
|
||
}
|
||
|
||
/* 금액 구간 분류: 0=소액 1=일반 2=고액 */
|
||
int @ID@_classify(long amount)
|
||
{
|
||
if (amount < 10000L)
|
||
return 0;
|
||
if (amount < 1000000L)
|
||
return 1;
|
||
return 2;
|
||
}
|
||
'''
|
||
|
||
# ======================================================================
|
||
# 독립 헤더 (전문 struct / 상수 / 레코드) - 4종 로테이션
|
||
# ======================================================================
|
||
HDR_REC = r'''/*
|
||
* @ID@.h - @KMOD@ 레코드/뷰 구조 정의
|
||
*/
|
||
#ifndef @IDU@_H
|
||
#define @IDU@_H
|
||
|
||
/* @KMOD@ 처리 뷰 레코드 */
|
||
typedef struct {
|
||
char key[16];
|
||
char merch_id[16];
|
||
long amount;
|
||
long fee;
|
||
char biz_date[9];
|
||
char status[2];
|
||
char reserved[16];
|
||
} @ID@_view_t;
|
||
|
||
#define @IDU@_STATUS_OPEN "O"
|
||
#define @IDU@_STATUS_HOLD "H"
|
||
#define @IDU@_STATUS_CLOSE "C"
|
||
|
||
#endif /* @IDU@_H */
|
||
'''
|
||
|
||
HDR_MSG = r'''/*
|
||
* @ID@.h - @KMOD@ 고정길이 전문 레이아웃 (positional, 재배치 금지)
|
||
*/
|
||
#ifndef @IDU@_H
|
||
#define @IDU@_H
|
||
|
||
typedef struct {
|
||
char msg_type[4]; /* 전문구분 4 */
|
||
char merch_id[15]; /* 가맹점번호 15 */
|
||
char key[9]; /* 처리키 9 */
|
||
char amount[12]; /* 금액 12 zero-pad */
|
||
char biz_date[8]; /* 영업일 8 */
|
||
char resp_code[4]; /* 응답코드 4 */
|
||
char filler[20]; /* 예비 20 */
|
||
} @ID@_msg_t;
|
||
|
||
#define @IDU@_MSG_LEN ((int)sizeof(@ID@_msg_t))
|
||
|
||
#endif /* @IDU@_H */
|
||
'''
|
||
|
||
HDR_CONST = r'''/*
|
||
* @ID@.h - @KMOD@ 코드/상수 테이블
|
||
*/
|
||
#ifndef @IDU@_H
|
||
#define @IDU@_H
|
||
|
||
#define @IDU@_RC_OK 0
|
||
#define @IDU@_RC_RETRY 1
|
||
#define @IDU@_RC_SKIP 2
|
||
#define @IDU@_RC_ERROR 9
|
||
|
||
#define @IDU@_LIMIT_DAILY 100000000L
|
||
#define @IDU@_LIMIT_SINGLE 50000000L
|
||
#define @IDU@_FEE_RATE_BP 250 /* 2.50% */
|
||
|
||
#define @IDU@_CHAN_ONLINE "01"
|
||
#define @IDU@_CHAN_BATCH "02"
|
||
#define @IDU@_CHAN_RECON "03"
|
||
|
||
#endif /* @IDU@_H */
|
||
'''
|
||
|
||
HDR_DECL = r'''/*
|
||
* @ID@.h - @KMOD@ 내부 헬퍼 선언
|
||
*/
|
||
#ifndef @IDU@_H
|
||
#define @IDU@_H
|
||
|
||
#include "txcore.h"
|
||
|
||
int @ID@_check(const char *key, long amount);
|
||
int @ID@_apply(TXBUF *in, TXBUF *out);
|
||
long @ID@_eval(long amount, int factor);
|
||
|
||
#endif /* @IDU@_H */
|
||
'''
|
||
|
||
HDR_ROTATION = [HDR_REC, HDR_MSG, HDR_CONST, HDR_DECL]
|
||
|
||
# ======================================================================
|
||
# 데모 서버 main (온라인 데모 링크용, 생성 산출물 검증)
|
||
# ======================================================================
|
||
DEMO_MAIN_TPL = r'''/*
|
||
* demo_@mod@_main.c - @KMOD@ 온라인 데모 부트스트랩 (링크 검증)
|
||
*
|
||
* 생성된 플래그십 서비스(@SVC@)를 TxCore 레지스트리에 등록하여
|
||
* 등록/디스패치/DBIO 링크 경로가 실제로 연결됨을 증명한다.
|
||
*/
|
||
#include <stdio.h>
|
||
|
||
#include "txcore.h"
|
||
|
||
void @SVC@(TXSVCINFO *ctx);
|
||
|
||
int main(void)
|
||
{
|
||
tx_set_loglevel(TX_LOG_INFO);
|
||
tx_log(TX_LOG_INFO, "@KMOD@ 온라인 데모 기동 (TxCore)");
|
||
|
||
tx_register("@SVC@", @SVC@);
|
||
|
||
tx_log(TX_LOG_INFO, "등록 서비스 수 = %d", tx_service_count());
|
||
printf("demo_@mod@_server 준비완료: 서비스 %d개 등록\n",
|
||
tx_service_count());
|
||
return 0;
|
||
}
|
||
'''
|
||
|
||
# ======================================================================
|
||
# 비컴파일 아티팩트 템플릿
|
||
# ======================================================================
|
||
FML_TPL = '''#
|
||
# @ID@.fml - @KMOD@ FML 필드 테이블 (UBF 필드 정의)
|
||
# *base 1@BASE@0
|
||
#
|
||
# name rel-number type flags comments
|
||
@ID@_KEY 1 string - 처리키
|
||
@ID@_MERCHID 2 string - 가맹점번호
|
||
@ID@_CARDNO 3 string - 카드번호(마스킹)
|
||
@ID@_AMOUNT 4 long - 거래금액
|
||
@ID@_BIZDATE 5 string - 영업일
|
||
@ID@_STATUS 6 char - 상태코드
|
||
@ID@_FEE 7 long - 수수료
|
||
@ID@_RESPCODE 8 string - 응답코드
|
||
'''
|
||
|
||
SH_TPL = '''#!/bin/sh
|
||
# @ID@.sh - @KMOD@ 실행 러너 (@TIER@)
|
||
# 사용법: @ID@.sh <YYYYMMDD>
|
||
set -eu
|
||
|
||
BIZDATE="${1:-$(date +%Y%m%d)}"
|
||
APP_HOME="${APP_HOME:-/app/acquire-core}"
|
||
BIN="${APP_HOME}/bin"
|
||
LOG="${APP_HOME}/log/@ID@_${BIZDATE}.log"
|
||
|
||
echo "[@ID@] @KMOD@ 처리 시작 date=${BIZDATE}"
|
||
if [ -x "${BIN}/@RUNBIN@" ]; then
|
||
"${BIN}/@RUNBIN@" "${BIZDATE}" "acquire@localhost" >> "${LOG}" 2>&1
|
||
RC=$?
|
||
else
|
||
echo "[@ID@] 실행 파일 없음: ${BIN}/@RUNBIN@" >&2
|
||
RC=0
|
||
fi
|
||
echo "[@ID@] 종료 rc=${RC}"
|
||
exit ${RC}
|
||
'''
|
||
|
||
MK_TPL = '''# @ID@.mk - @KMOD@ 빌드 조각 (참고용 include 단편)
|
||
# 이 조각은 상위 Makefile 이 아카이브/배포 시 참조하는 메타데이터다.
|
||
@IDU@_MODULE := @mod@
|
||
@IDU@_TIER := @TIER@
|
||
@IDU@_SRC := app/@SUBDIR@/@REF@
|
||
@IDU@_DESC := @KMOD@ @XFORM@ 처리 단위
|
||
|
||
.PHONY: @ID@-info
|
||
@ID@-info:
|
||
@echo "@ID@ module=@mod@ tier=@TIER@ transform=@XFORM@"
|
||
'''
|
||
|
||
UBB_TPL = '''#
|
||
# @ID@.ubb - @KMOD@ TP 도메인 구성 (Tuxedo UBBCONFIG 형식, 참고)
|
||
#
|
||
*RESOURCES
|
||
IPCKEY @IPCKEY@
|
||
MASTER SITE1
|
||
MODEL SHM
|
||
LDBAL Y
|
||
|
||
*MACHINES
|
||
"acqhost" LMID=SITE1
|
||
APPDIR="/app/acquire-core"
|
||
TUXCONFIG="/app/acquire-core/tuxconfig"
|
||
|
||
*GROUPS
|
||
GRP_@MOD@ LMID=SITE1 GRPNO=@GRPNO@
|
||
|
||
*SERVERS
|
||
@MOD@_SVR SRVGRP=GRP_@MOD@ SRVID=@GRPNO@ CLOPT="-A"
|
||
|
||
*SERVICES
|
||
@MOD@_SVC LOAD=50 PRIO=50
|
||
'''
|
||
|
||
CFG_TPL = '''# @ID@.cfg - @KMOD@ 런타임 설정 (@TIER@)
|
||
[general]
|
||
module = @mod@
|
||
transform = @XFORM@
|
||
tier = @TIER@
|
||
|
||
[db]
|
||
dsn = acquire@localhost
|
||
schema = public
|
||
fetch_size = 1000
|
||
|
||
[limits]
|
||
daily_cap = 100000000
|
||
single_cap = 50000000
|
||
fee_rate_bp = 250
|
||
'''
|
||
|
||
DAT_TPL = '''# @ID@.dat - @KMOD@ 코드 매핑 데이터 (파이프 구분)
|
||
# code|label|value
|
||
01|@KMOD@ 접수|R
|
||
02|@KMOD@ 완료|M
|
||
03|@KMOD@ 불일치|U
|
||
04|@KMOD@ 정산|S
|
||
'''
|
||
|
||
SQL_TPL = '''-- @ID@.sql - @KMOD@ DBIO 스키마/뷰 정의 (@TIER@)
|
||
-- 런타임 PostgreSQL 에 적용. 컴파일에는 불필요.
|
||
|
||
CREATE TABLE IF NOT EXISTS @mod@_ledger (
|
||
key VARCHAR(15) NOT NULL,
|
||
merch_id VARCHAR(15) NOT NULL,
|
||
card_no VARCHAR(19) NOT NULL,
|
||
amount BIGINT NOT NULL DEFAULT 0,
|
||
biz_date CHAR(8) NOT NULL,
|
||
status CHAR(1) NOT NULL DEFAULT 'R',
|
||
fee BIGINT NOT NULL DEFAULT 0,
|
||
reg_ts TIMESTAMP NOT NULL DEFAULT now(),
|
||
upd_ts TIMESTAMP,
|
||
CONSTRAINT pk_@mod@_ledger PRIMARY KEY (key)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS ix_@ID@_date_status
|
||
ON @mod@_ledger (biz_date, status);
|
||
|
||
CREATE OR REPLACE VIEW @ID@_daily_v AS
|
||
SELECT biz_date, status, count(*) AS cnt, sum(amount) AS total
|
||
FROM @mod@_ledger
|
||
GROUP BY biz_date, status;
|
||
'''
|
||
|
||
SCHEMA_SQL = '''-- schema.sql - acquire-core-full 공통 스키마 루트
|
||
-- 모듈별 원장 테이블은 db/<mod>_*.sql 참조.
|
||
|
||
CREATE TABLE IF NOT EXISTS merchant (
|
||
merch_id VARCHAR(15) NOT NULL,
|
||
merch_name VARCHAR(60) NOT NULL,
|
||
biz_no VARCHAR(12),
|
||
status CHAR(1) NOT NULL DEFAULT 'A',
|
||
CONSTRAINT pk_merchant PRIMARY KEY (merch_id)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS approval (
|
||
appr_no VARCHAR(9) NOT NULL,
|
||
merch_id VARCHAR(15) NOT NULL,
|
||
amount BIGINT NOT NULL,
|
||
appr_date CHAR(8) NOT NULL,
|
||
status CHAR(1) NOT NULL DEFAULT 'A',
|
||
CONSTRAINT pk_approval PRIMARY KEY (appr_no)
|
||
);
|
||
'''
|
||
|
||
|
||
# ======================================================================
|
||
# 생성 로직
|
||
# ======================================================================
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--fraction", type=float, default=1.0,
|
||
help="목표 건수 대비 생성 비율 (테스트용)")
|
||
args = ap.parse_args()
|
||
frac = args.fraction
|
||
|
||
def scaled(name, floor):
|
||
return max(floor, int(round(TARGETS[name] * frac)))
|
||
|
||
n_online = scaled("online", 11)
|
||
n_batch = scaled("batch", 11)
|
||
n_common = scaled("common", 11)
|
||
n_dbio = scaled("dbio", 22) # 11 main + extras
|
||
n_dbsql = scaled("dbio_sql", 11)
|
||
n_fml = scaled("fml", 11)
|
||
n_sh = scaled("sh", 11)
|
||
n_mk = scaled("mk", 11)
|
||
n_config = scaled("config", 11)
|
||
|
||
n_mods = len(MOD_CODES)
|
||
n_dbio_extra = max(0, n_dbio - n_mods) # main 은 모듈당 1개
|
||
|
||
# 헤더: core(11) + io헤더(extra dbio 수) + cu헤더(common 수) + standalone
|
||
n_hdr_target = scaled("headers", n_mods + 4)
|
||
n_standalone = max(0, n_hdr_target - n_mods - n_dbio_extra - n_common)
|
||
|
||
# ---- 디렉토리 초기화 -------------------------------------------------
|
||
for sub in ["framework", "app", "fml", "scripts", "mk", "config", "db",
|
||
"build", "bin"]:
|
||
p = os.path.join(ROOT, sub)
|
||
if os.path.isdir(p):
|
||
shutil.rmtree(p)
|
||
for f in ["Makefile", "inventory.json"]:
|
||
p = os.path.join(ROOT, f)
|
||
if os.path.isfile(p):
|
||
os.remove(p)
|
||
|
||
def wr(relpath, content):
|
||
p = os.path.join(ROOT, relpath)
|
||
os.makedirs(os.path.dirname(p), exist_ok=True)
|
||
with open(p, "w", encoding="utf-8") as fh:
|
||
fh.write(content)
|
||
|
||
inventory = []
|
||
counts = {}
|
||
|
||
def bump(k, n=1):
|
||
counts[k] = counts.get(k, 0) + n
|
||
|
||
def loc_of(s):
|
||
return s.count("\n") + (0 if s.endswith("\n") else 1)
|
||
|
||
def sqlcount(s):
|
||
return s.count("EXEC SQL")
|
||
|
||
def add_inv(pid, module, archetype, tier, path, content):
|
||
inventory.append({
|
||
"id": pid,
|
||
"module": module,
|
||
"archetype": archetype,
|
||
"tier": tier,
|
||
"path": path,
|
||
"loc": loc_of(content),
|
||
"sqlCount": sqlcount(content),
|
||
})
|
||
|
||
# ---- 프레임워크 (공유) ----------------------------------------------
|
||
wr("framework/txcore/include/txcore.h", TXCORE_H)
|
||
wr("framework/txcore/dbio/txcore_dbio.h", TXCORE_DBIO_H)
|
||
wr("framework/txcore/src/txcore.c", TXCORE_C)
|
||
wr("app/shared/include/msg_layout.h", MSG_LAYOUT_H)
|
||
wr("app/shared/include/acq_util.h", ACQ_UTIL_H)
|
||
wr("app/shared/common/util_date.c", UTIL_DATE_C)
|
||
wr("app/shared/common/util_amount.c", UTIL_AMOUNT_C)
|
||
wr("app/shared/common/util_msg.c", UTIL_MSG_C)
|
||
bump("framework", 3)
|
||
bump("shared_hdr", 2)
|
||
bump("shared_c", 3)
|
||
|
||
# ---- 모듈 core 헤더 + dbio_main -------------------------------------
|
||
first_online = {} # mod -> flagship online id
|
||
first_batch = {} # mod -> flagship batch id
|
||
|
||
for mod, kname, xform in MODULES:
|
||
core = render(CORE_H_TPL, mod=mod, MOD=mod.upper(),
|
||
KMOD=kname, XFORM=xform)
|
||
wr("app/include/%s_core.h" % mod, core)
|
||
bump("headers")
|
||
|
||
dm = render(DBIO_MAIN_TPL, mod=mod, MOD=mod.upper(),
|
||
KMOD=kname, XFORM=xform)
|
||
pid = "%s_dbio_main" % mod
|
||
path = "app/dbio/%s.pgc" % pid
|
||
wr(path, dm)
|
||
add_inv(pid, mod, "dbio", "medium", path, dm)
|
||
bump("dbio")
|
||
|
||
# ---- 온라인 서비스 ---------------------------------------------------
|
||
seq = {m: 0 for m in MOD_CODES}
|
||
for i in range(n_online):
|
||
mod = MOD_CODES[i % n_mods]
|
||
seq[mod] += 1
|
||
pid = "%s_ol_%04d" % (mod, seq[mod])
|
||
svc = pid.upper()
|
||
tier = tier_for(i)
|
||
kname = MOD_KNAME[mod]
|
||
xform = MOD_XFORM[mod]
|
||
if mod not in first_online:
|
||
first_online[mod] = (pid, svc)
|
||
body = {"easy": ONLINE_EASY, "medium": ONLINE_MEDIUM,
|
||
"hard": ONLINE_HARD}[tier]
|
||
content = render(ONLINE_HEAD + body, mod=mod, MOD=mod.upper(),
|
||
ID=pid, SVC=svc, TIER=tier, KMOD=kname, XFORM=xform,
|
||
NEXTSVC="%s_POST" % mod.upper())
|
||
path = "app/online/%s.pgc" % pid
|
||
wr(path, content)
|
||
add_inv(pid, mod, "online", tier, path, content)
|
||
bump("online")
|
||
|
||
# ---- 배치 ------------------------------------------------------------
|
||
bseq = {m: 0 for m in MOD_CODES}
|
||
for i in range(n_batch):
|
||
mod = MOD_CODES[i % n_mods]
|
||
bseq[mod] += 1
|
||
pid = "%s_bt_%04d" % (mod, bseq[mod])
|
||
tier = tier_for(i)
|
||
kname = MOD_KNAME[mod]
|
||
xform = MOD_XFORM[mod]
|
||
if mod not in first_batch:
|
||
first_batch[mod] = pid
|
||
content = render(BATCH_TPL, mod=mod, MOD=mod.upper(), ID=pid,
|
||
TIER=tier, KMOD=kname, XFORM=xform)
|
||
path = "app/batch/%s.pgc" % pid
|
||
wr(path, content)
|
||
add_inv(pid, mod, "batch", tier, path, content)
|
||
bump("batch")
|
||
|
||
# ---- 추가 DBIO -------------------------------------------------------
|
||
ioseq = {m: 0 for m in MOD_CODES}
|
||
for i in range(n_dbio_extra):
|
||
mod = MOD_CODES[i % n_mods]
|
||
ioseq[mod] += 1
|
||
pid = "%s_io_%04d" % (mod, ioseq[mod])
|
||
tier = tier_for(i)
|
||
kname = MOD_KNAME[mod]
|
||
xform = MOD_XFORM[mod]
|
||
hdr = render(DBIO_EXTRA_H, ID=pid, IDU=pid.upper(), KMOD=kname)
|
||
wr("app/include/%s.h" % pid, hdr)
|
||
bump("headers")
|
||
content = render(DBIO_EXTRA_TPL, mod=mod, ID=pid, TIER=tier,
|
||
KMOD=kname, XFORM=xform)
|
||
path = "app/dbio/%s.pgc" % pid
|
||
wr(path, content)
|
||
add_inv(pid, mod, "dbio", tier, path, content)
|
||
bump("dbio")
|
||
|
||
# ---- 공통 유틸 -------------------------------------------------------
|
||
cseq = {m: 0 for m in MOD_CODES}
|
||
for i in range(n_common):
|
||
mod = MOD_CODES[i % n_mods]
|
||
cseq[mod] += 1
|
||
pid = "%s_cu_%04d" % (mod, cseq[mod])
|
||
tier = tier_for(i)
|
||
kname = MOD_KNAME[mod]
|
||
xform = MOD_XFORM[mod]
|
||
hdr = render(COMMON_H, ID=pid, IDU=pid.upper(), KMOD=kname)
|
||
wr("app/include/%s.h" % pid, hdr)
|
||
bump("headers")
|
||
content = render(COMMON_TPL, mod=mod, ID=pid, TIER=tier,
|
||
KMOD=kname, XFORM=xform)
|
||
path = "app/common/%s.c" % pid
|
||
wr(path, content)
|
||
add_inv(pid, mod, "common", tier, path, content)
|
||
bump("common")
|
||
|
||
# ---- 독립 헤더 -------------------------------------------------------
|
||
hseq = {m: 0 for m in MOD_CODES}
|
||
for i in range(n_standalone):
|
||
mod = MOD_CODES[i % n_mods]
|
||
hseq[mod] += 1
|
||
pid = "%s_h_%04d" % (mod, hseq[mod])
|
||
kname = MOD_KNAME[mod]
|
||
tpl = HDR_ROTATION[i % len(HDR_ROTATION)]
|
||
content = render(tpl, ID=pid, IDU=pid.upper(), KMOD=kname)
|
||
wr("app/include/%s.h" % pid, content)
|
||
bump("headers")
|
||
|
||
# ---- 데모 서버 main (온라인 데모) -----------------------------------
|
||
for mod in ONLINE_DEMOS:
|
||
if mod not in first_online:
|
||
continue
|
||
_pid, svc = first_online[mod]
|
||
content = render(DEMO_MAIN_TPL, mod=mod, SVC=svc, KMOD=MOD_KNAME[mod])
|
||
wr("app/common/demo_%s_main.c" % mod, content)
|
||
bump("demo_main")
|
||
|
||
# ---- FML -------------------------------------------------------------
|
||
for i in range(n_fml):
|
||
mod = MOD_CODES[i % n_mods]
|
||
pid = "%s_fml_%04d" % (mod, i // n_mods + 1)
|
||
content = render(FML_TPL, ID=pid.upper(), KMOD=MOD_KNAME[mod],
|
||
BASE=(i % 9) + 1)
|
||
wr("fml/%s.fml" % pid, content)
|
||
bump("fml")
|
||
|
||
# ---- 셸 러너 ---------------------------------------------------------
|
||
for i in range(n_sh):
|
||
mod = MOD_CODES[i % n_mods]
|
||
pid = "%s_run_%04d" % (mod, i // n_mods + 1)
|
||
tier = tier_for(i)
|
||
runbin = "demo_%s_batch" % mod if mod in BATCH_DEMOS else \
|
||
first_batch.get(mod, "%s_bt_0001" % mod)
|
||
content = render(SH_TPL, ID=pid, TIER=tier, KMOD=MOD_KNAME[mod],
|
||
RUNBIN=runbin)
|
||
wr("scripts/%s.sh" % pid, content)
|
||
os.chmod(os.path.join(ROOT, "scripts/%s.sh" % pid), 0o755)
|
||
bump("sh")
|
||
|
||
# ---- Makefile 조각 ---------------------------------------------------
|
||
for i in range(n_mk):
|
||
mod = MOD_CODES[i % n_mods]
|
||
pid = "%s_frag_%04d" % (mod, i // n_mods + 1)
|
||
tier = tier_for(i)
|
||
content = render(MK_TPL, ID=pid, IDU=pid.upper(), mod=mod, TIER=tier,
|
||
KMOD=MOD_KNAME[mod], XFORM=MOD_XFORM[mod],
|
||
SUBDIR="online", REF="%s_ol_0001.pgc" % mod)
|
||
wr("mk/%s.mk" % pid, content)
|
||
bump("mk")
|
||
|
||
# ---- 설정 (.ubb/.cfg/.dat) ------------------------------------------
|
||
cfg_exts = ["ubb", "cfg", "dat"]
|
||
for i in range(n_config):
|
||
mod = MOD_CODES[i % n_mods]
|
||
ext = cfg_exts[i % 3]
|
||
pid = "%s_cfg_%04d" % (mod, i // n_mods + 1)
|
||
if ext == "ubb":
|
||
content = render(UBB_TPL, ID=pid, MOD=mod.upper(),
|
||
KMOD=MOD_KNAME[mod],
|
||
IPCKEY=str(70000 + i), GRPNO=str((i % 30) + 1))
|
||
elif ext == "cfg":
|
||
content = render(CFG_TPL, ID=pid, mod=mod,
|
||
XFORM=MOD_XFORM[mod], TIER=tier_for(i))
|
||
else:
|
||
content = render(DAT_TPL, ID=pid, KMOD=MOD_KNAME[mod])
|
||
wr("config/%s.%s" % (pid, ext), content)
|
||
bump("config")
|
||
|
||
# ---- DBIO SQL 스키마 -------------------------------------------------
|
||
wr("db/schema.sql", SCHEMA_SQL)
|
||
for i in range(n_dbsql):
|
||
mod = MOD_CODES[i % n_mods]
|
||
pid = "%s_ddl_%04d" % (mod, i // n_mods + 1)
|
||
content = render(SQL_TPL, ID=pid, mod=mod, KMOD=MOD_KNAME[mod],
|
||
TIER=tier_for(i))
|
||
wr("db/%s.sql" % pid, content)
|
||
bump("dbio_sql")
|
||
|
||
# ---- Makefile --------------------------------------------------------
|
||
wr("Makefile", build_makefile(first_online, first_batch))
|
||
|
||
# ---- inventory.json --------------------------------------------------
|
||
tier_hist = {}
|
||
arche_hist = {}
|
||
for r in inventory:
|
||
tier_hist[r["tier"]] = tier_hist.get(r["tier"], 0) + 1
|
||
arche_hist[r["archetype"]] = arche_hist.get(r["archetype"], 0) + 1
|
||
|
||
inv_doc = {
|
||
"corpus": "acquire-core-full",
|
||
"domain": "card acquiring / settlement (카드 매입·정산)",
|
||
"framework": "TxCore (ECPG + 고정길이 전문 + TP 관용구)",
|
||
"generatedBy": "generate.py",
|
||
"fraction": frac,
|
||
"fileCounts": counts,
|
||
"programCount": len(inventory),
|
||
"byTier": tier_hist,
|
||
"byArchetype": arche_hist,
|
||
"modules": {m[0]: m[1] for m in MODULES},
|
||
"programs": sorted(inventory, key=lambda r: r["path"]),
|
||
}
|
||
with open(os.path.join(ROOT, "inventory.json"), "w", encoding="utf-8") as fh:
|
||
json.dump(inv_doc, fh, ensure_ascii=False, indent=2)
|
||
|
||
# ---- 요약 출력 -------------------------------------------------------
|
||
total_files = 0
|
||
for _root, _dirs, files in os.walk(ROOT):
|
||
if "/.git" in _root:
|
||
continue
|
||
for f in files:
|
||
total_files += 1
|
||
print("=== acquire-core-full 생성 완료 (fraction=%.3f) ===" % frac)
|
||
for k in sorted(counts):
|
||
print(" %-12s %5d" % (k, counts[k]))
|
||
print(" programs(inv) %5d" % len(inventory))
|
||
print(" tier ", tier_hist)
|
||
print(" 총 파일수 ", total_files)
|
||
|
||
|
||
def build_makefile(first_online, first_batch):
|
||
lines = []
|
||
A = lines.append
|
||
A("# ==========================================================================")
|
||
A("# acquire-core-full / TxCore Makefile (생성기 산출물)")
|
||
A("#")
|
||
A("# .pgc --ecpg--> .c --gcc--> build/*.o --ar--> libtxcore.a --ld--> bin/*")
|
||
A("#")
|
||
A("# ~1000개 .c/.pgc 를 build/*.o 로 컴파일하는 것이 빌드 검증의 핵심.")
|
||
A("# 와일드카드로 전체 소스를 수집하고, 대표 데모 바이너리 몇 개를 링크한다.")
|
||
A("# ==========================================================================")
|
||
A("CC := gcc")
|
||
A("ECPG := ecpg")
|
||
A("AR := ar")
|
||
A("PG_INCDIR := $(shell pg_config --includedir 2>/dev/null)")
|
||
A("")
|
||
A("INCLUDES := -Iframework/txcore/include \\")
|
||
A(" -Iframework/txcore/dbio \\")
|
||
A(" -Iapp/include \\")
|
||
A(" -Iapp/shared/include \\")
|
||
A(" -I$(PG_INCDIR)")
|
||
A("")
|
||
A("# 고정길이 관용구 경고만 억제 (-Werror 없음)")
|
||
A("CFLAGS := -g -O2 -Wall -Wextra -Wno-unused-parameter \\")
|
||
A(" -Wno-stringop-truncation -Wno-format-truncation \\")
|
||
A(" -Wno-unused-variable -Wno-unused-but-set-variable $(INCLUDES)")
|
||
A("ECPGFLAGS := -Iframework/txcore/include -Iframework/txcore/dbio \\")
|
||
A(" -Iapp/include -Iapp/shared/include")
|
||
A("LDLIBS := -lecpg -lpq")
|
||
A("")
|
||
A("BUILD := build")
|
||
A("BIN := bin")
|
||
A("")
|
||
A("# ---- 소스 수집 (와일드카드) ----------------------------------------------")
|
||
A("ONLINE_PGC := $(wildcard app/online/*.pgc)")
|
||
A("BATCH_PGC := $(wildcard app/batch/*.pgc)")
|
||
A("DBIO_PGC := $(wildcard app/dbio/*.pgc)")
|
||
A("COMMON_C := $(wildcard app/common/*.c)")
|
||
A("SHARED_C := $(wildcard app/shared/common/*.c)")
|
||
A("")
|
||
A("PGC_ALL := $(ONLINE_PGC) $(BATCH_PGC) $(DBIO_PGC)")
|
||
A("GEN_C := $(PGC_ALL:.pgc=.c)")
|
||
A("")
|
||
A("PGC_OBJ := $(patsubst %,$(BUILD)/%.o,$(basename $(notdir $(PGC_ALL))))")
|
||
A("COMMON_OBJ := $(patsubst %,$(BUILD)/%.o,$(basename $(notdir $(COMMON_C))))")
|
||
A("SHARED_OBJ := $(patsubst %,$(BUILD)/%.o,$(basename $(notdir $(SHARED_C))))")
|
||
A("ALL_OBJ := $(PGC_OBJ) $(COMMON_OBJ) $(SHARED_OBJ)")
|
||
A("")
|
||
A("LIBTXCORE := $(BUILD)/libtxcore.a")
|
||
A("")
|
||
# 데모 바이너리 목록
|
||
demo_bins = []
|
||
for mod in ONLINE_DEMOS:
|
||
if mod in first_online:
|
||
demo_bins.append("$(BIN)/demo_%s_server" % mod)
|
||
for mod in BATCH_DEMOS:
|
||
if mod in first_batch:
|
||
demo_bins.append("$(BIN)/demo_%s_batch" % mod)
|
||
A("DEMO_BINS := " + " ".join(demo_bins))
|
||
A("")
|
||
A(".SECONDARY:")
|
||
A(".PHONY: all clean dirs gen objs demos")
|
||
A("")
|
||
A("all: dirs $(LIBTXCORE) objs demos")
|
||
A("\t@echo \"==> 빌드 완료\"")
|
||
A("\t@echo \"오브젝트: $(words $(ALL_OBJ)) 개\"")
|
||
A("")
|
||
A("dirs:")
|
||
A("\t@mkdir -p $(BUILD) $(BIN)")
|
||
A("")
|
||
A("gen: $(GEN_C)")
|
||
A("objs: $(ALL_OBJ)")
|
||
A("demos: $(DEMO_BINS)")
|
||
A("")
|
||
A("# ---- ecpg: .pgc -> .c (디렉토리별) ---------------------------------------")
|
||
A("app/online/%.c: app/online/%.pgc")
|
||
A("\t$(ECPG) $(ECPGFLAGS) -o $@ $<")
|
||
A("app/batch/%.c: app/batch/%.pgc")
|
||
A("\t$(ECPG) $(ECPGFLAGS) -o $@ $<")
|
||
A("app/dbio/%.c: app/dbio/%.pgc")
|
||
A("\t$(ECPG) $(ECPGFLAGS) -o $@ $<")
|
||
A("")
|
||
A("# ---- 컴파일: build/<basename>.o (gen 선행 보장) --------------------------")
|
||
A("$(BUILD)/%.o: app/online/%.c | gen")
|
||
A("\t$(CC) $(CFLAGS) -c $< -o $@")
|
||
A("$(BUILD)/%.o: app/batch/%.c | gen")
|
||
A("\t$(CC) $(CFLAGS) -c $< -o $@")
|
||
A("$(BUILD)/%.o: app/dbio/%.c | gen")
|
||
A("\t$(CC) $(CFLAGS) -c $< -o $@")
|
||
A("$(BUILD)/%.o: app/common/%.c")
|
||
A("\t$(CC) $(CFLAGS) -c $< -o $@")
|
||
A("$(BUILD)/%.o: app/shared/common/%.c")
|
||
A("\t$(CC) $(CFLAGS) -c $< -o $@")
|
||
A("")
|
||
A("# ---- TxCore 정적 라이브러리 ----------------------------------------------")
|
||
A("$(BUILD)/txcore.o: framework/txcore/src/txcore.c")
|
||
A("\t$(CC) $(CFLAGS) -c $< -o $@")
|
||
A("$(LIBTXCORE): $(BUILD)/txcore.o")
|
||
A("\t$(AR) rcs $@ $^")
|
||
A("")
|
||
A("# ---- 대표 데모 바이너리 (링크 검증) --------------------------------------")
|
||
for mod in ONLINE_DEMOS:
|
||
if mod not in first_online:
|
||
continue
|
||
flag_id, _svc = first_online[mod]
|
||
objs = "$(BUILD)/demo_%s_main.o $(BUILD)/%s.o $(BUILD)/%s_dbio_main.o $(SHARED_OBJ)" % (
|
||
mod, flag_id, mod)
|
||
A("$(BIN)/demo_%s_server: %s $(LIBTXCORE)" % (mod, objs))
|
||
A("\t$(CC) $(CFLAGS) -o $@ %s -L$(BUILD) -ltxcore $(LDLIBS)" % objs)
|
||
A("")
|
||
for mod in BATCH_DEMOS:
|
||
if mod not in first_batch:
|
||
continue
|
||
bid = first_batch[mod]
|
||
objs = "$(BUILD)/%s.o $(BUILD)/%s_dbio_main.o $(SHARED_OBJ)" % (bid, mod)
|
||
A("$(BIN)/demo_%s_batch: %s $(LIBTXCORE)" % (mod, objs))
|
||
A("\t$(CC) $(CFLAGS) -o $@ %s -L$(BUILD) -ltxcore $(LDLIBS)" % objs)
|
||
A("")
|
||
A("# ==========================================================================")
|
||
A("clean:")
|
||
A("\trm -rf $(BUILD) $(BIN)")
|
||
A("\trm -f $(GEN_C)")
|
||
A("")
|
||
return "\n".join(lines) + "\n"
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|