~strahinja/poe

81972837bddb516e0ca00476632bd77303058dbf — Страхиња Радић 7 days ago 5ba662b
Split code into several files, changed copyright notices to match suckless standards

Signed-off-by: Страхиња Радић <contact@strahinja.org>
12 files changed, 372 insertions(+), 321 deletions(-)

M all.do
A config.def.h
A config.h
A default.do
A default.h.do
A draw.c
A draw.h
A po.h
M poe.c
M poe.do
A util.c
A util.h
M all.do => all.do +1 -0
@@ 1,3 1,4 @@
redo-ifchange config.def.h config.h
for f in *.c; do
	echo $f | sed -e's/\.c$/.o/g'
done | xargs redo-ifchange

A config.def.h => config.def.h +10 -0
@@ 0,0 1,10 @@
/* See the file LICENSE for copyright and license details. */
void format_filename(char* result, size_t result_size, const char* format);
void format_msgs(char* result, size_t result_size, const char* format);

static const StatusSegment status_segments[] = {
	{ "%s",			format_filename, TB_BLACK,	     TB_WHITE,	LEFT	},
	{ "msg:%ld/%ld",	format_msgs,	 TB_WHITE | TB_BOLD, TB_BLUE,	CENTER	},
	{ "H=HELP, q=QUIT",	NULL,		 TB_BLACK,	     TB_WHITE,	RIGHT  },
};


A config.h => config.h +10 -0
@@ 0,0 1,10 @@
/* See the file LICENSE for copyright and license details. */
void format_filename(char* result, size_t result_size, const char* format);
void format_msgs(char* result, size_t result_size, const char* format);

static const StatusSegment status_segments[] = {
	{ "%s",			format_filename, TB_BLACK,	     TB_WHITE,	LEFT	},
	{ "msg:%ld/%ld",	format_msgs,	 TB_WHITE | TB_BOLD, TB_BLUE,	CENTER	},
	{ "H=HELP, q=QUIT",	NULL,		 TB_BLACK,	     TB_WHITE,	RIGHT  },
};


A default.do => default.do +4 -0
@@ 0,0 1,4 @@
for f in *.h; do
	echo $f
done | xargs redo-ifchange


A default.h.do => default.h.do +6 -0
@@ 0,0 1,6 @@
redo-ifchange $2.def.h
if [ -f $2.def.h ]; then
	cat $2.def.h > $3
fi



A draw.c => draw.c +221 -0
@@ 0,0 1,221 @@
/* See the file LICENSE for copyright and license details. */
#include <bits/stdint-uintn.h>
#include <stdlib.h>
#include <string.h>

#include "termbox.h"
#include "util.h"
#include "po.h"
#include "draw.h"
#include "config.h"

static const char* title = " poe - .po File Editor ";
static const char* help[] = {
	"ESC      - Close dialog, clear error",
	"H | F1   - Show this screen",
	"g | Home - First entry", 
	"k | \u2191    - Previous entry",
	"j | \u2193    - Next entry",
	"G | End  - Last entry",
	"q        - Quit"
};
const char* errors[] = {
	[ERR_UNKNOWN_KEY] = "Unknown key (press H for help)",
	[ERR_NO_PREV]     = "No previous entry",
	[ERR_NO_NEXT]     = "No next entry",
	[ERR_EXIT_KEY]    = "Press q to quit"
};
const char* flag_strings[] = {
	[FL_C_FORMAT]	  = "c-format",
	[FL_FUZZY]	  = "fuzzy"
};
const char* empty_msgid  = "*** No msgid ***";
const char* empty_msgstr = "*** No msgstr ***";
const char* program_name = "poe";

