From 7f37c580c4e55468ffedba910d01abffe26154f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Thu, 2 Jun 2022 02:27:43 +0200 Subject: Simplify C interface to Prolog. --- Makefile | 3 +- c/defs.h | 3 ++ c/episodelistview.c | 134 ++++++++++++++++------------------------------------ c/main.c | 133 ++++++++++++++++----------------------------------- c/pl.c | 76 +++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 186 deletions(-) create mode 100644 c/pl.c diff --git a/Makefile b/Makefile index a03c3b0..405e8c6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ B = build/default/ C = c/main.c PL = pl/cfg.pl pl/episode_data.pl pl/local_episodes.pl pl/track_episodes.pl -OBJ = $(B)common.obj $(B)datalistview.obj $(B)episodelistview.obj $(B)listview.obj $(B)resource.obj +OBJ = $(B)common.obj $(B)datalistview.obj $(B)episodelistview.obj $(B)listview.obj $(B)pl.obj $(B)resource.obj CC = swipl-ld CFLAGS += -O -ld-options,-mwindows @@ -10,6 +10,7 @@ CFLAGS += -DUNICODE -D_UNICODE LDFLAGS += -lcomctl32 -luxtheme all: $(B)EpisodeBrowser.exe + cp $< "C:\Users\John\Desktop\Delat" $(B)EpisodeBrowser.exe: $(C) $(OBJ) $(PL) c/defs.h Makefile $(CC) -v $(CFLAGS) $(LDFLAGS) -goal true -o $@ $(C) $(OBJ) $(PL) diff --git a/c/defs.h b/c/defs.h index 35f7431..2b4e0ba 100644 --- a/c/defs.h +++ b/c/defs.h @@ -30,6 +30,9 @@ void ElvUpdateItem(LPLVITEM); HWND DlvCreate(); void DlvShowEpisode(int); +/* pl.c */ +int Pl(char *, char *, char *, ...); + /* defs.h */ #define DLVSIKEY 0 #define DLVSIVALUE 1 diff --git a/c/episodelistview.c b/c/episodelistview.c index ac1ac2f..1fb20e2 100644 --- a/c/episodelistview.c +++ b/c/episodelistview.c @@ -15,7 +15,6 @@ HWND ElvCreate() { LVCOLUMN lvc; - term_t t; HElv = LvCreate((HMENU)IDC_EPISODELISTVIEW, 0); @@ -35,9 +34,8 @@ ElvCreate() lvc.cx = Dpi(30); ListView_InsertColumn(HElv, ELVSIRATING, &lvc); - t = T(1); - P("cfg","get_sort",1,t); - GI(t,&ISort) ISort = 1; + if (!Pl("cfg","get_sort","i",&ISort)) + ISort = 1; return HElv; } @@ -67,12 +65,9 @@ ElvHandleNotify(LPARAM lParam) case LVN_COLUMNCLICK: /* Sort by column. */ { int iColumn; - term_t t; - t = T(1); iColumn = lpNmLv->iSubItem+1; ISort = abs(ISort) == iColumn? -ISort: iColumn; - PI(t,ISort) goto s; - P("cfg","set_sort",1,t); + Pl("cfg","set_sort","I",ISort); s: ElvDoSort(); ElvShowFocus(); break; @@ -101,11 +96,8 @@ ElvHandleNotify(LPARAM lParam) break; case CDDS_ITEMPREPAINT: { - term_t t; extern HFONT HfBold; - t = T(1); - PI(t,lpLvCd->nmcd.lItemlParam) break; - P("track_episodes","watched",1,t) { + if (!Pl("track_episodes","watched","I",lpLvCd->nmcd.lItemlParam)) { SelectObject(lpLvCd->nmcd.hdc, HfBold); return CDRF_NEWFONT; } @@ -116,27 +108,25 @@ ElvHandleNotify(LPARAM lParam) } case NM_DBLCLK: /* Open clicked episode. */ { - term_t t; - t = T(1); - PI(t,LviElvFocus.lParam) break; - P("local_episodes","open_episode_locally",1,t) - P("local_episodes","open_episode_online",1,t); + if (!Pl("local_episodes","open_episode_locally","I", + LviElvFocus.lParam)) + Pl("local_episodes","open_episode_online","I", + LviElvFocus.lParam); break; } case NM_RETURN: /* Open all selected episodes. */ { LVITEM lvi; - term_t t; extern HWND HElv; lvi.mask = LVIF_PARAM; lvi.iItem = -1; - t = T(1); while ((lvi.iItem = ListView_GetNextItem( HElv, lvi.iItem, LVNI_SELECTED)) != -1) { if (!ListView_GetItem(HElv, &lvi)) goto b; - PI(t,lvi.lParam) goto b; - P("local_episodes","open_episode_locally",1,t) - P("local_episodes","open_episode_online",1,t); + if (!Pl("local_episodes","open_episode_locally","I", + lvi.lParam)) + Pl("local_episodes","open_episode_online","I", + lvi.lParam); } b: break; } @@ -178,12 +168,9 @@ ElvSelectFocus() { int i, iEpisode, iItem; LVFINDINFO lvfi; - term_t t; - t = T(1); iItem = 0; - P("cfg","get_focus",1,t) goto s; - GI(t,&iEpisode) return; + if (!Pl("cfg","get_focus","i",&iEpisode)) return; lvfi.flags = LVFI_PARAM; lvfi.lParam = iEpisode; @@ -214,10 +201,9 @@ s: ListView_SetItemState(HElv, -1, LVIF_STATE, LVIS_SELECTED); void ElvSelectUnwatched(int iDir) { - int bShift, i, iEpNew, iItemNew; + int i, iEpNew, iItemNew; LVFINDINFO lvfi; LVITEM lviFocus; - term_t t; /* Get focused episode. */ lviFocus.mask = LVIF_PARAM; @@ -232,11 +218,9 @@ ElvSelectUnwatched(int iDir) lvfi.lParam = lviFocus.lParam; do { - t = T(2); - PI(t,lvfi.lParam) return; - P("track_episodes",iDir > 0? "next_unwatched": "previous_unwatched",2,t) + if (!Pl("track_episodes",iDir > 0? "next_unwatched": "previous_unwatched", + "Ii",lvfi.lParam,&iEpNew)) return; - GI(t+1,&iEpNew) return; lvfi.lParam = iEpNew; if ((iItemNew = ListView_FindItem(HElv, -1, &lvfi)) != -1) { @@ -280,18 +264,11 @@ ElvSort(LPARAM iItem1, LPARAM iItem2, LPARAM iSort) case ELVSIRATING: { int iRating1, iRating2; - term_t t, t2; iRating1 = iSort > 0? 99: -1; iRating2 = iSort > 0? 99: -1; - t = T(2); - PI(t,lvi1.lParam) goto e; - P("episode_data","episode_rating",2,t) goto e; - GI(t+1,&iRating1); - e: t2 = T(2); - PI(t2,lvi2.lParam) goto f; - P("episode_data","episode_rating",2,t2) goto f; - GI(t2+1,&iRating2); - f: if (iRating1 == iRating2) + Pl("episode_data","episode_rating","Ii",lvi1.lParam,&iRating1); + Pl("episode_data","episode_rating","Ii",lvi2.lParam,&iRating2); + if (iRating1 == iRating2) return Cmp(lvi1.lParam, lvi2.lParam); return iOrder*Cmp(iRating1, iRating2); break; @@ -300,15 +277,10 @@ ElvSort(LPARAM iItem1, LPARAM iItem2, LPARAM iSort) { char *sz1, *sz2; int cch, cch1, cch2; - term_t t, t2; - t = T(2); - PI(t,lvi1.lParam) return 0; - P("episode_data","episode_title",2,t) return 0; - GAC(t+1,&sz1) return 0; - t2 = T(2); - PI(t2,lvi2.lParam) return 0; - P("episode_data","episode_title",2,t2) return 0; - GAC(t2+1,&sz2) return 0; + if (!Pl("episode_data","episode_title","Is",lvi1.lParam,&sz1)) + return 0; + if (!Pl("episode_data","episode_title","Is",lvi2.lParam,&sz2)) + return 0; cch1 = strlen(sz1); cch2 = strlen(sz2); cch = cch1 > cch2? cch2: cch1; @@ -327,7 +299,6 @@ ElvUpdate() int cEp, i, iEp, iEpFocus, iItem, iItemMark, iItemTopNew; LVITEM lvi, lviEpisode, lviTop; LVFINDINFO lvfi; - term_t t; extern HWND HWndStatus; static TCHAR tszDisp[16], tszEpisode[16], tszTotal[16]; static int aEpSel[2048]; @@ -360,45 +331,26 @@ ElvUpdate() SendMessage(HElv, WM_SETREDRAW, FALSE, 0); ListView_DeleteAllItems(HElv); - t = T(1); - P("episode_data","ensure_episode_data",0,t) return; - P("episode_data","episode_count",1,t) return; - GI(t,&cEp) return; + if (!Pl("episode_data","ensure_episode_data","")) return; + if (!Pl("episode_data","episode_count","i",&cEp)) return; for (iEp = 1, iItem = 0; iEp <= cEp; iEp++) { extern char SzLimitScreenwriter[]; extern int BViewTVOriginal, BViewWatched; - if (SzLimitScreenwriter[0]) { - atom_t a1, a2; - term_t t; - t = T(3); - a1 = A("Screenwriter"); - a2 = A(SzLimitScreenwriter); - PI(t,iEp) goto a; - PA(t+1,a1) goto a; - PA(t+2,a2) goto a; - P("episode_data","episode_datum",3,t) continue; - } - - a: if (!BViewWatched) { - term_t t; - t = T(1); - PI(t,iEp) goto b; - P("track_episodes","watched",1,t) goto b; - continue; - } + if (SzLimitScreenwriter[0]) + if (!Pl("episode_data","episode_datum","ISS", + iEp,"Screenwriter",SzLimitScreenwriter)) + continue; + + if (!BViewWatched) + if (Pl("track_episodes","watched","I",iEp)) continue; - b: if (!BViewTVOriginal) { - term_t t; - t = T(1); - PI(t,iEp) goto c; - P("episode_data","tv_original",1,t) goto c; - continue; - } + if (!BViewTVOriginal) + if (Pl("episode_data","tv_original","I",iEp)) continue; /* Format episode number string. */ - c: _stprintf_s(tszEpisode, sizeof(tszEpisode), TEXT("%d"), iEp); + _stprintf_s(tszEpisode, sizeof(tszEpisode), TEXT("%d"), iEp); /* Insert item. */ lviEpisode.iItem = iItem++; @@ -461,28 +413,22 @@ ElvUpdateItem(LPLVITEM lpLvi) char *szName; int iRating; TCHAR *tszName; - term_t t, t2; static TCHAR tszRating[3]; - t = T(2); tszName = NULL; - PI(t,lpLvi->lParam) goto r; - P("episode_data","episode_title",2,t) { - P("episode_data","update_episode_data",0,t) goto r; - P("episode_data","episode_title",2,t) goto r; + if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName)) { + if (!Pl("episode_data","update_episode_data","")) goto r; + if (!Pl("episode_data","episode_title","Is",lpLvi->lParam,&szName)) + goto r; } - GAC(t+1,&szName) goto r; tszName = TszFromSz(szName, CP_UTF8); if (tszName) ListView_SetItemText(HElv, lpLvi->iItem, ELVSITITLE, tszName); -r: t2 = T(2); - PI(t2,lpLvi->lParam) goto f; - P("episode_data","episode_rating",2,t2) { +r: if (!Pl("episode_data","episode_rating","Ii",lpLvi->lParam,&iRating)) { ListView_SetItemText(HElv, lpLvi->iItem, ELVSIRATING, TEXT("")); goto f; } - GI(t2+1,&iRating) goto f; _stprintf_s(tszRating, sizeof(tszRating), TEXT("%d"), iRating); ListView_SetItemText(HElv, lpLvi->iItem, ELVSIRATING, tszRating); diff --git a/c/main.c b/c/main.c index bcee867..3f94986 100644 --- a/c/main.c +++ b/c/main.c @@ -35,7 +35,6 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR tszErr; MSG msg; INITCOMMONCONTROLSEX icc; - term_t t; WNDCLASSEX wc; /* Set constant values. */ @@ -89,8 +88,7 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, ShowWindow(hWnd, nCmdShow); /* Populate episode list view. */ - t = T(0); - P("track_episodes","update_tracked_episodes",0,t); + Pl("track_episodes", "update_tracked_episodes", ""); ElvUpdate(); ElvSelectFocus(); @@ -124,34 +122,22 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) FreeLibrary(hModule); } SetWindowPos(hWnd, NULL, -1, -1, Dpi(510), Dpi(400), SWP_NOMOVE); - { - term_t t; - t = T(1); - P("cfg","get_view_watched",1,t) goto s; - GI(t,&BViewWatched) goto s; + if (Pl("cfg","get_view_watched","i",&BViewWatched)) CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, BViewWatched? MF_CHECKED: MF_UNCHECKED); - } - s: { - term_t t; - t = T(1); - P("cfg","get_view_tv_original",1,t) goto t; - GI(t,&BViewTVOriginal) goto t; + if (Pl("cfg","get_view_tv_original","i",&BViewTVOriginal)) CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, BViewTVOriginal? MF_CHECKED: MF_UNCHECKED); - } - t: { + { char *sz; - term_t t; - t = T(1); - P("cfg","get_limit_screenwriter",1,t) goto u; - GAC(t,&sz) goto u; + if (!Pl("cfg","get_limit_screenwriter","s",&sz)) + goto s; strcpy_s(SzLimitScreenwriter, sizeof(SzLimitScreenwriter), sz); CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, SzLimitScreenwriter[0]? MF_UNCHECKED: MF_CHECKED); } - u: SetupFonts(); + s: SetupFonts(); DlvCreate(); ElvCreate(); UpdateTheme(); @@ -167,13 +153,9 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) extern HWND HElv; lvi.mask = LVIF_PARAM; if ((lvi.iItem=ListView_GetNextItem(HElv,-1,LVNI_FOCUSED)) != -1 - && ListView_GetItem(HElv, &lvi)) { - term_t t; - t = T(1); - PI(t,lvi.lParam) goto q; - P("cfg","set_focus",1,t); - } - q: PostQuitMessage(0); + && ListView_GetItem(HElv, &lvi)) + Pl("cfg","set_focus","I",lvi.lParam); + PostQuitMessage(0); break; } case WM_SIZE: @@ -210,13 +192,9 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) break; case WA_ACTIVE: case WA_CLICKACTIVE: - { - term_t t; SetFocus(HFocus); - t = T(0); - P("track_episodes","update_tracked_episodes",0,t); + Pl("track_episodes","update_tracked_episodes",""); ElvRedraw(); - } } break; case WM_NOTIFY: @@ -234,21 +212,13 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) ElvUpdate(); break; case IDM_FILE_FETCH_DATA: - { - term_t t; - t = T(0); - P("episode_data","update_episode_data",0,t); + Pl("episode_data","update_episode_data",""); ElvUpdate(); break; - } case IDM_FILE_FETCH_SCREENWRITERS: - { - term_t t; - t = T(0); - P("episode_data","update_screenwriters",0,t); + Pl("episode_data","update_screenwriters",""); ElvUpdate(); break; - } case IDM_FILE_ABOUT: DialogBox( GetModuleHandle(NULL), @@ -260,15 +230,12 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case IDM_VIEW_WATCHED: { int iEpFocus; - term_t t; extern HWND HElv; CheckMenuItem(GetMenu(hWnd), IDM_VIEW_WATCHED, BViewWatched? MF_UNCHECKED: MF_CHECKED); BViewWatched = !BViewWatched; ElvUpdate(); - t = T(1); - PI(t,BViewWatched) break; - P("cfg","set_view_watched",1,t); + Pl("cfg","set_view_watched","I",BViewWatched); iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED); if (iEpFocus == -1) break; ListView_EnsureVisible(HElv, iEpFocus, TRUE); @@ -277,15 +244,12 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case IDM_VIEW_TV_ORIGINAL: { int iEpFocus; - term_t t; extern HWND HElv; CheckMenuItem(GetMenu(hWnd), IDM_VIEW_TV_ORIGINAL, BViewTVOriginal? MF_UNCHECKED: MF_CHECKED); BViewTVOriginal = !BViewTVOriginal; ElvUpdate(); - t = T(1); - PI(t,BViewTVOriginal) break; - P("cfg","set_view_tv_original",1,t); + Pl("cfg","set_view_tv_original","I",BViewTVOriginal); iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED); if (iEpFocus == -1) break; ListView_EnsureVisible(HElv, iEpFocus, TRUE); @@ -293,40 +257,31 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } case IDM_VIEW_OTHERS: /* Show/hide other screenwriters. */ { - atom_t a; int iEpFocus; - term_t t; extern HWND HElv; if (SzLimitScreenwriter[0]) { CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, MF_CHECKED); SzLimitScreenwriter[0] = 0; } else { - atom_t a; char *sz; LVITEM lvi; - term_t t; iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED); if (iEpFocus == -1) break; lvi.iItem = iEpFocus; lvi.mask = LVIF_PARAM; if (!ListView_GetItem(HElv, &lvi)) break; - t = T(3); - a = A("Screenwriter"); - PI(t,lvi.lParam) break; - PA(t+1,a) break; - P("episode_data","episode_datum",3,t) break; - GAC(t+2,&sz) break; + if (!Pl("episode_data","episode_datum","ISs", + lvi.lParam,"Screenwriter",&sz)) + break; strcpy_s(SzLimitScreenwriter, sizeof(SzLimitScreenwriter), sz); CheckMenuItem(GetMenu(hWnd), IDM_VIEW_OTHERS, MF_UNCHECKED); } ElvUpdate(); - t = T(1); - a = A(SzLimitScreenwriter); - PA(t,a) break; - P("cfg","set_limit_screenwriter",1,t); + Pl("cfg","set_limit_screenwriter","S", + SzLimitScreenwriter); iEpFocus = ListView_GetNextItem(HElv, -1, LVNI_FOCUSED); if (iEpFocus == -1) break; ListView_EnsureVisible(HElv, iEpFocus, TRUE); @@ -350,8 +305,8 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case IDM_RATE1: case IDM_RATE0: { + int iRating; LVITEM lvi; - term_t t; extern HWND HElv; /* Look through selected items, applying the @@ -359,70 +314,68 @@ WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) lvi.mask = LVIF_PARAM; lvi.iItem = -1; - t = T(2); while ((lvi.iItem = ListView_GetNextItem( HElv, lvi.iItem, LVNI_SELECTED)) != -1) { if (!ListView_GetItem(HElv, &lvi)) goto b; - PI(t,lvi.lParam) goto b; switch (LOWORD(wParam)) { case IDM_WATCH_LOCALLY: - P("local_episode","open_episode_locally",1,t); + Pl("local_episode","open_episode_locally","I",lvi.lParam); break; case IDM_WATCH_ONLINE: - P("local_episode","open_episode_online",1,t); + Pl("local_episode","open_episode_online","I",lvi.lParam); break; case IDM_TOGGLE: - P("track_episodes","toggle_episode",1,t); + Pl("track_episodes","toggle_episode","I",lvi.lParam); ElvRedraw(); break; case IDM_FORGET: - P("track_episodes","forget_episode",1,t); - P("track_episodes","update_tracked_episodes",0,t); + Pl("track_episodes","forget_episode","I",lvi.lParam); + Pl("track_episodes","update_tracked_episodes",""); ElvRedraw(); break; case IDM_LOOKUP: - P("episode_data","retract_episode",1,t); + Pl("episode_data","retract_episode","I",lvi.lParam); ElvUpdateItem(&lvi); ElvRedraw(); DlvShowEpisode(lvi.lParam); break; case IDM_WIKI: - P("episode_data","open_episode_wiki",1,t); + Pl("episode_data","open_episode_wiki","I",lvi.lParam); break; case IDM_RATE10: - PI(t+1,10) break; + iRating = 0; goto r; case IDM_RATE9: - PI(t+1,9) break; + iRating = 9; goto r; case IDM_RATE8: - PI(t+1,8) break; + iRating = 8; goto r; case IDM_RATE7: - PI(t+1,7) break; + iRating = 7; goto r; case IDM_RATE6: - PI(t+1,6) break; + iRating = 6; goto r; case IDM_RATE5: - PI(t+1,5) break; + iRating = 5; goto r; case IDM_RATE4: - PI(t+1,4) break; + iRating = 4; goto r; case IDM_RATE3: - PI(t+1,3) break; + iRating = 3; goto r; case IDM_RATE2: - PI(t+1,2) break; + iRating = 2; goto r; case IDM_RATE1: - PI(t+1,1) break; + iRating = 1; goto r; case IDM_RATE0: - PI(t+1,0) break; - r: P("episode_data","rate_episode",2,t); + iRating = 0; + r: Pl("episode_data","rate_episode","II",lvi.lParam,iRating); ElvUpdateItem(&lvi); break; } @@ -499,10 +452,8 @@ CreateStatusBar(HWND hWndParent, HINSTANCE hInstance) int Attach() { - term_t t; - t = T(0); - P("track_episodes","attach",0,t) return 0; - P("episode_data","attach",0,t) return 0; + if (!Pl("track_episodes","attach","")) return 0; + if (!Pl("episode_data","attach","")) return 0; return 1; } diff --git a/c/pl.c b/c/pl.c new file mode 100644 index 0000000..6f7a07d --- /dev/null +++ b/c/pl.c @@ -0,0 +1,76 @@ +#include +#include +#include + +int +Pl(char *szMod, char *szPred, char *szFmt, ...) +{ + int i, iArity; + term_t t; + va_list vl; + + va_start(vl, szFmt); + iArity = strlen(szFmt); + t = PL_new_term_refs(iArity); + + for (i = 0; szFmt[i]; i++) { + switch (szFmt[i]) { + case 'I': + { + int x; + x = va_arg(vl, int); + if (!PL_put_integer(t+i, x)) return 0; + break; + } + case 'A': + { + atom_t x; + x = va_arg(vl, atom_t); + if (!PL_put_atom(t+i, x)) return 0; + break; + } + case 'S': + { + atom_t a; + char *x; + x = va_arg(vl, char *); + a = PL_new_atom(x); + if (!PL_put_atom(t+i, a)) return 0; + break; + } + } + } + + if (!PL_call_predicate(NULL, PL_Q_NORMAL, + PL_predicate(szPred, iArity, szMod), t)) + return 0; + + for (i = 0; szFmt[i]; i++) { + switch (szFmt[i]) { + case 'i': + { + int *lp; + lp = va_arg(vl, int *); + if (!PL_get_integer(t+i, lp)) return 0; + break; + } + case 'a': + { + atom_t *lp; + lp = va_arg(vl, atom_t *); + if (!PL_get_atom(t+i, lp)) return 0; + break; + } + case 's': + { + char **lp; + lp = va_arg(vl, char **); + if (!PL_get_atom_chars(t+i, lp)) return 0; + break; + } + } + } + + va_end(vl); + return 1; +} -- cgit v1.2.3