diff options
Diffstat (limited to 'libsmartcols')
37 files changed, 10635 insertions, 0 deletions
diff --git a/libsmartcols/COPYING b/libsmartcols/COPYING new file mode 100644 index 0000000..1c4252a --- /dev/null +++ b/libsmartcols/COPYING @@ -0,0 +1,8 @@ +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later +version. + +The complete text of the license is available in the +../Documentation/licenses/COPYING.LGPL-2.1-or-later file. diff --git a/libsmartcols/Makemodule.am b/libsmartcols/Makemodule.am new file mode 100644 index 0000000..012848b --- /dev/null +++ b/libsmartcols/Makemodule.am @@ -0,0 +1,15 @@ +if BUILD_LIBSMARTCOLS + +include libsmartcols/src/Makemodule.am +include libsmartcols/samples/Makemodule.am + +if ENABLE_GTK_DOC +# Docs uses separate Makefiles +SUBDIRS += libsmartcols/docs +endif + +pkgconfig_DATA += libsmartcols/smartcols.pc +PATHFILES += libsmartcols/smartcols.pc +EXTRA_DIST += libsmartcols/COPYING + +endif # BUILD_LIBSMARTCOLS diff --git a/libsmartcols/docs/Makefile.am b/libsmartcols/docs/Makefile.am new file mode 100644 index 0000000..e8a7600 --- /dev/null +++ b/libsmartcols/docs/Makefile.am @@ -0,0 +1,93 @@ +## Process this file with automake to produce Makefile.in + +# We require automake 1.10 at least. +AUTOMAKE_OPTIONS = 1.10 + +# This is a blank Makefile.am for using gtk-doc. +# Copy this to your project's API docs directory and modify the variables to +# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples +# of using the various options. + +# The name of the module, e.g. 'glib'. +DOC_MODULE=libsmartcols + +# Uncomment for versioned docs and specify the version of the module, e.g. '2'. +#DOC_MODULE_VERSION=2 + +# The top-level SGML file. You can change this if you want to. +DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.xml + +# The directory containing the source code. Relative to $(srcdir). +# gtk-doc will search all .c & .h files beneath here for inline comments +# documenting the functions and macros. +# e.g. DOC_SOURCE_DIR=../../../gtk +DOC_SOURCE_DIR=../src + +# Extra options to pass to gtkdoc-scangobj. Not normally needed. +SCANGOBJ_OPTIONS= + +# Extra options to supply to gtkdoc-scan. +# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" +SCAN_OPTIONS= + +# Extra options to supply to gtkdoc-mkdb. +# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml +MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space scols + +# Extra options to supply to gtkdoc-mktmpl +# e.g. MKTMPL_OPTIONS=--only-section-tmpl +MKTMPL_OPTIONS= + +# Extra options to supply to gtkdoc-mkhtml +MKHTML_OPTIONS= + +# Extra options to supply to gtkdoc-fixref. Not normally needed. +# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html +FIXXREF_OPTIONS= + +# Used for dependencies. The docs will be rebuilt if any of these change. +# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h +# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c +HFILE_GLOB=$(top_builddir)/libsmartcols/src/libsmartcols.h +CFILE_GLOB=$(top_srcdir)/libsmartcols/src/*.c + +# Extra header to include when scanning, which are not under DOC_SOURCE_DIR +# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h +EXTRA_HFILES= + +# Header files to ignore when scanning. Use base file name, no paths +# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h +IGNORE_HFILES=smartcolsP.h + +# Images to copy into HTML directory. +# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png +HTML_IMAGES= + +# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). +# e.g. content_files=running.sgml building.sgml changes-2.0.sgml +content_files = $(builddir)/version.xml + +# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded +# These files must be listed here *and* in content_files +# e.g. expand_content_files=running.sgml +expand_content_files= + +# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. +# Only needed if you are using gtkdoc-scangobj to dynamically query widget +# signals and properties. +# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) +# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) +GTKDOC_CFLAGS= +GTKDOC_LIBS= + +# This includes the standard gtk-doc make rules, copied by gtkdocize. +include $(top_srcdir)/config/gtk-doc.make + +# Other files to distribute +# e.g. EXTRA_DIST += version.xml.in +EXTRA_DIST += version.xml.in + +# Files not to distribute +# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types +# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt +DISTCLEANFILES += version.xml diff --git a/libsmartcols/docs/Makefile.in b/libsmartcols/docs/Makefile.in new file mode 100644 index 0000000..de740f1 --- /dev/null +++ b/libsmartcols/docs/Makefile.in @@ -0,0 +1,897 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# +# WARNING: this is not gtk-doc.make file from gtk-doc project. This +# file has been modified to match with util-linux requirements: +# +# * install files to $datadir +# * don't maintain generated files in git repository +# * don't distribute the final html files +# * don't require --enable-gtk-doc for "make dist" +# * support out-of-tree build ($srcdir != $builddir) +# +# -- kzak, Nov 2009 +# + +#################################### +# Everything below here is generic # +#################################### +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = libsmartcols/docs +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_vscript.m4 \ + $(top_srcdir)/m4/ax_compare_version.m4 \ + $(top_srcdir)/m4/compiler.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \ + $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/tls.m4 \ + $(top_srcdir)/m4/ul.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = version.xml +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/version.xml.in \ + $(top_srcdir)/config/gtk-doc.make +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ADJTIME_PATH = @ADJTIME_PATH@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASAN_LDFLAGS = @ASAN_LDFLAGS@ +ASCIIDOCTOR = @ASCIIDOCTOR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BSD_WARN_CFLAGS = @BSD_WARN_CFLAGS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTSETUP_CFLAGS = @CRYPTSETUP_CFLAGS@ +CRYPTSETUP_LIBS = @CRYPTSETUP_LIBS@ +CRYPTSETUP_LIBS_STATIC = @CRYPTSETUP_LIBS_STATIC@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DAEMON_CFLAGS = @DAEMON_CFLAGS@ +DAEMON_LDFLAGS = @DAEMON_LDFLAGS@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +ECONF_CFLAGS = @ECONF_CFLAGS@ +ECONF_LIBS = @ECONF_LIBS@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +FUZZING_ENGINE_LDFLAGS = @FUZZING_ENGINE_LDFLAGS@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GREP = @GREP@ +GTKDOC_CHECK = @GTKDOC_CHECK@ +HTML_DIR = @HTML_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBBLKID_DATE = @LIBBLKID_DATE@ +LIBBLKID_VERSION = @LIBBLKID_VERSION@ +LIBBLKID_VERSION_INFO = @LIBBLKID_VERSION_INFO@ +LIBFDISK_MAJOR_VERSION = @LIBFDISK_MAJOR_VERSION@ +LIBFDISK_MINOR_VERSION = @LIBFDISK_MINOR_VERSION@ +LIBFDISK_PATCH_VERSION = @LIBFDISK_PATCH_VERSION@ +LIBFDISK_PC_REQUIRES = @LIBFDISK_PC_REQUIRES@ +LIBFDISK_VERSION = @LIBFDISK_VERSION@ +LIBFDISK_VERSION_INFO = @LIBFDISK_VERSION_INFO@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBMOUNT_MAJOR_VERSION = @LIBMOUNT_MAJOR_VERSION@ +LIBMOUNT_MINOR_VERSION = @LIBMOUNT_MINOR_VERSION@ +LIBMOUNT_PATCH_VERSION = @LIBMOUNT_PATCH_VERSION@ +LIBMOUNT_VERSION = @LIBMOUNT_VERSION@ +LIBMOUNT_VERSION_INFO = @LIBMOUNT_VERSION_INFO@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBSMARTCOLS_VERSION = @LIBSMARTCOLS_VERSION@ +LIBSMARTCOLS_VERSION_INFO = @LIBSMARTCOLS_VERSION_INFO@ +LIBTOOL = @LIBTOOL@ +LIBUSER_CFLAGS = @LIBUSER_CFLAGS@ +LIBUSER_LIBS = @LIBUSER_LIBS@ +LIBUUID_VERSION = @LIBUUID_VERSION@ +LIBUUID_VERSION_INFO = @LIBUUID_VERSION_INFO@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAGIC_LIBS = @MAGIC_LIBS@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MATH_LIBS = @MATH_LIBS@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NCURSES5_CONFIG = @NCURSES5_CONFIG@ +NCURSES6_CONFIG = @NCURSES6_CONFIG@ +NCURSESW5_CONFIG = @NCURSESW5_CONFIG@ +NCURSESW6_CONFIG = @NCURSESW6_CONFIG@ +NCURSESW_CFLAGS = @NCURSESW_CFLAGS@ +NCURSESW_LIBS = @NCURSESW_LIBS@ +NCURSES_CFLAGS = @NCURSES_CFLAGS@ +NCURSES_LIBS = @NCURSES_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +NO_UNUSED_WARN_CFLAGS = @NO_UNUSED_WARN_CFLAGS@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PO4A = @PO4A@ +POSUB = @POSUB@ +PYTHON = @PYTHON@ +PYTHON_CFLAGS = @PYTHON_CFLAGS@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_LIBS = @PYTHON_LIBS@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +PYTHON_WARN_CFLAGS = @PYTHON_WARN_CFLAGS@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +READLINE_LIBS_STATIC = @READLINE_LIBS_STATIC@ +REALTIME_LIBS = @REALTIME_LIBS@ +RTAS_LIBS = @RTAS_LIBS@ +SED = @SED@ +SELINUX_CFLAGS = @SELINUX_CFLAGS@ +SELINUX_LIBS = @SELINUX_LIBS@ +SELINUX_LIBS_STATIC = @SELINUX_LIBS_STATIC@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SOCKET_LIBS = @SOCKET_LIBS@ +SOLIB_CFLAGS = @SOLIB_CFLAGS@ +SOLIB_LDFLAGS = @SOLIB_LDFLAGS@ +STRIP = @STRIP@ +SUID_CFLAGS = @SUID_CFLAGS@ +SUID_LDFLAGS = @SUID_LDFLAGS@ +SYSCONFSTATICDIR = @SYSCONFSTATICDIR@ +SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@ +SYSTEMD_DAEMON_CFLAGS = @SYSTEMD_DAEMON_CFLAGS@ +SYSTEMD_DAEMON_LIBS = @SYSTEMD_DAEMON_LIBS@ +SYSTEMD_JOURNAL_CFLAGS = @SYSTEMD_JOURNAL_CFLAGS@ +SYSTEMD_JOURNAL_LIBS = @SYSTEMD_JOURNAL_LIBS@ +SYSTEMD_LIBS = @SYSTEMD_LIBS@ +TINFOW_CFLAGS = @TINFOW_CFLAGS@ +TINFOW_LIBS = @TINFOW_LIBS@ +TINFO_CFLAGS = @TINFO_CFLAGS@ +TINFO_LIBS = @TINFO_LIBS@ +TINFO_LIBS_STATIC = @TINFO_LIBS_STATIC@ +UBSAN_LDFLAGS = @UBSAN_LDFLAGS@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +VSCRIPT_LDFLAGS = @VSCRIPT_LDFLAGS@ +WARN_CFLAGS = @WARN_CFLAGS@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XSLTPROC = @XSLTPROC@ +YACC = @YACC@ +YFLAGS = @YFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bashcompletiondir = @bashcompletiondir@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +sysconfstaticdir = @sysconfstaticdir@ +systemdsystemunitdir = @systemdsystemunitdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +usrbin_execdir = @usrbin_execdir@ +usrlib_execdir = @usrlib_execdir@ +usrsbin_execdir = @usrsbin_execdir@ +vendordir = @vendordir@ +with_bashcompletiondir = @with_bashcompletiondir@ +with_systemdsystemunitdir = @with_systemdsystemunitdir@ + +# We require automake 1.10 at least. +AUTOMAKE_OPTIONS = 1.10 + +# This is a blank Makefile.am for using gtk-doc. +# Copy this to your project's API docs directory and modify the variables to +# suit your project. See the GTK+ Makefiles in gtk+/docs/reference for examples +# of using the various options. + +# The name of the module, e.g. 'glib'. +DOC_MODULE = libsmartcols + +# Uncomment for versioned docs and specify the version of the module, e.g. '2'. +#DOC_MODULE_VERSION=2 + +# The top-level SGML file. You can change this if you want to. +DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml + +# The directory containing the source code. Relative to $(srcdir). +# gtk-doc will search all .c & .h files beneath here for inline comments +# documenting the functions and macros. +# e.g. DOC_SOURCE_DIR=../../../gtk +DOC_SOURCE_DIR = ../src + +# Extra options to pass to gtkdoc-scangobj. Not normally needed. +SCANGOBJ_OPTIONS = + +# Extra options to supply to gtkdoc-scan. +# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" +SCAN_OPTIONS = + +# Extra options to supply to gtkdoc-mkdb. +# e.g. MKDB_OPTIONS=--sgml-mode --output-format=xml +MKDB_OPTIONS = --sgml-mode --output-format=xml --name-space scols + +# Extra options to supply to gtkdoc-mktmpl +# e.g. MKTMPL_OPTIONS=--only-section-tmpl +MKTMPL_OPTIONS = + +# Extra options to supply to gtkdoc-mkhtml +MKHTML_OPTIONS = + +# Extra options to supply to gtkdoc-fixref. Not normally needed. +# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html +FIXXREF_OPTIONS = + +# Used for dependencies. The docs will be rebuilt if any of these change. +# e.g. HFILE_GLOB=$(top_srcdir)/gtk/*.h +# e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c +HFILE_GLOB = $(top_builddir)/libsmartcols/src/libsmartcols.h +CFILE_GLOB = $(top_srcdir)/libsmartcols/src/*.c + +# Extra header to include when scanning, which are not under DOC_SOURCE_DIR +# e.g. EXTRA_HFILES=$(top_srcdir}/contrib/extra.h +EXTRA_HFILES = + +# Header files to ignore when scanning. Use base file name, no paths +# e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h +IGNORE_HFILES = smartcolsP.h + +# Images to copy into HTML directory. +# e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png +HTML_IMAGES = + +# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). +# e.g. content_files=running.sgml building.sgml changes-2.0.sgml +content_files = $(builddir)/version.xml + +# SGML files where gtk-doc abbreviations (#GtkWidget) are expanded +# These files must be listed here *and* in content_files +# e.g. expand_content_files=running.sgml +expand_content_files = + +# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. +# Only needed if you are using gtkdoc-scangobj to dynamically query widget +# signals and properties. +# e.g. GTKDOC_CFLAGS=-I$(top_srcdir) -I$(top_builddir) $(GTK_DEBUG_FLAGS) +# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) +GTKDOC_CFLAGS = +GTKDOC_LIBS = +@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_CC = $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_CC = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_LD = $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) +@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_LD = $(LIBTOOL) --tag=CC --mode=link $(CC) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) +@GTK_DOC_USE_LIBTOOL_FALSE@GTKDOC_RUN = +@GTK_DOC_USE_LIBTOOL_TRUE@GTKDOC_RUN = $(LIBTOOL) --mode=execute + +# We set GPATH here; this gives us semantics for GNU make +# which are more like other make's VPATH, when it comes to +# whether a source that is a target of one rule is then +# searched for in VPATH/GPATH. +# +GPATH = $(srcdir) +TARGET_DIR = $(docdir)/$(DOC_MODULE) + +# Files not to distribute +# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types +# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt +DISTCLEANFILES = version.xml + +# This includes the standard gtk-doc make rules, copied by gtkdocize. + +# Other files to distribute +# e.g. EXTRA_DIST += version.xml.in +EXTRA_DIST = $(content_files) $(HTML_IMAGES) $(DOC_MAIN_SGML_FILE) \ + $(DOC_MODULE)-sections.txt version.xml.in +# $(DOC_MODULE)-overrides.txt +DOC_STAMPS = scan-build.stamp sgml-build.stamp html-build.stamp \ + $(srcdir)/setup.stamp $(srcdir)/sgml.stamp \ + $(srcdir)/html.stamp + +SCANOBJ_FILES = \ + $(DOC_MODULE).args \ + $(DOC_MODULE).hierarchy \ + $(DOC_MODULE).interfaces \ + $(DOC_MODULE).prerequisites \ + $(DOC_MODULE).signals \ + $(DOC_MODULE).types # util-linux: we don't use types + +REPORT_FILES = \ + $(DOC_MODULE)-undocumented.txt \ + $(DOC_MODULE)-undeclared.txt \ + $(DOC_MODULE)-unused.txt + +CLEANFILES = $(SCANOBJ_FILES) $(REPORT_FILES) $(DOC_STAMPS) +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/config/gtk-doc.make $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign libsmartcols/docs/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign libsmartcols/docs/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/config/gtk-doc.make $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +version.xml: $(top_builddir)/config.status $(srcdir)/version.xml.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile all-local +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-local mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-local + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-data-local + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-local + +.MAKE: install-am install-strip + +.PHONY: all all-am all-local check check-am clean clean-generic \ + clean-libtool clean-local cscopelist-am ctags-am distclean \ + distclean-generic distclean-libtool distclean-local distdir \ + dvi dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-data-local install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am uninstall-local + +.PRECIOUS: Makefile + + +@ENABLE_GTK_DOC_TRUE@all-local: html-build.stamp +@ENABLE_GTK_DOC_FALSE@all-local: + +docs: html-build.stamp + +$(REPORT_FILES): sgml-build.stamp + +#### setup #### + +setup-build.stamp: + -@if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ + echo 'gtk-doc: Preparing build'; \ + files=`echo $(EXTRA_DIST) $(expand_content_files) $(srcdir)/$(DOC_MODULE).types`; \ + if test "x$$files" != "x" ; then \ + for file in $$files ; do \ + test -f $(abs_srcdir)/$$file && \ + cp -p $(abs_srcdir)/$$file $(abs_builddir)/; \ + done \ + fi \ + fi + @touch setup-build.stamp + +setup.stamp: setup-build.stamp + @true + +#### scan #### + +scan-build.stamp: $(HFILE_GLOB) $(CFILE_GLOB) $(srcdir)/$(DOC_MODULE)-*.txt $(content_files) + + @test -f $(DOC_MODULE)-sections.txt || \ + cp $(srcdir)/$(DOC_MODULE)-sections.txt $(builddir) + + $(AM_V_GEN)gtkdoc-scan --module=$(DOC_MODULE) \ + --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \ + --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \ + --ignore-decorators="__ul_attribute__\(.*\)" \ + --ignore-headers="$(IGNORE_HFILES)" \ + --output-dir=$(builddir) \ + $(SCAN_OPTIONS) $(EXTRA_HFILES) + + @ if grep -l '^..*$$' $(srcdir)/$(DOC_MODULE).types > /dev/null 2>&1 ; then \ + CC="$(GTKDOC_CC)" LD="$(GTKDOC_LD)" RUN="$(GTKDOC_RUN)" \ + CFLAGS="$(GTKDOC_CFLAGS) $(CFLAGS)" LDFLAGS="$(GTKDOC_LIBS) \ + $(LDFLAGS)" gtkdoc-scangobj $(SCANGOBJ_OPTIONS) \ + --module=$(DOC_MODULE) --output-dir=$(builddir) ; \ + else \ + for i in $(SCANOBJ_FILES) ; do \ + test -f $$i || touch $$i ; \ + done \ + fi + @ touch scan-build.stamp + +$(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt: scan-build.stamp + @true + +#### templates #### +# +#tmpl-build.stamp: $(DOC_MODULE)-decl.txt $(SCANOBJ_FILES) $(srcdir)/$(DOC_MODULE)-sections.txt $(DOC_MODULE)-overrides.txt +# @echo 'gtk-doc: Rebuilding template files' +# test -z $(builddir)/tmpl || $(MKDIR_P) $(builddir)/tmpl +# gtkdoc-mktmpl --module=$(DOC_MODULE) \ +# $(MKTMPL_OPTIONS) +# touch tmpl-build.stamp +# +#tmpl.stamp: tmpl-build.stamp +# @true +# +#tmpl/*.sgml: +# @true +# + +#### xml #### + +sgml-build.stamp: setup.stamp $(HFILE_GLOB) $(CFILE_GLOB) $(DOC_MODULE)-decl.txt $(DOC_MODULE)-sections.txt $(expand_content_files) + $(AM_V_GEN)gtkdoc-mkdb --module=$(DOC_MODULE) \ + --source-dir=$(srcdir)/$(DOC_SOURCE_DIR) \ + --source-dir=$(builddir)/$(DOC_SOURCE_DIR) \ + --output-format=xml \ + --ignore-files="$(IGNORE_HFILES)" \ + --expand-content-files="$(expand_content_files)" \ + --main-sgml-file=$(srcdir)/$(DOC_MAIN_SGML_FILE) \ + $(MKDB_OPTIONS) + @touch sgml-build.stamp + +sgml.stamp: sgml-build.stamp + @true + +#### html #### + +html-build.stamp: sgml.stamp $(srcdir)/$(DOC_MAIN_SGML_FILE) $(content_files) + @rm -rf $(builddir)/html + @$(MKDIR_P) $(builddir)/html + $(AM_V_GEN)cd $(builddir)/html && \ + gtkdoc-mkhtml --path="$(abs_builddir):$(abs_builddir)/xml:$(abs_srcdir)" \ + $(MKHTML_OPTIONS) \ + $(DOC_MODULE) \ + $(abs_srcdir)/$(DOC_MAIN_SGML_FILE) + + @test "x$(HTML_IMAGES)" = "x" || \ + ( cd $(srcdir) && cp $(HTML_IMAGES) $(abs_builddir)/html ) + + $(AM_V_GEN)gtkdoc-fixxref --module-dir=html \ + --module=$(DOC_MODULE) \ + --html-dir=$(HTML_DIR) \ + $(FIXXREF_OPTIONS) + @touch html-build.stamp + +############## + +clean-local: + rm -f *~ *.bak + rm -rf .libs + +distclean-local: + rm -rf xml html $(REPORT_FILES) *.stamp \ + $(DOC_MODULE)-overrides.txt \ + $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt + test $(abs_builddir) == $(abs_srcdir) || \ + rm -f $(DOC_MODULE)-*.txt $(DOC_MODULE)-*.xml *.xml.in + +install-data-local: + installfiles=`echo $(builddir)/html/*`; \ + if test "$$installfiles" = '$(builddir)/html/*'; \ + then echo '-- Nothing to install' ; \ + else \ + if test -n "$(DOC_MODULE_VERSION)"; then \ + installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \ + else \ + installdir="$(DESTDIR)$(TARGET_DIR)"; \ + fi; \ + $(mkinstalldirs) $${installdir} ; \ + for i in $$installfiles; do \ + echo '-- Installing '$$i ; \ + $(INSTALL_DATA) $$i $${installdir}; \ + done; \ + if test -n "$(DOC_MODULE_VERSION)"; then \ + mv -f $${installdir}/$(DOC_MODULE).devhelp2 \ + $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp2; \ + mv -f $${installdir}/$(DOC_MODULE).devhelp \ + $${installdir}/$(DOC_MODULE)-$(DOC_MODULE_VERSION).devhelp; \ + fi; \ + ! which gtkdoc-rebase >/dev/null 2>&1 || \ + gtkdoc-rebase --relative --dest-dir=$(DESTDIR) --html-dir=$${installdir} ; \ + fi + +uninstall-local: + if test -n "$(DOC_MODULE_VERSION)"; then \ + installdir="$(DESTDIR)$(TARGET_DIR)-$(DOC_MODULE_VERSION)"; \ + else \ + installdir="$(DESTDIR)$(TARGET_DIR)"; \ + fi; \ + rm -rf $${installdir} + +# +# Require gtk-doc when making dist +# +@ENABLE_GTK_DOC_TRUE@dist-check-gtkdoc: +@ENABLE_GTK_DOC_FALSE@dist-check-gtkdoc: +@ENABLE_GTK_DOC_FALSE@ @echo "*** gtk-doc must be installed and enabled in order to make dist" +@ENABLE_GTK_DOC_FALSE@ @false + +#dist-hook: dist-check-gtkdoc dist-hook-local sgml.stamp html-build.stamp +# mkdir $(distdir)/tmpl +# mkdir $(distdir)/xml +# mkdir $(distdir)/html +# -cp $(srcdir)/tmpl/*.sgml $(distdir)/tmpl +# -cp $(srcdir)/xml/*.xml $(distdir)/xml +# cp $(srcdir)/html/* $(distdir)/html +# -cp $(srcdir)/$(DOC_MODULE).types $(distdir)/ +# -cp $(srcdir)/$(DOC_MODULE)-sections.txt $(distdir)/ +# cd $(distdir) && rm -f $(DISTCLEANFILES) +# ! which gtkdoc-rebase >/dev/null 2>&1 || \ +# gtkdoc-rebase --online --relative --html-dir=$(distdir)/html +# +#.PHONY : dist-hook-local docs + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/libsmartcols/docs/libsmartcols-docs.xml b/libsmartcols/docs/libsmartcols-docs.xml new file mode 100644 index 0000000..9ded584 --- /dev/null +++ b/libsmartcols/docs/libsmartcols-docs.xml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" + "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" +[ + <!ENTITY version SYSTEM "version.xml"> +]> +<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude"> + <bookinfo> + <title>libsmartcols Reference Manual</title> + <releaseinfo>for libsmartcols version &version;</releaseinfo> + <copyright> + <year>2014-2022</year> + <holder>Karel Zak <kzak@redhat.com></holder> + </copyright> + </bookinfo> + + <part id="overview"> + <title>libsmartcols Overview</title> + <partintro> + <para> +The libsmartcols library is used for smart adaptive formatting of tabular data. + </para> + <para> +The library is part of the util-linux package since version 2.25 and is +available from https://www.kernel.org/pub/linux/utils/util-linux/. + </para> + </partintro> + </part> + + <part> + <title>Data manipulation</title> + <xi:include href="xml/table.xml"/> + <xi:include href="xml/column.xml"/> + <xi:include href="xml/line.xml"/> + <xi:include href="xml/cell.xml"/> + <xi:include href="xml/symbols.xml"/> + <xi:include href="xml/grouping.xml"/> + </part> + <part> + <title>Printing</title> + <xi:include href="xml/table_print.xml"/> + </part> + <part> + <title>Misc</title> + <xi:include href="xml/iter.xml"/> + <xi:include href="xml/version-utils.xml"/> + <xi:include href="xml/init.xml"/> + </part> + <index id="api-index"> + <title>API Index</title> + <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include> + </index> + <index role="2.27"> + <title>Index of new symbols in 2.27</title> + <xi:include href="xml/api-index-2.27.xml"><xi:fallback /></xi:include> + </index> + <index role="2.28"> + <title>Index of new symbols in 2.28</title> + <xi:include href="xml/api-index-2.28.xml"><xi:fallback /></xi:include> + </index> + <index role="2.29"> + <title>Index of new symbols in 2.29</title> + <xi:include href="xml/api-index-2.29.xml"><xi:fallback /></xi:include> + </index> + <index role="2.30"> + <title>Index of new symbols in 2.30</title> + <xi:include href="xml/api-index-2.30.xml"><xi:fallback /></xi:include> + </index> + <index role="2.31"> + <title>Index of new symbols in 2.31</title> + <xi:include href="xml/api-index-2.31.xml"><xi:fallback /></xi:include> + </index> + <index role="2.33"> + <title>Index of new symbols in 2.33</title> + <xi:include href="xml/api-index-2.33.xml"><xi:fallback /></xi:include> + </index> + <index role="2.34"> + <title>Index of new symbols in 2.34</title> + <xi:include href="xml/api-index-2.34.xml"><xi:fallback /></xi:include> + </index> + <index role="2.35"> + <title>Index of new symbols in 2.35</title> + <xi:include href="xml/api-index-2.35.xml"><xi:fallback /></xi:include> + </index> +</book> diff --git a/libsmartcols/docs/libsmartcols-sections.txt b/libsmartcols/docs/libsmartcols-sections.txt new file mode 100644 index 0000000..fd6f2ba --- /dev/null +++ b/libsmartcols/docs/libsmartcols-sections.txt @@ -0,0 +1,215 @@ +<SECTION> +<FILE>cell</FILE> +libscols_cell +scols_cell_copy_content +scols_cell_get_alignment +scols_cell_get_color +scols_cell_get_data +scols_cell_get_flags +scols_cell_get_userdata +scols_cell_refer_data +scols_cell_set_color +scols_cell_set_data +scols_cell_set_flags +scols_cell_set_userdata +scols_cmpstr_cells +scols_reset_cell +</SECTION> + +<SECTION> +<FILE>column</FILE> +libscols_column +scols_column_get_color +scols_column_get_flags +scols_column_get_header +scols_column_get_json_type +scols_column_get_name +scols_column_get_name_as_shellvar +scols_column_get_safechars +scols_column_get_table +scols_column_get_whint +scols_column_get_width +scols_column_is_customwrap +scols_column_is_hidden +scols_column_is_noextremes +scols_column_is_right +scols_column_is_strict_width +scols_column_is_tree +scols_column_is_trunc +scols_column_is_wrap +scols_column_set_cmpfunc +scols_column_set_color +scols_column_set_flags +scols_column_set_json_type +scols_column_set_name +scols_column_set_properties +scols_column_set_safechars +scols_column_set_whint +scols_column_set_wrapfunc +scols_copy_column +scols_new_column +scols_ref_column +scols_unref_column +scols_wrapnl_chunksize +scols_wrapnl_nextchunk +</SECTION> + +<SECTION> +<FILE>iter</FILE> +libscols_iter +scols_free_iter +scols_iter_get_direction +scols_new_iter +scols_reset_iter +</SECTION> + +<SECTION> +<FILE>line</FILE> +libscols_line +scols_copy_line +scols_line_add_child +scols_line_alloc_cells +scols_line_free_cells +scols_line_get_cell +scols_line_get_color +scols_line_get_column_cell +scols_line_get_column_data +scols_line_get_ncells +scols_line_get_parent +scols_line_get_userdata +scols_line_has_children +scols_line_is_ancestor +scols_line_next_child +scols_line_refer_column_data +scols_line_refer_data +scols_line_remove_child +scols_line_set_color +scols_line_set_column_data +scols_line_set_data +scols_line_set_userdata +scols_new_line +scols_ref_line +scols_unref_line +</SECTION> + +<SECTION> +<FILE>grouping</FILE> +scols_line_link_group +scols_table_group_lines +</SECTION> + +<SECTION> +<FILE>symbols</FILE> +libscols_symbols +scols_copy_symbols +scols_new_symbols +scols_ref_symbols +scols_symbols_set_branch +scols_symbols_set_right +scols_symbols_set_vertical +scols_symbols_set_title_padding +scols_symbols_set_cell_padding +scols_symbols_set_group_vertical +scols_symbols_set_group_horizontal +scols_symbols_set_group_first_member +scols_symbols_set_group_last_member +scols_symbols_set_group_middle_member +scols_symbols_set_group_last_child +scols_symbols_set_group_middle_child +scols_unref_symbols +</SECTION> + +<SECTION> +<FILE>table</FILE> +libscols_table +scols_copy_table +scols_new_table +scols_ref_table +scols_sort_table +scols_sort_table_by_tree +scols_table_add_column +scols_table_add_line +scols_table_colors_wanted +scols_table_enable_ascii +scols_table_enable_colors +scols_table_enable_export +scols_table_enable_header_repeat +scols_table_enable_json +scols_table_enable_maxout +scols_table_enable_minout +scols_table_enable_noencoding +scols_table_enable_noheadings +scols_table_enable_nolinesep +scols_table_enable_nowrap +scols_table_enable_raw +scols_table_enable_shellvar +scols_table_get_column +scols_table_get_column_by_name +scols_table_get_column_separator +scols_table_get_line +scols_table_get_line_separator +scols_table_get_name +scols_table_get_ncols +scols_table_get_nlines +scols_table_get_stream +scols_table_get_symbols +scols_table_get_termforce +scols_table_get_termheight +scols_table_get_termwidth +scols_table_get_title +scols_table_is_ascii +scols_table_is_empty +scols_table_is_export +scols_table_is_header_repeat +scols_table_is_json +scols_table_is_maxout +scols_table_is_minout +scols_table_is_noencoding +scols_table_is_noheadings +scols_table_is_nolinesep +scols_table_is_nowrap +scols_table_is_raw +scols_table_is_shellvar +scols_table_is_tree +scols_table_move_column +scols_table_new_column +scols_table_new_line +scols_table_next_column +scols_table_next_line +scols_table_reduce_termwidth +scols_table_remove_column +scols_table_remove_columns +scols_table_remove_line +scols_table_remove_lines +scols_table_set_column_separator +scols_table_set_columns_iter +scols_table_set_default_symbols +scols_table_set_line_separator +scols_table_set_name +scols_table_set_stream +scols_table_set_symbols +scols_table_set_termforce +scols_table_set_termheight +scols_table_set_termwidth +scols_unref_table +</SECTION> + +<SECTION> +<FILE>table_print</FILE> +scols_print_table +scols_print_table_to_string +scols_table_print_range +scols_table_print_range_to_string +</SECTION> + +<SECTION> +<FILE>version-utils</FILE> +scols_get_library_version +scols_parse_version_string +LIBSMARTCOLS_VERSION +</SECTION> + +<SECTION> +<FILE>init</FILE> +scols_init_debug +</SECTION> diff --git a/libsmartcols/docs/version.xml b/libsmartcols/docs/version.xml new file mode 100644 index 0000000..a69af57 --- /dev/null +++ b/libsmartcols/docs/version.xml @@ -0,0 +1 @@ +2.39.3 diff --git a/libsmartcols/docs/version.xml.in b/libsmartcols/docs/version.xml.in new file mode 100644 index 0000000..d78bda9 --- /dev/null +++ b/libsmartcols/docs/version.xml.in @@ -0,0 +1 @@ +@VERSION@ diff --git a/libsmartcols/meson.build b/libsmartcols/meson.build new file mode 100644 index 0000000..122b1e8 --- /dev/null +++ b/libsmartcols/meson.build @@ -0,0 +1,57 @@ +dir_libsmartcols = include_directories('.', 'src') + +defs = configuration_data() +defs.set('LIBSMARTCOLS_VERSION', pc_version) + +configure_file( + input : 'src/libsmartcols.h.in', + output : 'libsmartcols.h', + configuration : defs, + install : build_libsmartcols, + install_dir : join_paths(get_option('includedir'), 'libsmartcols'), +) + +lib_smartcols_sources = ''' + src/smartcolsP.h + src/iter.c + src/symbols.c + src/cell.c + src/column.c + src/line.c + src/table.c + src/print.c + src/print-api.c + src/version.c + src/calculate.c + src/grouping.c + src/walk.c + src/init.c +'''.split() + +libsmartcols_sym = 'src/libsmartcols.sym' +libsmartcols_sym_path = '@0@/@1@'.format(meson.current_source_dir(), libsmartcols_sym) + +lib_smartcols = both_libraries( + 'smartcols', + list_h, + lib_smartcols_sources, + include_directories : [dir_include, dir_libsmartcols], + link_depends : libsmartcols_sym, + version : libsmartcols_version, + link_args : ['-Wl,--version-script=@0@'.format(libsmartcols_sym_path)], + link_with : lib_common, + dependencies : build_libsmartcols ? [] : disabler(), + install : build_libsmartcols) +smartcols_dep = declare_dependency(link_with: lib_smartcols, include_directories: '.') + +lib_smartcols_static = lib_smartcols.get_static_lib() + +if build_libsmartcols + pkgconfig.generate(lib_smartcols, + description : 'table or tree library', + subdirs : 'libsmartcols', + version : pc_version) + if meson.version().version_compare('>=0.54.0') + meson.override_dependency('smartcols', smartcols_dep) + endif +endif diff --git a/libsmartcols/samples/Makemodule.am b/libsmartcols/samples/Makemodule.am new file mode 100644 index 0000000..c0130b9 --- /dev/null +++ b/libsmartcols/samples/Makemodule.am @@ -0,0 +1,53 @@ + +check_PROGRAMS += \ + sample-scols-colors \ + sample-scols-title \ + sample-scols-wrap \ + sample-scols-continuous \ + sample-scols-fromfile \ + sample-scols-grouping-simple \ + sample-scols-grouping-overlay \ + sample-scols-maxout + +sample_scols_cflags = $(AM_CFLAGS) $(NO_UNUSED_WARN_CFLAGS) \ + -I$(ul_libsmartcols_incdir) +sample_scols_ldadd = libsmartcols.la $(LDADD) + +if HAVE_OPENAT +check_PROGRAMS += sample-scols-tree +sample_scols_tree_SOURCES = libsmartcols/samples/tree.c +sample_scols_tree_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_tree_CFLAGS = $(sample_scols_cflags) +endif + +sample_scols_colors_SOURCES = libsmartcols/samples/colors.c +sample_scols_colors_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_colors_CFLAGS = $(sample_scols_cflags) + +sample_scols_title_SOURCES = libsmartcols/samples/title.c +sample_scols_title_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_title_CFLAGS = $(sample_scols_cflags) + +sample_scols_wrap_SOURCES = libsmartcols/samples/wrap.c +sample_scols_wrap_LDADD = $(sample_scols_ldadd) +sample_scols_wrap_CFLAGS = $(sample_scols_cflags) + +sample_scols_continuous_SOURCES = libsmartcols/samples/continuous.c +sample_scols_continuous_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_continuous_CFLAGS = $(sample_scols_cflags) + +sample_scols_maxout_SOURCES = libsmartcols/samples/maxout.c +sample_scols_maxout_LDADD = $(sample_scols_ldadd) +sample_scols_maxout_CFLAGS = $(sample_scols_cflags) + +sample_scols_fromfile_SOURCES = libsmartcols/samples/fromfile.c +sample_scols_fromfile_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_fromfile_CFLAGS = $(sample_scols_cflags) + +sample_scols_grouping_simple_SOURCES = libsmartcols/samples/grouping-simple.c +sample_scols_grouping_simple_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_grouping_simple_CFLAGS = $(sample_scols_cflags) + +sample_scols_grouping_overlay_SOURCES = libsmartcols/samples/grouping-overlay.c +sample_scols_grouping_overlay_LDADD = $(sample_scols_ldadd) libcommon.la +sample_scols_grouping_overlay_CFLAGS = $(sample_scols_cflags) diff --git a/libsmartcols/samples/colors.c b/libsmartcols/samples/colors.c new file mode 100644 index 0000000..0e9ab66 --- /dev/null +++ b/libsmartcols/samples/colors.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + + +enum { COL_NAME, COL_FOO, COL_BAR }; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + if (!scols_table_new_column(tb, "NAME", 0, 0)) + goto fail; + if (!scols_table_new_column(tb, "BAR", 0, 0)) + goto fail; + if (!scols_table_new_column(tb, "FOO", 0, 0)) + goto fail; + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static struct libscols_line *add_line(struct libscols_table *tb, const char *name, const char *data) +{ + struct libscols_line *ln = scols_table_new_line(tb, NULL); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + if (scols_line_set_data(ln, COL_NAME, name)) + goto fail; + if (scols_line_set_data(ln, COL_FOO, data)) + goto fail; + if (scols_line_set_data(ln, COL_BAR, data)) + goto fail; + return ln; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + struct libscols_column *cl; + struct libscols_line *ln; + struct libscols_cell *ce; + int c; + + static const struct option longopts[] = { + { "maxout", 0, NULL, 'm' }, + { "width", 1, NULL, 'w' }, + { "help", 1, NULL, 'h' }, + + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) { + switch(c) { + case 'h': + printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name); + break; + case 'm': + scols_table_enable_maxout(tb, TRUE); + break; + case 'w': + scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS); + scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width")); + break; + } + } + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb); + add_line(tb, "AAA", "bla bla bla"); + add_line(tb, "BB", "b"); + add_line(tb, "CCCC", "fooo"); + add_line(tb, "D", "baaar"); + add_line(tb, "EE", "eee"); + + cl = scols_table_get_column(tb, 1); + scols_column_set_color(cl, "red"); /* red column */ + + cl = scols_table_get_column(tb, 2); + scols_column_set_color(cl, "reverse"); /* reverse column */ + + ln = scols_table_get_line(tb, 0); + scols_line_set_color(ln, "\033[37;41m"); /* line with red bg */ + ce = scols_line_get_cell(ln, 0); + scols_cell_set_color(ce, "\033[37;45m"); /* cell with purple bg */ + + ln = scols_table_get_line(tb, 3); + scols_line_set_color(ln, "\033[37;41m"); /* line with red bg */ + ce = scols_line_get_cell(ln, 2); + scols_cell_set_color(ce, "\033[37;44m"); /* cell with blue bg */ + + scols_print_table(tb); + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/continuous.c b/libsmartcols/samples/continuous.c new file mode 100644 index 0000000..6bdba6e --- /dev/null +++ b/libsmartcols/samples/continuous.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/time.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + +#define TIME_PERIOD 3.0 /* seconds */ + +enum { COL_NUM, COL_DATA, COL_TIME }; + +static double time_diff(struct timeval *a, struct timeval *b) +{ + return (a->tv_sec - b->tv_sec) + (a->tv_usec - b->tv_usec) / 1E6; +} + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + scols_table_enable_maxout(tb, 1); + if (!scols_table_new_column(tb, "#NUM", 0.1, SCOLS_FL_RIGHT)) + goto fail; + if (!scols_table_new_column(tb, "DATA", 0.7, 0)) + goto fail; + if (!scols_table_new_column(tb, "TIME", 0.2, 0)) + goto fail; + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static struct libscols_line *add_line(struct libscols_table *tb, size_t i) +{ + char *p; + struct libscols_line *ln = scols_table_new_line(tb, NULL); + + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + xasprintf(&p, "%zu", i); + if (scols_line_refer_data(ln, COL_NUM, p)) + goto fail; + + xasprintf(&p, "data-%02zu-%02zu-%02zu-end", i + 1, i + 2, i + 3); + if (scols_line_refer_data(ln, COL_DATA, p)) + goto fail; + + return ln; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + size_t i; + const size_t timecellsz = 500; + struct timeval last; + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + setup_columns(tb); + gettimeofday(&last, NULL); + + for (i = 0; i < 10; i++) { + struct libscols_line *line; + struct timeval now; + int done = 0; + char *timecell = xmalloc( timecellsz ); + + line = add_line(tb, i); + + /* Make a reference from cell data to the buffer, then we can + * update cell data without any interaction with libsmartcols + */ + if (scols_line_refer_data(line, COL_TIME, timecell) != 0) + err(EXIT_FAILURE, "failed to add data to table"); + + do { + double diff; + + gettimeofday(&now, NULL); + diff = time_diff(&now, &last); + + if (now.tv_sec == last.tv_sec + (long) TIME_PERIOD) + done = 1; + else + xusleep(100000); + + /* update "TIME" cell data */ + snprintf(timecell, timecellsz, "%f [%3d%%]", diff, + done ? 100 : (int)(diff / (TIME_PERIOD / 100.0))); + + /* Note that libsmartcols don't print \n for last line + * in the table, but if you print a line somewhere in + * the midle of the table you need + * + * scols_table_enable_nolinesep(tb, !done); + * + * to disable line breaks. In this example it's + * unnecessary as we print the latest line only. + */ + + /* print the line */ + scols_table_print_range(tb, line, NULL); + + if (!done) { + /* terminal is waiting for \n, fflush() to force output */ + fflush(scols_table_get_stream(tb)); + /* move to the begin of the line */ + fputc('\r', scols_table_get_stream(tb)); + } else + fputc('\n', scols_table_get_stream(tb)); + } while (!done); + + last = now; + } + + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/fromfile.c b/libsmartcols/samples/fromfile.c new file mode 100644 index 0000000..0fdc929 --- /dev/null +++ b/libsmartcols/samples/fromfile.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2016 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" +#include "optutils.h" + +#include "libsmartcols.h" + +struct column_flag { + const char *name; + int mask; +}; + +static const struct column_flag flags[] = { + { "trunc", SCOLS_FL_TRUNC }, + { "tree", SCOLS_FL_TREE }, + { "right", SCOLS_FL_RIGHT }, + { "strictwidth",SCOLS_FL_STRICTWIDTH }, + { "noextremes", SCOLS_FL_NOEXTREMES }, + { "hidden", SCOLS_FL_HIDDEN }, + { "wrap", SCOLS_FL_WRAP }, + { "wrapnl", SCOLS_FL_WRAP }, + { "none", 0 } +}; + +static long name_to_flag(const char *name, size_t namesz) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(flags); i++) { + const char *cn = flags[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return flags[i].mask; + } + warnx("unknown flag: %s", name); + return -1; +} + +static int parse_column_flags(char *str) +{ + unsigned long num_flags = 0; + + if (string_to_bitmask(str, &num_flags, name_to_flag)) + err(EXIT_FAILURE, "failed to parse column flags"); + + return num_flags; +} + +static struct libscols_column *parse_column(FILE *f) +{ + size_t len = 0; + char *line = NULL; + int nlines = 0; + + struct libscols_column *cl = NULL; + + while (getline(&line, &len, f) != -1) { + + char *p = strrchr(line, '\n'); + if (p) + *p = '\0'; + + switch (nlines) { + case 0: /* NAME */ + cl = scols_new_column(); + if (!cl) + goto fail; + if (scols_column_set_name(cl, line) != 0) + goto fail; + break; + + case 1: /* WIDTH-HINT */ + { + double whint = strtod_or_err(line, "failed to parse column whint"); + if (scols_column_set_whint(cl, whint)) + goto fail; + break; + } + case 2: /* FLAGS */ + { + int num_flags = parse_column_flags(line); + if (scols_column_set_flags(cl, num_flags)) + goto fail; + if (strcmp(line, "wrapnl") == 0) { + scols_column_set_wrapfunc(cl, + scols_wrapnl_chunksize, + scols_wrapnl_nextchunk, + NULL); + scols_column_set_safechars(cl, "\n"); + } + break; + } + case 3: /* COLOR */ + if (scols_column_set_color(cl, line)) + goto fail; + break; + default: + break; + } + + nlines++; + } + + free(line); + return cl; +fail: + free(line); + scols_unref_column(cl); + return NULL; +} + +static int parse_column_data(FILE *f, struct libscols_table *tb, int col) +{ + size_t len = 0, nlines = 0; + int i; + char *str = NULL; + + while ((i = getline(&str, &len, f)) != -1) { + + struct libscols_line *ln; + char *p = strrchr(str, '\n'); + if (p) + *p = '\0'; + + while ((p = strrchr(str, '\\')) && *(p + 1) == 'n') { + *p = '\n'; + memmove(p + 1, p + 2, i - (p + 2 - str)); + } + + ln = scols_table_get_line(tb, nlines++); + if (!ln) + break; + + if (*str && scols_line_set_data(ln, col, str) != 0) + err(EXIT_FAILURE, "failed to add output data"); + } + + free(str); + return 0; + +} + +static struct libscols_line *get_line_with_id(struct libscols_table *tb, + int col_id, const char *id) +{ + struct libscols_line *ln; + struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD); + + while (scols_table_next_line(tb, itr, &ln) == 0) { + struct libscols_cell *ce = scols_line_get_cell(ln, col_id); + const char *data = ce ? scols_cell_get_data(ce) : NULL; + + if (data && strcmp(data, id) == 0) + break; + } + + scols_free_iter(itr); + return ln; +} + +static void compose_tree(struct libscols_table *tb, int parent_col, int id_col) +{ + struct libscols_line *ln; + struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD); + + while (scols_table_next_line(tb, itr, &ln) == 0) { + struct libscols_line *parent = NULL; + struct libscols_cell *ce = scols_line_get_cell(ln, parent_col); + const char *data = ce ? scols_cell_get_data(ce) : NULL; + + if (data) + parent = get_line_with_id(tb, id_col, data); + if (parent) + scols_line_add_child(parent, ln); + } + + scols_free_iter(itr); +} + + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fprintf(out, + "\n %s [options] <column-data-file> ...\n\n", program_invocation_short_name); + + fputs(" -m, --maxout fill all terminal width\n", out); + fputs(" -M, --minout minimize tailing padding\n", out); + fputs(" -c, --column <file> column definition\n", out); + fputs(" -n, --nlines <num> number of lines\n", out); + fputs(" -J, --json JSON output format\n", out); + fputs(" -r, --raw RAW output format\n", out); + fputs(" -E, --export use key=\"value\" output format\n", out); + fputs(" -C, --colsep <str> set columns separator\n", out); + fputs(" -w, --width <num> hardcode terminal width\n", out); + fputs(" -p, --tree-parent-column <n> parent column\n", out); + fputs(" -i, --tree-id-column <n> id column\n", out); + fputs(" -h, --help this help\n", out); + fputs("\n", out); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + int c, n, nlines = 0; + int parent_col = -1, id_col = -1; + + static const struct option longopts[] = { + { "maxout", 0, NULL, 'm' }, + { "minout", 0, NULL, 'M' }, + { "column", 1, NULL, 'c' }, + { "nlines", 1, NULL, 'n' }, + { "width", 1, NULL, 'w' }, + { "tree-parent-column", 1, NULL, 'p' }, + { "tree-id-column", 1, NULL, 'i' }, + { "json", 0, NULL, 'J' }, + { "raw", 0, NULL, 'r' }, + { "export", 0, NULL, 'E' }, + { "colsep", 1, NULL, 'C' }, + { "help", 0, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'E', 'J', 'r' }, + { 'M', 'm' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "hCc:Ei:JMmn:p:rw:", longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch(c) { + case 'c': /* add column from file */ + { + struct libscols_column *cl; + FILE *f = fopen(optarg, "r"); + + if (!f) + err(EXIT_FAILURE, "%s: open failed", optarg); + cl = parse_column(f); + if (cl && scols_table_add_column(tb, cl)) + err(EXIT_FAILURE, "%s: failed to add column", optarg); + scols_unref_column(cl); + fclose(f); + break; + } + case 'p': + parent_col = strtou32_or_err(optarg, "failed to parse tree PARENT column"); + break; + case 'i': + id_col = strtou32_or_err(optarg, "failed to parse tree ID column"); + break; + case 'J': + scols_table_enable_json(tb, 1); + scols_table_set_name(tb, "testtable"); + break; + case 'm': + scols_table_enable_maxout(tb, TRUE); + break; + case 'M': + scols_table_enable_minout(tb, TRUE); + break; + case 'r': + scols_table_enable_raw(tb, TRUE); + break; + case 'E': + scols_table_enable_export(tb, TRUE); + break; + case 'C': + scols_table_set_column_separator(tb, optarg); + break; + case 'n': + nlines = strtou32_or_err(optarg, "failed to parse number of lines"); + break; + case 'w': + scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS); + scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width")); + break; + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (nlines <= 0) + errx(EXIT_FAILURE, "--nlines not set"); + + for (n = 0; n < nlines; n++) { + struct libscols_line *ln = scols_new_line(); + + if (!ln || scols_table_add_line(tb, ln)) + err(EXIT_FAILURE, "failed to add a new line"); + + scols_unref_line(ln); + } + + n = 0; + + while (optind < argc) { + FILE *f = fopen(argv[optind], "r"); + + if (!f) + err(EXIT_FAILURE, "%s: open failed", argv[optind]); + + parse_column_data(f, tb, n); + optind++; + n++; + } + + if (scols_table_is_tree(tb) && parent_col >= 0 && id_col >= 0) + compose_tree(tb, parent_col, id_col); + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + + scols_print_table(tb); + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/grouping-overlay.c b/libsmartcols/samples/grouping-overlay.c new file mode 100644 index 0000000..d55c57d --- /dev/null +++ b/libsmartcols/samples/grouping-overlay.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2018 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + + +enum { COL_NAME, COL_DATA }; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + struct libscols_column *cl; + + if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE)) + goto fail; + cl = scols_table_new_column(tb, "DATA", 0, SCOLS_FL_WRAP); + if (!cl) + goto fail; + scols_column_set_wrapfunc(cl, scols_wrapnl_chunksize, + scols_wrapnl_nextchunk, NULL); + scols_column_set_safechars(cl, "\n"); + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static struct libscols_line *add_line(struct libscols_table *tb, struct libscols_line *parent, const char *name, const char *data) +{ + struct libscols_line *ln = scols_table_new_line(tb, parent); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + if (scols_line_set_data(ln, COL_NAME, name)) + goto fail; + if (scols_line_set_data(ln, COL_DATA, data)) + goto fail; + return ln; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + struct libscols_line *ln; /* any line */ + struct libscols_line *g1, *g2, *g3; /* groups */ + struct libscols_line *p1; /* parents */ + int c; + + static const struct option longopts[] = { + { "maxout", 0, NULL, 'm' }, + { "width", 1, NULL, 'w' }, + { "help", 1, NULL, 'h' }, + + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) { + switch(c) { + case 'h': + printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name); + break; + case 'm': + scols_table_enable_maxout(tb, TRUE); + break; + case 'w': + scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS); + scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width")); + break; + } + } + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb); + + add_line(tb, NULL, "Alone", "bla bla bla"); + + p1 = add_line(tb, NULL, "A", "bla bla\nbla"); + add_line(tb, p1, "A:B", "bla bla\nbla"); + add_line(tb, p1, "A:C", "bla bla\nbla"); + + g1 = add_line(tb, NULL, "B", "bla bla\nbla"); + + g2 = add_line(tb, NULL, "C", "bla bla\nbla"); + ln = add_line(tb, NULL, "D", "bla bla\nbla"); + scols_table_group_lines(tb, g2, ln, 0); + + ln = add_line(tb, NULL, "G2:A", "alb alb\nalb"); + scols_line_link_group(ln, g2, 0); + + ln = add_line(tb, NULL, "E", "bla bla\nbla"); + scols_table_group_lines(tb, g1, ln, 0); + + + ln = add_line(tb, NULL, "G1:A", "alb alb alb"); + scols_line_link_group(ln, g1, 0); + + add_line(tb, NULL, "G", "bla bla bla"); + + g3 = ln = add_line(tb, NULL, "G1:B", "alb alb\nalb"); + scols_line_link_group(ln, g1, 0); + + ln = add_line(tb, NULL, "F", "bla bla bla"); + scols_table_group_lines(tb, g3, ln, 0); + + ln = add_line(tb, NULL, "G3:A", "alb alb alb"); + scols_line_link_group(ln, g3, 0); + + add_line(tb, NULL, "foo", "bla bla bla"); + add_line(tb, NULL, "bar", "bla bla bla"); + + scols_print_table(tb); + + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/grouping-simple.c b/libsmartcols/samples/grouping-simple.c new file mode 100644 index 0000000..6b9cbcf --- /dev/null +++ b/libsmartcols/samples/grouping-simple.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + + +enum { COL_NAME, COL_DATA }; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + struct libscols_column *cl; + + if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE)) + goto fail; + cl = scols_table_new_column(tb, "DATA", 0, SCOLS_FL_WRAP); + if (!cl) + goto fail; + + scols_column_set_wrapfunc(cl, scols_wrapnl_chunksize, + scols_wrapnl_nextchunk, + NULL); + scols_column_set_safechars(cl, "\n"); + + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static struct libscols_line *add_line(struct libscols_table *tb, struct libscols_line *parent, const char *name, const char *data) +{ + struct libscols_line *ln = scols_table_new_line(tb, parent); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + if (scols_line_set_data(ln, COL_NAME, name)) + goto fail; + if (scols_line_set_data(ln, COL_DATA, data)) + goto fail; + return ln; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + struct libscols_line *ln; /* any line */ + struct libscols_line *g1; /* groups */ + struct libscols_line *p1, *p2; /* parents */ + int c; + + static const struct option longopts[] = { + { "maxout", 0, NULL, 'm' }, + { "width", 1, NULL, 'w' }, + { "help", 1, NULL, 'h' }, + + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) { + switch(c) { + case 'h': + printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name); + break; + case 'm': + scols_table_enable_maxout(tb, TRUE); + break; + case 'w': + scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS); + scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width")); + break; + } + } + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb); + + add_line(tb, NULL, "Alone", "bla bla bla"); + + p1 = add_line(tb, NULL, "A", "bla bla bla"); + add_line(tb, p1, "A:B", "bla bla bla"); + add_line(tb, p1, "A:C", "bla bla bla"); + + g1 = add_line(tb, NULL, "B", "bla bla bla"); + add_line(tb, NULL, "C", "bla\nfoo"); + p1 = add_line(tb, NULL, "D", "bla bla\nbar"); + + p2 = add_line(tb, p1, "D:A", "bla bla bla"); + + ln = add_line(tb, p2, "D:A:A", "bla\nbla\nbla"); + scols_table_group_lines(tb, g1, ln, 0); + + add_line(tb, p1, "D:B", "bla bla bla"); + add_line(tb, p1, "D:C", "bla\nother bla"); + add_line(tb, p1, "D:D", "bla bla bla"); + + ln = add_line(tb, NULL, "E", "bla bla bla"); + scols_table_group_lines(tb, g1, ln, 0); + + p1 = ln; + add_line(tb, p1, "E:A", "bla bla bla"); + add_line(tb, p1, "E:B", "bla bla bla"); + add_line(tb, p1, "E:C", "bla bla bla"); + + add_line(tb, NULL, "F", "bla bla bla"); + + ln = add_line(tb, NULL, "G1:A", "alb alb alb"); + scols_line_link_group(ln, g1, 0); + + p1 = ln; + add_line(tb, p1, "G1:A:A", "bla\nbla bla"); + add_line(tb, p1, "G1:A:B", "bla bla bla"); + add_line(tb, p1, "G1:A:C", "bla bla bla"); + + add_line(tb, NULL, "G", "bla bla bla"); + + ln = add_line(tb, NULL, "G1:B", "alb alb\nalb"); + scols_line_link_group(ln, g1, 0); + + add_line(tb, NULL, "foo", "bla bla bla"); + add_line(tb, NULL, "bar", "bla bla bla"); + + scols_print_table(tb); + + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/maxout.c b/libsmartcols/samples/maxout.c new file mode 100644 index 0000000..20c6424 --- /dev/null +++ b/libsmartcols/samples/maxout.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "libsmartcols.h" + +enum { COL_LEFT, COL_FOO, COL_RIGHT }; + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + int rc = -1, nlines = 3; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + scols_table_enable_maxout(tb, TRUE); + if (!scols_table_new_column(tb, "LEFT", 0, 0)) + goto done; + if (!scols_table_new_column(tb, "FOO", 0, 0)) + goto done; + if (!scols_table_new_column(tb, "RIGHT", 0, SCOLS_FL_RIGHT)) + goto done; + + while (nlines--) { + struct libscols_line *ln = scols_table_new_line(tb, NULL); + + rc = scols_line_set_data(ln, COL_LEFT, "A"); + if (!rc) + rc = scols_line_set_data(ln, COL_FOO, "B"); + if (!rc) + rc = scols_line_set_data(ln, COL_RIGHT, "C"); + if (rc) + err(EXIT_FAILURE, "failed to set line data"); + } + + scols_print_table(tb); + rc = 0; +done: + scols_unref_table(tb); + return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/libsmartcols/samples/title.c b/libsmartcols/samples/title.c new file mode 100644 index 0000000..131400d --- /dev/null +++ b/libsmartcols/samples/title.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + + +enum { COL_NAME, COL_DATA }; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + if (!scols_table_new_column(tb, "NAME", 0, 0)) + goto fail; + if (!scols_table_new_column(tb, "DATA", 0, 0)) + goto fail; + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static void add_line(struct libscols_table *tb, const char *name, const char *data) +{ + struct libscols_line *ln = scols_table_new_line(tb, NULL); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + if (scols_line_set_data(ln, COL_NAME, name)) + goto fail; + if (scols_line_set_data(ln, COL_DATA, data)) + goto fail; + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + struct libscols_symbols *sy; + struct libscols_cell *title; + int c; + + static const struct option longopts[] = { + { "maxout", 0, NULL, 'm' }, + { "width", 1, NULL, 'w' }, + { "help", 1, NULL, 'h' }, + + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "hmw:", longopts, NULL)) != -1) { + switch(c) { + case 'h': + printf("%s [--help | --maxout | --width <num>]\n", program_invocation_short_name); + break; + case 'm': + scols_table_enable_maxout(tb, TRUE); + break; + case 'w': + scols_table_set_termforce(tb, SCOLS_TERMFORCE_ALWAYS); + scols_table_set_termwidth(tb, strtou32_or_err(optarg, "failed to parse terminal width")); + break; + } + } + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb); + add_line(tb, "foo", "bla bla bla"); + add_line(tb, "bar", "alb alb alb"); + + title = scols_table_get_title(tb); + + /* right */ + scols_cell_set_data(title, "This is right title"); + scols_cell_set_color(title, "red"); + scols_cell_set_flags(title, SCOLS_CELL_FL_RIGHT); + scols_print_table(tb); + + /* left without padding */ + scols_cell_set_data(title, "This is left title (without padding)"); + scols_cell_set_color(title, "yellow"); + scols_cell_set_flags(title, SCOLS_CELL_FL_LEFT); + scols_print_table(tb); + + /* center */ + sy = scols_new_symbols(); + if (!sy) + err_oom(); + scols_table_set_symbols(tb, sy); + scols_unref_symbols(sy); + + scols_symbols_set_title_padding(sy, "="); + scols_cell_set_data(title, "This is center title (with padding)"); + scols_cell_set_color(title, "green"); + scols_cell_set_flags(title, SCOLS_CELL_FL_CENTER); + scols_print_table(tb); + + /* left with padding */ + scols_symbols_set_title_padding(sy, "-"); + scols_cell_set_data(title, "This is left title (with padding)"); + scols_cell_set_color(title, "blue"); + scols_cell_set_flags(title, SCOLS_CELL_FL_LEFT); + scols_print_table(tb); + + + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/tree.c b/libsmartcols/samples/tree.c new file mode 100644 index 0000000..e40ea9a --- /dev/null +++ b/libsmartcols/samples/tree.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" + +#include "libsmartcols.h" + +static int add_children(struct libscols_table *tb, + struct libscols_line *ln, int fd); + + +enum { COL_MODE, COL_SIZE, COL_NAME }; + +struct libscols_column *sort_column; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb, int notree) +{ + if (!scols_table_new_column(tb, "MODE", 0.3, 0)) + goto fail; + if (!scols_table_new_column(tb, "SIZE", 5, SCOLS_FL_RIGHT)) + goto fail; + + sort_column = scols_table_new_column(tb, "NAME", 0.5, + (notree ? 0 : SCOLS_FL_TREE) | SCOLS_FL_NOEXTREMES); + if (!sort_column) + goto fail; + scols_column_set_cmpfunc(sort_column, scols_cmpstr_cells, NULL); + + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +/* add a new line to @tb, the content is based on @st */ +static int add_line_from_stat(struct libscols_table *tb, + struct libscols_line *parent, + int parent_fd, + struct stat *st, + const char *name) +{ + struct libscols_line *ln; + char modbuf[11], *p; + mode_t mode = st->st_mode; + int rc = 0; + + ln = scols_table_new_line(tb, parent); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + /* MODE; local buffer, use scols_line_set_data() that calls strdup() */ + xstrmode(mode, modbuf); + if (scols_line_set_data(ln, COL_MODE, modbuf)) + goto fail; + + /* SIZE; already allocated string, use scols_line_refer_data() */ + p = size_to_human_string(0, st->st_size); + if (!p || scols_line_refer_data(ln, COL_SIZE, p)) + goto fail; + + /* NAME */ + if (scols_line_set_data(ln, COL_NAME, name)) + goto fail; + + /* colors */ + if (scols_table_colors_wanted(tb)) { + struct libscols_cell *ce = scols_line_get_cell(ln, COL_NAME); + + if (S_ISDIR(mode)) + scols_cell_set_color(ce, "blue"); + else if (S_ISLNK(mode)) + scols_cell_set_color(ce, "cyan"); + else if (S_ISBLK(mode)) + scols_cell_set_color(ce, "magenta"); + else if ((mode & S_IXOTH) || (mode & S_IXGRP) || (mode & S_IXUSR)) + scols_cell_set_color(ce, "green"); + } + + if (S_ISDIR(st->st_mode)) { + int fd; + + if (parent_fd >= 0) + fd = openat(parent_fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC); + else + fd = open(name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC); + if (fd >= 0) { + rc = add_children(tb, ln, fd); + close(fd); + } + } + return rc; +fail: + err(EXIT_FAILURE, "failed to create cell data"); + return -1; +} + +/* read all entries from directory addressed by @fd */ +static int add_children(struct libscols_table *tb, + struct libscols_line *ln, + int fd) +{ + DIR *dir; + struct dirent *d; + + dir = fdopendir(fd); + if (!dir) + return -errno; + + while ((d = readdir(dir))) { + struct stat st; + + if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) + continue; + if (fstatat(fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) + continue; + add_line_from_stat(tb, ln, fd, &st, d->d_name); + } + closedir(dir); + return 0; +} + +static void add_lines(struct libscols_table *tb, const char *dirname) +{ + struct stat st; + + if (lstat(dirname, &st)) + err(EXIT_FAILURE, "%s", dirname); + + add_line_from_stat(tb, NULL, -1, &st, dirname); +} + +static void __attribute__((__noreturn__)) usage(FILE *out) +{ + fprintf(out, " %s [options] [<dir> ...]\n\n", program_invocation_short_name); + fputs(" -c, --csv display a csv-like output\n", out); + fputs(" -i, --ascii use ascii characters only\n", out); + fputs(" -l, --list use list format output\n", out); + fputs(" -n, --noheadings don't print headings\n", out); + fputs(" -p, --pairs use key=\"value\" output format\n", out); + fputs(" -J, --json use JSON output format\n", out); + fputs(" -r, --raw use raw output format\n", out); + fputs(" -s, --sort sort by NAME\n", out); + fputs(" -x, --tree-sort keep tree-like order (also for --list)\n", out); + fputs(" -S, --range-start <n> first line to print\n", out); + fputs(" -E, --range-end <n> last line to print\n", out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + int c, notree = 0, nstart = -1, nend = -1, sort = 0, force_tree_sort = 0; + + + static const struct option longopts[] = { + { "ascii", 0, NULL, 'i' }, + { "csv", 0, NULL, 'c' }, + { "list", 0, NULL, 'l' }, + { "noheadings", 0, NULL, 'n' }, + { "pairs", 0, NULL, 'p' }, + { "json", 0, NULL, 'J' }, + { "raw", 0, NULL, 'r' }, + { "range-start",1, NULL, 'S' }, + { "range-end", 1, NULL, 'E' }, + { "sort", 0, NULL, 's' }, + { "tree-sort", 0, NULL, 'x' }, + { NULL, 0, NULL, 0 }, + }; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + while((c = getopt_long(argc, argv, "ciJlnprS:sE:x", longopts, NULL)) != -1) { + switch(c) { + case 'c': + scols_table_set_column_separator(tb, ","); + scols_table_enable_raw(tb, 1); + notree = 1; + break; + case 'i': + scols_table_enable_ascii(tb, 1); + break; + case 'J': + scols_table_set_name(tb, "scolstest"); + scols_table_enable_json(tb, 1); + break; + case 'l': + notree = 1; + break; + case 'n': + scols_table_enable_noheadings(tb, 1); + break; + case 'p': + scols_table_enable_export(tb, 1); + notree = 1; + break; + case 'r': + scols_table_enable_raw(tb, 1); + notree = 1; + break; + case 'S': + nstart = strtos32_or_err(optarg, "failed to parse range start") - 1; + break; + case 's': + sort = 1; + break; + case 'E': + nend = strtos32_or_err(optarg, "failed to parse range end") - 1; + break; + case 'x': + force_tree_sort = 1; + break; + default: + usage(stderr); + } + } + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb, notree); + + if (optind == argc) + add_lines(tb, "."); + else while (optind < argc) + add_lines(tb, argv[optind++]); + + if (sort) + scols_sort_table(tb, sort_column); + if (force_tree_sort) + scols_sort_table_by_tree(tb); + + if (nstart >= 0 || nend >= 0) { + /* print subset */ + struct libscols_line *start = NULL, *end = NULL; + + if (nstart >= 0) + start = scols_table_get_line(tb, nstart); + if (nend >= 0) + end = scols_table_get_line(tb, nend); + + if (start || end) + scols_table_print_range(tb, start, end); + } else + /* print all table */ + scols_print_table(tb); + + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/samples/wrap.c b/libsmartcols/samples/wrap.c new file mode 100644 index 0000000..795bef7 --- /dev/null +++ b/libsmartcols/samples/wrap.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <getopt.h> + +#include "c.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" + +#include "libsmartcols.h" + + +enum { COL_NAME, COL_DESC, COL_FOO, COL_LIKE, COL_TEXT }; + +/* add columns to the @tb */ +static void setup_columns(struct libscols_table *tb) +{ + if (!scols_table_new_column(tb, "NAME", 0, SCOLS_FL_TREE)) + goto fail; + if (!scols_table_new_column(tb, "DESC", 0, 0)) + goto fail; + if (!scols_table_new_column(tb, "FOO", 0, SCOLS_FL_WRAP)) + goto fail; + if (!scols_table_new_column(tb, "LIKE", 0, SCOLS_FL_RIGHT)) + goto fail; + if (!scols_table_new_column(tb, "TEXT", 0, SCOLS_FL_WRAP)) + goto fail; + return; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output columns"); +} + +static char *gen_text(const char *prefix, const char *sub_prefix, char *buf, size_t sz) +{ + int x = snprintf(buf, sz, "%s-%s-", prefix, sub_prefix); + + for ( ; (size_t)x < sz - 1; x++) + buf[x] = *prefix; + + buf[x++] = 'x'; + buf[x] = '\0'; + return buf; +} + +static struct libscols_line * add_line( struct libscols_table *tb, + struct libscols_line *parent, + const char *prefix) +{ + char buf[BUFSIZ]; + struct libscols_line *ln = scols_table_new_line(tb, parent); + if (!ln) + err(EXIT_FAILURE, "failed to create output line"); + + if (scols_line_set_data(ln, COL_NAME, gen_text(prefix, "N", buf, 15))) + goto fail; + if (scols_line_set_data(ln, COL_DESC, gen_text(prefix, "D", buf, 10))) + goto fail; + if (scols_line_set_data(ln, COL_FOO, gen_text(prefix, "U", buf, 55))) + goto fail; + if (scols_line_set_data(ln, COL_LIKE, "1")) + goto fail; + if (scols_line_set_data(ln, COL_TEXT, gen_text(prefix, "T", buf, 50))) + goto fail; + return ln; +fail: + scols_unref_table(tb); + err(EXIT_FAILURE, "failed to create output line"); +} + +int main(int argc, char *argv[]) +{ + struct libscols_table *tb; + struct libscols_line *ln, *xln; + + setlocale(LC_ALL, ""); /* just to have enable UTF8 chars */ + + scols_init_debug(0); + + tb = scols_new_table(); + if (!tb) + err(EXIT_FAILURE, "failed to create output table"); + + scols_table_enable_colors(tb, isatty(STDOUT_FILENO)); + setup_columns(tb); + + ln = add_line(tb, NULL, "A"); + add_line(tb, ln, "aa"); + add_line(tb, ln, "ab"); + + ln = add_line(tb, NULL, "B"); + xln = add_line(tb, ln, "ba"); + add_line(tb, xln, "baa"); + add_line(tb, xln, "bab"); + add_line(tb, ln, "bb"); + + scols_print_table(tb); + scols_unref_table(tb); + return EXIT_SUCCESS; +} diff --git a/libsmartcols/smartcols.pc.in b/libsmartcols/smartcols.pc.in new file mode 100644 index 0000000..0b16739 --- /dev/null +++ b/libsmartcols/smartcols.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@usrlib_execdir@ +includedir=@includedir@ + +Name: smartcols +Description: table or tree library +Version: @LIBSMARTCOLS_VERSION@ +Cflags: -I${includedir}/libsmartcols +Libs: -L${libdir} -lsmartcols diff --git a/libsmartcols/src/Makemodule.am b/libsmartcols/src/Makemodule.am new file mode 100644 index 0000000..2bb19fd --- /dev/null +++ b/libsmartcols/src/Makemodule.am @@ -0,0 +1,62 @@ + + +# smartcols.h is generated, so it's stored in builddir! +smartcolsincdir = $(includedir)/libsmartcols +nodist_smartcolsinc_HEADERS = libsmartcols/src/libsmartcols.h + +usrlib_exec_LTLIBRARIES += libsmartcols.la +libsmartcols_la_SOURCES= \ + include/list.h \ + \ + libsmartcols/src/smartcolsP.h \ + libsmartcols/src/iter.c \ + libsmartcols/src/symbols.c \ + libsmartcols/src/cell.c \ + libsmartcols/src/column.c \ + libsmartcols/src/line.c \ + libsmartcols/src/table.c \ + libsmartcols/src/print.c \ + libsmartcols/src/print-api.c \ + libsmartcols/src/version.c \ + libsmartcols/src/calculate.c \ + libsmartcols/src/grouping.c \ + libsmartcols/src/walk.c \ + libsmartcols/src/init.c + +libsmartcols_la_LIBADD = $(LDADD) libcommon.la + +libsmartcols_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SOLIB_CFLAGS) \ + -I$(ul_libsmartcols_incdir) \ + -I$(top_srcdir)/libsmartcols/src + +EXTRA_libsmartcols_la_DEPENDENCIES = \ + libsmartcols/src/libsmartcols.sym + +libsmartcols_la_LDFLAGS = $(SOLIB_LDFLAGS) +if HAVE_VSCRIPT +libsmartcols_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libsmartcols/src/libsmartcols.sym +endif +libsmartcols_la_LDFLAGS += -version-info $(LIBSMARTCOLS_VERSION_INFO) + +EXTRA_DIST += \ + libsmartcols/src/libsmartcols.sym + +# move lib from $(usrlib_execdir) to $(libdir) if needed +install-exec-hook-libsmartcols: + if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libsmartcols.so"; then \ + $(MKDIR_P) $(DESTDIR)$(libdir); \ + mv $(DESTDIR)$(usrlib_execdir)/libsmartcols.so.* $(DESTDIR)$(libdir); \ + so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libsmartcols.so); \ + so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \ + (cd $(DESTDIR)$(usrlib_execdir) && \ + rm -f libsmartcols.so && \ + $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libsmartcols.so); \ + fi + +uninstall-hook-libsmartcols: + rm -f $(DESTDIR)$(libdir)/libsmartcols.so* + +INSTALL_EXEC_HOOKS += install-exec-hook-libsmartcols +UNINSTALL_HOOKS += uninstall-hook-libsmartcols diff --git a/libsmartcols/src/calculate.c b/libsmartcols/src/calculate.c new file mode 100644 index 0000000..ad0b15d --- /dev/null +++ b/libsmartcols/src/calculate.c @@ -0,0 +1,608 @@ +#include "smartcolsP.h" +#include "mbsalign.h" + +static void dbg_column(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_wstat *st; + + if (scols_column_is_hidden(cl)) { + DBG(COL, ul_debugobj(cl, "%s (hidden) ignored", cl->header.data)); + return; + } + + st = &cl->wstat; + + DBG(COL, ul_debugobj(cl, "%15s seq=%zu, width=%zd, " + "hint=%d, max=%zu, min=%zu, " + "0x04%x [%s%s%s]", + + cl->header.data, cl->seqnum, cl->width, + cl->width_hint >= 1.0 ? (int) cl->width_hint : + (int) (cl->width_hint * tb->termwidth), + st->width_max, + st->width_min, + cl->flags, + cl->flags & SCOLS_FL_TRUNC ? "trunc" : "", + scols_column_is_right(cl) ? " right" : "", + scols_column_is_noextremes(cl) ? " noextrem" : "")); +} + +static void dbg_columns(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) + dbg_column(tb, cl); +} + +static int count_cell_width(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct ul_buffer *buf) +{ + size_t len; + char *data; + int rc; + struct libscols_cell *ce; + struct libscols_wstat *st; + + rc = __cell_to_buffer(tb, ln, cl, buf); + if (rc) + return rc; + + data = ul_buffer_get_data(buf, NULL, NULL); + if (!data) + len = 0; + else if (scols_column_is_customwrap(cl)) + len = cl->wrap_chunksize(cl, data, cl->wrapfunc_data); + else if (scols_table_is_noencoding(tb)) + len = mbs_width(data); + else + len = mbs_safe_width(data); + + if (len == (size_t) -1) /* ignore broken multibyte strings */ + len = 0; + + ce = scols_line_get_cell(ln, cl->seqnum); + ce->width = len; + + st = &cl->wstat; + st->width_max = max(len, st->width_max); + + if (scols_column_is_tree(cl)) { + size_t treewidth = ul_buffer_get_safe_pointer_width(buf, SCOLS_BUFPTR_TREEEND); + cl->width_treeart = max(cl->width_treeart, treewidth); + } + + return 0; +} + +static int walk_count_cell_width(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + void *data) +{ + return count_cell_width(tb, ln, cl, (struct ul_buffer *) data); +} + +static double sqrtroot(double num) +{ + double tmp = 0, sq = num / 2; + + while (sq != tmp){ + tmp = sq; + sq = (num / tmp + tmp) / 2; + } + return sq; +} + +static void count_column_deviation(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_wstat *st; + struct libscols_iter itr; + struct libscols_line *ln; + struct libscols_cell *ce; + size_t sum = 0, n = 0, extra = 0; + + st = &cl->wstat; + + if (scols_column_is_tree(cl) && has_groups(tb)) + extra = tb->grpset_size + 1; + + /* count average */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + ce = scols_line_get_cell(ln, cl->seqnum); + + n++; + sum += ce->width + extra; + } + + if (n) + st->width_avg = sum / n; + + /* count deviation */ + if (n > 1) { + double variance; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + double diff; + ce = scols_line_get_cell(ln, cl->seqnum); + + diff = (double) ce->width - st->width_avg; + st->width_sqr_sum += diff * diff; /* aka pow(x, 2) */ + } + + variance = st->width_sqr_sum / (n - 1); + st->width_deviation = sqrtroot(variance); + } + + DBG(COL, ul_debugobj(cl, "%15s avg=%g, deviation=%g", + cl->header.data, + st->width_avg, + st->width_deviation)); +} + +/* + * This function counts column width. + */ +static int count_column_width(struct libscols_table *tb, + struct libscols_column *cl, + struct ul_buffer *buf) +{ + int rc = 0, no_header = 0; + const char *data; + struct libscols_wstat *st; + struct libscols_iter itr; + struct libscols_line *ln; + + assert(tb); + assert(cl); + + st = &cl->wstat; + + cl->width = 0; + memset(st, 0, sizeof(struct libscols_wstat)); + + /* set minimal width according to width_hint */ + if (cl->width_hint < 1 && scols_table_is_maxout(tb) && tb->is_term) { + st->width_min = (size_t) (cl->width_hint * tb->termwidth); + if (st->width_min && !is_last_column(cl)) + st->width_min--; + } + + /* set minimal width according to header width */ + data = scols_cell_get_data(&cl->header); + if (data) { + size_t len = scols_table_is_noencoding(tb) ? + mbs_width(data) : mbs_safe_width(data); + + st->width_min = max(st->width_min, len); + } else + no_header = 1; + + if (!st->width_min) + st->width_min = 1; + + /* count width according to cells data */ + if (scols_table_is_tree(tb)) { + /* Count width for tree */ + rc = scols_walk_tree(tb, cl, walk_count_cell_width, (void *) buf); + if (rc) + goto done; + } else { + /* Count width for list */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + rc = count_cell_width(tb, ln, cl, buf); + if (rc) + goto done; + } + } + + if (scols_column_is_tree(cl) && has_groups(tb)) { + /* We don't fill buffer with groups tree ascii art during width + * calculation. The print function only enlarge grpset[] and we + * calculate final width from grpset_size. + */ + size_t gprwidth = tb->grpset_size + 1; + st->width_treeart += gprwidth; + st->width_max += gprwidth; + } + + /* this is default, may be later reduced */ + cl->width = st->width_max; + + /* enlarge to minimal width */ + if (cl->width < st->width_min && !scols_column_is_strict_width(cl)) + cl->width = st->width_min; + + /* use absolute size for large columns */ + else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint + && st->width_min < (size_t) cl->width_hint) + + cl->width = (size_t) cl->width_hint; + + + /* Column without header and data, set minimal size to zero (default is 1) */ + if (st->width_max == 0 && no_header && st->width_min == 1 && cl->width <= 1) + cl->width = st->width_min = 0; + +done: + ON_DBG(COL, dbg_column(tb, cl)); + return rc; +} + +static int cmp_deviation(struct list_head *a, struct list_head *b, + void *data __attribute__((__unused__))) +{ + struct libscols_column *ca = list_entry(a, struct libscols_column, cl_columns); + struct libscols_column *cb = list_entry(b, struct libscols_column, cl_columns); + + double xa = ca->wstat.width_avg + (3*ca->wstat.width_deviation); + double xb = cb->wstat.width_avg + (3*cb->wstat.width_deviation); + + return cmp_numbers(xa, xb); +} + +static int cmp_seqnum(struct list_head *a, struct list_head *b, + void *data __attribute__((__unused__))) +{ + struct libscols_column *ca = list_entry(a, struct libscols_column, cl_columns); + struct libscols_column *cb = list_entry(b, struct libscols_column, cl_columns); + + return cmp_numbers(ca->seqnum, cb->seqnum); +} + +static inline void sort_columns(struct libscols_table *tb, + int (*cmp)(struct list_head *, struct list_head *, void *)) +{ + list_sort(&tb->tb_columns, cmp, NULL); +} + + +/* 68%–95%–99% rule (aka empirical rule) defines relation ship between + * mean (avg) and and standard deviation. + * + * avg + (n * deviation) + * + * n=1 : covers 68% of data + * n=2 : covers 95% of data + * n=3 : covers 99.7% of data + * + */ +static void reduce_to_68(struct libscols_column *cl, size_t wanted) +{ + struct libscols_wstat *st = &cl->wstat; + size_t new; + + if (st->width_deviation < 1.0) + return; + + new = st->width_avg + st->width_deviation; + if (new < st->width_min) + new = st->width_min; + + if (cl->width - new > wanted) + cl->width -= wanted; + else + cl->width = new; +} + +static int reduce_column(struct libscols_table *tb, + struct libscols_column *cl, + size_t *width, + int stage, + int nth) +{ + struct libscols_wstat *st = &cl->wstat; + size_t wanted, org_width, reduce = 1; + int is_trunc = 0; + + if (tb->termwidth >= *width) + return 1; + /* ignore hidden columns */ + if (scols_column_is_hidden(cl)) + return 0; + /* never truncate if already minimal width */ + if (cl->width == cl->wstat.width_min) + return 0; + /* nothing to truncate */ + if (cl->width == 0) + return 0; + /* never truncate the tree */ + if (scols_column_is_tree(cl) && *width <= cl->width_treeart) + return 0; + + org_width = cl->width; + wanted = *width - tb->termwidth; + + is_trunc = scols_column_is_trunc(cl) + || (scols_column_is_wrap(cl) && !scols_column_is_customwrap(cl)); + + switch (stage) { + case 0: + /* reduce 1st column if with trunc or extreme flag (the + * columns are sorted by deviation, so 1st is the worst) */ + if (!is_trunc && !scols_column_is_noextremes(cl)) + break; + if (nth != 0) + break; + reduce_to_68(cl, wanted); + break; + + case 1: + /* reduce extreme columns with large width deviation */ + if (st->width_deviation < st->width_avg / 2.0) + break; + /* fallthrough */ + case 2: + /* reduce extreme columns */ + if (!scols_column_is_noextremes(cl)) + break; + reduce_to_68(cl, wanted); + break; + + case 3: + /* reduce columns with trunc flag and relative whint and large width deviation */ + if (st->width_deviation < st->width_avg / 2.0) + break; + /* fallthrough */ + case 4: + /* reduce columns with trunc flag and relative whint */ + if (!is_trunc) + break; + if (cl->width_hint <= 0 || cl->width_hint >= 1) + break; + if (cl->width < (size_t) (cl->width_hint * tb->termwidth)) + break; + reduce_to_68(cl, wanted); + break; + + case 5: + /* reduce all columns with trunc flag large width deviation */ + if (st->width_deviation < st->width_avg / 2.2) + break; + /* fallthrough */ + case 6: + /* reduce all columns with trunc flag */ + if (!is_trunc && !scols_column_is_noextremes(cl)) + break; + if (nth == 0) + /* columns are reduced in "bad first" way, be more + * agresive for the the worst column */ + reduce = 3; + if (cl->width - reduce < st->width_min) + reduce = cl->width - st->width_min; + cl->width -= reduce; + break; + default: + return -1; /* no more stages */ + } + + /* hide zero width columns */ + if (cl->width == 0) + cl->flags |= SCOLS_FL_HIDDEN; + + if (cl->width != org_width) + DBG(COL, ul_debugobj(cl, " [%02zd] %s reduced %zu-->%zu", + cl->seqnum, + cl->header.data, org_width, cl->width)); + + *width -= org_width - cl->width; + return 0; +} + +/* + * This is core of the scols_* voodoo... + */ +int __scols_calculate(struct libscols_table *tb, struct ul_buffer *buf) +{ + struct libscols_column *cl, *last_cl; + struct libscols_iter itr; + size_t width = 0, width_min = 0; /* output width */ + int stage = 0, rc = 0; + int ignore_extremes = 0, group_ncolumns = 0; + size_t colsepsz; + int sorted = 0; + + + DBG(TAB, ul_debugobj(tb, "-----calculate-(termwidth=%zu)-----", tb->termwidth)); + tb->is_dummy_print = 1; + + colsepsz = scols_table_is_noencoding(tb) ? + mbs_width(colsep(tb)) : + mbs_safe_width(colsep(tb)); + + if (has_groups(tb)) + group_ncolumns = 1; + + /* set basic columns width + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + int is_last; + + if (scols_column_is_hidden(cl)) + continue; + + /* we print groups chart only for the for the first tree column */ + if (scols_column_is_tree(cl) && group_ncolumns == 1) { + cl->is_groups = 1; + group_ncolumns++; + } + + rc = count_column_width(tb, cl, buf); + if (rc) + goto done; + + is_last = is_last_column(cl); + + width += cl->width + (is_last ? 0 : colsepsz); /* separator for non-last column */ + width_min += cl->wstat.width_min + (is_last ? 0 : colsepsz); + } + + if (!tb->is_term) { + DBG(TAB, ul_debugobj(tb, " non-terminal output")); + goto done; + } + + /* be paranoid */ + if (width_min > tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " min width larger than terminal! [width=%zu, term=%zu]", width_min, tb->termwidth)); + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (width_min > tb->termwidth + && scols_table_next_column(tb, &itr, &cl) == 0) { + + if (scols_column_is_hidden(cl)) + continue; + width_min--; + cl->wstat.width_min--; + } + DBG(TAB, ul_debugobj(tb, " min width reduced to %zu", width_min)); + } + + /* calculate statistics */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + count_column_deviation(tb, cl); + + if (scols_column_is_noextremes(cl)) + ignore_extremes++; + } + + /* remember last column before we sort columns */ + last_cl = list_entry(tb->tb_columns.prev, struct libscols_column, cl_columns); + + /* reduce columns width */ + while (width > tb->termwidth) { + size_t org_width = width; + int rc = 0, n = 0; + + if (!sorted) { + DBG(TAB, ul_debugobj(tb, "sorting by deviation")); + sort_columns(tb, cmp_deviation); + ON_DBG(TAB, dbg_columns(tb)); + sorted = 1; + } + + DBG(TAB, ul_debugobj(tb, "#%d reduce stage (width=%zu, term=%zu)", + stage, width, tb->termwidth)); + + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + + while (width > tb->termwidth + && rc == 0 + && scols_table_next_column(tb, &itr, &cl) == 0) { + rc = reduce_column(tb, cl, &width, stage, n++); + } + + if (rc != 0) + break; + if (org_width == width) + stage++; + } + + /* enlarge */ + if (width < tb->termwidth) { + if (ignore_extremes) { + if (!sorted) { + sort_columns(tb, cmp_deviation); + sorted = 1; + } + + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + size_t add; + + if (!scols_column_is_noextremes(cl) || scols_column_is_hidden(cl)) + continue; + if (cl->wstat.width_min == 0 && cl->width == 0) + continue; + + add = tb->termwidth - width; + if (add && cl->wstat.width_max && + cl->width + add > cl->wstat.width_max) + add = cl->wstat.width_max - cl->width; + if (!add) + continue; + DBG(TAB, ul_debugobj(tb, " add +%zd (extreme %s)", + add, cl->header.data)); + cl->width += add; + width += add; + + if (width == tb->termwidth) + break; + } + } + + if (width < tb->termwidth && scols_table_is_maxout(tb)) { + DBG(TAB, ul_debugobj(tb, " enlarge width (max-out)")); + + /* try enlarging all columns */ + while (width < tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + DBG(TAB, ul_debugobj(tb, " enlarge (max-out %s)", + cl->header.data)); + cl->width++; + width++; + if (width == tb->termwidth) + break; + } + } + } else if (width < tb->termwidth) { + /* enlarge the last column */ + DBG(TAB, ul_debugobj(tb, " enlarge width (last column)")); + + if (!scols_column_is_right(last_cl)) { + last_cl->width += tb->termwidth - width; + width = tb->termwidth; + } + } + } + + + if (sorted) { + sort_columns(tb, cmp_seqnum); + sorted = 0; + } + + /* ignore last column(s) or force last column to be truncated if + * nowrap mode enabled */ + if (tb->no_wrap && width > tb->termwidth) { + scols_reset_iter(&itr, SCOLS_ITER_BACKWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + + if (scols_column_is_hidden(cl)) + continue; + if (width <= tb->termwidth) + break; + if (width - cl->width < tb->termwidth) { + size_t r = width - tb->termwidth; + + cl->flags |= SCOLS_FL_TRUNC; + cl->width -= r; + width -= r; + } else { + cl->flags |= SCOLS_FL_HIDDEN; + width -= cl->width + colsepsz; + } + } + } +done: + if (sorted) + sort_columns(tb, cmp_seqnum); + + tb->is_dummy_print = 0; + DBG(TAB, ul_debugobj(tb, "-----final width: %zu (rc=%d)-----", width, rc)); + ON_DBG(TAB, dbg_columns(tb)); + + return rc; +} diff --git a/libsmartcols/src/cell.c b/libsmartcols/src/cell.c new file mode 100644 index 0000000..5b83123 --- /dev/null +++ b/libsmartcols/src/cell.c @@ -0,0 +1,260 @@ +/* + * cell.c - functions for table handling at the cell level + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: cell + * @title: Cell + * @short_description: container for your data + * + * An API to access and modify per-cell data and information. Note that cell is + * always part of the line. If you destroy (un-reference) a line than it + * destroys all line cells too. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "smartcolsP.h" + +/* + * The cell has no ref-counting, free() and new() functions. All is + * handled by libscols_line. + */ + +/** + * scols_reset_cell: + * @ce: pointer to a struct libscols_cell instance + * + * Frees the cell's internal data and resets its status. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_reset_cell(struct libscols_cell *ce) +{ + if (!ce) + return -EINVAL; + + /*DBG(CELL, ul_debugobj(ce, "reset"));*/ + free(ce->data); + free(ce->color); + memset(ce, 0, sizeof(*ce)); + return 0; +} + +/** + * scols_cell_set_data: + * @ce: a pointer to a struct libscols_cell instance + * @data: data (used for scols_print_table()) + * + * Stores a copy of the @str in @ce, the old data are deallocated by free(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_data(struct libscols_cell *ce, const char *data) +{ + return strdup_to_struct_member(ce, data, data); +} + +/** + * scols_cell_refer_data: + * @ce: a pointer to a struct libscols_cell instance + * @data: data (used for scols_print_table()) + * + * Adds a reference to @str to @ce. The pointer is deallocated by + * scols_reset_cell() or scols_unref_line(). This function is mostly designed + * for situations when the data for the cell are already composed in allocated + * memory (e.g. asprintf()) to avoid extra unnecessary strdup(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_refer_data(struct libscols_cell *ce, char *data) +{ + if (!ce) + return -EINVAL; + free(ce->data); + ce->data = data; + return 0; +} + +/** + * scols_cell_get_data: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: data in @ce or NULL. + */ +const char *scols_cell_get_data(const struct libscols_cell *ce) +{ + return ce ? ce->data : NULL; +} + +/** + * scols_cell_set_userdata: + * @ce: a pointer to a struct libscols_cell instance + * @data: private user data + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_userdata(struct libscols_cell *ce, void *data) +{ + if (!ce) + return -EINVAL; + ce->userdata = data; + return 0; +} + +/** + * scols_cell_get_userdata + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: user data + */ +void *scols_cell_get_userdata(struct libscols_cell *ce) +{ + return ce->userdata; +} + +/** + * scols_cmpstr_cells: + * @a: pointer to cell + * @b: pointer to cell + * @data: unused pointer to private data (defined by API) + * + * Compares cells data by strcoll(). The function is designed for + * scols_column_set_cmpfunc() and scols_sort_table(). + * + * Returns: follows strcoll() return values. + */ +int scols_cmpstr_cells(struct libscols_cell *a, + struct libscols_cell *b, + __attribute__((__unused__)) void *data) +{ + const char *adata, *bdata; + + if (a == b) + return 0; + + adata = scols_cell_get_data(a); + bdata = scols_cell_get_data(b); + + if (adata == NULL && bdata == NULL) + return 0; + if (adata == NULL) + return -1; + if (bdata == NULL) + return 1; + return strcoll(adata, bdata); +} + +/** + * scols_cell_set_color: + * @ce: a pointer to a struct libscols_cell instance + * @color: color name or ESC sequence + * + * Set the color of @ce to @color. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_color(struct libscols_cell *ce, const char *color) +{ + if (color && !color_is_sequence(color)) { + char *seq = color_get_sequence(color); + if (!seq) + return -EINVAL; + free(ce->color); + ce->color = seq; + return 0; + } + return strdup_to_struct_member(ce, color, color); +} + +/** + * scols_cell_get_color: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: the current color of @ce. + */ +const char *scols_cell_get_color(const struct libscols_cell *ce) +{ + return ce->color; +} + +/** + * scols_cell_set_flags: + * @ce: a pointer to a struct libscols_cell instance + * @flags: SCOLS_CELL_FL_* flags + * + * Note that cells in the table are always aligned by column flags. The cell + * flags are used for table title only (now). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_set_flags(struct libscols_cell *ce, int flags) +{ + if (!ce) + return -EINVAL; + ce->flags = flags; + return 0; +} + +/** + * scols_cell_get_flags: + * @ce: a pointer to a struct libscols_cell instance + * + * Returns: the current flags + */ +int scols_cell_get_flags(const struct libscols_cell *ce) +{ + return ce->flags; +} + +/** + * scols_cell_get_alignment: + * @ce: a pointer to a struct libscols_cell instance + * + * Since: 2.30 + * + * Returns: SCOLS_CELL_FL_{RIGHT,CELNTER,LEFT} + */ +int scols_cell_get_alignment(const struct libscols_cell *ce) +{ + if (ce->flags & SCOLS_CELL_FL_RIGHT) + return SCOLS_CELL_FL_RIGHT; + if (ce->flags & SCOLS_CELL_FL_CENTER) + return SCOLS_CELL_FL_CENTER; + + return SCOLS_CELL_FL_LEFT; /* default */ +} + +/** + * scols_cell_copy_content: + * @dest: a pointer to a struct libscols_cell instance + * @src: a pointer to an immutable struct libscols_cell instance + * + * Copy the contents of @src into @dest. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_cell_copy_content(struct libscols_cell *dest, + const struct libscols_cell *src) +{ + int rc; + + rc = scols_cell_set_data(dest, scols_cell_get_data(src)); + if (!rc) + rc = scols_cell_set_color(dest, scols_cell_get_color(src)); + if (!rc) + dest->userdata = src->userdata; + + DBG(CELL, ul_debugobj(src, "copy")); + return rc; +} diff --git a/libsmartcols/src/column.c b/libsmartcols/src/column.c new file mode 100644 index 0000000..db4c357 --- /dev/null +++ b/libsmartcols/src/column.c @@ -0,0 +1,737 @@ +/* + * column.c - functions for table handling at the column level + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: column + * @title: Column + * @short_description: defines output columns formats, headers, etc. + * + * An API to access and modify per-column data and information. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "mbsalign.h" +#include "strutils.h" +#include "smartcolsP.h" + +/** + * scols_new_column: + * + * Allocates space for a new column. + * + * Returns: a pointer to a new struct libscols_column instance, NULL in case of an ENOMEM error. + */ +struct libscols_column *scols_new_column(void) +{ + struct libscols_column *cl; + + cl = calloc(1, sizeof(*cl)); + if (!cl) + return NULL; + DBG(COL, ul_debugobj(cl, "alloc")); + cl->refcount = 1; + INIT_LIST_HEAD(&cl->cl_columns); + return cl; +} + +/** + * scols_ref_column: + * @cl: a pointer to a struct libscols_column instance + * + * Increases the refcount of @cl. + */ +void scols_ref_column(struct libscols_column *cl) +{ + if (cl) + cl->refcount++; +} + +/** + * scols_unref_column: + * @cl: a pointer to a struct libscols_column instance + * + * Decreases the refcount of @cl. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_column(struct libscols_column *cl) +{ + if (cl && --cl->refcount <= 0) { + DBG(COL, ul_debugobj(cl, "dealloc")); + list_del(&cl->cl_columns); + scols_reset_cell(&cl->header); + free(cl->color); + free(cl->safechars); + free(cl->pending_data_buf); + free(cl->shellvar); + free(cl); + } +} + +/** + * scols_copy_column: + * @cl: a pointer to a struct libscols_column instance + * + * Creates a new column and copies @cl's data over to it. + * + * Returns: a pointer to a new struct libscols_column instance. + */ +struct libscols_column *scols_copy_column(const struct libscols_column *cl) +{ + struct libscols_column *ret; + + if (!cl) + return NULL; + ret = scols_new_column(); + if (!ret) + return NULL; + + DBG(COL, ul_debugobj(cl, "copy")); + + if (scols_column_set_color(ret, cl->color)) + goto err; + if (scols_cell_copy_content(&ret->header, &cl->header)) + goto err; + + ret->width = cl->width; + ret->width_hint = cl->width_hint; + ret->flags = cl->flags; + ret->is_groups = cl->is_groups; + + memcpy(&ret->wstat, &cl->wstat, sizeof(cl->wstat)); + + return ret; +err: + scols_unref_column(ret); + return NULL; +} + +/** + * scols_column_set_whint: + * @cl: a pointer to a struct libscols_column instance + * @whint: a width hint + * + * Sets the width hint of column @cl to @whint. See scols_table_new_column(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_whint(struct libscols_column *cl, double whint) +{ + if (!cl) + return -EINVAL; + + cl->width_hint = whint; + return 0; +} + +/** + * scols_column_get_whint: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The width hint of column @cl, a negative value in case of an error. + */ +double scols_column_get_whint(const struct libscols_column *cl) +{ + return cl->width_hint; +} + +/** + * scols_column_set_flags: + * @cl: a pointer to a struct libscols_column instance + * @flags: a flag mask + * + * Sets the flags of @cl to @flags. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_flags(struct libscols_column *cl, int flags) +{ + if (!cl) + return -EINVAL; + + if (cl->table) { + if (!(cl->flags & SCOLS_FL_TREE) && (flags & SCOLS_FL_TREE)) + cl->table->ntreecols++; + else if ((cl->flags & SCOLS_FL_TREE) && !(flags & SCOLS_FL_TREE)) + cl->table->ntreecols--; + } + + DBG(COL, ul_debugobj(cl, "setting flags from 0x%04x to 0x%04x", cl->flags, flags)); + cl->flags = flags; + return 0; +} + +/** + * scols_column_set_json_type: + * @cl: a pointer to a struct libscols_column instance + * @type: SCOLS_JSON_* type + * + * Sets the type used for JSON formatting, the default is SCOLS_JSON_STRING. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.33 + */ +int scols_column_set_json_type(struct libscols_column *cl, int type) +{ + if (!cl) + return -EINVAL; + + cl->json_type = type; + return 0; + +} + +/** + * scols_column_get_json_type: + * @cl: a pointer to a struct libscols_column instance + * + * Note that SCOLS_JSON_BOOLEAN interprets NULL, empty strings, '0', 'N' and + * 'n' as "false"; and everything else as "true". + * + * Returns: JSON type used for formatting or a negative value in case of an error. + * + * Since: 2.33 + */ +int scols_column_get_json_type(const struct libscols_column *cl) +{ + return cl ? cl->json_type : -EINVAL; +} + + +/** + * scols_column_get_table: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: pointer to the table where columns is used + */ +struct libscols_table *scols_column_get_table(const struct libscols_column *cl) +{ + return cl->table; +} + +/** + * scols_column_get_flags: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The flag mask of @cl, a negative value in case of an error. + */ +int scols_column_get_flags(const struct libscols_column *cl) +{ + return cl->flags; +} + +/** + * scols_column_get_header: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: A pointer to a struct libscols_cell instance, representing the + * header info of column @cl or NULL in case of an error. + */ +struct libscols_cell *scols_column_get_header(struct libscols_column *cl) +{ + return &cl->header; +} + +/** + * scols_column_set_name: + * @cl: a pointer to a struct libscols_column instance + * @name: column name + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.38 + */ +int scols_column_set_name(struct libscols_column *cl, const char *name) +{ + struct libscols_cell *hr = scols_column_get_header(cl); + + if (!hr) + return -EINVAL; + + free(cl->shellvar); + cl->shellvar = NULL; + + return scols_cell_set_data(hr, name); +} + +/** + * scols_column_get_name: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: A pointer to a column name, which is stored in column header + * + * Since: 2.38 + */ +const char *scols_column_get_name(struct libscols_column *cl) +{ + return scols_cell_get_data(&cl->header); +} + +/** + * scols_column_get_name_as_shellvar + * @cl: a pointer to a struct libscols_column instance + * + * Like scols_column_get_name(), but column name is modified to be compatible with shells + * requirements for variable names. + * + * Since: 2.38 + */ +const char *scols_column_get_name_as_shellvar(struct libscols_column *cl) +{ + if (!cl->shellvar) { + const char *s, *name = scols_column_get_name(cl); + char *p; + size_t sz; + + if (!name || !*name) + return NULL; + + /* "1FOO%" --> "_1FOO_PCT */ + sz = strlen(name) + 1 + 3; + p = cl->shellvar = calloc(1, sz + 1); + if (!cl->shellvar) + return NULL; + + /* convert "1FOO" to "_1FOO" */ + if (!isalpha(*name)) + *p++ = '_'; + + /* replace all "bad" chars with "_" */ + for (s = name; *s; s++) + *p++ = !isalnum(*s) ? '_' : *s; + + if (!*s && *(s - 1) == '%') { + *p++ = 'P'; + *p++ = 'C'; + *p++ = 'T'; + } + } + return cl->shellvar; +} + + +/** + * scols_column_set_color: + * @cl: a pointer to a struct libscols_column instance + * @color: color name or ESC sequence + * + * The default color for data cells and column header. + * + * If you want to set header specific color then use scols_column_get_header() + * and scols_cell_set_color(). + * + * If you want to set data cell specific color the use scols_line_get_cell() + + * scols_cell_set_color(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_color(struct libscols_column *cl, const char *color) +{ + if (color && !color_is_sequence(color)) { + char *seq = color_get_sequence(color); + if (!seq) + return -EINVAL; + free(cl->color); + cl->color = seq; + return 0; + } + return strdup_to_struct_member(cl, color, color); +} + +/** + * scols_column_get_color: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: The current color setting of the column @cl. + */ +const char *scols_column_get_color(const struct libscols_column *cl) +{ + return cl->color; +} + +/** + * scols_wrapnl_nextchunk: + * @cl: a pointer to a struct libscols_column instance + * @data: string + * @userdata: callback private data + * + * This is built-in function for scols_column_set_wrapfunc(). This function + * terminates the current chunk by \0 and returns pointer to the begin of + * the next chunk. The chunks are based on \n. + * + * For example for data "AAA\nBBB\nCCC" the next chunk is "BBB". + * + * Returns: next chunk + * + * Since: 2.29 + */ +char *scols_wrapnl_nextchunk(const struct libscols_column *cl __attribute__((unused)), + char *data, + void *userdata __attribute__((unused))) +{ + char *p = data ? strchr(data, '\n') : NULL; + + if (p) { + *p = '\0'; + return p + 1; + } + return NULL; +} + +/** + * scols_wrapnl_chunksize: + * @cl: a pointer to a struct libscols_column instance + * @data: string + * @userdata: callback private data + * + * Analyzes @data and returns size of the largest chunk. The chunks are based + * on \n. For example for data "AAA\nBBB\nCCCC" the largest chunk size is 4. + * + * Note that the size has to be based on number of terminal cells rather than + * bytes to support multu-byte output. + * + * Returns: size of the largest chunk. + * + * Since: 2.29 + */ +size_t scols_wrapnl_chunksize(const struct libscols_column *cl __attribute__((unused)), + const char *data, + void *userdata __attribute__((unused))) +{ + size_t sum = 0; + + while (data && *data) { + const char *p; + size_t sz; + + p = strchr(data, '\n'); + if (p) { + sz = cl->table && scols_table_is_noencoding(cl->table) ? + mbs_nwidth(data, p - data) : + mbs_safe_nwidth(data, p - data, NULL); + p++; + } else { + sz = cl->table && scols_table_is_noencoding(cl->table) ? + mbs_width(data) : + mbs_safe_width(data); + } + sum = max(sum, sz); + data = p; + } + + return sum; +} + +/** + * scols_column_set_cmpfunc: + * @cl: column + * @cmp: pointer to compare function + * @data: private data for cmp function + * + * Returns: 0, a negative value in case of an error. + */ +int scols_column_set_cmpfunc(struct libscols_column *cl, + int (*cmp)(struct libscols_cell *, + struct libscols_cell *, + void *), + void *data) +{ + if (!cl) + return -EINVAL; + + cl->cmpfunc = cmp; + cl->cmpfunc_data = data; + return 0; +} + +/** + * scols_column_set_wrapfunc: + * @cl: a pointer to a struct libscols_column instance + * @wrap_chunksize: function to return size of the largest chink of data + * @wrap_nextchunk: function to return next zero terminated data + * @userdata: optional stuff for callbacks + * + * Extends SCOLS_FL_WRAP and can be used to set custom wrap function. The default + * is to wrap by column size, but you can create functions to wrap for example + * after \n or after words, etc. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_column_set_wrapfunc(struct libscols_column *cl, + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, + void *), + char * (*wrap_nextchunk)(const struct libscols_column *, + char *, + void *), + void *userdata) +{ + if (!cl) + return -EINVAL; + + cl->wrap_nextchunk = wrap_nextchunk; + cl->wrap_chunksize = wrap_chunksize; + cl->wrapfunc_data = userdata; + return 0; +} + +/** + * scols_column_set_safechars: + * @cl: a pointer to a struct libscols_column instance + * @safe: safe characters (e.g. "\n\t") + * + * Use for bytes you don't want to encode on output. This is for example + * necessary if you want to use custom wrap function based on \n, in this case + * you have to set "\n" as a safe char. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_column_set_safechars(struct libscols_column *cl, const char *safe) +{ + return strdup_to_struct_member(cl, safechars, safe); +} + +/** + * scols_column_get_safechars: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: safe chars + * + * Since: 2.29 + */ +const char *scols_column_get_safechars(const struct libscols_column *cl) +{ + return cl->safechars; +} + +/** + * scols_column_get_width: + * @cl: a pointer to a struct libscols_column instance + * + * Important note: the column width is unknown until library starts printing + * (width is calculated before printing). The function is usable for example in + * nextchunk() callback specified by scols_column_set_wrapfunc(). + * + * See also scols_column_get_whint(), it returns wanted size (!= final size). + * + * Returns: column width + * + * Since: 2.29 + */ +size_t scols_column_get_width(const struct libscols_column *cl) +{ + return cl->width; +} + +/** + * scols_column_is_hidden: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag hidden. + * + * Returns: 0 or 1 + * + * Since: 2.27 + */ +int scols_column_is_hidden(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_HIDDEN ? 1 : 0; +} + +/** + * scols_column_is_trunc: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag trunc. + * + * Returns: 0 or 1 + */ +int scols_column_is_trunc(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_TRUNC ? 1 : 0; +} +/** + * scols_column_is_tree: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag tree. + * + * Returns: 0 or 1 + */ +int scols_column_is_tree(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_TREE ? 1 : 0; +} +/** + * scols_column_is_right: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag right. + * + * Returns: 0 or 1 + */ +int scols_column_is_right(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_RIGHT ? 1 : 0; +} +/** + * scols_column_is_strict_width: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag strict_width. + * + * Returns: 0 or 1 + */ +int scols_column_is_strict_width(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_STRICTWIDTH ? 1 : 0; +} +/** + * scols_column_is_noextremes: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag no_extremes. + * + * Returns: 0 or 1 + */ +int scols_column_is_noextremes(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_NOEXTREMES ? 1 : 0; +} +/** + * scols_column_is_wrap: + * @cl: a pointer to a struct libscols_column instance + * + * Gets the value of @cl's flag wrap. + * + * Returns: 0 or 1 + * + * Since: 2.28 + */ +int scols_column_is_wrap(const struct libscols_column *cl) +{ + return cl->flags & SCOLS_FL_WRAP ? 1 : 0; +} +/** + * scols_column_is_customwrap: + * @cl: a pointer to a struct libscols_column instance + * + * Returns: 0 or 1 + * + * Since: 2.29 + */ +int scols_column_is_customwrap(const struct libscols_column *cl) +{ + return (cl->flags & SCOLS_FL_WRAP) + && cl->wrap_chunksize + && cl->wrap_nextchunk ? 1 : 0; +} + +/** + * scols_column_set_properties: + * @cl: a pointer to a struct libscols_column instance + * @opts: options string + * + * Set properties from string, the string is comma seprated list, like + * "trunc,right,json=number", ... + * + * Returns: 0 on success, <0 on error + * + * Since: 2.39 + */ +int scols_column_set_properties(struct libscols_column *cl, const char *opts) +{ + char *str = (char *) opts; + char *name, *value; + size_t namesz, valuesz; + unsigned int flags = 0; + int rc = 0; + + DBG(COL, ul_debugobj(cl, "apply properties '%s'", opts)); + + while (rc == 0 + && !ul_optstr_next(&str, &name, &namesz, &value, &valuesz)) { + + if (strncmp(name, "trunc", namesz) == 0) + flags |= SCOLS_FL_TRUNC; + + else if (strncmp(name, "tree", namesz) == 0) + flags |= SCOLS_FL_TREE; + + else if (strncmp(name, "right", namesz) == 0) + flags |= SCOLS_FL_RIGHT; + + else if (strncmp(name, "strictwidth", namesz) == 0) + flags |= SCOLS_FL_STRICTWIDTH; + + else if (strncmp(name, "noextremes", namesz) == 0) + flags |= SCOLS_FL_STRICTWIDTH; + + else if (strncmp(name, "hidden", namesz) == 0) + flags |= SCOLS_FL_HIDDEN; + + else if (strncmp(name, "wrap", namesz) == 0) + flags |= SCOLS_FL_WRAP; + + else if (value && strncmp(name, "json", namesz) == 0) { + + if (strncmp(value, "string", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_STRING); + else if (strncmp(value, "number", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); + else if (strncmp(value, "array-string", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_STRING); + else if (strncmp(value, "array-number", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_ARRAY_NUMBER); + else if (strncmp(value, "boolean", valuesz) == 0) + rc = scols_column_set_json_type(cl, SCOLS_JSON_BOOLEAN); + + } else if (value && strncmp(name, "width", namesz) == 0) { + + char *end = NULL; + double x = strtod(value, &end); + if (errno || str == end) + return -EINVAL; + + rc = scols_column_set_whint(cl, x); + + } else if (value && strncmp(name, "color", namesz) == 0) { + + char *x = strndup(value, valuesz); + if (x) { + scols_column_set_color(cl, x); + free(x); + } + + } else if (value && strncmp(name, "name", namesz) == 0) { + + char *x = strndup(value, valuesz); + if (x) { + scols_column_set_name(cl, x); + free(x); + } + } + } + + if (!rc && flags) + rc = scols_column_set_flags(cl, flags); + + return rc; +} + diff --git a/libsmartcols/src/grouping.c b/libsmartcols/src/grouping.c new file mode 100644 index 0000000..0b27cb2 --- /dev/null +++ b/libsmartcols/src/grouping.c @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2018 Karel Zak <kzak@redhat.com> + */ +#include "smartcolsP.h" + +/** + * SECTION: grouping + * @title: Grouping + * @short_description: lines grouing + * + * Lines groups manipulation API. The grouping API can be used to create M:N + * relations between lines and on tree-like output it prints extra chart to + * visualize these relations. The group has unlimited number of members and + * group childs. See libsmartcols/sample/grouping* for more details. + */ + +/* Private API */ +void scols_ref_group(struct libscols_group *gr) +{ + if (gr) + gr->refcount++; +} + +void scols_group_remove_children(struct libscols_group *gr) +{ + if (!gr) + return; + + while (!list_empty(&gr->gr_children)) { + struct libscols_line *ln = list_entry(gr->gr_children.next, + struct libscols_line, ln_children); + + DBG(GROUP, ul_debugobj(gr, "remove child")); + list_del_init(&ln->ln_children); + scols_ref_group(ln->parent_group); + ln->parent_group = NULL; + scols_unref_line(ln); + } +} + +void scols_group_remove_members(struct libscols_group *gr) +{ + if (!gr) + return; + + while (!list_empty(&gr->gr_members)) { + struct libscols_line *ln = list_entry(gr->gr_members.next, + struct libscols_line, ln_groups); + + DBG(GROUP, ul_debugobj(gr, "remove member [%p]", ln)); + list_del_init(&ln->ln_groups); + + scols_unref_group(ln->group); + ln->group->nmembers++; + ln->group = NULL; + + scols_unref_line(ln); + } +} + +/* note group has to be already without members to deallocate */ +void scols_unref_group(struct libscols_group *gr) +{ + if (gr && --gr->refcount <= 0) { + DBG(GROUP, ul_debugobj(gr, "dealloc")); + scols_group_remove_children(gr); + list_del(&gr->gr_groups); + free(gr); + return; + } +} + + +static void groups_fix_members_order(struct libscols_line *ln) +{ + struct libscols_iter itr; + struct libscols_line *child; + + if (ln->group) { + INIT_LIST_HEAD(&ln->ln_groups); + list_add_tail(&ln->ln_groups, &ln->group->gr_members); + DBG(GROUP, ul_debugobj(ln->group, "fixing member line=%p [%zu/%zu]", + ln, ln->group->nmembers, + list_count_entries(&ln->group->gr_members))); + } + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_child(ln, &itr, &child) == 0) + groups_fix_members_order(child); + + /* + * We modify gr_members list, so is_last_group_member() does not have + * to provide reliable answer, we need to verify by list_count_entries(). + */ + if (ln->group + && is_last_group_member(ln) + && ln->group->nmembers == list_count_entries(&ln->group->gr_members)) { + + DBG(GROUP, ul_debugobj(ln->group, "fixing childs")); + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_line_next_group_child(ln, &itr, &child) == 0) + groups_fix_members_order(child); + } +} + +void scols_groups_fix_members_order(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_line *ln; + struct libscols_group *gr; + + /* remove all from groups lists */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + while (!list_empty(&gr->gr_members)) { + struct libscols_line *line = list_entry(gr->gr_members.next, + struct libscols_line, ln_groups); + list_del_init(&line->ln_groups); + } + } + + /* add again to the groups list in order we walk in tree */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent || ln->parent_group) + continue; + groups_fix_members_order(ln); + } + + /* If group child is member of another group * + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + struct libscols_iter xitr; + struct libscols_line *child; + + scols_reset_iter(&xitr, SCOLS_ITER_FORWARD); + while (scols_line_next_group_child(ln, &xitr, &child) == 0) + groups_fix_members_order(child); + } + */ +} + +static inline const char *group_state_to_string(int state) +{ + static const char *grpstates[] = { + [SCOLS_GSTATE_NONE] = "none", + [SCOLS_GSTATE_FIRST_MEMBER] = "1st-member", + [SCOLS_GSTATE_MIDDLE_MEMBER] = "middle-member", + [SCOLS_GSTATE_LAST_MEMBER] = "last-member", + [SCOLS_GSTATE_MIDDLE_CHILD] = "middle-child", + [SCOLS_GSTATE_LAST_CHILD] = "last-child", + [SCOLS_GSTATE_CONT_MEMBERS] = "continue-members", + [SCOLS_GSTATE_CONT_CHILDREN] = "continue-children" + }; + + assert(state >= 0); + assert((size_t) state < ARRAY_SIZE(grpstates)); + + return grpstates[state]; +} +/* +static void grpset_debug(struct libscols_table *tb, struct libscols_line *ln) +{ + size_t i; + + for (i = 0; i < tb->grpset_size; i++) { + if (tb->grpset[i]) { + struct libscols_group *gr = tb->grpset[i]; + + if (ln) + DBG(LINE, ul_debugobj(ln, "grpset[%zu]: %p %s", i, + gr, group_state_to_string(gr->state))); + else + DBG(LINE, ul_debug("grpset[%zu]: %p %s", i, + gr, group_state_to_string(gr->state))); + } else if (ln) { + DBG(LINE, ul_debugobj(ln, "grpset[%zu]: free", i)); + } else + DBG(LINE, ul_debug("grpset[%zu]: free", i)); + } +} +*/ +static int group_state_for_line(struct libscols_group *gr, struct libscols_line *ln) +{ + if (gr->state == SCOLS_GSTATE_NONE && + (ln->group != gr || !is_first_group_member(ln))) + /* + * NONE is possible to translate to FIRST_MEMBER only, and only if + * line group matches with the current group. + */ + return SCOLS_GSTATE_NONE; + + if (ln->group != gr && ln->parent_group != gr) { + /* Not our line, continue */ + if (gr->state == SCOLS_GSTATE_FIRST_MEMBER || + gr->state == SCOLS_GSTATE_MIDDLE_MEMBER || + gr->state == SCOLS_GSTATE_CONT_MEMBERS) + return SCOLS_GSTATE_CONT_MEMBERS; + + if (gr->state == SCOLS_GSTATE_LAST_MEMBER || + gr->state == SCOLS_GSTATE_MIDDLE_CHILD || + gr->state == SCOLS_GSTATE_CONT_CHILDREN) + return SCOLS_GSTATE_CONT_CHILDREN; + + } else if (ln->group == gr && is_first_group_member(ln)) { + return SCOLS_GSTATE_FIRST_MEMBER; + + } else if (ln->group == gr && is_last_group_member(ln)) { + return SCOLS_GSTATE_LAST_MEMBER; + + } else if (ln->group == gr && is_group_member(ln)) { + return SCOLS_GSTATE_MIDDLE_MEMBER; + + } else if (ln->parent_group == gr && is_last_group_child(ln)) { + return SCOLS_GSTATE_LAST_CHILD; + + } else if (ln->parent_group == gr && is_group_child(ln)) { + return SCOLS_GSTATE_MIDDLE_CHILD; + } + + return SCOLS_GSTATE_NONE; +} + +/* + * apply new @state to the chunk (addressed by @xx) of grpset used for the group (@gr) + */ +static void grpset_apply_group_state(struct libscols_group **xx, int state, struct libscols_group *gr) +{ + size_t i; + + DBG(GROUP, ul_debugobj(gr, " applying state to grpset")); + + /* gr->state holds the old state, @state is the new state + */ + for (i = 0; i < SCOLS_GRPSET_CHUNKSIZ; i++) + xx[i] = state == SCOLS_GSTATE_NONE ? NULL : gr; + + gr->state = state; +} + +static struct libscols_group **grpset_locate_freespace(struct libscols_table *tb, int chunks, int prepend) +{ + size_t i, avail = 0; + struct libscols_group **tmp, **first = NULL; + const size_t wanted = chunks * SCOLS_GRPSET_CHUNKSIZ; + + if (!tb->grpset_size) + prepend = 0; + /* + DBG(TAB, ul_debugobj(tb, "orig grpset:")); + grpset_debug(tb, NULL); + */ + if (prepend) { + for (i = tb->grpset_size - 1; ; i--) { + if (tb->grpset[i] == NULL) { + first = &tb->grpset[i]; + avail++; + } else + avail = 0; + if (avail == wanted) + goto done; + if (i == 0) + break; + } + } else { + for (i = 0; i < tb->grpset_size; i++) { + if (tb->grpset[i] == NULL) { + if (avail == 0) + first = &tb->grpset[i]; + avail++; + } else + avail = 0; + if (avail == wanted) + goto done; + } + } + + DBG(TAB, ul_debugobj(tb, " realocate grpset [sz: old=%zu, new=%zu, new_chunks=%d]", + tb->grpset_size, tb->grpset_size + wanted, chunks)); + + tmp = realloc(tb->grpset, (tb->grpset_size + wanted) * sizeof(struct libscols_group *)); + if (!tmp) + return NULL; + + tb->grpset = tmp; + + if (prepend) { + DBG(TAB, ul_debugobj(tb, " prepending free space")); + char *dest = (char *) tb->grpset; + + memmove( dest + (wanted * sizeof(struct libscols_group *)), + tb->grpset, + tb->grpset_size * sizeof(struct libscols_group *)); + first = tb->grpset; + } else { + first = tb->grpset + tb->grpset_size; + } + + memset(first, 0, wanted * sizeof(struct libscols_group *)); + tb->grpset_size += wanted; + +done: + /* + DBG(TAB, ul_debugobj(tb, "new grpset:")); + grpset_debug(tb, NULL); + */ + return first; +} + +static struct libscols_group **grpset_locate_group(struct libscols_table *tb, struct libscols_group *gr) +{ + size_t i; + + for (i = 0; i < tb->grpset_size; i++) { + if (gr == tb->grpset[i]) + return &tb->grpset[i]; + } + + return NULL; +} + + +static int grpset_update(struct libscols_table *tb, struct libscols_line *ln, struct libscols_group *gr) +{ + struct libscols_group **xx; + int state; + + DBG(LINE, ul_debugobj(ln, " group [%p] grpset update [grpset size=%zu]", gr, tb->grpset_size)); + + /* new state, note that gr->state still holds the original state */ + state = group_state_for_line(gr, ln); + DBG(LINE, ul_debugobj(ln, " state %s --> %s", + group_state_to_string(gr->state), + group_state_to_string(state))); + + if (state == SCOLS_GSTATE_FIRST_MEMBER && gr->state != SCOLS_GSTATE_NONE) { + DBG(LINE, ul_debugobj(ln, "wrong group initialization (%s)", group_state_to_string(gr->state))); + abort(); + } + if (state != SCOLS_GSTATE_NONE && gr->state == SCOLS_GSTATE_LAST_CHILD) { + DBG(LINE, ul_debugobj(ln, "wrong group termination (%s)", group_state_to_string(gr->state))); + abort(); + } + if (gr->state == SCOLS_GSTATE_LAST_MEMBER && + !(state == SCOLS_GSTATE_LAST_CHILD || + state == SCOLS_GSTATE_CONT_CHILDREN || + state == SCOLS_GSTATE_MIDDLE_CHILD || + state == SCOLS_GSTATE_NONE)) { + DBG(LINE, ul_debugobj(ln, "wrong group member->child order")); + abort(); + } + + /* should not happen; probably wrong line... */ + if (gr->state == SCOLS_GSTATE_NONE && state == SCOLS_GSTATE_NONE) + return 0; + + /* locate place in grpset where we draw the group */ + if (!tb->grpset || gr->state == SCOLS_GSTATE_NONE) + xx = grpset_locate_freespace(tb, 1, 1); + else + xx = grpset_locate_group(tb, gr); + if (!xx) { + DBG(LINE, ul_debugobj(ln, "failed to locate group or reallocate grpset")); + return -ENOMEM; + } + + grpset_apply_group_state(xx, state, gr); + /*ON_DBG(LINE, grpset_debug(tb, ln));*/ + return 0; +} + +static int grpset_update_active_groups(struct libscols_table *tb, struct libscols_line *ln) +{ + int rc = 0; + size_t i; + struct libscols_group *last = NULL; + + DBG(LINE, ul_debugobj(ln, " update for active groups")); + + for (i = 0; i < tb->grpset_size; i++) { + struct libscols_group *gr = tb->grpset[i]; + + if (!gr || last == gr) + continue; + last = gr; + rc = grpset_update(tb, ln, gr); + if (rc) + break; + } + + DBG(LINE, ul_debugobj(ln, " <- active groups updated [rc=%d]", rc)); + return rc; +} + +int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln) +{ + int rc = 0; + + DBG(LINE, ul_debugobj(ln, " grpset update [line: group=%p, parent_group=%p", + ln->group, ln->parent_group)); + + rc = grpset_update_active_groups(tb, ln); + if (!rc && ln->group && ln->group->state == SCOLS_GSTATE_NONE) { + DBG(LINE, ul_debugobj(ln, " introduce a new group")); + rc = grpset_update(tb, ln, ln->group); + } + return rc; +} + +void scols_groups_reset_state(struct libscols_table *tb) +{ + struct libscols_iter itr; + struct libscols_group *gr; + + DBG(TAB, ul_debugobj(tb, "reset groups states")); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_group(tb, &itr, &gr) == 0) { + DBG(GROUP, ul_debugobj(gr, " reset to NONE")); + gr->state = SCOLS_GSTATE_NONE; + } + + if (tb->grpset) { + DBG(TAB, ul_debugobj(tb, " zeroize grpset")); + memset(tb->grpset, 0, tb->grpset_size * sizeof(struct libscols_group *)); + } + tb->ngrpchlds_pending = 0; +} + +static void add_member(struct libscols_group *gr, struct libscols_line *ln) +{ + DBG(GROUP, ul_debugobj(gr, "add member %p", ln)); + + ln->group = gr; + gr->nmembers++; + scols_ref_group(gr); + + INIT_LIST_HEAD(&ln->ln_groups); + list_add_tail(&ln->ln_groups, &gr->gr_members); + scols_ref_line(ln); +} + +/* + * Returns first group which is ready to print group children. + * + * This function scans grpset[] in backward order and returns first group + * with SCOLS_GSTATE_CONT_CHILDREN or SCOLS_GSTATE_LAST_MEMBER state. + */ +struct libscols_group *scols_grpset_get_printable_children(struct libscols_table *tb) +{ + size_t i; + + for (i = tb->grpset_size; i > 0; i -= SCOLS_GRPSET_CHUNKSIZ) { + struct libscols_group *gr = tb->grpset[i-1]; + + if (gr == NULL) + continue; + if (gr->state == SCOLS_GSTATE_CONT_CHILDREN || + gr->state == SCOLS_GSTATE_LAST_MEMBER) + return gr; + } + + return NULL; +} + + +/** + * scols_table_group_lines: + * @tb: a pointer to a struct libscols_table instance + * @ln: new group member + * @member: group member + * @id: group identifier (unused, not implemented yet), use zero. + * + * This function add line @ln to group of lines represented by @member. If the + * group is not yet defined (@member is not member of any group) than a new one + * is allocated. + * + * The @ln maybe a NULL -- in this case only a new group is allocated if not + * defined yet. + * + * Note that the same line cannot be member of more groups (not implemented + * yet). The child of any group can be member of another group. + * + * The @id is not used for now, use 0. The plan is to use it to support + * multi-group membership in future. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_table_group_lines( struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_line *member, + __attribute__((__unused__)) int id) +{ + struct libscols_group *gr = NULL; + + if (!tb || !member) { + DBG(GROUP, ul_debugobj(gr, "failed group lines (no table or member)")); + return -EINVAL; + } + if (ln) { + if (ln->group && !member->group) { + DBG(GROUP, ul_debugobj(gr, "failed group lines (new group, line member of another)")); + return -EINVAL; + } + if (ln->group && member->group && ln->group != member->group) { + DBG(GROUP, ul_debugobj(gr, "failed group lines (groups mismatch bwteen member and line")); + return -EINVAL; + } + } + + gr = member->group; + + /* create a new group */ + if (!gr) { + gr = calloc(1, sizeof(*gr)); + if (!gr) + return -ENOMEM; + DBG(GROUP, ul_debugobj(gr, "alloc")); + gr->refcount = 1; + INIT_LIST_HEAD(&gr->gr_members); + INIT_LIST_HEAD(&gr->gr_children); + INIT_LIST_HEAD(&gr->gr_groups); + + /* add group to the table */ + list_add_tail(&gr->gr_groups, &tb->tb_groups); + + /* add the first member */ + add_member(gr, member); + } + + /* add to group */ + if (ln && !ln->group) + add_member(gr, ln); + + return 0; +} + +/** + * scols_line_link_group: + * @ln: line instance + * @member: group member + * @id: group identifier (unused, not implemented yet)) + * + * Define @ln as child of group represented by group @member. The line @ln + * cannot be child of any other line. It's possible to create group->child or + * parent->child relationship, but no both for the same line (child). + * + * The @id is not used for now, use 0. The plan is to use it to support + * multi-group membership in future. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, + __attribute__((__unused__)) int id) +{ + if (!ln || !member || !member->group || ln->parent) + return -EINVAL; + + if (!list_empty(&ln->ln_children)) + return -EINVAL; + + DBG(GROUP, ul_debugobj(member->group, "add child")); + + list_add_tail(&ln->ln_children, &member->group->gr_children); + scols_ref_line(ln); + + ln->parent_group = member->group; + scols_ref_group(member->group); + + return 0; +} diff --git a/libsmartcols/src/init.c b/libsmartcols/src/init.c new file mode 100644 index 0000000..dfd7510 --- /dev/null +++ b/libsmartcols/src/init.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: init + * @title: Library initialization + * @short_description: initialize debugging + * + * The library debug stuff. + */ + +#include <stdarg.h> + +#include "smartcolsP.h" + +UL_DEBUG_DEFINE_MASK(libsmartcols); +UL_DEBUG_DEFINE_MASKNAMES(libsmartcols) = +{ + { "all", SCOLS_DEBUG_ALL, "info about all subsystems" }, + { "buff", SCOLS_DEBUG_BUFF, "output buffer utils" }, + { "cell", SCOLS_DEBUG_CELL, "table cell utils" }, + { "col", SCOLS_DEBUG_COL, "cols utils" }, + { "help", SCOLS_DEBUG_HELP, "this help" }, + { "group", SCOLS_DEBUG_GROUP, "lines grouping utils" }, + { "line", SCOLS_DEBUG_LINE, "table line utils" }, + { "tab", SCOLS_DEBUG_TAB, "table utils" }, + { NULL, 0, NULL } +}; + +/** + * scols_init_debug: + * @mask: debug mask (0xffff to enable full debugging) + * + * If the @mask is not specified, then this function reads + * the LIBSMARTCOLS_DEBUG environment variable to get the mask. + * + * Already initialized debugging stuff cannot be changed. Calling + * this function twice has no effect. + */ +void scols_init_debug(int mask) +{ + if (libsmartcols_debug_mask) + return; + + __UL_INIT_DEBUG_FROM_ENV(libsmartcols, SCOLS_DEBUG_, mask, LIBSMARTCOLS_DEBUG); + + if (libsmartcols_debug_mask != SCOLS_DEBUG_INIT + && libsmartcols_debug_mask != (SCOLS_DEBUG_HELP|SCOLS_DEBUG_INIT)) { + const char *ver = NULL; + + scols_get_library_version(&ver); + + DBG(INIT, ul_debug("library debug mask: 0x%04x", libsmartcols_debug_mask)); + DBG(INIT, ul_debug("library version: %s", ver)); + } + ON_DBG(HELP, ul_debug_print_masks("LIBSMARTCOLS_DEBUG", + UL_DEBUG_MASKNAMES(libsmartcols))); +} diff --git a/libsmartcols/src/iter.c b/libsmartcols/src/iter.c new file mode 100644 index 0000000..91cc080 --- /dev/null +++ b/libsmartcols/src/iter.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009-2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: iter + * @title: Iterator + * @short_description: unified iterator + * + * The iterator keeps the direction and the last position + * for access to the internal library tables/lists. + */ + +#include <string.h> +#include <stdlib.h> + +#include "smartcolsP.h" + +/** + * scols_new_iter: + * @direction: SCOLS_INTER_{FOR,BACK}WARD direction + * + * Returns: newly allocated generic libmount iterator. + */ +struct libscols_iter *scols_new_iter(int direction) +{ + struct libscols_iter *itr = calloc(1, sizeof(*itr)); + if (!itr) + return NULL; + itr->direction = direction; + return itr; +} + +/** + * scols_free_iter: + * @itr: iterator pointer + * + * Deallocates the iterator. + */ +void scols_free_iter(struct libscols_iter *itr) +{ + free(itr); +} + +/** + * scols_reset_iter: + * @itr: iterator pointer + * @direction: SCOLS_INTER_{FOR,BACK}WARD or -1 to keep the direction unchanged + * + * Resets the iterator. + */ +void scols_reset_iter(struct libscols_iter *itr, int direction) +{ + if (direction == -1) + direction = itr->direction; + + memset(itr, 0, sizeof(*itr)); + itr->direction = direction; +} + +/** + * scols_iter_get_direction: + * @itr: iterator pointer + * + * Returns: SCOLS_INTER_{FOR,BACK}WARD + */ +int scols_iter_get_direction(const struct libscols_iter *itr) +{ + return itr->direction; +} diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in new file mode 100644 index 0000000..f5820e9 --- /dev/null +++ b/libsmartcols/src/libsmartcols.h.in @@ -0,0 +1,349 @@ +/* + * Prints table or tree. + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ +#ifndef _LIBSMARTCOLS_H +#define _LIBSMARTCOLS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> + +/** + * LIBSMARTCOLS_VERSION: + * + * Library version string + */ +#define LIBSMARTCOLS_VERSION "@LIBSMARTCOLS_VERSION@" + +/** + * libscols_iter: + * + * Generic iterator + */ +struct libscols_iter; + +/** + * libscols_symbols: + * + * Symbol groups for printing tree hierarchies + */ +struct libscols_symbols; + +/** + * libscols_cell: + * + * A cell - the smallest library object + */ +struct libscols_cell; + +/** + * libscols_line: + * + * A line - an array of cells + */ +struct libscols_line; + +/** + * libscols_table: + * + * A table - The most abstract object, encapsulating lines, columns, symbols and cells + */ +struct libscols_table; + +/** + * libscols_column: + * + * A column - defines the number of columns and column names + */ +struct libscols_column; + +/* iter.c */ +enum { + + SCOLS_ITER_FORWARD = 0, + SCOLS_ITER_BACKWARD +}; + +/* + * Column flags + */ +enum { + SCOLS_FL_TRUNC = (1 << 0), /* truncate fields data if necessary */ + SCOLS_FL_TREE = (1 << 1), /* use tree "ascii art" */ + SCOLS_FL_RIGHT = (1 << 2), /* align to the right */ + SCOLS_FL_STRICTWIDTH = (1 << 3), /* don't reduce width if column is empty */ + SCOLS_FL_NOEXTREMES = (1 << 4), /* ignore extreme fields when count column width*/ + SCOLS_FL_HIDDEN = (1 << 5), /* maintain data, but don't print */ + SCOLS_FL_WRAP = (1 << 6) /* wrap long lines to multi-line cells */ +}; + +/* + * Column JSON types + */ +enum { + SCOLS_JSON_STRING = 0, /* default */ + SCOLS_JSON_NUMBER = 1, + SCOLS_JSON_BOOLEAN = 2, + SCOLS_JSON_ARRAY_STRING = 3, /* e.g. for multi-line (SCOLS_FL_WRAP) cells */ + SCOLS_JSON_ARRAY_NUMBER = 4, + SCOLS_JSON_BOOLEAN_OPTIONAL = 5, +}; + +/* + * Cell flags, see scols_cell_set_flags() before use + */ +enum { + /* alignment evaluated in order: right,center,left */ + SCOLS_CELL_FL_LEFT = 0, + SCOLS_CELL_FL_CENTER = (1 << 0), + SCOLS_CELL_FL_RIGHT = (1 << 1) +}; + +extern struct libscols_iter *scols_new_iter(int direction); +extern void scols_free_iter(struct libscols_iter *itr); +extern void scols_reset_iter(struct libscols_iter *itr, int direction); +extern int scols_iter_get_direction(const struct libscols_iter *itr); + +/* init.c */ +extern void scols_init_debug(int mask); + +/* version.c */ +extern int scols_parse_version_string(const char *ver_string); +extern int scols_get_library_version(const char **ver_string); + +/* symbols.c */ +extern struct libscols_symbols *scols_new_symbols(void); +extern void scols_ref_symbols(struct libscols_symbols *sy); +extern void scols_unref_symbols(struct libscols_symbols *sy); +extern struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy); +extern int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_right(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str); + +extern int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str); +extern int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str); + +/* cell.c */ +extern int scols_reset_cell(struct libscols_cell *ce); +extern int scols_cell_copy_content(struct libscols_cell *dest, + const struct libscols_cell *src); +extern int scols_cell_set_data(struct libscols_cell *ce, const char *data); +extern int scols_cell_refer_data(struct libscols_cell *ce, char *data); +extern const char *scols_cell_get_data(const struct libscols_cell *ce); +extern int scols_cell_set_color(struct libscols_cell *ce, const char *color); +extern const char *scols_cell_get_color(const struct libscols_cell *ce); + +extern int scols_cell_set_flags(struct libscols_cell *ce, int flags); +extern int scols_cell_get_flags(const struct libscols_cell *ce); +extern int scols_cell_get_alignment(const struct libscols_cell *ce); + +extern void *scols_cell_get_userdata(struct libscols_cell *ce); +extern int scols_cell_set_userdata(struct libscols_cell *ce, void *data); + +extern int scols_cmpstr_cells(struct libscols_cell *a, + struct libscols_cell *b, void *data); +/* column.c */ +extern int scols_column_is_tree(const struct libscols_column *cl); +extern int scols_column_is_trunc(const struct libscols_column *cl); +extern int scols_column_is_right(const struct libscols_column *cl); +extern int scols_column_is_strict_width(const struct libscols_column *cl); +extern int scols_column_is_hidden(const struct libscols_column *cl); +extern int scols_column_is_noextremes(const struct libscols_column *cl); +extern int scols_column_is_wrap(const struct libscols_column *cl); +extern int scols_column_is_customwrap(const struct libscols_column *cl); + +extern size_t scols_column_get_width(const struct libscols_column *cl); + +extern int scols_column_set_safechars(struct libscols_column *cl, const char *safe); +extern const char *scols_column_get_safechars(const struct libscols_column *cl); + +extern int scols_column_set_json_type(struct libscols_column *cl, int type); +extern int scols_column_get_json_type(const struct libscols_column *cl); + +extern int scols_column_set_flags(struct libscols_column *cl, int flags); +extern int scols_column_get_flags(const struct libscols_column *cl); +extern struct libscols_column *scols_new_column(void); +extern void scols_ref_column(struct libscols_column *cl); +extern void scols_unref_column(struct libscols_column *cl); +extern struct libscols_column *scols_copy_column(const struct libscols_column *cl); +extern int scols_column_set_whint(struct libscols_column *cl, double whint); +extern double scols_column_get_whint(const struct libscols_column *cl); +extern struct libscols_cell *scols_column_get_header(struct libscols_column *cl); +extern int scols_column_set_color(struct libscols_column *cl, const char *color); +extern const char *scols_column_get_color(const struct libscols_column *cl); +extern struct libscols_table *scols_column_get_table(const struct libscols_column *cl); + +extern int scols_column_set_name(struct libscols_column *cl, const char *name); +extern const char *scols_column_get_name(struct libscols_column *cl); +extern const char *scols_column_get_name_as_shellvar(struct libscols_column *cl); + +extern int scols_column_set_properties(struct libscols_column *cl, const char *opts); + +extern int scols_column_set_cmpfunc(struct libscols_column *cl, + int (*cmp)(struct libscols_cell *a, + struct libscols_cell *b, void *), + void *data); + +extern int scols_column_set_wrapfunc(struct libscols_column *cl, + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, void *), + char * (*wrap_nextchunk)(const struct libscols_column *, + char *, void *), + void *userdata); + +extern char *scols_wrapnl_nextchunk(const struct libscols_column *cl, char *data, void *userdata); +extern size_t scols_wrapnl_chunksize(const struct libscols_column *cl, const char *data, void *userdata); + +/* line.c */ +extern struct libscols_line *scols_new_line(void); +extern void scols_ref_line(struct libscols_line *ln); +extern void scols_unref_line(struct libscols_line *ln); +extern int scols_line_alloc_cells(struct libscols_line *ln, size_t n); +extern void scols_line_free_cells(struct libscols_line *ln); +extern int scols_line_set_userdata(struct libscols_line *ln, void *data); +extern void *scols_line_get_userdata(struct libscols_line *ln); +extern int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child); +extern int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child); +extern int scols_line_has_children(struct libscols_line *ln); +extern int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent); +extern int scols_line_next_child(struct libscols_line *ln, + struct libscols_iter *itr, struct libscols_line **chld); +extern struct libscols_line *scols_line_get_parent(const struct libscols_line *ln); +extern int scols_line_set_color(struct libscols_line *ln, const char *color); +extern const char *scols_line_get_color(const struct libscols_line *ln); +extern size_t scols_line_get_ncells(const struct libscols_line *ln); +extern struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, size_t n); +extern struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl); +extern int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data); +extern int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data); +extern int scols_line_set_column_data(struct libscols_line *ln, struct libscols_column *cl, const char *data); +extern const char *scols_line_get_column_data(struct libscols_line *ln, struct libscols_column *cl); +extern int scols_line_refer_column_data(struct libscols_line *ln, struct libscols_column *cl, char *data); +extern struct libscols_line *scols_copy_line(const struct libscols_line *ln); + +/* table */ +extern int scols_table_colors_wanted(const struct libscols_table *tb); +extern int scols_table_set_name(struct libscols_table *tb, const char *name); +extern const char *scols_table_get_name(const struct libscols_table *tb); +extern struct libscols_cell *scols_table_get_title(struct libscols_table *tb); +extern int scols_table_is_raw(const struct libscols_table *tb); +extern int scols_table_is_ascii(const struct libscols_table *tb); +extern int scols_table_is_json(const struct libscols_table *tb); +extern int scols_table_is_noheadings(const struct libscols_table *tb); +extern int scols_table_is_header_repeat(const struct libscols_table *tb); +extern int scols_table_is_empty(const struct libscols_table *tb); +extern int scols_table_is_export(const struct libscols_table *tb); +extern int scols_table_is_shellvar(const struct libscols_table *tb); +extern int scols_table_is_maxout(const struct libscols_table *tb); +extern int scols_table_is_minout(const struct libscols_table *tb); +extern int scols_table_is_nowrap(const struct libscols_table *tb); +extern int scols_table_is_nolinesep(const struct libscols_table *tb); +extern int scols_table_is_tree(const struct libscols_table *tb); +extern int scols_table_is_noencoding(const struct libscols_table *tb); + +extern int scols_table_enable_colors(struct libscols_table *tb, int enable); +extern int scols_table_enable_raw(struct libscols_table *tb, int enable); +extern int scols_table_enable_ascii(struct libscols_table *tb, int enable); +extern int scols_table_enable_json(struct libscols_table *tb, int enable); +extern int scols_table_enable_noheadings(struct libscols_table *tb, int enable); +extern int scols_table_enable_header_repeat(struct libscols_table *tb, int enable); +extern int scols_table_enable_export(struct libscols_table *tb, int enable); +extern int scols_table_enable_shellvar(struct libscols_table *tb, int enable); +extern int scols_table_enable_maxout(struct libscols_table *tb, int enable); +extern int scols_table_enable_minout(struct libscols_table *tb, int enable); +extern int scols_table_enable_nowrap(struct libscols_table *tb, int enable); +extern int scols_table_enable_nolinesep(struct libscols_table *tb, int enable); +extern int scols_table_enable_noencoding(struct libscols_table *tb, int enable); + +extern int scols_table_set_column_separator(struct libscols_table *tb, const char *sep); +extern int scols_table_set_line_separator(struct libscols_table *tb, const char *sep); + +extern struct libscols_table *scols_new_table(void); +extern void scols_ref_table(struct libscols_table *tb); +extern void scols_unref_table(struct libscols_table *tb); +extern int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_table_remove_column(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_table_remove_columns(struct libscols_table *tb); +extern int scols_table_move_column(struct libscols_table *tb, struct libscols_column *pre, struct libscols_column *cl); +extern struct libscols_column *scols_table_new_column(struct libscols_table *tb, const char *name, double whint, int flags); +extern int scols_table_next_column(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column **cl); +extern int scols_table_set_columns_iter(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_column *cl); +extern const char *scols_table_get_column_separator(const struct libscols_table *tb); +extern const char *scols_table_get_line_separator(const struct libscols_table *tb); +extern size_t scols_table_get_ncols(const struct libscols_table *tb); +extern size_t scols_table_get_nlines(const struct libscols_table *tb); +extern struct libscols_column *scols_table_get_column(struct libscols_table *tb, size_t n); +struct libscols_column *scols_table_get_column_by_name(struct libscols_table *tb, const char *name); +extern int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln); +extern int scols_table_remove_line(struct libscols_table *tb, struct libscols_line *ln); +extern void scols_table_remove_lines(struct libscols_table *tb); +extern int scols_table_next_line(struct libscols_table *tb, struct libscols_iter *itr, struct libscols_line **ln); +extern struct libscols_line *scols_table_new_line(struct libscols_table *tb, struct libscols_line *parent); +extern struct libscols_line *scols_table_get_line(struct libscols_table *tb, size_t n); +extern struct libscols_table *scols_copy_table(struct libscols_table *tb); +extern int scols_table_set_symbols(struct libscols_table *tb, struct libscols_symbols *sy); +extern int scols_table_set_default_symbols(struct libscols_table *tb); +extern struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb); + +extern int scols_table_set_stream(struct libscols_table *tb, FILE *stream); +extern FILE *scols_table_get_stream(const struct libscols_table *tb); +extern int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce); + +extern int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl); +extern int scols_sort_table_by_tree(struct libscols_table *tb); +/* + * + */ +enum { + SCOLS_TERMFORCE_AUTO = 0, + SCOLS_TERMFORCE_NEVER, + SCOLS_TERMFORCE_ALWAYS +}; +extern int scols_table_set_termforce(struct libscols_table *tb, int force); +extern int scols_table_get_termforce(const struct libscols_table *tb); +extern int scols_table_set_termwidth(struct libscols_table *tb, size_t width); +extern size_t scols_table_get_termwidth(const struct libscols_table *tb); +extern int scols_table_set_termheight(struct libscols_table *tb, size_t height); +extern size_t scols_table_get_termheight(const struct libscols_table *tb); + + +/* table_print.c */ +extern int scols_print_table(struct libscols_table *tb); +extern int scols_print_table_to_string(struct libscols_table *tb, char **data); + +extern int scols_table_print_range( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end); +extern int scols_table_print_range_to_string( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end, + char **data); + +/* grouping.c */ +int scols_line_link_group(struct libscols_line *ln, struct libscols_line *member, int id); +int scols_table_group_lines(struct libscols_table *tb, struct libscols_line *ln, + struct libscols_line *member, int id); +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSMARTCOLS_H */ diff --git a/libsmartcols/src/libsmartcols.sym b/libsmartcols/src/libsmartcols.sym new file mode 100644 index 0000000..4499908 --- /dev/null +++ b/libsmartcols/src/libsmartcols.sym @@ -0,0 +1,217 @@ +/* + * symbols since util-linux 2.25 + * + * Copyright (C) 2014-2016 Karel Zak <kzak@redhat.com> + */ +SMARTCOLS_2.25 { +global: + scols_cell_copy_content; + scols_cell_get_color; + scols_cell_get_data; + scols_cell_get_userdata; + scols_cell_refer_data; + scols_cell_set_color; + scols_cell_set_data; + scols_cell_set_userdata; + scols_cmpstr_cells; + scols_column_get_color; + scols_column_get_flags; + scols_column_get_header; + scols_column_get_whint; + scols_column_is_noextremes; + scols_column_is_right; + scols_column_is_strict_width; + scols_column_is_tree; + scols_column_is_trunc; + scols_column_set_cmpfunc; + scols_column_set_color; + scols_column_set_flags; + scols_column_set_whint; + scols_copy_column; + scols_copy_line; + scols_copy_symbols; + scols_copy_table; + scols_free_iter; + scols_get_library_version; + scols_init_debug; + scols_iter_get_direction; + scols_line_add_child; + scols_line_alloc_cells; + scols_line_free_cells; + scols_line_get_cell; + scols_line_get_color; + scols_line_get_column_cell; + scols_line_get_ncells; + scols_line_get_parent; + scols_line_get_userdata; + scols_line_has_children; + scols_line_next_child; + scols_line_refer_data; + scols_line_remove_child; + scols_line_set_color; + scols_line_set_data; + scols_line_set_userdata; + scols_new_column; + scols_new_iter; + scols_new_line; + scols_new_symbols; + scols_new_table; + scols_parse_version_string; + scols_print_table; + scols_print_table_to_string; + scols_ref_column; + scols_ref_line; + scols_ref_symbols; + scols_ref_table; + scols_reset_cell; + scols_reset_iter; + scols_sort_table; + scols_symbols_set_branch; + scols_symbols_set_right; + scols_symbols_set_vertical; + scols_table_add_column; + scols_table_add_line; + scols_table_colors_wanted; + scols_table_enable_ascii; + scols_table_enable_colors; + scols_table_enable_export; + scols_table_enable_maxout; + scols_table_enable_noheadings; + scols_table_enable_raw; + scols_table_get_column; + scols_table_get_column_separator; + scols_table_get_line; + scols_table_get_line_separator; + scols_table_get_ncols; + scols_table_get_nlines; + scols_table_get_stream; + scols_table_is_ascii; + scols_table_is_empty; + scols_table_is_export; + scols_table_is_maxout; + scols_table_is_noheadings; + scols_table_is_raw; + scols_table_is_tree; + scols_table_new_column; + scols_table_new_line; + scols_table_next_column; + scols_table_next_line; + scols_table_reduce_termwidth; + scols_table_remove_column; + scols_table_remove_columns; + scols_table_remove_line; + scols_table_remove_lines; + scols_table_set_column_separator; + scols_table_set_line_separator; + scols_table_set_stream; + scols_table_set_symbols; + scols_unref_column; + scols_unref_line; + scols_unref_symbols; + scols_unref_table; +local: + *; +}; + +SMARTCOLS_2.27 { +global: + scols_column_is_hidden; + scols_table_enable_json; + scols_table_is_json; + scols_table_set_name; +} SMARTCOLS_2.25; + +SMARTCOLS_2.28 { +global: + scols_column_is_wrap; + scols_line_refer_column_data; + scols_line_set_column_data; + scols_symbols_set_title_padding; + scols_table_enable_nowrap; + scols_table_get_title; + scols_cell_get_flags; + scols_cell_set_flags; + scols_table_print_range; + scols_table_print_range_to_string; + scols_table_enable_nolinesep; +} SMARTCOLS_2.27; + +SMARTCOLS_2.29 { +global: + scols_column_get_safechars; + scols_column_get_table; + scols_column_get_width; + scols_column_is_customwrap; + scols_column_set_safechars; + scols_column_set_wrapfunc; + scols_symbols_set_cell_padding; + scols_table_get_name; + scols_table_get_symbols; + scols_table_get_termforce; + scols_table_get_termwidth; + scols_table_is_nolinesep; + scols_table_is_nowrap; + scols_table_set_default_symbols; + scols_table_set_termforce; + scols_table_set_termwidth; + scols_wrapnl_chunksize; + scols_wrapnl_nextchunk; +} SMARTCOLS_2.28; + + +SMARTCOLS_2.30 { +global: + scols_cell_get_alignment; + scols_table_move_column; + scols_sort_table_by_tree; + scols_line_is_ancestor; +} SMARTCOLS_2.29; + + +SMARTCOLS_2.31 { + scols_table_set_termheight; + scols_table_get_termheight; + scols_table_is_header_repeat; + scols_table_enable_header_repeat; + scols_table_enable_noencoding; + scols_table_is_noencoding; +} SMARTCOLS_2.30; + + +SMARTCOLS_2.33 { + scols_column_set_json_type; + scols_column_get_json_type; +} SMARTCOLS_2.31; + +SMARTCOLS_2.34 { + scols_table_group_lines; + scols_line_link_group; + scols_symbols_set_group_vertical; + scols_symbols_set_group_horizontal; + scols_symbols_set_group_first_member; + scols_symbols_set_group_last_member; + scols_symbols_set_group_middle_member; + scols_symbols_set_group_last_child; + scols_symbols_set_group_middle_child; +} SMARTCOLS_2.33; + +SMARTCOLS_2.35 { + scols_table_enable_minout; + scols_table_is_minout; + scols_table_set_columns_iter; +} SMARTCOLS_2.34; + +SMARTCOLS_2.38 { + scols_line_get_column_data; + scols_column_set_name; + scols_column_get_name; + scols_column_get_name_as_shellvar; + scols_table_is_shellvar; + scols_table_enable_shellvar; +} SMARTCOLS_2.35; + + +SMARTCOLS_2.39 { + scols_column_set_properties; + scols_table_get_column_by_name; +} SMARTCOLS_2.38; diff --git a/libsmartcols/src/line.c b/libsmartcols/src/line.c new file mode 100644 index 0000000..cab99c5 --- /dev/null +++ b/libsmartcols/src/line.c @@ -0,0 +1,563 @@ +/* + * line.c - functions for table handling at the line level + * + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: line + * @title: Line + * @short_description: cells container, also keeps tree (parent->child) information + * + * An API to access and modify per-line data and information. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "smartcolsP.h" + +/** + * scols_new_line: + * + * Note that the line is allocated without cells, the cells will be allocated + * later when you add the line to the table. If you want to use the line + * without table then you have to explicitly allocate the cells by + * scols_line_alloc_cells(). + * + * Returns: a pointer to a new struct libscols_line instance. + */ +struct libscols_line *scols_new_line(void) +{ + struct libscols_line *ln; + + ln = calloc(1, sizeof(*ln)); + if (!ln) + return NULL; + + DBG(LINE, ul_debugobj(ln, "alloc")); + ln->refcount = 1; + INIT_LIST_HEAD(&ln->ln_lines); + INIT_LIST_HEAD(&ln->ln_children); + INIT_LIST_HEAD(&ln->ln_branch); + INIT_LIST_HEAD(&ln->ln_groups); + return ln; +} + +/** + * scols_ref_line: + * @ln: a pointer to a struct libscols_line instance + * + * Increases the refcount of @ln. + */ +void scols_ref_line(struct libscols_line *ln) +{ + if (ln) + ln->refcount++; +} + +/** + * scols_unref_line: + * @ln: a pointer to a struct libscols_line instance + * + * Decreases the refcount of @ln. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_line(struct libscols_line *ln) +{ + if (ln && --ln->refcount <= 0) { + DBG(CELL, ul_debugobj(ln, "dealloc")); + list_del(&ln->ln_lines); + list_del(&ln->ln_children); + list_del(&ln->ln_groups); + scols_unref_group(ln->group); + scols_line_free_cells(ln); + free(ln->color); + free(ln); + return; + } +} + +/** + * scols_line_free_cells: + * @ln: a pointer to a struct libscols_line instance + * + * Frees the allocated cells referenced to by @ln. + */ +void scols_line_free_cells(struct libscols_line *ln) +{ + size_t i; + + if (!ln || !ln->cells) + return; + + DBG(LINE, ul_debugobj(ln, "free cells")); + + for (i = 0; i < ln->ncells; i++) + scols_reset_cell(&ln->cells[i]); + + free(ln->cells); + ln->ncells = 0; + ln->cells = NULL; +} + +/** + * scols_line_alloc_cells: + * @ln: a pointer to a struct libscols_line instance + * @n: the number of elements + * + * Allocates space for @n cells. This function is optional, + * and libsmartcols automatically allocates necessary cells + * according to number of columns in the table when you add + * the line to the table. See scols_table_add_line(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_alloc_cells(struct libscols_line *ln, size_t n) +{ + struct libscols_cell *ce; + + if (!ln) + return -EINVAL; + if (ln->ncells == n) + return 0; + + if (!n) { + scols_line_free_cells(ln); + return 0; + } + + DBG(LINE, ul_debugobj(ln, "alloc %zu cells", n)); + + ce = realloc(ln->cells, n * sizeof(struct libscols_cell)); + if (!ce) + return -errno; + + if (n > ln->ncells) + memset(ce + ln->ncells, 0, + (n - ln->ncells) * sizeof(struct libscols_cell)); + + ln->cells = ce; + ln->ncells = n; + return 0; +} + +int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn) +{ + struct libscols_cell ce; + + if (!ln || newn >= ln->ncells || oldn >= ln->ncells) + return -EINVAL; + if (oldn == newn) + return 0; + + DBG(LINE, ul_debugobj(ln, "move cells[%zu] -> cells[%zu]", oldn, newn)); + + /* remember data from old position */ + memcpy(&ce, &ln->cells[oldn], sizeof(struct libscols_cell)); + + /* remove old position (move data behind oldn to oldn) */ + if (oldn + 1 < ln->ncells) + memmove(ln->cells + oldn, ln->cells + oldn + 1, + (ln->ncells - oldn - 1) * sizeof(struct libscols_cell)); + + /* create a space for new position */ + if (newn + 1 < ln->ncells) + memmove(ln->cells + newn + 1, ln->cells + newn, + (ln->ncells - newn - 1) * sizeof(struct libscols_cell)); + + /* copy original data to new position */ + memcpy(&ln->cells[newn], &ce, sizeof(struct libscols_cell)); + return 0; +} + +/** + * scols_line_set_userdata: + * @ln: a pointer to a struct libscols_line instance + * @data: user data + * + * Binds @data to @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_userdata(struct libscols_line *ln, void *data) +{ + if (!ln) + return -EINVAL; + ln->userdata = data; + return 0; +} + +/** + * scols_line_get_userdata: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: user data + */ +void *scols_line_get_userdata(struct libscols_line *ln) +{ + return ln->userdata; +} + +/** + * scols_line_remove_child: + * @ln: a pointer to a struct libscols_line instance + * @child: a pointer to a struct libscols_line instance + * + * Removes @child as a child of @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_remove_child(struct libscols_line *ln, struct libscols_line *child) +{ + if (!ln || !child) + return -EINVAL; + + DBG(LINE, ul_debugobj(ln, "remove child")); + + list_del_init(&child->ln_children); + child->parent = NULL; + scols_unref_line(child); + + scols_unref_line(ln); + return 0; +} + +/** + * scols_line_add_child: + * @ln: a pointer to a struct libscols_line instance + * @child: a pointer to a struct libscols_line instance + * + * Sets @child as a child of @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_add_child(struct libscols_line *ln, struct libscols_line *child) +{ + if (!ln || !child) + return -EINVAL; + + DBG(LINE, ul_debugobj(ln, "add child")); + scols_ref_line(child); + scols_ref_line(ln); + + /* unref old<->parent */ + if (child->parent) + scols_line_remove_child(child->parent, child); + + /* new reference from parent to child */ + list_add_tail(&child->ln_children, &ln->ln_branch); + + /* new reference from child to parent */ + child->parent = ln; + return 0; +} + +/** + * scols_line_get_parent: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: a pointer to @ln's parent, NULL in case it has no parent or if there was an error. + */ +struct libscols_line *scols_line_get_parent(const struct libscols_line *ln) +{ + return ln ? ln->parent : NULL; +} + +/** + * scols_line_has_children: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: 1 if @ln has any children, otherwise 0. + */ +int scols_line_has_children(struct libscols_line *ln) +{ + return ln ? !list_empty(&ln->ln_branch) : 0; +} + +/** + * scols_line_next_child: + * @ln: a pointer to a struct libscols_line instance + * @itr: a pointer to a struct libscols_iter instance + * @chld: a pointer to a pointer to a struct libscols_line instance + * + * Finds the next child and returns a pointer to it via @chld. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_next_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld) +{ + int rc = 1; + + if (!ln || !itr || !chld) + return -EINVAL; + *chld = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &ln->ln_branch); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children); + rc = 0; + } + + return rc; +} + +/* private API */ +int scols_line_next_group_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld) +{ + int rc = 1; + + if (!ln || !itr || !chld || !ln->group) + return -EINVAL; + *chld = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &ln->group->gr_children); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *chld, struct libscols_line, ln_children); + rc = 0; + } + + return rc; +} + +/** + * scols_line_is_ancestor: + * @ln: line + * @parent: potential parent + * + * The function is designed to detect circular dependencies between @ln and + * @parent. It checks if @ln is not any (grand) parent in the @parent's tree. + * + * Since: 2.30 + * + * Returns: 0 or 1 + */ +int scols_line_is_ancestor(struct libscols_line *ln, struct libscols_line *parent) +{ + while (parent) { + if (parent == ln) + return 1; + parent = scols_line_get_parent(parent); + }; + return 0; +} + +/** + * scols_line_set_color: + * @ln: a pointer to a struct libscols_line instance + * @color: color name or ESC sequence + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_color(struct libscols_line *ln, const char *color) +{ + if (color && !color_is_sequence(color)) { + char *seq = color_get_sequence(color); + if (!seq) + return -EINVAL; + free(ln->color); + ln->color = seq; + return 0; + } + return strdup_to_struct_member(ln, color, color); +} + +/** + * scols_line_get_color: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: @ln's color string, NULL in case of an error. + */ +const char *scols_line_get_color(const struct libscols_line *ln) +{ + return ln->color; +} + +/** + * scols_line_get_ncells: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: number of cells + */ +size_t scols_line_get_ncells(const struct libscols_line *ln) +{ + return ln->ncells; +} + +/** + * scols_line_get_cell: + * @ln: a pointer to a struct libscols_line instance + * @n: cell number to retrieve + * + * Returns: the @n-th cell in @ln, NULL in case of an error. + */ +struct libscols_cell *scols_line_get_cell(struct libscols_line *ln, + size_t n) +{ + if (!ln || n >= ln->ncells) + return NULL; + return &ln->cells[n]; +} + +/** + * scols_line_get_column_cell: + * @ln: a pointer to a struct libscols_line instance + * @cl: pointer to cell + * + * Like scols_line_get_cell() by cell is referenced by column. + * + * Returns: the @n-th cell in @ln, NULL in case of an error. + */ +struct libscols_cell *scols_line_get_column_cell( + struct libscols_line *ln, + struct libscols_column *cl) +{ + if (!ln || !cl) + return NULL; + + return scols_line_get_cell(ln, cl->seqnum); +} + +/** + * scols_line_set_data: + * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell, whose data is to be set + * @data: actual data to set + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_set_data(struct libscols_line *ln, size_t n, const char *data) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, n); + + if (!ce) + return -EINVAL; + return scols_cell_set_data(ce, data); +} + +/** + * scols_line_set_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be set + * @data: actual data to set + * + * The same as scols_line_set_data() but cell is referenced by column object. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_line_set_column_data(struct libscols_line *ln, + struct libscols_column *cl, + const char *data) +{ + return scols_line_set_data(ln, cl->seqnum, data); +} + +/** + * scols_line_get_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be get + * + * See also scols_cell_get_data() + * + * Returns: cell data or NULL. + * + * Since: 2.38 + */ +const char *scols_line_get_column_data(struct libscols_line *ln, + struct libscols_column *cl) +{ + struct libscols_cell *cell = scols_line_get_column_cell(ln, cl); + + return cell ? scols_cell_get_data(cell) : NULL; +} + + +/** + * scols_line_refer_data: + * @ln: a pointer to a struct libscols_line instance + * @n: number of the cell which will refer to @data + * @data: actual data to refer to + * + * Returns: 0, a negative value in case of an error. + */ +int scols_line_refer_data(struct libscols_line *ln, size_t n, char *data) +{ + struct libscols_cell *ce = scols_line_get_cell(ln, n); + + if (!ce) + return -EINVAL; + return scols_cell_refer_data(ce, data); +} + +/** + * scols_line_refer_column_data: + * @ln: a pointer to a struct libscols_line instance + * @cl: column, whose data is to be set + * @data: actual data to refer to + * + * The same as scols_line_refer_data() but cell is referenced by column object. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_line_refer_column_data(struct libscols_line *ln, + struct libscols_column *cl, + char *data) +{ + return scols_line_refer_data(ln, cl->seqnum, data); +} + +/** + * scols_copy_line: + * @ln: a pointer to a struct libscols_line instance + * + * Returns: A newly allocated copy of @ln, NULL in case of an error. + */ +struct libscols_line *scols_copy_line(const struct libscols_line *ln) +{ + struct libscols_line *ret; + size_t i; + + if (!ln) + return NULL; + + ret = scols_new_line(); + if (!ret) + return NULL; + if (scols_line_set_color(ret, ln->color)) + goto err; + if (scols_line_alloc_cells(ret, ln->ncells)) + goto err; + + ret->userdata = ln->userdata; + ret->ncells = ln->ncells; + ret->seqnum = ln->seqnum; + + DBG(LINE, ul_debugobj(ln, "copy")); + + for (i = 0; i < ret->ncells; ++i) { + if (scols_cell_copy_content(&ret->cells[i], &ln->cells[i])) + goto err; + } + + return ret; +err: + scols_unref_line(ret); + return NULL; +} diff --git a/libsmartcols/src/print-api.c b/libsmartcols/src/print-api.c new file mode 100644 index 0000000..52b2664 --- /dev/null +++ b/libsmartcols/src/print-api.c @@ -0,0 +1,220 @@ +#include "smartcolsP.h" + +/** + * scola_table_print_range: + * @tb: table + * @start: first printed line or NULL to print from the begin of the table + * @end: last printed line or NULL to print all from start. + * + * If the start is the first line in the table than prints table header too. + * The header is printed only once. This does not work for trees. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_print_range( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end) +{ + struct ul_buffer buf = UL_INIT_BUFFER; + struct libscols_iter itr; + int rc; + + if (scols_table_is_tree(tb)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range from API")); + + rc = __scols_initialize_printing(tb, &buf); + if (rc) + return rc; + + if (start) { + itr.direction = SCOLS_ITER_FORWARD; + itr.head = &tb->tb_lines; + itr.p = &start->ln_lines; + } else + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + if (!start || itr.p == tb->tb_lines.next) { + rc = __scols_print_header(tb, &buf); + if (rc) + goto done; + } + + rc = __scols_print_range(tb, &buf, &itr, end); +done: + __scols_cleanup_printing(tb, &buf); + return rc; +} + +/** + * scols_table_print_range_to_string: + * @tb: table + * @start: first printed line or NULL to print from the beginning of the table + * @end: last printed line or NULL to print all from start. + * @data: pointer to the beginning of a memory area to print to + * + * The same as scols_table_print_range(), but prints to @data instead of + * stream. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_table_print_range_to_string( struct libscols_table *tb, + struct libscols_line *start, + struct libscols_line *end, + char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing range to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = scols_table_print_range(tb, start, end); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_table_print_range_to_string( + struct libscols_table *tb __attribute__((__unused__)), + struct libscols_line *start __attribute__((__unused__)), + struct libscols_line *end __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif + +static int do_print_table(struct libscols_table *tb, int *is_empty) +{ + int rc = 0; + struct ul_buffer buf = UL_INIT_BUFFER; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing")); + if (is_empty) + *is_empty = 0; + + if (list_empty(&tb->tb_columns)) { + DBG(TAB, ul_debugobj(tb, "error -- no columns")); + return -EINVAL; + } + if (list_empty(&tb->tb_lines)) { + DBG(TAB, ul_debugobj(tb, "ignore -- no lines")); + if (scols_table_is_json(tb)) { + ul_jsonwrt_init(&tb->json, tb->out, 0); + ul_jsonwrt_root_open(&tb->json); + ul_jsonwrt_array_open(&tb->json, tb->name ? tb->name : ""); + ul_jsonwrt_array_close(&tb->json); + ul_jsonwrt_root_close(&tb->json); + } else if (is_empty) + *is_empty = 1; + return 0; + } + + tb->header_printed = 0; + rc = __scols_initialize_printing(tb, &buf); + if (rc) + return rc; + + if (scols_table_is_json(tb)) { + ul_jsonwrt_root_open(&tb->json); + ul_jsonwrt_array_open(&tb->json, tb->name ? tb->name : ""); + } + + if (tb->format == SCOLS_FMT_HUMAN) + __scols_print_title(tb); + + rc = __scols_print_header(tb, &buf); + if (rc) + goto done; + + if (scols_table_is_tree(tb)) + rc = __scols_print_tree(tb, &buf); + else + rc = __scols_print_table(tb, &buf); + + if (scols_table_is_json(tb)) { + ul_jsonwrt_array_close(&tb->json); + ul_jsonwrt_root_close(&tb->json); + } +done: + __scols_cleanup_printing(tb, &buf); + return rc; +} + +/** + * scols_print_table: + * @tb: table + * + * Prints the table to the output stream and terminate by \n. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_print_table(struct libscols_table *tb) +{ + int empty = 0; + int rc = do_print_table(tb, &empty); + + if (rc == 0 && !empty && !scols_table_is_json(tb)) + fputc('\n', tb->out); + return rc; +} + +/** + * scols_print_table_to_string: + * @tb: table + * @data: pointer to the beginning of a memory area to print to + * + * Prints the table to @data. + * + * Returns: 0, a negative value in case of an error. + */ +#ifdef HAVE_OPEN_MEMSTREAM +int scols_print_table_to_string(struct libscols_table *tb, char **data) +{ + FILE *stream, *old_stream; + size_t sz; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "printing to string")); + + /* create a stream for output */ + stream = open_memstream(data, &sz); + if (!stream) + return -ENOMEM; + + old_stream = scols_table_get_stream(tb); + scols_table_set_stream(tb, stream); + rc = do_print_table(tb, NULL); + fclose(stream); + scols_table_set_stream(tb, old_stream); + + return rc; +} +#else +int scols_print_table_to_string( + struct libscols_table *tb __attribute__((__unused__)), + char **data __attribute__((__unused__))) +{ + return -ENOSYS; +} +#endif diff --git a/libsmartcols/src/print.c b/libsmartcols/src/print.c new file mode 100644 index 0000000..6a7e6da --- /dev/null +++ b/libsmartcols/src/print.c @@ -0,0 +1,1243 @@ + /* print.c - functions to print table + * + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: table_print + * @title: Table print + * @short_description: output functions + * + * Table output API. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <termios.h> +#include <ctype.h> + +#include "mbsalign.h" +#include "carefulputc.h" +#include "smartcolsP.h" + +/* Fallback for symbols + * + * Note that by default library define all the symbols, but in case user does + * not define all symbols or if we extended the symbols struct then we need + * fallback to be more robust and backwardly compatible. + */ +#define titlepadding_symbol(tb) ((tb)->symbols->title_padding ? (tb)->symbols->title_padding : " ") +#define branch_symbol(tb) ((tb)->symbols->tree_branch ? (tb)->symbols->tree_branch : "|-") +#define vertical_symbol(tb) ((tb)->symbols->tree_vert ? (tb)->symbols->tree_vert : "| ") +#define right_symbol(tb) ((tb)->symbols->tree_right ? (tb)->symbols->tree_right : "`-") + +#define grp_vertical_symbol(tb) ((tb)->symbols->group_vert ? (tb)->symbols->group_vert : "|") +#define grp_horizontal_symbol(tb) ((tb)->symbols->group_horz ? (tb)->symbols->group_horz : "-") +#define grp_m_first_symbol(tb) ((tb)->symbols->group_first_member ? (tb)->symbols->group_first_member : ",->") +#define grp_m_last_symbol(tb) ((tb)->symbols->group_last_member ? (tb)->symbols->group_last_member : "\\->") +#define grp_m_middle_symbol(tb) ((tb)->symbols->group_middle_member ? (tb)->symbols->group_middle_member : "|->") +#define grp_c_middle_symbol(tb) ((tb)->symbols->group_middle_child ? (tb)->symbols->group_middle_child : "|-") +#define grp_c_last_symbol(tb) ((tb)->symbols->group_last_child ? (tb)->symbols->group_last_child : "`-") + +#define cellpadding_symbol(tb) ((tb)->padding_debug ? "." : \ + ((tb)->symbols->cell_padding ? (tb)->symbols->cell_padding: " ")) + +#define want_repeat_header(tb) (!(tb)->header_repeat || (tb)->header_next <= (tb)->termlines_used) + +static int is_next_columns_empty( + struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln) +{ + struct libscols_iter itr; + + if (!tb || !cl) + return 0; + if (is_last_column(cl)) + return 1; + if (!ln) + return 0; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + scols_table_set_columns_iter(tb, &itr, cl); + + /* skip current column */ + scols_table_next_column(tb, &itr, &cl); + + while (scols_table_next_column(tb, &itr, &cl) == 0) { + struct libscols_cell *ce; + const char *data = NULL; + + if (scols_column_is_hidden(cl)) + continue; + if (scols_column_is_tree(cl)) + return 0; + + ce = scols_line_get_cell(ln, cl->seqnum); + if (ce) + data = scols_cell_get_data(ce); + if (data && *data) + return 0; + } + return 1; +} + +/* returns pointer to the end of used data */ +static int tree_ascii_art_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct ul_buffer *buf) +{ + const char *art; + int rc; + + assert(ln); + assert(buf); + + if (!ln->parent) + return 0; + + rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); + if (rc) + return rc; + + if (is_last_child(ln)) + art = " "; + else + art = vertical_symbol(tb); + + return ul_buffer_append_string(buf, art); +} + +static int grpset_is_empty( struct libscols_table *tb, + size_t idx, + size_t *rest) +{ + size_t i; + + for (i = idx; i < tb->grpset_size; i++) { + if (tb->grpset[i] == NULL) { + if (rest) + (*rest)++; + } else + return 0; + } + return 1; +} + +static int groups_ascii_art_to_buffer( struct libscols_table *tb, + struct libscols_line *ln, + struct ul_buffer *buf, + int empty) +{ + int filled = 0; + size_t i, rest = 0; + const char *filler = cellpadding_symbol(tb); + + if (!has_groups(tb)) + return 0; + + DBG(LINE, ul_debugobj(ln, "printing groups chart")); + + if (tb->is_dummy_print) + return 0; /* allocate grpset[] only */ + + for (i = 0; i < tb->grpset_size; i += SCOLS_GRPSET_CHUNKSIZ) { + struct libscols_group *gr = tb->grpset[i]; + + if (!gr) { + ul_buffer_append_ntimes(buf, SCOLS_GRPSET_CHUNKSIZ, cellpadding_symbol(tb)); + continue; + } + + /* + * Empty cells (multi-line entries, etc.), print vertical symbols only + * to show that the group continues. + */ + if (empty) { + switch (gr->state) { + case SCOLS_GSTATE_FIRST_MEMBER: + case SCOLS_GSTATE_MIDDLE_MEMBER: + case SCOLS_GSTATE_CONT_MEMBERS: + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_ntimes(buf, 2, filler); + break; + + case SCOLS_GSTATE_LAST_MEMBER: + case SCOLS_GSTATE_MIDDLE_CHILD: + case SCOLS_GSTATE_CONT_CHILDREN: + ul_buffer_append_string(buf, filler); + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_string(buf, filler); + break; + case SCOLS_GSTATE_LAST_CHILD: + ul_buffer_append_ntimes(buf, 3, filler); + break; + } + continue; + } + + /* + * Regular cell + */ + switch (gr->state) { + case SCOLS_GSTATE_FIRST_MEMBER: + ul_buffer_append_string(buf, grp_m_first_symbol(tb)); + break; + case SCOLS_GSTATE_MIDDLE_MEMBER: + ul_buffer_append_string(buf, grp_m_middle_symbol(tb)); + break; + case SCOLS_GSTATE_LAST_MEMBER: + ul_buffer_append_string(buf, grp_m_last_symbol(tb)); + break; + case SCOLS_GSTATE_CONT_MEMBERS: + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_ntimes(buf, 2, filler); + break; + case SCOLS_GSTATE_MIDDLE_CHILD: + ul_buffer_append_string(buf, filler); + ul_buffer_append_string(buf, grp_c_middle_symbol(tb)); + if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) { + ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); + filled = 1; + } + filler = grp_horizontal_symbol(tb); + break; + case SCOLS_GSTATE_LAST_CHILD: + ul_buffer_append_string(buf, cellpadding_symbol(tb)); + ul_buffer_append_string(buf, grp_c_last_symbol(tb)); + if (grpset_is_empty(tb, i + SCOLS_GRPSET_CHUNKSIZ, &rest)) { + ul_buffer_append_ntimes(buf, rest+1, grp_horizontal_symbol(tb)); + filled = 1; + } + filler = grp_horizontal_symbol(tb); + break; + case SCOLS_GSTATE_CONT_CHILDREN: + ul_buffer_append_string(buf, filler); + ul_buffer_append_string(buf, grp_vertical_symbol(tb)); + ul_buffer_append_string(buf, filler); + break; + } + + if (filled) + break; + } + + if (!filled) + ul_buffer_append_string(buf, filler); + return 0; +} + +static int has_pending_data(struct libscols_table *tb) +{ + struct libscols_column *cl; + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) + return 1; + } + return 0; +} + +static void fputs_color_reset(struct libscols_table *tb) +{ + if (tb->cur_color) { + fputs(UL_COLOR_RESET, tb->out); + tb->cur_color = NULL; + } +} + +static void fputs_color(struct libscols_table *tb, const char *color) +{ + if (tb->cur_color) + fputs_color_reset(tb); + + tb->cur_color = color; + if (color) + fputs(color, tb->out); +} + +static const char *get_cell_color(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, + struct libscols_cell *ce) +{ + const char *color = NULL; + + if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) + return NULL; + if (ce) + color = ce->color; + if (!color && (!ln || !ln->color) && cl) + color = cl->color; + return color; +} + +/* switch from line color to cell/column color */ +static void fputs_color_cell_open(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, + struct libscols_cell *ce) +{ + const char *color = get_cell_color(tb, cl, ln, ce); + + if (color) + fputs_color(tb, color); +} + +/* switch from cell/column color to line color or reset */ +static void fputs_color_cell_close(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, + struct libscols_cell *ce) +{ + const char *color = get_cell_color(tb, cl, ln, ce); + + if (color) + fputs_color(tb, ln ? ln->color : NULL); +} + +/* switch to line color */ +static void fputs_color_line_open(struct libscols_table *tb, + struct libscols_line *ln) +{ + if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) + return; + fputs_color(tb, ln ? ln->color : NULL); +} + +/* switch off all colors */ +static void fputs_color_line_close(struct libscols_table *tb) +{ + if (!tb || !tb->colors_wanted || tb->format != SCOLS_FMT_HUMAN) + return; + fputs_color_reset(tb); +} + +/* print padding or ASCII-art instead of data of @cl */ +static void print_empty_cell(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, + size_t bufsz) +{ + size_t len_pad = 0; /* in screen cells as opposed to bytes */ + + DBG(COL, ul_debugobj(cl, " printing empty cell")); + + fputs_color_cell_open(tb, cl, ln, ce); + + /* generate tree/group ASCII-art rather than padding + */ + if (ln && scols_column_is_tree(cl)) { + struct ul_buffer art = UL_INIT_BUFFER; + char *data; + + if (ul_buffer_alloc_data(&art, bufsz) != 0) + goto done; + + if (cl->is_groups) + groups_ascii_art_to_buffer(tb, ln, &art, 1); + + tree_ascii_art_to_buffer(tb, ln, &art); + + if (!list_empty(&ln->ln_branch) && has_pending_data(tb)) + ul_buffer_append_string(&art, vertical_symbol(tb)); + + if (scols_table_is_noencoding(tb)) + data = ul_buffer_get_data(&art, NULL, &len_pad); + else + data = ul_buffer_get_safe_data(&art, NULL, &len_pad, NULL); + + if (data && len_pad) + fputs(data, tb->out); + ul_buffer_free_data(&art); + } + +done: + /* minout -- don't fill */ + if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { + fputs_color_cell_close(tb, cl, ln, ce); + return; + } + + /* default -- fill except last column */ + if (!scols_table_is_maxout(tb) && is_last_column(cl)) { + fputs_color_cell_close(tb, cl, ln, ce); + return; + } + + /* fill rest of cell with space */ + for(; len_pad < cl->width; ++len_pad) + fputs(cellpadding_symbol(tb), tb->out); + + fputs_color_cell_close(tb, cl, ln, ce); + + if (!is_last_column(cl)) + fputs(colsep(tb), tb->out); +} + + + +/* Fill the start of a line with padding (or with tree ascii-art). + * + * This is necessary after a long non-truncated column, as this requires the + * next column to be printed on the next line. For example (see 'DDD'): + * + * aaa bbb ccc ddd eee + * AAA BBB CCCCCCC + * DDD EEE + * ^^^^^^^^^^^^ + * new line padding + */ +static void print_newline_padding(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, + size_t bufsz) +{ + size_t i; + + assert(tb); + assert(cl); + + DBG(LINE, ul_debugobj(ln, "printing newline padding")); + + fputs(linesep(tb), tb->out); /* line break */ + tb->termlines_used++; + + fputs_color_line_open(tb, ln); + + /* fill cells after line break */ + for (i = 0; i <= (size_t) cl->seqnum; i++) + print_empty_cell(tb, scols_table_get_column(tb, i), ln, ce, bufsz); + + fputs_color_line_close(tb); +} + +/* + * Pending data + * + * The first line in the multi-line cells (columns with SCOLS_FL_WRAP flag) is + * printed as usually and output is truncated to match column width. + * + * The rest of the long text is printed on next extra line(s). The extra lines + * don't exist in the table (not represented by libscols_line). The data for + * the extra lines are stored in libscols_column->pending_data_buf and the + * function print_line() adds extra lines until the buffer is not empty in all + * columns. + */ + +/* set data that will be printed by extra lines */ +static int set_pending_data(struct libscols_column *cl, const char *data, size_t sz) +{ + char *p = NULL; + + if (data && *data) { + DBG(COL, ul_debugobj(cl, "setting pending data")); + assert(sz); + p = strdup(data); + if (!p) + return -ENOMEM; + } + + free(cl->pending_data_buf); + cl->pending_data_buf = p; + cl->pending_data_sz = sz; + cl->pending_data = cl->pending_data_buf; + return 0; +} + +/* the next extra line has been printed, move pending data cursor */ +static int step_pending_data(struct libscols_column *cl, size_t bytes) +{ + DBG(COL, ul_debugobj(cl, "step pending data %zu -= %zu", cl->pending_data_sz, bytes)); + + if (bytes >= cl->pending_data_sz) + return set_pending_data(cl, NULL, 0); + + cl->pending_data += bytes; + cl->pending_data_sz -= bytes; + return 0; +} + +/* print next pending data for the column @cl */ +static int print_pending_data( + struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce) +{ + size_t width = cl->width, bytes; + size_t len = width, i; + char *data; + char *nextchunk = NULL; + + if (!cl->pending_data) + return 0; + if (!width) + return -EINVAL; + + DBG(COL, ul_debugobj(cl, "printing pending data")); + + data = strdup(cl->pending_data); + if (!data) + goto err; + + if (scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + bytes = nextchunk - data; + + len = scols_table_is_noencoding(tb) ? + mbs_nwidth(data, bytes) : + mbs_safe_nwidth(data, bytes, NULL); + } else + bytes = mbs_truncate(data, &len); + + if (bytes == (size_t) -1) + goto err; + + if (bytes) + step_pending_data(cl, bytes); + + fputs_color_cell_open(tb, cl, ln, ce); + + fputs(data, tb->out); + free(data); + + /* minout -- don't fill */ + if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* default -- fill except last column */ + if (!scols_table_is_maxout(tb) && is_last_column(cl)) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* fill rest of cell with space */ + for(i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + + fputs_color_cell_close(tb, cl, ln, ce); + + if (!is_last_column(cl)) + fputs(colsep(tb), tb->out); + + return 0; +err: + free(data); + return -errno; +} + +static void print_json_data(struct libscols_table *tb, + struct libscols_column *cl, + const char *name, + char *data) +{ + switch (cl->json_type) { + case SCOLS_JSON_STRING: + /* name: "aaa" */ + ul_jsonwrt_value_s(&tb->json, name, data); + break; + case SCOLS_JSON_NUMBER: + /* name: 123 */ + ul_jsonwrt_value_raw(&tb->json, name, data); + break; + case SCOLS_JSON_BOOLEAN: + case SCOLS_JSON_BOOLEAN_OPTIONAL: + /* name: true|false|null */ + if (cl->json_type == SCOLS_JSON_BOOLEAN_OPTIONAL && (!*data || !strcmp(data, "-"))) { + ul_jsonwrt_value_null(&tb->json, name); + } else { + ul_jsonwrt_value_boolean(&tb->json, name, + !*data ? 0 : + *data == '0' ? 0 : + *data == 'N' || *data == 'n' ? 0 : 1); + } + break; + case SCOLS_JSON_ARRAY_STRING: + case SCOLS_JSON_ARRAY_NUMBER: + /* name: [ "aaa", "bbb", "ccc" ] */ + ul_jsonwrt_array_open(&tb->json, name); + + if (!scols_column_is_customwrap(cl)) + ul_jsonwrt_value_s(&tb->json, NULL, data); + else do { + char *next = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data); + + if (cl->json_type == SCOLS_JSON_ARRAY_STRING) + ul_jsonwrt_value_s(&tb->json, NULL, data); + else + ul_jsonwrt_value_raw(&tb->json, NULL, data); + data = next; + } while (data); + + ul_jsonwrt_array_close(&tb->json); + break; + } +} + +static int print_data(struct libscols_table *tb, + struct libscols_column *cl, + struct libscols_line *ln, /* optional */ + struct libscols_cell *ce, /* optional */ + struct ul_buffer *buf) +{ + size_t len = 0, i, width, bytes; + char *data, *nextchunk; + const char *name = NULL; + int is_last; + + assert(tb); + assert(cl); + + data = ul_buffer_get_data(buf, NULL, NULL); + if (!data) + data = ""; + + if (tb->format != SCOLS_FMT_HUMAN) { + name = scols_table_is_shellvar(tb) ? + scols_column_get_name_as_shellvar(cl) : + scols_column_get_name(cl); + } + + is_last = is_last_column(cl); + + if (is_last && scols_table_is_json(tb) && + scols_table_is_tree(tb) && has_children(ln)) + /* "children": [] is the real last value */ + is_last = 0; + + switch (tb->format) { + case SCOLS_FMT_RAW: + fputs_nonblank(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_EXPORT: + fputs(name ?: "", tb->out); + fputc('=', tb->out); + fputs_quoted(data, tb->out); + if (!is_last) + fputs(colsep(tb), tb->out); + return 0; + + case SCOLS_FMT_JSON: + print_json_data(tb, cl, name, data); + return 0; + + case SCOLS_FMT_HUMAN: + break; /* continue below */ + } + + /* Encode. Note that 'len' and 'width' are number of cells, not bytes. + */ + if (scols_table_is_noencoding(tb)) + data = ul_buffer_get_data(buf, &bytes, &len); + else + data = ul_buffer_get_safe_data(buf, &bytes, &len, scols_column_get_safechars(cl)); + + if (!data) + data = ""; + width = cl->width; + + /* custom multi-line cell based */ + if (*data && scols_column_is_customwrap(cl) + && (nextchunk = cl->wrap_nextchunk(cl, data, cl->wrapfunc_data))) { + set_pending_data(cl, nextchunk, bytes - (nextchunk - data)); + bytes = nextchunk - data; + + len = scols_table_is_noencoding(tb) ? + mbs_nwidth(data, bytes) : + mbs_safe_nwidth(data, bytes, NULL); + } + + if (is_last + && len < width + && !scols_table_is_maxout(tb) + && !scols_column_is_right(cl)) + width = len; + + /* truncate data */ + if (len > width && scols_column_is_trunc(cl)) { + len = width; + bytes = mbs_truncate(data, &len); /* updates 'len' */ + } + + /* standard multi-line cell */ + if (len > width && scols_column_is_wrap(cl) + && !scols_column_is_customwrap(cl)) { + set_pending_data(cl, data, bytes); + + len = width; + bytes = mbs_truncate(data, &len); + if (bytes != (size_t) -1 && bytes > 0) + step_pending_data(cl, bytes); + } + + if (bytes == (size_t) -1) { + bytes = len = 0; + data = NULL; + } + + fputs_color_cell_open(tb, cl, ln, ce); + + if (data && *data) { + if (scols_column_is_right(cl)) { + for (i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + len = width; + } + fputs(data, tb->out); + + } + + /* minout -- don't fill */ + if (scols_table_is_minout(tb) && is_next_columns_empty(tb, cl, ln)) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* default -- fill except last column */ + if (!scols_table_is_maxout(tb) && is_last) { + fputs_color_cell_close(tb, cl, ln, ce); + return 0; + } + + /* fill rest of cell with space */ + for(i = len; i < width; i++) + fputs(cellpadding_symbol(tb), tb->out); + + fputs_color_cell_close(tb, cl, ln, ce); + + if (len > width && !scols_column_is_trunc(cl)) { + DBG(COL, ul_debugobj(cl, "*** data len=%zu > column width=%zu", len, width)); + print_newline_padding(tb, cl, ln, ce, ul_buffer_get_bufsiz(buf)); /* next column starts on next line */ + + } else if (!is_last) + fputs(colsep(tb), tb->out); /* columns separator */ + + return 0; +} + +int __cell_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct ul_buffer *buf) +{ + const char *data; + struct libscols_cell *ce; + int rc = 0; + + assert(tb); + assert(ln); + assert(cl); + assert(buf); + assert(cl->seqnum <= tb->ncols); + + ul_buffer_reset_data(buf); + + ce = scols_line_get_cell(ln, cl->seqnum); + data = ce ? scols_cell_get_data(ce) : NULL; + + if (!scols_column_is_tree(cl)) + return data ? ul_buffer_append_string(buf, data) : 0; + + /* + * Group stuff + */ + if (!scols_table_is_json(tb) && cl->is_groups) + rc = groups_ascii_art_to_buffer(tb, ln, buf, 0); + + /* + * Tree stuff + */ + if (!rc && ln->parent && !scols_table_is_json(tb)) { + rc = tree_ascii_art_to_buffer(tb, ln->parent, buf); + + if (!rc && is_last_child(ln)) + rc = ul_buffer_append_string(buf, right_symbol(tb)); + else if (!rc) + rc = ul_buffer_append_string(buf, branch_symbol(tb)); + } + + if (!rc && (ln->parent || cl->is_groups) && !scols_table_is_json(tb)) + ul_buffer_save_pointer(buf, SCOLS_BUFPTR_TREEEND); + + if (!rc && data) + rc = ul_buffer_append_string(buf, data); + return rc; +} + +/* + * Prints data. Data can be printed in more formats (raw, NAME=xxx pairs), and + * control and non-printable characters can be encoded in the \x?? encoding. + */ +static int print_line(struct libscols_table *tb, + struct libscols_line *ln, + struct ul_buffer *buf) +{ + int rc = 0, pending = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(ln); + + DBG(LINE, ul_debugobj(ln, " printing line")); + + fputs_color_line_open(tb, ln); + + /* regular line */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + rc = __cell_to_buffer(tb, ln, cl, buf); + if (rc == 0) + rc = print_data(tb, cl, ln, + scols_line_get_cell(ln, cl->seqnum), + buf); + if (rc == 0 && cl->pending_data) + pending = 1; + } + fputs_color_line_close(tb); + + /* extra lines of the multi-line cells */ + while (rc == 0 && pending) { + DBG(LINE, ul_debugobj(ln, "printing pending data")); + pending = 0; + fputs(linesep(tb), tb->out); + fputs_color_line_open(tb, ln); + tb->termlines_used++; + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + if (cl->pending_data) { + rc = print_pending_data(tb, cl, ln, scols_line_get_cell(ln, cl->seqnum)); + if (rc == 0 && cl->pending_data) + pending = 1; + } else + print_empty_cell(tb, cl, ln, NULL, ul_buffer_get_bufsiz(buf)); + } + fputs_color_line_close(tb); + } + + return 0; +} + +int __scols_print_title(struct libscols_table *tb) +{ + int rc; + mbs_align_t align; + size_t width, len = 0, bufsz, titlesz; + char *title = NULL, *buf = NULL; + + assert(tb); + + if (!tb->title.data) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing title")); + + /* encode data */ + if (tb->no_encode) { + len = bufsz = strlen(tb->title.data) + 1; + buf = strdup(tb->title.data); + if (!buf) { + rc = -ENOMEM; + goto done; + } + } else { + bufsz = mbs_safe_encode_size(strlen(tb->title.data)) + 1; + if (bufsz == 1) { + DBG(TAB, ul_debugobj(tb, "title is empty string -- ignore")); + return 0; + } + buf = malloc(bufsz); + if (!buf) { + rc = -ENOMEM; + goto done; + } + + if (!mbs_safe_encode_to_buffer(tb->title.data, &len, buf, NULL) || + !len || len == (size_t) -1) { + rc = -EINVAL; + goto done; + } + } + + /* truncate and align */ + width = tb->is_term ? tb->termwidth : 80; + titlesz = width + bufsz; + + title = malloc(titlesz); + if (!title) { + rc = -EINVAL; + goto done; + } + + switch (scols_cell_get_alignment(&tb->title)) { + case SCOLS_CELL_FL_RIGHT: + align = MBS_ALIGN_RIGHT; + break; + case SCOLS_CELL_FL_CENTER: + align = MBS_ALIGN_CENTER; + break; + case SCOLS_CELL_FL_LEFT: + default: + align = MBS_ALIGN_LEFT; + /* + * Don't print extra blank chars after the title if on left + * (that's same as we use for the last column in the table). + */ + if (len < width + && !scols_table_is_maxout(tb) + && isblank(*titlepadding_symbol(tb))) + width = len; + break; + + } + + /* copy from buf to title and align to width with title_padding */ + rc = mbsalign_with_padding(buf, title, titlesz, + &width, align, + 0, (int) *titlepadding_symbol(tb)); + + if (rc == -1) { + rc = -EINVAL; + goto done; + } + + + if (tb->colors_wanted) + fputs_color(tb, tb->title.color); + + fputs(title, tb->out); + + if (tb->colors_wanted) + fputs_color_reset(tb); + + fputc('\n', tb->out); + rc = 0; +done: + free(buf); + free(title); + DBG(TAB, ul_debugobj(tb, "printing title done [rc=%d]", rc)); + return rc; +} + +int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf) +{ + int rc = 0; + struct libscols_column *cl; + struct libscols_iter itr; + + assert(tb); + + if ((tb->header_printed == 1 && tb->header_repeat == 0) || + scols_table_is_noheadings(tb) || + scols_table_is_export(tb) || + scols_table_is_json(tb) || + list_empty(&tb->tb_lines)) + return 0; + + DBG(TAB, ul_debugobj(tb, "printing header")); + + /* set the width according to the size of the data */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + + ul_buffer_reset_data(buf); + + if (cl->is_groups + && scols_table_is_tree(tb) && scols_column_is_tree(cl)) { + size_t i; + for (i = 0; i < tb->grpset_size + 1; i++) { + rc = ul_buffer_append_data(buf, " ", 1); + if (rc) + break; + } + } + if (!rc) + rc = ul_buffer_append_string(buf, + scols_table_is_shellvar(tb) ? + scols_column_get_name_as_shellvar(cl) : + scols_column_get_name(cl)); + if (!rc) + rc = print_data(tb, cl, NULL, &cl->header, buf); + } + + if (rc == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + tb->header_printed = 1; + tb->header_next = tb->termlines_used + tb->termheight; + if (tb->header_repeat) + DBG(TAB, ul_debugobj(tb, "\tnext header: %zu [current=%zu, rc=%d]", + tb->header_next, tb->termlines_used, rc)); + return rc; +} + + +int __scols_print_range(struct libscols_table *tb, + struct ul_buffer *buf, + struct libscols_iter *itr, + struct libscols_line *end) +{ + int rc = 0; + struct libscols_line *ln; + + assert(tb); + DBG(TAB, ul_debugobj(tb, "printing range")); + + while (rc == 0 && scols_table_next_line(tb, itr, &ln) == 0) { + + int last = scols_iter_is_last(itr); + + if (scols_table_is_json(tb)) + ul_jsonwrt_object_open(&tb->json, NULL); + + rc = print_line(tb, ln, buf); + + if (scols_table_is_json(tb)) + ul_jsonwrt_object_close(&tb->json); + else if (last == 0 && tb->no_linesep == 0) { + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + + if (end && ln == end) + break; + + if (!last && want_repeat_header(tb)) + __scols_print_header(tb, buf); + } + + return rc; + +} + +int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf) +{ + struct libscols_iter itr; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + return __scols_print_range(tb, buf, &itr, NULL); +} + +/* scols_walk_tree() callback to print tree line */ +static int print_tree_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl __attribute__((__unused__)), + void *data) +{ + struct ul_buffer *buf = (struct ul_buffer *) data; + int rc; + + DBG(LINE, ul_debugobj(ln, " printing tree line")); + + if (scols_table_is_json(tb)) + ul_jsonwrt_object_open(&tb->json, NULL); + + rc = print_line(tb, ln, buf); + if (rc) + return rc; + + if (has_children(ln)) { + if (scols_table_is_json(tb)) + ul_jsonwrt_array_open(&tb->json, "children"); + else { + /* between parent and child is separator */ + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + } else { + int last; + + /* terminate all open last children for JSON */ + if (scols_table_is_json(tb)) { + do { + last = (is_child(ln) && is_last_child(ln)) || + (is_tree_root(ln) && is_last_tree_root(tb, ln)); + + ul_jsonwrt_object_close(&tb->json); + if (last && is_child(ln)) + ul_jsonwrt_array_close(&tb->json); + ln = ln->parent; + } while(ln && last); + + } else if (tb->no_linesep == 0) { + int last_in_tree = scols_walk_is_last(tb, ln); + + if (last_in_tree == 0) { + /* standard output */ + fputs(linesep(tb), tb->out); + tb->termlines_used++; + } + } + } + + return 0; +} + +int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf) +{ + assert(tb); + DBG(TAB, ul_debugobj(tb, "----printing-tree-----")); + + return scols_walk_tree(tb, NULL, print_tree_line, (void *) buf); +} + +static size_t strlen_line(struct libscols_line *ln) +{ + size_t i, sz = 0; + + assert(ln); + + for (i = 0; i < ln->ncells; i++) { + struct libscols_cell *ce = scols_line_get_cell(ln, i); + const char *data = ce ? scols_cell_get_data(ce) : NULL; + + sz += data ? strlen(data) : 0; + } + + return sz; +} + +void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf) +{ + if (!tb) + return; + + ul_buffer_free_data(buf); + + if (tb->priv_symbols) { + scols_table_set_symbols(tb, NULL); + tb->priv_symbols = 0; + } +} + +int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf) +{ + size_t bufsz, extra_bufsz = 0; + struct libscols_line *ln; + struct libscols_iter itr; + int rc; + + DBG(TAB, ul_debugobj(tb, "initialize printing")); + + if (!tb->symbols) { + rc = scols_table_set_default_symbols(tb); + if (rc) + goto err; + tb->priv_symbols = 1; + } else + tb->priv_symbols = 0; + + if (tb->format == SCOLS_FMT_HUMAN) + tb->is_term = tb->termforce == SCOLS_TERMFORCE_NEVER ? 0 : + tb->termforce == SCOLS_TERMFORCE_ALWAYS ? 1 : + isatty(STDOUT_FILENO); + + if (tb->is_term) { + size_t width = (size_t) scols_table_get_termwidth(tb); + + if (tb->termreduce > 0 && tb->termreduce < width) { + width -= tb->termreduce; + scols_table_set_termwidth(tb, width); + } + bufsz = width; + } else + bufsz = BUFSIZ; + + if (!tb->is_term || tb->format != SCOLS_FMT_HUMAN || scols_table_is_tree(tb)) + tb->header_repeat = 0; + + /* + * Estimate extra space necessary for tree, JSON or another output + * decoration. + */ + if (scols_table_is_tree(tb)) + extra_bufsz += tb->nlines * strlen(vertical_symbol(tb)); + + switch (tb->format) { + case SCOLS_FMT_RAW: + extra_bufsz += tb->ncols; /* separator between columns */ + break; + case SCOLS_FMT_JSON: + ul_jsonwrt_init(&tb->json, tb->out, 0); + extra_bufsz += tb->nlines * 3; /* indentation */ + /* fallthrough */ + case SCOLS_FMT_EXPORT: + { + struct libscols_column *cl; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (scols_column_is_hidden(cl)) + continue; + + if (scols_column_get_name(cl)) + extra_bufsz += strlen(scols_column_get_name(cl)); /* data */ + extra_bufsz += 2; /* separators */ + } + break; + } + case SCOLS_FMT_HUMAN: + break; + } + + /* + * Enlarge buffer if necessary, the buffer should be large enough to + * store line data and tree ascii art (or another decoration). + */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + size_t sz; + + sz = strlen_line(ln) + extra_bufsz; + if (sz > bufsz) + bufsz = sz; + } + + /* pre-allocate space for data */ + rc = ul_buffer_alloc_data(buf, bufsz + 1); /* data + space for \0 */ + if (rc) + goto err; + + /* + * Make sure groups members are in the same orders as the tree + */ + if (has_groups(tb) && scols_table_is_tree(tb)) + scols_groups_fix_members_order(tb); + + if (tb->format == SCOLS_FMT_HUMAN) { + rc = __scols_calculate(tb, buf); + if (rc != 0) + goto err; + } + + return 0; +err: + __scols_cleanup_printing(tb, buf); + return rc; +} + diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h new file mode 100644 index 0000000..8a7ee9b --- /dev/null +++ b/libsmartcols/src/smartcolsP.h @@ -0,0 +1,455 @@ +/* + * smartcolsP.h - private library header file + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#ifndef _LIBSMARTCOLS_PRIVATE_H +#define _LIBSMARTCOLS_PRIVATE_H + +#include "c.h" +#include "list.h" +#include "strutils.h" +#include "color-names.h" +#include "jsonwrt.h" +#include "debug.h" +#include "buffer.h" + +#include "libsmartcols.h" + +/* + * Debug + */ +#define SCOLS_DEBUG_HELP (1 << 0) +#define SCOLS_DEBUG_INIT (1 << 1) +#define SCOLS_DEBUG_CELL (1 << 2) +#define SCOLS_DEBUG_LINE (1 << 3) +#define SCOLS_DEBUG_TAB (1 << 4) +#define SCOLS_DEBUG_COL (1 << 5) +#define SCOLS_DEBUG_BUFF (1 << 6) +#define SCOLS_DEBUG_GROUP (1 << 7) +#define SCOLS_DEBUG_ALL 0xFFFF + +UL_DEBUG_DECLARE_MASK(libsmartcols); +#define DBG(m, x) __UL_DBG(libsmartcols, SCOLS_DEBUG_, m, x) +#define ON_DBG(m, x) __UL_DBG_CALL(libsmartcols, SCOLS_DEBUG_, m, x) +#define DBG_FLUSH __UL_DBG_FLUSH(libsmartcols, SCOLS_DEBUG_) + +#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libsmartcols) +#include "debugobj.h" + +#define SCOLS_BUFPTR_TREEEND 0 + +/* + * Generic iterator + */ +struct libscols_iter { + struct list_head *p; /* current position */ + struct list_head *head; /* start position */ + int direction; /* SCOLS_ITER_{FOR,BACK}WARD */ +}; + +/* + * Tree symbols + */ +struct libscols_symbols { + int refcount; + + char *tree_branch; + char *tree_vert; + char *tree_right; + + char *group_vert; + char *group_horz; + char *group_first_member; + char *group_last_member; + char *group_middle_member; + char *group_last_child; + char *group_middle_child; + + char *title_padding; + char *cell_padding; +}; + +/* + * Table cells + */ +struct libscols_cell { + char *data; + char *color; + void *userdata; + int flags; + size_t width; +}; + +extern int scols_line_move_cells(struct libscols_line *ln, size_t newn, size_t oldn); + +struct libscols_wstat { + size_t width_min; + size_t width_max; + double width_avg; + double width_sqr_sum; + double width_deviation; + + size_t width_treeart; +}; + +/* + * Table column + */ +struct libscols_column { + int refcount; /* reference counter */ + size_t seqnum; /* column index */ + + size_t width; /* expected column width */ + size_t width_treeart; + double width_hint; /* hint (N < 1 is in percent of termwidth) */ + + struct libscols_wstat wstat; /* private __scols_calculate() data */ + + int json_type; /* SCOLS_JSON_* */ + + int flags; + char *color; /* default column color */ + char *safechars; /* do not encode this bytes */ + + char *pending_data; + size_t pending_data_sz; + char *pending_data_buf; + + int (*cmpfunc)(struct libscols_cell *, + struct libscols_cell *, + void *); /* cells comparison function */ + void *cmpfunc_data; + + size_t (*wrap_chunksize)(const struct libscols_column *, + const char *, void *); + char *(*wrap_nextchunk)(const struct libscols_column *, + char *, void *); + void *wrapfunc_data; + + + struct libscols_cell header; /* column name with color etc. */ + char *shellvar; /* raw colum name in shell compatible format */ + + struct list_head cl_columns; /* member of table->tb_columns */ + + struct libscols_table *table; + + unsigned int is_groups : 1; /* print group chart */ + +}; + +#define colsep(tb) ((tb)->colsep ? (tb)->colsep : " ") +#define linesep(tb) ((tb)->linesep ? (tb)->linesep : "\n") + +enum { + SCOLS_GSTATE_NONE = 0, /* not activate yet */ + SCOLS_GSTATE_FIRST_MEMBER, + SCOLS_GSTATE_MIDDLE_MEMBER, + SCOLS_GSTATE_LAST_MEMBER, + SCOLS_GSTATE_MIDDLE_CHILD, + SCOLS_GSTATE_LAST_CHILD, + SCOLS_GSTATE_CONT_MEMBERS, + SCOLS_GSTATE_CONT_CHILDREN +}; + +/* + * Every group needs at least 3 columns + */ +#define SCOLS_GRPSET_CHUNKSIZ 3 + +struct libscols_group { + int refcount; + + size_t nmembers; + + struct list_head gr_members; /* head of line->ln_group */ + struct list_head gr_children; /* head of line->ln_children */ + struct list_head gr_groups; /* member of table->tb_groups */ + + int state; /* SCOLS_GSTATE_* */ +}; + +/* + * Table line + */ +struct libscols_line { + int refcount; + size_t seqnum; + + void *userdata; + char *color; /* default line color */ + + struct libscols_cell *cells; /* array with data */ + size_t ncells; /* number of cells */ + + struct list_head ln_lines; /* member of table->tb_lines */ + struct list_head ln_branch; /* head of line->ln_children */ + struct list_head ln_children; /* member of line->ln_children or group->gr_children */ + struct list_head ln_groups; /* member of group->gr_groups */ + + struct libscols_line *parent; + struct libscols_group *parent_group; /* for group childs */ + struct libscols_group *group; /* for group members */ +}; + +enum { + SCOLS_FMT_HUMAN = 0, /* default, human readable */ + SCOLS_FMT_RAW, /* space separated */ + SCOLS_FMT_EXPORT, /* COLNAME="data" ... */ + SCOLS_FMT_JSON /* http://en.wikipedia.org/wiki/JSON */ +}; + +/* + * The table + */ +struct libscols_table { + int refcount; + char *name; /* optional table name (for JSON) */ + size_t ncols; /* number of columns */ + size_t ntreecols; /* number of columns with SCOLS_FL_TREE */ + size_t nlines; /* number of lines */ + size_t termwidth; /* terminal width (number of columns) */ + size_t termheight; /* terminal height (number of lines) */ + size_t termreduce; /* extra blank space */ + int termforce; /* SCOLS_TERMFORCE_* */ + FILE *out; /* output stream */ + + char *colsep; /* column separator */ + char *linesep; /* line separator */ + + struct list_head tb_columns; /* list of columns, items: column->cl_columns */ + struct list_head tb_lines; /* list of lines; items: line->ln_lines */ + + struct list_head tb_groups; /* all defined groups */ + struct libscols_group **grpset; + size_t grpset_size; + + size_t ngrpchlds_pending; /* groups with not yet printed children */ + struct libscols_line *walk_last_tree_root; /* last root, used by scols_walk_() */ + + struct libscols_column *dflt_sort_column; /* default sort column, set by scols_sort_table() */ + + struct libscols_symbols *symbols; + struct libscols_cell title; /* optional table title (for humans) */ + + struct ul_jsonwrt json; /* JSON formatting */ + + int format; /* SCOLS_FMT_* */ + + size_t termlines_used; /* printed line counter */ + size_t header_next; /* where repeat header */ + + const char *cur_color; /* current active color when printing */ + + /* flags */ + unsigned int ascii :1, /* don't use unicode */ + colors_wanted :1, /* enable colors */ + is_term :1, /* isatty() */ + padding_debug :1, /* output visible padding chars */ + is_dummy_print :1, /* printing used for width calculation only */ + is_shellvar :1, /* shell compatible column names */ + maxout :1, /* maximize output */ + minout :1, /* minimize output (mutually exclusive to maxout) */ + header_repeat :1, /* print header after libscols_table->termheight */ + header_printed :1, /* header already printed */ + priv_symbols :1, /* default private symbols */ + walk_last_done :1, /* last tree root walked */ + no_headings :1, /* don't print header */ + no_encode :1, /* don't care about control and non-printable chars */ + no_linesep :1, /* don't print line separator */ + no_wrap :1; /* never wrap lines */ +}; + +#define IS_ITER_FORWARD(_i) ((_i)->direction == SCOLS_ITER_FORWARD) +#define IS_ITER_BACKWARD(_i) ((_i)->direction == SCOLS_ITER_BACKWARD) + +#define SCOLS_ITER_INIT(itr, list) \ + do { \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (list)->next : (list)->prev; \ + (itr)->head = (list); \ + } while(0) + +#define SCOLS_ITER_ITERATE(itr, res, restype, member) \ + do { \ + res = list_entry((itr)->p, restype, member); \ + (itr)->p = IS_ITER_FORWARD(itr) ? \ + (itr)->p->next : (itr)->p->prev; \ + } while(0) + + +static inline int scols_iter_is_last(const struct libscols_iter *itr) +{ + if (!itr || !itr->head || !itr->p) + return 0; + + return itr->p == itr->head; +} + +/* + * line.c + */ +int scols_line_next_group_child(struct libscols_line *ln, + struct libscols_iter *itr, + struct libscols_line **chld); + + +/* + * table.c + */ +int scols_table_next_group(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_group **gr); + +/* + * grouping.c + */ +void scols_ref_group(struct libscols_group *gr); +void scols_group_remove_children(struct libscols_group *gr); +void scols_group_remove_members(struct libscols_group *gr); +void scols_unref_group(struct libscols_group *gr); +void scols_groups_fix_members_order(struct libscols_table *tb); +int scols_groups_update_grpset(struct libscols_table *tb, struct libscols_line *ln); +void scols_groups_reset_state(struct libscols_table *tb); +struct libscols_group *scols_grpset_get_printable_children(struct libscols_table *tb); + +/* + * walk.c + */ +extern int scols_walk_tree(struct libscols_table *tb, + struct libscols_column *cl, + int (*callback)(struct libscols_table *, + struct libscols_line *, + struct libscols_column *, + void *), + void *data); +extern int scols_walk_is_last(struct libscols_table *tb, struct libscols_line *ln); + +/* + * calculate.c + */ +extern int __scols_calculate(struct libscols_table *tb, struct ul_buffer *buf); + +/* + * print.c + */ +extern int __cell_to_buffer(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + struct ul_buffer *buf); + +void __scols_cleanup_printing(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_initialize_printing(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_tree(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_table(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_header(struct libscols_table *tb, struct ul_buffer *buf); +int __scols_print_title(struct libscols_table *tb); +int __scols_print_range(struct libscols_table *tb, + struct ul_buffer *buf, + struct libscols_iter *itr, + struct libscols_line *end); + +static inline int is_tree_root(struct libscols_line *ln) +{ + return ln && !ln->parent && !ln->parent_group; +} + +static inline int is_last_tree_root(struct libscols_table *tb, struct libscols_line *ln) +{ + if (!ln || !tb || tb->walk_last_tree_root != ln) + return 0; + + return 1; +} + +static inline int is_child(struct libscols_line *ln) +{ + return ln && ln->parent; +} + +static inline int is_last_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent) + return 0; + + return list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch); +} + +static inline int is_first_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent) + return 0; + + return list_entry_is_first(&ln->ln_children, &ln->parent->ln_branch); +} + + +static inline int is_last_column(struct libscols_column *cl) +{ + struct libscols_column *next; + + if (list_entry_is_last(&cl->cl_columns, &cl->table->tb_columns)) + return 1; + + next = list_entry(cl->cl_columns.next, struct libscols_column, cl_columns); + if (next && scols_column_is_hidden(next) && is_last_column(next)) + return 1; + return 0; +} + +static inline int is_last_group_member(struct libscols_line *ln) +{ + if (!ln || !ln->group) + return 0; + + return list_entry_is_last(&ln->ln_groups, &ln->group->gr_members); +} + +static inline int is_first_group_member(struct libscols_line *ln) +{ + if (!ln || !ln->group) + return 0; + + return list_entry_is_first(&ln->ln_groups, &ln->group->gr_members); +} + +static inline int is_group_member(struct libscols_line *ln) +{ + return ln && ln->group; +} + +static inline int is_last_group_child(struct libscols_line *ln) +{ + if (!ln || !ln->parent_group) + return 0; + + return list_entry_is_last(&ln->ln_children, &ln->parent_group->gr_children); +} + +static inline int is_group_child(struct libscols_line *ln) +{ + return ln && ln->parent_group; +} + +static inline int has_groups(struct libscols_table *tb) +{ + return tb && !list_empty(&tb->tb_groups); +} + +static inline int has_children(struct libscols_line *ln) +{ + return ln && !list_empty(&ln->ln_branch); +} + +static inline int has_group_children(struct libscols_line *ln) +{ + return ln && ln->group && !list_empty(&ln->group->gr_children); +} + +#endif /* _LIBSMARTCOLS_PRIVATE_H */ diff --git a/libsmartcols/src/symbols.c b/libsmartcols/src/symbols.c new file mode 100644 index 0000000..2fadfc7 --- /dev/null +++ b/libsmartcols/src/symbols.c @@ -0,0 +1,293 @@ +/* + * symbols.c - routines for symbol handling + * + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: symbols + * @title: Symbols + * @short_description: can be used to overwrite default output chars (for ascii art) + * + * An API to access and modify data and information per symbol/symbol group. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "smartcolsP.h" + +/** + * scols_new_symbols: + * + * Returns: a pointer to a newly allocated struct libscols_symbols instance. + */ +struct libscols_symbols *scols_new_symbols(void) +{ + struct libscols_symbols *sy = calloc(1, sizeof(struct libscols_symbols)); + + if (!sy) + return NULL; + sy->refcount = 1; + return sy; +} + +/** + * scols_ref_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Increases the refcount of @sy. + */ +void scols_ref_symbols(struct libscols_symbols *sy) +{ + if (sy) + sy->refcount++; +} + +/** + * scols_unref_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Decreases the refcount of @sy. + */ +void scols_unref_symbols(struct libscols_symbols *sy) +{ + if (sy && --sy->refcount <= 0) { + free(sy->tree_branch); + free(sy->tree_vert); + free(sy->tree_right); + free(sy->group_last_member); + free(sy->group_middle_member); + free(sy->group_first_member); + free(sy->group_vert); + free(sy->group_horz); + free(sy->group_last_child); + free(sy->group_middle_child); + free(sy->title_padding); + free(sy->cell_padding); + free(sy); + } +} + +/** + * scols_symbols_set_branch: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the branch part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_branch(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, tree_branch, str); +} + +/** + * scols_symbols_set_vertical: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the vertical part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_vertical(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, tree_vert, str); +} + +/** + * scols_symbols_set_right: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the right part of a tree output + * + * Returns: 0, a negative value in case of an error. + */ +int scols_symbols_set_right(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, tree_right, str); +} + +/** + * scols_symbols_set_title_padding: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the symbols which fill title output + * + * The current implementation uses only the first byte from the padding string. + * A multibyte chars are not supported yet. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.28 + */ +int scols_symbols_set_title_padding(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, title_padding, str); +} + +/** + * scols_symbols_set_cell_padding: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the symbols which fill cells + * + * The padding char has to take up just one cell on the terminal. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_symbols_set_cell_padding(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, cell_padding, str); +} + + +/** + * scols_symbols_set_group_vertical: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the vertival line + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_vertical(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_vert, str); +} + +/** + * scols_symbols_set_group_horizontal: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent the horizontal line + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_horizontal(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_horz, str); +} + +/** + * scols_symbols_set_group_first_member: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent first member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_first_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_first_member, str); +} + +/** + * scols_symbols_set_group_last_member: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_last_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_last_member, str); +} + +/** + * scols_symbols_set_group_middle: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent middle member + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_middle_member(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_middle_member, str); +} + +/** + * scols_symbols_set_group_last_child: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last child + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_last_child(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_last_child, str); +} + +/** + * scols_symbols_set_group_middle_child: + * @sy: a pointer to a struct libscols_symbols instance + * @str: a string which will represent last child + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.34 + */ +int scols_symbols_set_group_middle_child(struct libscols_symbols *sy, const char *str) +{ + return strdup_to_struct_member(sy, group_middle_child, str); +} + +/** + * scols_copy_symbols: + * @sy: a pointer to a struct libscols_symbols instance + * + * Returns: a newly allocated copy of the @sy symbol group or NULL in case of an error. + */ +struct libscols_symbols *scols_copy_symbols(const struct libscols_symbols *sy) +{ + struct libscols_symbols *ret; + int rc; + + assert(sy); + if (!sy) + return NULL; + + ret = scols_new_symbols(); + if (!ret) + return NULL; + + rc = scols_symbols_set_branch(ret, sy->tree_branch); + if (!rc) + rc = scols_symbols_set_vertical(ret, sy->tree_vert); + if (!rc) + rc = scols_symbols_set_right(ret, sy->tree_right); + if (!rc) + rc = scols_symbols_set_group_vertical(ret, sy->group_vert); + if (!rc) + rc = scols_symbols_set_group_horizontal(ret, sy->group_horz); + if (!rc) + rc = scols_symbols_set_group_first_member(ret, sy->group_first_member); + if (!rc) + rc = scols_symbols_set_group_last_member(ret, sy->group_last_member); + if (!rc) + rc = scols_symbols_set_group_middle_member(ret, sy->group_middle_member); + if (!rc) + rc = scols_symbols_set_group_middle_child(ret, sy->group_middle_child); + if (!rc) + rc = scols_symbols_set_group_last_child(ret, sy->group_last_child); + if (!rc) + rc = scols_symbols_set_title_padding(ret, sy->title_padding); + if (!rc) + rc = scols_symbols_set_cell_padding(ret, sy->cell_padding); + if (!rc) + return ret; + + scols_unref_symbols(ret); + return NULL; +} diff --git a/libsmartcols/src/table.c b/libsmartcols/src/table.c new file mode 100644 index 0000000..8449c4f --- /dev/null +++ b/libsmartcols/src/table.c @@ -0,0 +1,1783 @@ +/* + * table.c - functions handling the data at the table level + * + * Copyright (C) 2010-2014 Karel Zak <kzak@redhat.com> + * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com> + * Copyright (C) 2016 Igor Gnatenko <i.gnatenko.brain@gmail.com> + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +/** + * SECTION: table + * @title: Table + * @short_description: container for rows and columns + * + * Table data manipulation API. + */ + + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <termios.h> +#include <ctype.h> + +#include "nls.h" +#include "ttyutils.h" +#include "smartcolsP.h" + +#ifdef HAVE_WIDECHAR +#define UTF_V "\342\224\202" /* U+2502, Vertical line drawing char | */ +#define UTF_VR "\342\224\234" /* U+251C, Vertical and right |- */ +#define UTF_H "\342\224\200" /* U+2500, Horizontal - */ +#define UTF_UR "\342\224\224" /* U+2514, Up and right '- */ + +#define UTF_V3 "\342\224\206" /* U+2506 Triple Dash Vertical | */ +#define UTF_H3 "\342\224\210" /* U+2504 Triple Dash Horizontal - */ +#define UTF_DR "\342\224\214" /* U+250C Down and Right ,- */ +#define UTF_DH "\342\224\254" /* U+252C Down and Horizontal |' */ + +#define UTF_TR "\342\226\266" /* U+25B6 Black Right-Pointing Triangle > */ +#endif /* !HAVE_WIDECHAR */ + +#define is_last_column(_tb, _cl) \ + list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns) + + +static void check_padding_debug(struct libscols_table *tb) +{ + const char *str; + + assert(libsmartcols_debug_mask); /* debug has to be already initialized! */ + + str = getenv("LIBSMARTCOLS_DEBUG_PADDING"); + if (!str || (strcmp(str, "on") != 0 && strcmp(str, "1") != 0)) + return; + + DBG(INIT, ul_debugobj(tb, "padding debug: ENABLE")); + tb->padding_debug = 1; +} + +/** + * scols_new_table: + * + * Returns: A newly allocated table. + */ +struct libscols_table *scols_new_table(void) +{ + struct libscols_table *tb; + int c, l; + + tb = calloc(1, sizeof(struct libscols_table)); + if (!tb) + return NULL; + + tb->refcount = 1; + tb->out = stdout; + + get_terminal_dimension(&c, &l); + tb->termwidth = c > 0 ? c : 80; + tb->termheight = l > 0 ? l : 24; + + INIT_LIST_HEAD(&tb->tb_lines); + INIT_LIST_HEAD(&tb->tb_columns); + INIT_LIST_HEAD(&tb->tb_groups); + + DBG(TAB, ul_debugobj(tb, "alloc")); + ON_DBG(INIT, check_padding_debug(tb)); + + return tb; +} + +/** + * scols_ref_table: + * @tb: a pointer to a struct libscols_table instance + * + * Increases the refcount of @tb. + */ +void scols_ref_table(struct libscols_table *tb) +{ + if (tb) + tb->refcount++; +} + +static void scols_table_remove_groups(struct libscols_table *tb) +{ + while (!list_empty(&tb->tb_groups)) { + struct libscols_group *gr = list_entry(tb->tb_groups.next, + struct libscols_group, gr_groups); + scols_group_remove_children(gr); + scols_group_remove_members(gr); + scols_unref_group(gr); + } +} + +/** + * scols_unref_table: + * @tb: a pointer to a struct libscols_table instance + * + * Decreases the refcount of @tb. When the count falls to zero, the instance + * is automatically deallocated. + */ +void scols_unref_table(struct libscols_table *tb) +{ + if (tb && (--tb->refcount <= 0)) { + DBG(TAB, ul_debugobj(tb, "dealloc <-")); + scols_table_remove_groups(tb); + scols_table_remove_lines(tb); + scols_table_remove_columns(tb); + scols_unref_symbols(tb->symbols); + scols_reset_cell(&tb->title); + free(tb->grpset); + free(tb->linesep); + free(tb->colsep); + free(tb->name); + free(tb); + DBG(TAB, ul_debug("<- done")); + } +} + +/* Private API */ +int scols_table_next_group(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_group **gr) +{ + int rc = 1; + + if (!tb || !itr || !gr) + return -EINVAL; + *gr = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_groups); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *gr, struct libscols_group, gr_groups); + rc = 0; + } + + return rc; +} + +/** + * scols_table_set_name: + * @tb: a pointer to a struct libscols_table instance + * @name: a name + * + * The table name is used for example for JSON top level object name. + * + * Returns: 0, a negative number in case of an error. + * + * Since: 2.27 + */ +int scols_table_set_name(struct libscols_table *tb, const char *name) +{ + return strdup_to_struct_member(tb, name, name); +} + +/** + * scols_table_get_name: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: The current name setting of the table @tb + * + * Since: 2.29 + */ +const char *scols_table_get_name(const struct libscols_table *tb) +{ + return tb->name; +} + +/** + * scols_table_get_title: + * @tb: a pointer to a struct libscols_table instance + * + * The returned pointer is possible to modify by cell functions. Note that + * title output alignment on non-tty is hardcoded to 80 output chars. For the + * regular terminal it's based on terminal width. + * + * Returns: Title of the table, or NULL in case of blank title. + * + * Since: 2.28 + */ +struct libscols_cell *scols_table_get_title(struct libscols_table *tb) +{ + return &tb->title; +} + +/** + * scols_table_add_column: + * @tb: a pointer to a struct libscols_table instance + * @cl: a pointer to a struct libscols_column instance + * + * Adds @cl to @tb's column list. The column cannot be shared between more + * tables. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_add_column(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_iter itr; + struct libscols_line *ln; + int rc = 0; + + if (!tb || !cl || cl->table) + return -EINVAL; + + if (!list_empty(&cl->cl_columns)) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols++; + + DBG(TAB, ul_debugobj(tb, "add column")); + list_add_tail(&cl->cl_columns, &tb->tb_columns); + cl->seqnum = tb->ncols++; + cl->table = tb; + scols_ref_column(cl); + + if (list_empty(&tb->tb_lines)) + return 0; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + + /* Realloc line cell arrays + */ + while (scols_table_next_line(tb, &itr, &ln) == 0) { + rc = scols_line_alloc_cells(ln, tb->ncols); + if (rc) + break; + } + + return rc; +} + +/** + * scols_table_remove_column: + * @tb: a pointer to a struct libscols_table instance + * @cl: a pointer to a struct libscols_column instance + * + * Removes @cl from @tb. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_remove_column(struct libscols_table *tb, + struct libscols_column *cl) +{ + if (!tb || !cl || !list_empty(&tb->tb_lines)) + return -EINVAL; + + if (cl->flags & SCOLS_FL_TREE) + tb->ntreecols--; + if (tb->dflt_sort_column == cl) + tb->dflt_sort_column = NULL; + + DBG(TAB, ul_debugobj(tb, "remove column")); + list_del_init(&cl->cl_columns); + tb->ncols--; + cl->table = NULL; + scols_unref_column(cl); + return 0; +} + +/** + * scols_table_remove_columns: + * @tb: a pointer to a struct libscols_table instance + * + * Removes all of @tb's columns. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_remove_columns(struct libscols_table *tb) +{ + if (!tb || !list_empty(&tb->tb_lines)) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "remove all columns")); + while (!list_empty(&tb->tb_columns)) { + struct libscols_column *cl = list_entry(tb->tb_columns.next, + struct libscols_column, cl_columns); + scols_table_remove_column(tb, cl); + } + return 0; +} + +/** + * scols_table_move_column: + * @tb: table + * @pre: column before the column + * @cl: column to move + * + * Move the @cl behind @pre. If the @pre is NULL then the @col is the first + * column in the table. + * + * Since: 2.30 + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_move_column(struct libscols_table *tb, + struct libscols_column *pre, + struct libscols_column *cl) +{ + struct list_head *head; + struct libscols_iter itr; + struct libscols_column *p; + struct libscols_line *ln; + size_t n = 0, oldseq; + + if (!tb || !cl) + return -EINVAL; + + if (pre && pre->seqnum + 1 == cl->seqnum) + return 0; + if (pre == NULL && cl->seqnum == 0) + return 0; + + DBG(TAB, ul_debugobj(tb, "move column %zu behind %zu", + cl->seqnum, pre? pre->seqnum : 0)); + + list_del_init(&cl->cl_columns); /* remove from old position */ + + head = pre ? &pre->cl_columns : &tb->tb_columns; + list_add(&cl->cl_columns, head); /* add to the new place */ + + oldseq = cl->seqnum; + + /* fix seq. numbers */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &p) == 0) + p->seqnum = n++; + + /* move data in lines */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + scols_line_move_cells(ln, cl->seqnum, oldseq); + return 0; +} + +/** + * scols_table_new_column: + * @tb: table + * @name: column header + * @whint: column width hint (absolute width: N > 1; relative width: 0 < N < 1) + * @flags: flags integer + * + * This is shortcut for + * + * cl = scols_new_column(); + * scols_column_set_....(cl, ...); + * scols_table_add_column(tb, cl); + * + * The column width is possible to define by: + * + * @whint: 0 < N < 1 : relative width, percent of terminal width + * + * @whint: N >= 1 : absolute width, empty column will be truncated to + * the column header width if no specified STRICTWIDTH flag + * + * Note that if table has disabled "maxout" flag (disabled by default) than + * relative width is used as a hint only. It's possible that column will be + * narrow if the specified size is too large for column data. + * + * + * If the width of all columns is greater than terminal width then library + * tries to reduce width of the individual columns. It's done in three stages: + * + * #1 reduce columns with SCOLS_FL_TRUNC flag and with relative width if the + * width is greater than width defined by @whint (@whint * terminal_width) + * + * #2 reduce all columns with SCOLS_FL_TRUNC flag + * + * #3 reduce all columns with relative width + * + * The next stage is always used if the previous stage is unsuccessful. Note + * that SCOLS_FL_WRAP is interpreted as SCOLS_FL_TRUNC when calculate column + * width (if custom wrap function is not specified), but the final text is not + * truncated, but wrapped to multi-line cell. + * + * + * The column is necessary to address by sequential number. The first defined + * column has the colnum = 0. For example: + * + * scols_table_new_column(tab, "FOO", 0.5, 0); // colnum = 0 + * scols_table_new_column(tab, "BAR", 0.5, 0); // colnum = 1 + * . + * . + * scols_line_get_cell(line, 0); // FOO column + * scols_line_get_cell(line, 1); // BAR column + * + * Returns: newly allocated column + */ +struct libscols_column *scols_table_new_column(struct libscols_table *tb, + const char *name, + double whint, + int flags) +{ + struct libscols_column *cl; + + if (!tb) + return NULL; + + DBG(TAB, ul_debugobj(tb, "new column name=%s, whint=%g, flags=0x%04x", + name, whint, flags)); + cl = scols_new_column(); + if (!cl) + return NULL; + + if (name && scols_column_set_name(cl, name)) + goto err; + scols_column_set_whint(cl, whint); + scols_column_set_flags(cl, flags); + + if (scols_table_add_column(tb, cl)) /* this increments column ref-counter */ + goto err; + + scols_unref_column(cl); + return cl; +err: + scols_unref_column(cl); + return NULL; +} + +/** + * scols_table_next_column: + * @tb: a pointer to a struct libscols_table instance + * @itr: a pointer to a struct libscols_iter instance + * @cl: a pointer to a pointer to a struct libscols_column instance + * + * Returns the next column of @tb via @cl. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_next_column(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_column **cl) +{ + int rc = 1; + + if (!tb || !itr || !cl) + return -EINVAL; + *cl = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_columns); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *cl, struct libscols_column, cl_columns); + rc = 0; + } + + return rc; +} + +/** + * scols_table_set_columns_iter: + * @tb: tab pointer + * @itr: iterator + * @cl: tab entry + * + * Sets @iter to the position of @cl in the file @tb. + * + * Returns: 0 on success, negative number in case of error. + * + * Since: 2.35 + */ +int scols_table_set_columns_iter( + struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_column *cl) +{ + if (!tb || !itr || !cl) + return -EINVAL; + + if (cl->table != tb) + return -EINVAL; + + SCOLS_ITER_INIT(itr, &tb->tb_columns); + itr->p = &cl->cl_columns; + + return 0; +} + +/** + * scols_table_get_ncols: + * @tb: table + * + * Returns: the ncols table member. + */ +size_t scols_table_get_ncols(const struct libscols_table *tb) +{ + return tb->ncols; +} + +/** + * scols_table_get_nlines: + * @tb: table + * + * Returns: the nlines table member. + */ +size_t scols_table_get_nlines(const struct libscols_table *tb) +{ + return tb->nlines; +} + +/** + * scols_table_set_stream: + * @tb: table + * @stream: output stream + * + * Sets the output stream for table @tb. + * + * Returns: 0, a negative number in case of an error. + */ +int scols_table_set_stream(struct libscols_table *tb, FILE *stream) +{ + assert(tb); + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "setting alternative stream")); + tb->out = stream; + return 0; +} + +/** + * scols_table_get_stream: + * @tb: table + * + * Gets the output stream for table @tb. + * + * Returns: stream pointer, NULL in case of an error or an unset stream. + */ +FILE *scols_table_get_stream(const struct libscols_table *tb) +{ + return tb->out; +} + +/** + * scols_table_reduce_termwidth: + * @tb: table + * @reduce: width + * + * If necessary then libsmartcols use all terminal width, the @reduce setting + * provides extra space (for example for borders in ncurses applications). + * + * The @reduce must be smaller than terminal width, otherwise it's silently + * ignored. The reduction is not applied when STDOUT_FILENO is not terminal. + * + * Note that after output initialization (scols_table_print_* calls) the width + * will be reduced, this behavior affects subsequenced scols_table_get_termwidth() + * calls. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_reduce_termwidth(struct libscols_table *tb, size_t reduce) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "reduce terminal width: %zu", reduce)); + tb->termreduce = reduce; + return 0; +} + +/** + * scols_table_get_column: + * @tb: table + * @n: number of column (0..N) + * + * Returns: pointer to column or NULL + */ +struct libscols_column *scols_table_get_column(struct libscols_table *tb, + size_t n) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + if (!tb) + return NULL; + if (n >= tb->ncols) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + if (cl->seqnum == n) + return cl; + } + return NULL; +} + +/** + * scols_table_get_column_ny_name + * @tb: table + * @name: column name + * + * Returns: pointer to column or NULL + * + * Since: 2.39 + */ +struct libscols_column *scols_table_get_column_by_name( + struct libscols_table *tb, const char *name) +{ + struct libscols_iter itr; + struct libscols_column *cl; + + if (!tb || !name) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + const char *cn = scols_column_get_name(cl); + + if (cn && strcmp(cn, name) == 0) + return cl; + } + return NULL; +} + + +/** + * scols_table_add_line: + * @tb: table + * @ln: line + * + * Note that this function calls scols_line_alloc_cells() if number + * of the cells in the line is too small for @tb. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_add_line(struct libscols_table *tb, struct libscols_line *ln) +{ + if (!tb || !ln) + return -EINVAL; + + if (!list_empty(&ln->ln_lines)) + return -EINVAL; + + if (tb->ncols > ln->ncells) { + int rc = scols_line_alloc_cells(ln, tb->ncols); + if (rc) + return rc; + } + + DBG(TAB, ul_debugobj(tb, "add line")); + list_add_tail(&ln->ln_lines, &tb->tb_lines); + ln->seqnum = tb->nlines++; + scols_ref_line(ln); + return 0; +} + +/** + * scols_table_remove_line: + * @tb: table + * @ln: line + * + * Note that this function does not destroy the parent<->child relationship between lines. + * You have to call scols_line_remove_child() + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_remove_line(struct libscols_table *tb, + struct libscols_line *ln) +{ + if (!tb || !ln) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "remove line")); + list_del_init(&ln->ln_lines); + tb->nlines--; + scols_unref_line(ln); + return 0; +} + +/** + * scols_table_remove_lines: + * @tb: table + * + * This empties the table and also destroys all the parent<->child relationships. + */ +void scols_table_remove_lines(struct libscols_table *tb) +{ + if (!tb) + return; + + DBG(TAB, ul_debugobj(tb, "remove all lines")); + while (!list_empty(&tb->tb_lines)) { + struct libscols_line *ln = list_entry(tb->tb_lines.next, + struct libscols_line, ln_lines); + if (ln->parent) + scols_line_remove_child(ln->parent, ln); + scols_table_remove_line(tb, ln); + } +} + +/** + * scols_table_next_line: + * @tb: a pointer to a struct libscols_table instance + * @itr: a pointer to a struct libscols_iter instance + * @ln: a pointer to a pointer to a struct libscols_line instance + * + * Finds the next line and returns a pointer to it via @ln. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_next_line(struct libscols_table *tb, + struct libscols_iter *itr, + struct libscols_line **ln) +{ + int rc = 1; + + if (!tb || !itr || !ln) + return -EINVAL; + *ln = NULL; + + if (!itr->head) + SCOLS_ITER_INIT(itr, &tb->tb_lines); + if (itr->p != itr->head) { + SCOLS_ITER_ITERATE(itr, *ln, struct libscols_line, ln_lines); + rc = 0; + } + + return rc; +} + +/** + * scols_table_new_line: + * @tb: table + * @parent: parental line or NULL + * + * This is shortcut for + * + * ln = scols_new_line(); + * scols_table_add_line(tb, ln); + * scols_line_add_child(parent, ln); + * + * + * Returns: newly allocate line + */ +struct libscols_line *scols_table_new_line(struct libscols_table *tb, + struct libscols_line *parent) +{ + struct libscols_line *ln; + + if (!tb) + return NULL; + + ln = scols_new_line(); + if (!ln) + return NULL; + + if (scols_table_add_line(tb, ln)) + goto err; + if (parent) + scols_line_add_child(parent, ln); + + scols_unref_line(ln); /* ref-counter incremented by scols_table_add_line() */ + return ln; +err: + scols_unref_line(ln); + return NULL; +} + +/** + * scols_table_get_line: + * @tb: table + * @n: column number (0..N) + * + * Returns: a line or NULL + */ +struct libscols_line *scols_table_get_line(struct libscols_table *tb, + size_t n) +{ + struct libscols_iter itr; + struct libscols_line *ln; + + if (!tb) + return NULL; + if (n >= tb->nlines) + return NULL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->seqnum == n) + return ln; + } + return NULL; +} + +/** + * scols_copy_table: + * @tb: table + * + * Creates a new independent table copy, except struct libscols_symbols that + * are shared between the tables. + * + * Returns: a newly allocated copy of @tb + */ +struct libscols_table *scols_copy_table(struct libscols_table *tb) +{ + struct libscols_table *ret; + struct libscols_line *ln; + struct libscols_column *cl; + struct libscols_iter itr; + + if (!tb) + return NULL; + ret = scols_new_table(); + if (!ret) + return NULL; + + DBG(TAB, ul_debugobj(tb, "copy")); + + if (tb->symbols) + scols_table_set_symbols(ret, tb->symbols); + + /* columns */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_column(tb, &itr, &cl) == 0) { + cl = scols_copy_column(cl); + if (!cl) + goto err; + if (scols_table_add_column(ret, cl)) + goto err; + scols_unref_column(cl); + } + + /* lines */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + struct libscols_line *newln = scols_copy_line(ln); + if (!newln) + goto err; + if (scols_table_add_line(ret, newln)) + goto err; + if (ln->parent) { + struct libscols_line *p = + scols_table_get_line(ret, ln->parent->seqnum); + if (p) + scols_line_add_child(p, newln); + } + scols_unref_line(newln); + } + + /* separators */ + if (scols_table_set_column_separator(ret, tb->colsep) || + scols_table_set_line_separator(ret, tb->linesep)) + goto err; + + return ret; +err: + scols_unref_table(ret); + return NULL; +} + +/** + * scols_table_set_default_symbols: + * @tb: table + * + * The library check the current environment to select ASCII or UTF8 symbols. + * This default behavior could be controlled by scols_table_enable_ascii(). + * + * Use scols_table_set_symbols() to unset symbols or use your own setting. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_default_symbols(struct libscols_table *tb) +{ + struct libscols_symbols *sy; + int rc; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "setting default symbols")); + + sy = scols_new_symbols(); + if (!sy) + return -ENOMEM; + +#if defined(HAVE_WIDECHAR) + if (!scols_table_is_ascii(tb) && + !strcmp(nl_langinfo(CODESET), "UTF-8")) { + /* tree chart */ + scols_symbols_set_branch(sy, UTF_VR UTF_H); + scols_symbols_set_vertical(sy, UTF_V " "); + scols_symbols_set_right(sy, UTF_UR UTF_H); + /* groups chart */ + scols_symbols_set_group_horizontal(sy, UTF_H3); + scols_symbols_set_group_vertical(sy, UTF_V3); + + scols_symbols_set_group_first_member(sy, UTF_DR UTF_H3 UTF_TR); + scols_symbols_set_group_last_member(sy, UTF_UR UTF_DH UTF_TR); + scols_symbols_set_group_middle_member(sy, UTF_VR UTF_H3 UTF_TR); + scols_symbols_set_group_last_child(sy, UTF_UR UTF_H3); + scols_symbols_set_group_middle_child(sy, UTF_VR UTF_H3); + } else +#endif + { + /* tree chart */ + scols_symbols_set_branch(sy, "|-"); + scols_symbols_set_vertical(sy, "| "); + scols_symbols_set_right(sy, "`-"); + /* groups chart */ + scols_symbols_set_group_horizontal(sy, "-"); + scols_symbols_set_group_vertical(sy, "|"); + + scols_symbols_set_group_first_member(sy, ",->"); + scols_symbols_set_group_last_member(sy, "'->"); + scols_symbols_set_group_middle_member(sy, "|->"); + scols_symbols_set_group_last_child(sy, "`-"); + scols_symbols_set_group_middle_child(sy, "|-"); + } + scols_symbols_set_title_padding(sy, " "); + scols_symbols_set_cell_padding(sy, " "); + + rc = scols_table_set_symbols(tb, sy); + scols_unref_symbols(sy); + return rc; +} + + +/** + * scols_table_set_symbols: + * @tb: table + * @sy: symbols or NULL + * + * Add a reference to @sy from the table. The symbols are used by library to + * draw tree output. If no symbols are used for the table then library creates + * default temporary symbols to draw output by scols_table_set_default_symbols(). + * + * If @sy is NULL then remove reference from the currently used symbols. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_symbols(struct libscols_table *tb, + struct libscols_symbols *sy) +{ + if (!tb) + return -EINVAL; + + /* remove old */ + if (tb->symbols) { + DBG(TAB, ul_debugobj(tb, "remove symbols reference")); + scols_unref_symbols(tb->symbols); + tb->symbols = NULL; + } + + /* set new */ + if (sy) { /* ref user defined */ + DBG(TAB, ul_debugobj(tb, "set symbols")); + tb->symbols = sy; + scols_ref_symbols(sy); + } + return 0; +} + +/** + * scols_table_get_symbols: + * @tb: table + * + * Returns: pointer to symbols table. + * + * Since: 2.29 + */ +struct libscols_symbols *scols_table_get_symbols(const struct libscols_table *tb) +{ + return tb->symbols; +} + +/** + * scols_table_enable_nolinesep: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable line separator printing. This is useful if you want to + * re-printing the same line more than once (e.g. progress bar). Don't use it + * if you're not sure. + * + * Note that for the last line in the table the separator is disabled at all. + * The library differentiate between table terminator and line terminator + * (although for standard output \n byte is used in both cases). + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_nolinesep(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "nolinesep: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_linesep = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_nolinesep: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if line separator printing is disabled. + * + * Since: 2.29 + */ +int scols_table_is_nolinesep(const struct libscols_table *tb) +{ + return tb->no_linesep; +} + +/** + * scols_table_enable_colors: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable colors. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_colors(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "colors: %s", enable ? "ENABLE" : "DISABLE")); + tb->colors_wanted = enable; + return 0; +} + +/** + * scols_table_enable_raw: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable raw output format. The parsable output formats + * (export, raw, JSON, ...) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_raw(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "raw: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_RAW; + else if (tb->format == SCOLS_FMT_RAW) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_json: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable JSON output format. The parsable output formats + * (export, raw, JSON, ...) are mutually exclusive. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.27 + */ +int scols_table_enable_json(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "json: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_JSON; + else if (tb->format == SCOLS_FMT_JSON) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_export: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable export output format (COLUMNAME="value" ...). + * The parsable output formats (export and raw) are mutually exclusive. + * + * See also scols_table_enable_shellvar(). Note that in version 2.37 (and only + * in this version) scols_table_enable_shellvar() functionality has been + * automatically enabled for "export" format. This behavior has been reverted + * in version 2.38 due to backward compatibility issues. Now it's necessary to + * explicitly call scols_table_enable_shellvar(). + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_export(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "export: %s", enable ? "ENABLE" : "DISABLE")); + if (enable) + tb->format = SCOLS_FMT_EXPORT; + else if (tb->format == SCOLS_FMT_EXPORT) + tb->format = 0; + return 0; +} + +/** + * scols_table_enable_shellvar: + * @tb: table + * @enable: 1 or 0 + * + * Force library to print column names to be compatible with shell requirements + * to variable names. For example "1FOO%" will be printed as "_1FOO_PCT". + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.38 + */ +int scols_table_enable_shellvar(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "shellvar: %s", enable ? "ENABLE" : "DISABLE")); + tb->is_shellvar = enable ? 1 : 0; + return 0; +} + + +/** + * scols_table_enable_ascii: + * @tb: table + * @enable: 1 or 0 + * + * The ASCII-only output is relevant for tree-like outputs. The library + * checks if the current environment is UTF8 compatible by default. This + * function overrides this check and force the library to use ASCII chars + * for the tree. + * + * If a custom libcols_symbols are specified (see scols_table_set_symbols() + * then ASCII flag setting is ignored. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_ascii(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "ascii: %s", enable ? "ENABLE" : "DISABLE")); + tb->ascii = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_noheadings: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable header line. + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_noheadings(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "noheading: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_headings = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_header_repeat: + * @tb: table + * @enable: 1 or 0 + * + * Enable/disable header line repeat. The header line is printed only once by + * default. Note that the flag will be silently ignored and disabled if the + * output is not on terminal or output format is JSON, raw, etc. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.31 + */ +int scols_table_enable_header_repeat(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "header-repeat: %s", enable ? "ENABLE" : "DISABLE")); + tb->header_repeat = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_maxout: + * @tb: table + * @enable: 1 or 0 + * + * The extra space after last column is ignored by default. The output + * maximization add padding for all columns. + * + * This setting is mutually exclusive to scols_table_enable_minout(). + * + * Returns: 0 on success, negative number in case of an error. + */ +int scols_table_enable_maxout(struct libscols_table *tb, int enable) +{ + if (!tb || tb->minout) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "maxout: %s", enable ? "ENABLE" : "DISABLE")); + tb->maxout = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_minout: + * @tb: table + * @enable: 1 or 0 + * + * Force library to terminate line after last column with data. The extra + * padding is not added to the empty cells at the end of the line. The default is fill + * tailing empty cells except the last line cell. + * + * This setting is mutually exclusive to scols_table_enable_maxout(). + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.35 + */ +int scols_table_enable_minout(struct libscols_table *tb, int enable) +{ + if (!tb || tb->maxout) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "minout: %s", enable ? "ENABLE" : "DISABLE")); + tb->minout = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_enable_nowrap: + * @tb: table + * @enable: 1 or 0 + * + * Never continue on next line, remove last column(s) when too large, truncate last column. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.28 + */ +int scols_table_enable_nowrap(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "nowrap: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_wrap = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_nowrap: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if nowrap is enabled. + * + * Since: 2.29 + */ +int scols_table_is_nowrap(const struct libscols_table *tb) +{ + return tb->no_wrap; +} + +/** + * scols_table_enable_noencoding: + * @tb: table + * @enable: 1 or 0 + * + * The library encode non-printable and control chars by \xHEX by default. + * + * Returns: 0 on success, negative number in case of an error. + * + * Since: 2.31 + */ +int scols_table_enable_noencoding(struct libscols_table *tb, int enable) +{ + if (!tb) + return -EINVAL; + DBG(TAB, ul_debugobj(tb, "encoding: %s", enable ? "ENABLE" : "DISABLE")); + tb->no_encode = enable ? 1 : 0; + return 0; +} + +/** + * scols_table_is_noencoding: + * @tb: a pointer to a struct libscols_table instance + * + * Returns: 1 if encoding is disabled. + * + * Since: 2.31 + */ +int scols_table_is_noencoding(const struct libscols_table *tb) +{ + return tb->no_encode; +} + +/** + * scols_table_colors_wanted: + * @tb: table + * + * Returns: 1 if colors are enabled. + */ +int scols_table_colors_wanted(const struct libscols_table *tb) +{ + return tb->colors_wanted; +} + +/** + * scols_table_is_empty: + * @tb: table + * + * Returns: 1 if the table is empty. + */ +int scols_table_is_empty(const struct libscols_table *tb) +{ + return !tb->nlines; +} + +/** + * scols_table_is_ascii: + * @tb: table + * + * Returns: 1 if ASCII tree is enabled. + */ +int scols_table_is_ascii(const struct libscols_table *tb) +{ + return tb->ascii; +} + +/** + * scols_table_is_noheadings: + * @tb: table + * + * Returns: 1 if header output is disabled. + */ +int scols_table_is_noheadings(const struct libscols_table *tb) +{ + return tb->no_headings; +} + +/** + * scols_table_is_header_repeat + * @tb: table + * + * Returns: 1 if header repeat is enabled. + * + * Since: 2.31 + */ +int scols_table_is_header_repeat(const struct libscols_table *tb) +{ + return tb->header_repeat; +} + +/** + * scols_table_is_export: + * @tb: table + * + * Returns: 1 if export output format is enabled. + */ +int scols_table_is_export(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_EXPORT; +} + +/** + * scols_table_is_shellvar: + * @tb: table + * + * Returns: 1 if column names has to be compatible with shell requirements + * to variable names + * + * Since: 2.38 + */ +int scols_table_is_shellvar(const struct libscols_table *tb) +{ + return tb->is_shellvar; +} + +/** + * scols_table_is_raw: + * @tb: table + * + * Returns: 1 if raw output format is enabled. + */ +int scols_table_is_raw(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_RAW; +} + +/** + * scols_table_is_json: + * @tb: table + * + * Returns: 1 if JSON output format is enabled. + * + * Since: 2.27 + */ +int scols_table_is_json(const struct libscols_table *tb) +{ + return tb->format == SCOLS_FMT_JSON; +} + +/** + * scols_table_is_maxout + * @tb: table + * + * Returns: 1 if output maximization is enabled or 0 + */ +int scols_table_is_maxout(const struct libscols_table *tb) +{ + return tb->maxout; +} + +/** + * scols_table_is_minout + * @tb: table + * + * Returns: 1 if output minimization is enabled or 0 + * + * Since: 2.35 + */ +int scols_table_is_minout(const struct libscols_table *tb) +{ + return tb->minout; +} + +/** + * scols_table_is_tree: + * @tb: table + * + * Returns: returns 1 tree-like output is expected. + */ +int scols_table_is_tree(const struct libscols_table *tb) +{ + return tb->ntreecols > 0; +} + +/** + * scols_table_set_column_separator: + * @tb: table + * @sep: separator + * + * Sets the column separator of @tb to @sep. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_column_separator(struct libscols_table *tb, const char *sep) +{ + return strdup_to_struct_member(tb, colsep, sep); +} + +/** + * scols_table_set_line_separator: + * @tb: table + * @sep: separator + * + * Sets the line separator of @tb to @sep. + * + * Returns: 0, a negative value in case of an error. + */ +int scols_table_set_line_separator(struct libscols_table *tb, const char *sep) +{ + return strdup_to_struct_member(tb, linesep, sep); +} + +/** + * scols_table_get_column_separator: + * @tb: table + * + * Returns: @tb column separator, NULL in case of an error + */ +const char *scols_table_get_column_separator(const struct libscols_table *tb) +{ + return tb->colsep; +} + +/** + * scols_table_get_line_separator: + * @tb: table + * + * Returns: @tb line separator, NULL in case of an error + */ +const char *scols_table_get_line_separator(const struct libscols_table *tb) +{ + return tb->linesep; +} +/* for lines in the struct libscols_line->ln_lines list */ +static int cells_cmp_wrapper_lines(struct list_head *a, struct list_head *b, void *data) +{ + struct libscols_column *cl = (struct libscols_column *) data; + struct libscols_line *ra, *rb; + struct libscols_cell *ca, *cb; + + assert(a); + assert(b); + assert(cl); + + ra = list_entry(a, struct libscols_line, ln_lines); + rb = list_entry(b, struct libscols_line, ln_lines); + ca = scols_line_get_cell(ra, cl->seqnum); + cb = scols_line_get_cell(rb, cl->seqnum); + + return cl->cmpfunc(ca, cb, cl->cmpfunc_data); +} + +/* for lines in the struct libscols_line->ln_children list */ +static int cells_cmp_wrapper_children(struct list_head *a, struct list_head *b, void *data) +{ + struct libscols_column *cl = (struct libscols_column *) data; + struct libscols_line *ra, *rb; + struct libscols_cell *ca, *cb; + + assert(a); + assert(b); + assert(cl); + + ra = list_entry(a, struct libscols_line, ln_children); + rb = list_entry(b, struct libscols_line, ln_children); + ca = scols_line_get_cell(ra, cl->seqnum); + cb = scols_line_get_cell(rb, cl->seqnum); + + return cl->cmpfunc(ca, cb, cl->cmpfunc_data); +} + + +static int sort_line_children(struct libscols_line *ln, struct libscols_column *cl) +{ + struct list_head *p; + + if (!list_empty(&ln->ln_branch)) { + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + sort_line_children(chld, cl); + } + + list_sort(&ln->ln_branch, cells_cmp_wrapper_children, cl); + } + + if (is_first_group_member(ln)) { + list_for_each(p, &ln->group->gr_children) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + sort_line_children(chld, cl); + } + + list_sort(&ln->group->gr_children, cells_cmp_wrapper_children, cl); + } + + return 0; +} + +static int __scols_sort_tree(struct libscols_table *tb, struct libscols_column *cl) +{ + struct libscols_line *ln; + struct libscols_iter itr; + + if (!tb || !cl || !cl->cmpfunc) + return -EINVAL; + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + sort_line_children(ln, cl); + return 0; +} + +/** + * scols_sort_table: + * @tb: table + * @cl: order by this column or NULL + * + * Orders the table by the column. See also scols_column_set_cmpfunc(). If the + * tree output is enabled then children in the tree are recursively sorted too. + * + * The column @cl is saved as the default sort column to the @tb and the next time + * is possible to call scols_sort_table(tb, NULL). The saved column is also used by + * scols_sort_table_by_tree(). + * + * Returns: 0, a negative value in case of an error. + */ +int scols_sort_table(struct libscols_table *tb, struct libscols_column *cl) +{ + if (!tb) + return -EINVAL; + if (!cl) + cl = tb->dflt_sort_column; + if (!cl || !cl->cmpfunc) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "sorting table by %zu column", cl->seqnum)); + list_sort(&tb->tb_lines, cells_cmp_wrapper_lines, cl); + + if (scols_table_is_tree(tb)) + __scols_sort_tree(tb, cl); + + if (cl && cl != tb->dflt_sort_column) + tb->dflt_sort_column = cl; + + return 0; +} + +/* + * Move all @ln's children after @ln in the table. + */ +static struct libscols_line *move_line_and_children(struct libscols_line *ln, struct libscols_line *pre) +{ + if (pre) { + list_del_init(&ln->ln_lines); /* remove from old position */ + list_add(&ln->ln_lines, &pre->ln_lines); /* add to the new place (after @pre) */ + } + pre = ln; + + if (!list_empty(&ln->ln_branch)) { + struct list_head *p; + + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + pre = move_line_and_children(chld, pre); + } + } + + return pre; +} + +/** + * scols_sort_table_by_tree: + * @tb: table + * + * Reorders lines in the table by parent->child relation. Note that order of + * the lines in the table is independent on the tree hierarchy by default. + * + * The children of the lines are sorted according to the default sort column + * if scols_sort_table() has been previously called. + * + * Since: 2.30 + * + * Returns: 0, a negative value in case of an error. + */ +int scols_sort_table_by_tree(struct libscols_table *tb) +{ + struct libscols_line *ln; + struct libscols_iter itr; + + if (!tb) + return -EINVAL; + + DBG(TAB, ul_debugobj(tb, "sorting table by tree")); + + if (tb->dflt_sort_column) + __scols_sort_tree(tb, tb->dflt_sort_column); + + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) + move_line_and_children(ln, NULL); + + return 0; +} + + +/** + * scols_table_set_termforce: + * @tb: table + * @force: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} + * + * Forces library to use stdout as terminal, non-terminal or use automatic + * detection (default). + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_termforce(struct libscols_table *tb, int force) +{ + if (!tb) + return -EINVAL; + tb->termforce = force; + return 0; +} + +/** + * scols_table_get_termforce: + * @tb: table + * + * Returns: SCOLS_TERMFORCE_{NEVER,ALWAYS,AUTO} or a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_get_termforce(const struct libscols_table *tb) +{ + return tb->termforce; +} + +/** + * scols_table_set_termwidth + * @tb: table + * @width: terminal width + * + * The library automatically detects terminal width or defaults to 80 chars if + * detections is unsuccessful. This function override this behaviour. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.29 + */ +int scols_table_set_termwidth(struct libscols_table *tb, size_t width) +{ + DBG(TAB, ul_debugobj(tb, "set terminatl width: %zu", width)); + tb->termwidth = width; + return 0; +} + +/** + * scols_table_get_termwidth + * @tb: table + * + * Returns: terminal width. + */ +size_t scols_table_get_termwidth(const struct libscols_table *tb) +{ + return tb->termwidth; +} + +/** + * scols_table_set_termheight + * @tb: table + * @height: terminal height (number of lines) + * + * The library automatically detects terminal height or defaults to 24 lines if + * detections is unsuccessful. This function override this behaviour. + * + * Returns: 0, a negative value in case of an error. + * + * Since: 2.31 + */ +int scols_table_set_termheight(struct libscols_table *tb, size_t height) +{ + DBG(TAB, ul_debugobj(tb, "set terminatl height: %zu", height)); + tb->termheight = height; + return 0; +} + +/** + * scols_table_get_termheight + * @tb: table + * + * Returns: terminal height (number of lines). + * + * Since: 2.31 + */ +size_t scols_table_get_termheight(const struct libscols_table *tb) +{ + return tb->termheight; +} diff --git a/libsmartcols/src/version.c b/libsmartcols/src/version.c new file mode 100644 index 0000000..e592ccc --- /dev/null +++ b/libsmartcols/src/version.c @@ -0,0 +1,62 @@ +/* + * version.c - Return the version of the library + * + * Copyright (C) 2014 Karel Zak <kzak@redhat.com> + * + * See COPYING.libmount for the License of this software. + */ + +/** + * SECTION: version-utils + * @title: Version functions + * @short_description: functions to get the library version. + * + * Note that library version is not the same thing as SONAME version. The + * libsmarcols uses symbols versioning and SONAME is not modified for releases. + * + * The library version and symbols version follow util-linux package versioning. + */ + +#include <ctype.h> + +#include "smartcolsP.h" + +static const char *lib_version = LIBSMARTCOLS_VERSION; + +/** + * scols_parse_version_string: + * @ver_string: version string (e.g "2.18.0") + * + * Returns: release version code. + */ +int scols_parse_version_string(const char *ver_string) +{ + const char *cp; + int version = 0; + + assert(ver_string); + + for (cp = ver_string; *cp; cp++) { + if (*cp == '.') + continue; + if (!isdigit(*cp)) + break; + version = (version * 10) + (*cp - '0'); + } + return version; +} + +/** + * scols_get_library_version: + * @ver_string: return pointer to the static library version string if not NULL + * + * Returns: release version number. + */ +int scols_get_library_version(const char **ver_string) +{ + if (ver_string) + *ver_string = lib_version; + + return scols_parse_version_string(lib_version); +} + diff --git a/libsmartcols/src/walk.c b/libsmartcols/src/walk.c new file mode 100644 index 0000000..0b51fed --- /dev/null +++ b/libsmartcols/src/walk.c @@ -0,0 +1,152 @@ +#include "smartcolsP.h" + +static int walk_line(struct libscols_table *tb, + struct libscols_line *ln, + struct libscols_column *cl, + int (*callback)(struct libscols_table *, + struct libscols_line *, + struct libscols_column *, + void *), + void *data) +{ + int rc = 0; + +/* DBG(LINE, ul_debugobj(ln, " wall line")); */ + + /* we list group children in __scols_print_tree() after tree root node */ + if (is_group_member(ln) && is_last_group_member(ln) && has_group_children(ln)) + tb->ngrpchlds_pending++; + + if (has_groups(tb)) + rc = scols_groups_update_grpset(tb, ln); + if (rc == 0) + rc = callback(tb, ln, cl, data); + + /* children */ + if (rc == 0 && has_children(ln)) { + struct list_head *p; + +/* DBG(LINE, ul_debugobj(ln, " children walk"));*/ + + list_for_each(p, &ln->ln_branch) { + struct libscols_line *chld = list_entry(p, + struct libscols_line, ln_children); + + rc = walk_line(tb, chld, cl, callback, data); + if (rc) + break; + } + } + +/* DBG(LINE, ul_debugobj(ln, "<- walk line done [rc=%d]", rc)); */ + return rc; +} + +/* last line in the tree? */ +int scols_walk_is_last(struct libscols_table *tb, struct libscols_line *ln) +{ + if (tb->walk_last_done == 0) + return 0; + if (tb->ngrpchlds_pending > 0) + return 0; + if (has_children(ln)) + return 0; + if (is_tree_root(ln) && !is_last_tree_root(tb, ln)) + return 0; + if (is_group_member(ln) && (!is_last_group_member(ln) || has_group_children(ln))) + return 0; + if (is_child(ln)) { + struct libscols_line *parent = ln->parent; + + if (!is_last_child(ln)) + return 0; + while (parent) { + if (is_child(parent) && !is_last_child(parent)) + return 0; + if (!parent->parent) + break; + parent = parent->parent; + } + if (is_tree_root(parent) && !is_last_tree_root(tb, parent)) + return 0; + } + if (is_group_child(ln) && !is_last_group_child(ln)) + return 0; + + DBG(LINE, ul_debugobj(ln, "last in table")); + return 1; +} + +int scols_walk_tree(struct libscols_table *tb, + struct libscols_column *cl, + int (*callback)(struct libscols_table *, + struct libscols_line *, + struct libscols_column *, + void *), + void *data) +{ + int rc = 0; + struct libscols_line *ln; + struct libscols_iter itr; + + assert(tb); +/* DBG(TAB, ul_debugobj(tb, ">> walk start"));*/ + + /* init */ + tb->ngrpchlds_pending = 0; + tb->walk_last_tree_root = NULL; + tb->walk_last_done = 0; + + if (has_groups(tb)) + scols_groups_reset_state(tb); + + /* set pointer to last tree root */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (scols_table_next_line(tb, &itr, &ln) == 0) { + if (!tb->walk_last_tree_root) + tb->walk_last_tree_root = ln; + if (is_child(ln) || is_group_child(ln)) + continue; + tb->walk_last_tree_root = ln; + } + + /* walk */ + scols_reset_iter(&itr, SCOLS_ITER_FORWARD); + while (rc == 0 && scols_table_next_line(tb, &itr, &ln) == 0) { + if (ln->parent || ln->parent_group) + continue; + + if (tb->walk_last_tree_root == ln) + tb->walk_last_done = 1; + rc = walk_line(tb, ln, cl, callback, data); + + /* walk group's children */ + while (rc == 0 && tb->ngrpchlds_pending) { + struct libscols_group *gr = scols_grpset_get_printable_children(tb); + struct list_head *p; + + DBG(LINE, ul_debugobj(ln, " walk group children [pending=%zu]", tb->ngrpchlds_pending)); + if (!gr) { + DBG(LINE, ul_debugobj(ln, " *** ngrpchlds_pending counter invalid")); + tb->ngrpchlds_pending = 0; + break; + } + + tb->ngrpchlds_pending--; + + list_for_each(p, &gr->gr_children) { + struct libscols_line *chld = + list_entry(p, struct libscols_line, ln_children); + + rc = walk_line(tb, chld, cl, callback, data); + if (rc) + break; + } + } + } + + tb->ngrpchlds_pending = 0; + tb->walk_last_done = 0; +/* DBG(TAB, ul_debugobj(tb, "<< walk end [rc=%d]", rc));*/ + return rc; +} |