void
draw_box(const int startx, const int endx, const int starty, const int endy,
		const uint16_t fg, const uint16_t bg)
{
	int x, y;

	tb_change_cell(startx+1, starty, BORDER_NW, fg, bg);
	for (x = startx+2; x < endx-2; x++)
		tb_change_cell(x, starty, BORDER_HOR, fg, bg);
	tb_change_cell(endx-2, starty, BORDER_NE, fg, bg);

	for (y = starty+1; y < endy-2; y++)
	{
		tb_change_cell(startx+1, y, BORDER_VER, fg, bg);
		for (x = startx+2; x < endx-2; x++)
			tb_change_cell(x, y, ' ', fg, bg);
		tb_change_cell(endx-2, y, BORDER_VER, fg, bg);
		tb_change_cell(endx-1, y, ' ', SHADOW_FG, SHADOW_BG);
	}

	tb_change_cell(startx+1, endy-2, BORDER_SW, fg, bg);
	for (x = startx+2; x < endx-2; x++)
		tb_change_cell(x, endy-2, BORDER_HOR, fg, bg);
	tb_change_cell(endx-2, endy-2, BORDER_SE, fg, bg);
	tb_change_cell(endx-1, endy-2, ' ', SHADOW_FG, SHADOW_BG);

	for (x = startx+2; x < endx; x++)
		tb_change_cell(x, endy-1, ' ', SHADOW_FG, SHADOW_BG);
}

void
draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
		const char* s, const int max_cols, const int fill_cols,
		const int padding, const Alignment align)
{
	int col;
	uint32_t* us = calloc(strlen(s)+1, sizeof(uint32_t));
	uint32_t* pus = NULL;
	size_t us_len = 0;
	us_len = u8_string_to_unicode(us, s);
	pus = us;

	if (align == LEFT)
	{
		for (col = 0; col < fill_cols; col++)
			tb_change_cell(x+col, y, ' ', fg, bg);
		col = 0;
	}
	else if (align == RIGHT)
	{
		for (col = 0; col < fill_cols; col++)
			tb_change_cell(x+col, y, ' ', fg, bg);
		col = max_cols-us_len-padding-1;
	}
	else if (align == CENTER)
	{
		for (col = (max_cols-fill_cols)/2; 
				col < (max_cols+fill_cols)/2; col++)
			tb_change_cell(x+col, y, ' ', fg, bg);

		if (s)
		{
			if (us_len > max_cols)
			{
				pus += (us_len-max_cols)/2;
				col = 0;
			}
			else
				col = (max_cols-us_len)/2-padding;
		}
	}

	while (*pus && col < max_cols-padding)
	{
		tb_change_cell(x+col+padding, y, *pus, fg, bg);
		pus++;
		col++;
	}
	if (us)
		free(us);
}

void
draw_help()
{
	int startx, endx, starty, endy;
	int w = 50;
	int h = 20;
	int row = 0;

	startx = w > maxx ? 0 : (maxx - w) / 2;
	endx = w > maxx ? maxx-1 : maxx-startx;
	
	starty = h > maxy-1 ? 0 : (maxy-h)/2;
	endy = h > maxy-1 ? maxy-2 : maxy-starty;

	draw_box(startx, endx, starty, endy, TB_BLACK, TB_WHITE);
	draw_string(startx+1, starty, TB_BLACK | TB_BOLD | TB_REVERSE, TB_WHITE, 
			title, w-2, strlen(title)+2, 1, CENTER);
	while (row < LEN(help))
	{
		draw_string(startx+2, starty+3+row, TB_BLACK, TB_WHITE, 
				help[row], w-4, w-4, 2, LEFT);
		row++;
	}
}

void
draw_status()
{
	char buf[MAXBUF];
	int seg_size = maxx/3;
	int current_start = 0;
	const StatusSegment* pseg = status_segments;

	if (error[0])
		draw_string(0, maxy-1, ERROR_FG, ERROR_BG, error, maxx, maxx, 
				1, CENTER);
	else
		while (pseg < status_segments + LEN(status_segments))
		{
			buf[0] = 0;
			if (pseg->callback)
				pseg->callback(buf, MAXBUF, pseg->format); 
			else
				strncpy(buf, pseg->format, MAXBUF);
			buf[MAXBUF-1] = 0;
			draw_string(current_start, maxy-1, pseg->fg, pseg->bg,
					buf, seg_size, seg_size, 1, 
					pseg->alignment);
			pseg++;
			current_start += seg_size;
		}
}

void
draw_entry(PoEntry* entry)
{
	uint16_t flags;

	if (entry)
	{
		draw_string(0, maxy/3, MSGID_FG, MSGID_BG, entry->msgid,
			maxx, maxx, maxx/4, LEFT);
		flags = entry->flags;
		while (flags)
		{
			if (flags & FL_C_FORMAT)
			{
				flags &= ~FL_C_FORMAT;
				draw_string(0, 1, FLAG_FG, FLAG_BG,
					flag_strings[FL_C_FORMAT], maxx, maxx, 1, LEFT);
			}
			else if (flags & FL_FUZZY)
			{
				flags &= ~FL_FUZZY;
				draw_string(10, 1, FLAG_FG, FLAG_BG,
					flag_strings[FL_FUZZY], maxx-10, maxx-10, 1, LEFT);
			}
		}
		draw_string(0, 2*maxy/3, MSGSTR_FG, MSGSTR_BG, entry->msgstr,
			maxx, maxx, maxx/4, LEFT);
	}
	else
	{
		draw_string(0, maxy/3, MSGID_FG, MSGID_BG, empty_msgid,
			maxx, maxx, maxx/4, LEFT);
		draw_string(0, 2*maxy/3, MSGSTR_FG, MSGSTR_BG, empty_msgstr,
			maxx, maxx, maxx/4, LEFT);
	}
}

void
draw()
{
	if (msgid_number > 0)
		draw_entry(&entries[msgid_number-1]);
	else
		draw_entry(NULL);

	if (show_help)
		draw_help();

	draw_status();
}


A draw.h => draw.h +50 -0
@@ 0,0 1,50 @@
/* See the file LICENSE for copyright and license details. */
#define BORDER_NW u'\u250c'
#define BORDER_NE u'\u2510'
#define BORDER_SW u'\u2514'
#define BORDER_SE u'\u2518'
#define BORDER_VER u'\u2502'
#define BORDER_HOR u'\u2500'

#define ERROR_FG TB_WHITE | TB_BOLD
#define ERROR_BG TB_RED
#define FLAG_FG  TB_YELLOW | TB_BOLD
#define FLAG_BG  TB_DEFAULT
#define MSGID_FG TB_GREEN
#define MSGID_BG TB_DEFAULT
#define MSGSTR_FG TB_WHITE
#define MSGSTR_BG TB_DEFAULT
#define SHADOW_FG TB_BLACK
#define SHADOW_BG TB_BLACK | TB_BOLD

typedef enum {
	LEFT,
	CENTER,
	RIGHT
} Alignment;

typedef struct {
	const char* format;
	void (*callback)(char*, size_t, const char*);
	uint16_t fg;
	uint16_t bg;
	Alignment alignment;
} StatusSegment;

extern char error[MAXBUF];
extern PoEntry entries[];
extern long msgid_count;
extern long msgid_number;
extern int show_help;
extern int maxx, maxy;

void draw_box(const int startx, const int endx, const int starty, const int endy,
		const uint16_t fg, const uint16_t bg);
void draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
		const char* s, const int max_cols, const int fill_cols,
		const int padding, const Alignment align);
void draw_help();
void draw_status();
void draw_entry(PoEntry* entry);
void draw();


A po.h => po.h +14 -0
@@ 0,0 1,14 @@
/* See the file LICENSE for copyright and license details. */

typedef enum {
	FL_NONE		= 0,
	FL_C_FORMAT	= 1,
	FL_FUZZY	= 1 << 1,
} Flags;

typedef struct {
	Flags flags;
	char* msgid;
	char* msgstr;
} PoEntry;


M poe.c => poe.c +12 -320
@@ 1,22 1,4 @@
/*
 *    poe - .po file editor
 *    Copyright (C) 2021 Страхиња Радић
 *
 *    This program is free software: you can redistribute it and/or modify it
 *    under the terms of the GNU General Public License as published by the Free
 *    Software Foundation, either version 3 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful, but
 *    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *    for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

/* See the file LICENSE for copyright and license details. */
#include <bits/stdint-uintn.h>
#include <errno.h>
#include <stdarg.h>


@@ 28,106 10,24 @@
#include <time.h>

#include "termbox.h"
#include "util.h"
#include "po.h"
#include "draw.h"
#include "config.h"

#define BORDER_NW u'\u250c'
#define BORDER_NE u'\u2510'
#define BORDER_SW u'\u2514'
#define BORDER_SE u'\u2518'
#define BORDER_VER u'\u2502'
#define BORDER_HOR u'\u2500'

#define ERROR_FG TB_WHITE | TB_BOLD
#define ERROR_BG TB_RED
#define FLAG_FG  TB_YELLOW | TB_BOLD
#define FLAG_BG  TB_DEFAULT
#define MSGID_FG TB_GREEN
#define MSGID_BG TB_DEFAULT
#define MSGSTR_FG TB_WHITE
#define MSGSTR_BG TB_DEFAULT
#define SHADOW_FG TB_BLACK
#define SHADOW_BG TB_BLACK | TB_BOLD

#define LEN(x)	(sizeof(x) / sizeof(x[0]))

typedef enum {
	LEFT,
	CENTER,
	RIGHT
} Alignment;

typedef enum {
	FL_NONE		= 0,
	FL_C_FORMAT	= 1,
	FL_FUZZY	= 1 << 1,
} Flags;

enum {
	ERR_UNKNOWN_KEY,
	ERR_NO_PREV,
	ERR_NO_NEXT,
	ERR_EXIT_KEY
};

typedef struct {
	const char* format;
	void (*callback)(char*, size_t, const char*);
	uint16_t fg;
	uint16_t bg;
	Alignment alignment;
} StatusSegment;

typedef struct {
	Flags flags;
	char* msgid;
	char* msgstr;
} PoEntry;

static const char* title = " poe - .po File Editor ";
static const char* help[] = {
	"ESC      - Close dialog, clear error",
	"H | F1   - Show this screen",
	"g | Home - First entry", 
	"k | \u2191    - Previous entry",
	"j | \u2193    - Next entry",
	"G | End  - Last entry",
	"q        - Quit"
};
static const char* errors[] = {
	[ERR_UNKNOWN_KEY] = "Unknown key (press H for help)",
	[ERR_NO_PREV]     = "No previous entry",
	[ERR_NO_NEXT]     = "No next entry",
	[ERR_EXIT_KEY]    = "Press q to quit"
};
static const char* flag_strings[] = {
	[FL_C_FORMAT]	  = "c-format",
	[FL_FUZZY]	  = "fuzzy"
};
static const char* empty_msgid  = "*** No msgid ***";
static const char* empty_msgstr = "*** No msgstr ***";
static const char* program_name = "poe";

void format_filename(char* result, size_t result_size, const char* format);
void format_msgs(char* result, size_t result_size, const char* format);

static const StatusSegment status_segments[] = {
	{ "%s",			format_filename, TB_BLACK,	     TB_WHITE,	LEFT	},
	{ "msg:%ld/%ld",	format_msgs,	 TB_WHITE | TB_BOLD, TB_BLUE,	CENTER	},
	{ "H=HELP, q=QUIT",	NULL,		 TB_BLACK,	     TB_WHITE,	RIGHT  },
};

#define MAXBUF 255
#define MAXPATH 1024
extern const char* errors[];
extern const char* program_name;

char filename[MAXPATH];
char error[MAXBUF];
/* Test */
static PoEntry entries[] = {
PoEntry entries[] = {
	{ flags: FL_C_FORMAT | FL_FUZZY,	msgid: "First msgid: %s", 
						msgstr: "Први msgid: %s"	},
						msgstr: "Први msgid: %s" },
	{ flags: FL_NONE,			msgid: "Test msgid",	  
						msgstr: "Пробни msgstr"		},
						msgstr: "Пробни msgstr"	 },
	{ flags: FL_FUZZY,			msgid: "Final text",	  
						msgstr: "Последњи текст"	},
						msgstr: "Последњи текст" },
};

long msgid_count  = 0;


@@ 165,214 65,6 @@ format_msgs(char* result, size_t result_size, const char* format)
	snprintf(result, result_size, format, msgid_number, msgid_count);
}

void
draw_box(const int startx, const int endx, const int starty, const int endy,
		const uint16_t fg, const uint16_t bg)
{
	int x, y;

	tb_change_cell(startx+1, starty, BORDER_NW, fg, bg);
	for (x = startx+2; x < endx-2; x++)
		tb_change_cell(x, starty, BORDER_HOR, fg, bg);
	tb_change_cell(endx-2, starty, BORDER_NE, fg, bg);

	for (y = starty+1; y < endy-2; y++)
	{
		tb_change_cell(startx+1, y, BORDER_VER, fg, bg);
		for (x = startx+2; x < endx-2; x++)
			tb_change_cell(x, y, ' ', fg, bg);
		tb_change_cell(endx-2, y, BORDER_VER, fg, bg);
		tb_change_cell(endx-1, y, ' ', SHADOW_FG, SHADOW_BG);
	}

	tb_change_cell(startx+1, endy-2, BORDER_SW, fg, bg);
	for (x = startx+2; x < endx-2; x++)
		tb_change_cell(x, endy-2, BORDER_HOR, fg, bg);
	tb_change_cell(endx-2, endy-2, BORDER_SE, fg, bg);
	tb_change_cell(endx-1, endy-2, ' ', SHADOW_FG, SHADOW_BG);

	for (x = startx+2; x < endx; x++)
		tb_change_cell(x, endy-1, ' ', SHADOW_FG, SHADOW_BG);
}

size_t
u8_string_to_unicode(uint32_t* us, const char* s)
{
	uint32_t uch;
	uint32_t* pus = us;
	int u8_len = 0;
	size_t result = 0;

	while (*s && u8_len != TB_EOF)
	{
		u8_len = tb_utf8_char_to_unicode(&uch, s);
		*pus++ = uch;
		if (u8_len != TB_EOF)
		{
			s += u8_len;
			result++;
		}
	}
	return result;
}

void
draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
		const char* s, const int max_cols, const int fill_cols,
		const int padding, const Alignment align)
{
	int col;
	uint32_t* us = calloc(strlen(s)+1, sizeof(uint32_t));
	uint32_t* pus = NULL;
	size_t us_len = 0;
	us_len = u8_string_to_unicode(us, s);
	pus = us;

	if (align == LEFT)
	{
		for (col = 0; col < fill_cols; col++)
			tb_change_cell(x+col, y, ' ', fg, bg);
		col = 0;
	}
	else if (align == RIGHT)
	{
		for (col = 0; col < fill_cols; col++)
			tb_change_cell(x+col, y, ' ', fg, bg);
		col = max_cols-us_len-padding-1;
	}
	else if (align == CENTER)
	{
		for (col = (max_cols-fill_cols)/2; 
				col < (max_cols+fill_cols)/2; col++)
			tb_change_cell(x+col, y, ' ', fg, bg);

		if (s)
		{
			if (us_len > max_cols)
			{
				pus += (us_len-max_cols)/2;
				col = 0;
			}
			else
				col = (max_cols-us_len)/2-padding;
		}
	}

	while (*pus && col < max_cols-padding)
	{
		tb_change_cell(x+col+padding, y, *pus, fg, bg);
		pus++;
		col++;
	}
	if (us)
		free(us);
}

void
draw_help()
{
	int startx, endx, starty, endy;
	int w = 50;
	int h = 20;
	int row = 0;

	startx = w > maxx ? 0 : (maxx - w) / 2;
	endx = w > maxx ? maxx-1 : maxx-startx;
	
	starty = h > maxy-1 ? 0 : (maxy-h)/2;
	endy = h > maxy-1 ? maxy-2 : maxy-starty;

	draw_box(startx, endx, starty, endy, TB_BLACK, TB_WHITE);
	draw_string(startx+1, starty, TB_BLACK | TB_BOLD | TB_REVERSE, TB_WHITE, 
			title, w-2, strlen(title)+2, 1, CENTER);
	while (row < LEN(help))
	{
		draw_string(startx+2, starty+3+row, TB_BLACK, TB_WHITE, 
				help[row], w-4, w-4, 2, LEFT);
		row++;
	}
}

void
draw_status()
{
	int seg_size = maxx/3;
	char buf[MAXBUF];
	int current_start = 0;
	const StatusSegment* pseg = status_segments;

	if (error[0])
		draw_string(0, maxy-1, ERROR_FG, ERROR_BG, error, maxx, maxx, 
				1, CENTER);
	else
		while (pseg < status_segments + LEN(status_segments))
		{
			buf[0] = 0;
			if (pseg->callback)
				pseg->callback(buf, MAXBUF, pseg->format); 
			else
				strncpy(buf, pseg->format, MAXBUF);
			draw_string(current_start, maxy-1, pseg->fg, pseg->bg,
					buf, seg_size, seg_size, 1, 
					pseg->alignment);
			pseg++;
			current_start += seg_size;
		}
}

void
draw_entry(PoEntry* entry)
{
	char flags_buf[MAXBUF];
	uint16_t flags;

	if (entry)
	{
		draw_string(0, maxy/3, MSGID_FG, MSGID_BG, entry->msgid,
			maxx, maxx, maxx/4, LEFT);
		flags = entry->flags;
		flags_buf[0] = 0;
		while (flags)
		{
			if (flags & FL_C_FORMAT)
			{
				flags &= ~FL_C_FORMAT;
				draw_string(0, 1, FLAG_FG, FLAG_BG,
					flag_strings[FL_C_FORMAT], maxx, maxx, 1, LEFT);
			}
			else if (flags & FL_FUZZY)
			{
				flags &= ~FL_FUZZY;
				draw_string(10, 1, FLAG_FG, FLAG_BG,
					flag_strings[FL_FUZZY], maxx-10, maxx-10, 1, LEFT);
			}
		}
		draw_string(0, 2*maxy/3, MSGSTR_FG, MSGSTR_BG, entry->msgstr,
			maxx, maxx, maxx/4, LEFT);
	}
	else
	{
		draw_string(0, maxy/3, MSGID_FG, MSGID_BG, empty_msgid,
			maxx, maxx, maxx/4, LEFT);
		draw_string(0, 2*maxy/3, MSGSTR_FG, MSGSTR_BG, empty_msgstr,
			maxx, maxx, maxx/4, LEFT);
	}
}

void
draw()
{
	if (msgid_number > 0)
		draw_entry(&entries[msgid_number-1]);
	else
		draw_entry(NULL);

	if (show_help)
		draw_help();

	draw_status();
}

int
main(int argc, char** argv)
{


@@ 385,7 77,7 @@ main(int argc, char** argv)

	if (argc > 1)
	{
		strncpy(filename, argv[1], MAXPATH-1);
		strncpy(filename, argv[1], MAXPATH);
		filename[MAXPATH-1] = 0;
		if (!filename[0])
			print_error(EINVAL, "Invalid argument");

M poe.do => poe.do +1 -1
@@ 1,5 1,5 @@
for f in *.c; do
	echo $f
done | xargs redo-ifchange
gcc -g -Wall -o $3 poe.o termbox.o utf8.o
gcc -g -Wall -o $3 poe.o draw.o termbox.o utf8.o util.o


A util.c => util.c +29 -0
@@ 0,0 1,29 @@
/* See the file LICENSE for copyright and license details. */
#include <bits/stdint-uintn.h>
#include <stdlib.h>

#include "termbox.h"
#include "po.h"
#include "util.h"

size_t
u8_string_to_unicode(uint32_t* us, const char* s)
{
	uint32_t uch;
	uint32_t* pus = us;
	int u8_len = 0;
	size_t result = 0;

	while (*s && u8_len != TB_EOF)
	{
		u8_len = tb_utf8_char_to_unicode(&uch, s);
		*pus++ = uch;
		if (u8_len != TB_EOF)
		{
			s += u8_len;
			result++;
		}
	}
	return result;
}


A util.h => util.h +14 -0
@@ 0,0 1,14 @@
#define LEN(x)	(sizeof(x) / sizeof(x[0]))

enum {
	ERR_UNKNOWN_KEY,
	ERR_NO_PREV,
	ERR_NO_NEXT,
	ERR_EXIT_KEY
};

#define MAXBUF 255
#define MAXPATH 1024

size_t u8_string_to_unicode(uint32_t* us, const char* s);