├── README.md ├── cprint-scm-0.rockspec ├── cprint.c ├── pr_ansi.h ├── pr_ansi.rh └── test.lua /README.md: -------------------------------------------------------------------------------- 1 | # cprint # 2 | 3 | Improved print function that handles ANSI escapes even on Windows 4 | 5 | 6 | ## Usage ## 7 | 8 | The function returned by this Lua module is compatible with the 9 | default Lua `print` function and can serve as a replacement for it. 10 | 11 | print = require( "cprint" ) 12 | print( "hello world" ) 13 | 14 | Additionally, the following ANSI color escape sequences are supported 15 | on both Windows and POSIX platforms: 16 | 17 | * `"\027[30m"` to `"\027[37m"`, and `"\027[39m"` 18 | 19 | Those escape sequences set the foreground color to black, red, 20 | green, yellow, blue, magenta/purple, cyan/aqua, white, and the 21 | default foreground color, respectively. 22 | 23 | * `"\027[40m"` to `"\027[47m"`, and `"\027[49m"` 24 | 25 | Those escape sequences set the background color to black, red, 26 | green, yellow, blue, magenta/purple, cyan/aqua, white, and the 27 | default background color, respectively. 28 | 29 | * `"\027[0m"`, `"\027[1m"`, and `"\027[7m"` 30 | 31 | Those escape sequences represent normal mode, bright/highlighted 32 | mode, and inverted mode. 33 | 34 | The escape sequences can be combined by separating the numbers with 35 | `;`, e.g.: 36 | 37 | print( "\027[31;1mbright red\027[39;49;0m" ) 38 | 39 | If the standard output stream is not connected to a terminal/console, 40 | or if the terminal is not recognized as compatible, the escape 41 | sequences are stripped from the output. 42 | 43 | 44 | ## License ## 45 | 46 | cprint is *copyrighted free software* distributed under the MIT 47 | license (the same license as Lua 5.1). The full license text follows: 48 | 49 | cprint (c) 2013 Philipp Janda 50 | 51 | Permission is hereby granted, free of charge, to any person obtaining 52 | a copy of this software and associated documentation files (the 53 | "Software"), to deal in the Software without restriction, including 54 | without limitation the rights to use, copy, modify, merge, publish, 55 | distribute, sublicense, and/or sell copies of the Software, and to 56 | permit persons to whom the Software is furnished to do so, subject to 57 | the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be 60 | included in all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 63 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 64 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 65 | IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY 66 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 67 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 68 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 69 | 70 | The actual code implementing the `print` function is taken almost 71 | verbatim from the [Lua][1] source code, which is 72 | 73 | Copyright (C) 1994-2013 Lua.org, PUC-Rio. 74 | 75 | [1]: http://www.lua.org/ 76 | 77 | -------------------------------------------------------------------------------- /cprint-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | package="cprint" 2 | version="scm-0" 3 | source = { 4 | url = "git://github.com/siffiejoe/lua-cprint.git", 5 | } 6 | description = { 7 | summary = "An improved print function", 8 | detailed = [[ 9 | An improved print function that can handle simple 10 | ANSI color escape sequences. 11 | ]], 12 | homepage = "https://github.com/siffiejoe/lua-cprint/", 13 | license = "MIT" 14 | } 15 | dependencies = { 16 | "lua >= 5.1" 17 | } 18 | build = { 19 | type = "builtin", 20 | modules = { 21 | cprint = "cprint.c", 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /cprint.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "lua.h" 5 | #include "lauxlib.h" 6 | 7 | #ifndef CPRINT_STREAM 8 | # define CPRINT_STREAM stdout 9 | #endif 10 | 11 | #ifndef LUA_QL 12 | # define LUA_QL(s) "'" s "'" 13 | #endif 14 | 15 | #define w( s, n ) (fwrite( (s), sizeof( char ), (n), CPRINT_STREAM )) 16 | #define tab() (putc( '\t', CPRINT_STREAM )) 17 | #define nl() (putc( '\n', CPRINT_STREAM ), fflush( CPRINT_STREAM )) 18 | 19 | 20 | /* Try to figure out if we are a posix or windows environment */ 21 | #if defined( _WIN32 ) || defined( _WIN64 ) || defined( __WIN32__ ) || \ 22 | defined( __TOS_WIN__ ) || defined( __WINDOWS__ ) 23 | 24 | /* windows */ 25 | #define WIN32_LEAN_AND_MEAN 26 | #include 27 | #include 28 | 29 | static int color_mapping[] = { 30 | 0, /* black */ 31 | 4, /* red */ 32 | 2, /* green */ 33 | 6, /* yellow */ 34 | 1, /* blue */ 35 | 5, /* magenta/purple */ 36 | 3, /* cyan/aqua */ 37 | 7, /* white */ 38 | 0, 0 /* unused */ 39 | }; 40 | 41 | typedef struct { 42 | int enabled; 43 | HANDLE hConsole; 44 | int last_fg; 45 | int last_bg; 46 | int last_md; 47 | } cprint_info; 48 | 49 | static void cprint_init( cprint_info* info ) { 50 | int fd = _fileno( CPRINT_STREAM ); 51 | info->last_fg = 7; 52 | info->last_bg = 0; 53 | info->last_md = 0; 54 | info->enabled = _isatty( fd ); 55 | info->hConsole = (HANDLE)_get_osfhandle( fd ); 56 | if( info->hConsole == INVALID_HANDLE_VALUE ) 57 | info->enabled = 0; 58 | } 59 | 60 | static void cprint_doansi( cprint_info* info, 61 | char const* esc, size_t n ) { 62 | if( info->enabled ) { 63 | char const* p = esc; 64 | int high, inverse; 65 | p += 2; /* skip ESC and [ */ 66 | while( *p != 'm' ) { 67 | if( *p == '3' && '0' <= p[1] && p[1] <= '9' ) { 68 | int fg = p[1] - '0'; 69 | if( fg > 7 ) 70 | fg = 7; /* default foreground */ 71 | info->last_fg = color_mapping[ fg ]; 72 | p += 2; 73 | } else if( *p == '4' && '0' <= p[1] && p[1] <= '9' ) { 74 | int bg = p[1] - '0'; 75 | if( bg > 7 ) 76 | bg = 0; /* default background */ 77 | info->last_bg = color_mapping[ bg ]; 78 | p += 2; 79 | } else if( *p != ';' ) { 80 | switch( *p ) { 81 | case '0': 82 | if( p == esc+2 ) { 83 | info->last_fg = 7; 84 | info->last_bg = 0; 85 | } 86 | info->last_md = 0; 87 | break; 88 | case '1': 89 | info->last_md = 1; 90 | break; 91 | case '7': 92 | info->last_md = 7; 93 | break; 94 | default: 95 | info->last_md = 0; 96 | break; 97 | } 98 | p += 1; 99 | } else 100 | p += 1; 101 | } 102 | high = info->last_md == 1; 103 | inverse = info->last_md == 7; 104 | SetConsoleTextAttribute( info->hConsole, 105 | info->last_fg*(1+inverse*15)+high*8 + 106 | info->last_bg*(1+(!inverse)*15) ); 107 | } 108 | } 109 | 110 | #elif defined( unix ) || defined( __unix ) || defined( __unix__ ) || \ 111 | defined( __TOS_AIX__ ) || defined( _SYSTYPE_BSD ) 112 | 113 | /* some form of unix */ 114 | #include 115 | #include 116 | 117 | typedef struct { 118 | int enabled; 119 | } cprint_info; 120 | 121 | /* defined in pr_ansi.h */ 122 | static int terminal_supported( char const* term ); 123 | 124 | static void cprint_init( cprint_info* info ) { 125 | char const* term = getenv( "TERM" ); 126 | info->enabled = term != NULL && terminal_supported( term ) && 127 | isatty( fileno( CPRINT_STREAM ) ); 128 | } 129 | 130 | static void cprint_doansi( cprint_info* info, 131 | char const* esc, size_t n ) { 132 | if( info->enabled ) 133 | w( esc, n ); 134 | } 135 | 136 | #else 137 | 138 | /* unknown/unsupported OS */ 139 | typedef struct { 140 | int dummy; 141 | } cprint_info; 142 | 143 | static void cprint_init( cprint_info* info ) { 144 | info->dummy = 0; 145 | } 146 | 147 | static void cprint_doansi( cprint_info* info, 148 | char const* esc, size_t n ) { 149 | (void)info; 150 | (void)esc; 151 | (void)n; 152 | } 153 | 154 | #endif 155 | 156 | 157 | #include "pr_ansi.h" 158 | 159 | static int cprint( lua_State* L ) { 160 | cprint_info* info = lua_touserdata( L, lua_upvalueindex( 1 ) ); 161 | int top = lua_gettop( L ); 162 | int i = 0; 163 | lua_getglobal( L, "tostring" ); 164 | for( i = 1; i <= top; ++i ) { 165 | size_t n = 0; 166 | char const* s = NULL; 167 | lua_pushvalue( L, -1 ); 168 | lua_pushvalue( L, i ); 169 | lua_call( L, 1, 1 ); 170 | s = lua_tolstring( L, -1, &n ); 171 | if( s == NULL ) 172 | return luaL_error( L, LUA_QL("tostring") 173 | " must return a string to " 174 | LUA_QL("print") ); 175 | if( i > 1 ) 176 | tab(); 177 | write_ansi( info, s, n ); 178 | lua_pop( L, 1 ); 179 | } 180 | nl(); 181 | return 0; 182 | } 183 | 184 | 185 | #ifndef CPRINT_API 186 | # define CPRINT_API 187 | #endif 188 | 189 | CPRINT_API int luaopen_cprint( lua_State* L ) { 190 | cprint_info* info = lua_newuserdata( L, sizeof( cprint_info ) ); 191 | cprint_init( info ); 192 | lua_pushcclosure( L, cprint, 1 ); 193 | return 1; 194 | } 195 | 196 | -------------------------------------------------------------------------------- /pr_ansi.h: -------------------------------------------------------------------------------- 1 | 2 | #line 1 "pr_ansi.rh" 3 | /* 4 | * Compile (to pr_ansi.h): 5 | * $ ragel -C -G2 pr_ansi.rh 6 | * For graphviz output: 7 | * $ ragel -V -p pr_ansi.rh | dot -T png -o out.png 8 | */ 9 | 10 | 11 | 12 | #line 20 "pr_ansi.rh" 13 | 14 | 15 | 16 | static int terminal_supported( char const* p ) { 17 | int res = 0; 18 | int cs = 0; 19 | 20 | #line 21 "pr_ansi.h" 21 | static const int term_support_start = 1; 22 | 23 | static const int term_support_en_main = 1; 24 | 25 | 26 | #line 27 "pr_ansi.h" 27 | { 28 | cs = term_support_start; 29 | } 30 | 31 | #line 32 "pr_ansi.h" 32 | { 33 | switch ( cs ) 34 | { 35 | case 1: 36 | switch( (*p) ) { 37 | case 69: goto st2; 38 | case 97: goto st6; 39 | case 108: goto st10; 40 | case 112: goto st14; 41 | case 114: goto st16; 42 | case 115: goto st19; 43 | case 120: goto st2; 44 | } 45 | goto st0; 46 | st0: 47 | cs = 0; 48 | goto _out; 49 | st2: 50 | p += 1; 51 | case 2: 52 | if ( (*p) == 116 ) 53 | goto st3; 54 | goto st0; 55 | st3: 56 | p += 1; 57 | case 3: 58 | if ( (*p) == 101 ) 59 | goto st4; 60 | goto st0; 61 | st4: 62 | p += 1; 63 | case 4: 64 | if ( (*p) == 114 ) 65 | goto st5; 66 | goto st0; 67 | st5: 68 | p += 1; 69 | case 5: 70 | if ( (*p) == 109 ) 71 | goto tr10; 72 | goto st0; 73 | tr10: 74 | #line 19 "pr_ansi.rh" 75 | { res = 1; {p++; cs = 24; goto _out;} } 76 | goto st24; 77 | st24: 78 | p += 1; 79 | case 24: 80 | #line 81 "pr_ansi.h" 81 | goto st0; 82 | st6: 83 | p += 1; 84 | case 6: 85 | if ( (*p) == 110 ) 86 | goto st7; 87 | goto st0; 88 | st7: 89 | p += 1; 90 | case 7: 91 | if ( (*p) == 115 ) 92 | goto st8; 93 | goto st0; 94 | st8: 95 | p += 1; 96 | case 8: 97 | if ( (*p) == 105 ) 98 | goto st9; 99 | goto st0; 100 | st9: 101 | p += 1; 102 | case 9: 103 | if ( (*p) == 0 ) 104 | goto tr10; 105 | goto st0; 106 | st10: 107 | p += 1; 108 | case 10: 109 | if ( (*p) == 105 ) 110 | goto st11; 111 | goto st0; 112 | st11: 113 | p += 1; 114 | case 11: 115 | if ( (*p) == 110 ) 116 | goto st12; 117 | goto st0; 118 | st12: 119 | p += 1; 120 | case 12: 121 | if ( (*p) == 117 ) 122 | goto st13; 123 | goto st0; 124 | st13: 125 | p += 1; 126 | case 13: 127 | if ( (*p) == 120 ) 128 | goto st9; 129 | goto st0; 130 | st14: 131 | p += 1; 132 | case 14: 133 | if ( (*p) == 99 ) 134 | goto st15; 135 | goto st0; 136 | st15: 137 | p += 1; 138 | case 15: 139 | if ( (*p) == 97 ) 140 | goto st6; 141 | goto st0; 142 | st16: 143 | p += 1; 144 | case 16: 145 | if ( (*p) == 120 ) 146 | goto st17; 147 | goto st0; 148 | st17: 149 | p += 1; 150 | case 17: 151 | if ( (*p) == 118 ) 152 | goto st18; 153 | goto st0; 154 | st18: 155 | p += 1; 156 | case 18: 157 | if ( (*p) == 116 ) 158 | goto tr10; 159 | goto st0; 160 | st19: 161 | p += 1; 162 | case 19: 163 | if ( (*p) == 99 ) 164 | goto st20; 165 | goto st0; 166 | st20: 167 | p += 1; 168 | case 20: 169 | if ( (*p) == 114 ) 170 | goto st21; 171 | goto st0; 172 | st21: 173 | p += 1; 174 | case 21: 175 | if ( (*p) == 101 ) 176 | goto st22; 177 | goto st0; 178 | st22: 179 | p += 1; 180 | case 22: 181 | if ( (*p) == 101 ) 182 | goto st23; 183 | goto st0; 184 | st23: 185 | p += 1; 186 | case 23: 187 | if ( (*p) == 110 ) 188 | goto tr10; 189 | goto st0; 190 | } 191 | 192 | _out: {} 193 | } 194 | 195 | #line 31 "pr_ansi.rh" 196 | 197 | (void)term_support_en_main; 198 | return res; 199 | } 200 | 201 | 202 | 203 | #line 54 "pr_ansi.rh" 204 | 205 | 206 | static void write_ansi( cprint_info* info, char const* p, size_t n ) { 207 | int cs = 0; 208 | char const* pe = p + n; 209 | char const* eof = pe; 210 | char const* prev = p; 211 | 212 | #line 213 "pr_ansi.h" 213 | static const int ansi_escape_start = 0; 214 | 215 | static const int ansi_escape_en_main = 0; 216 | 217 | 218 | #line 219 "pr_ansi.h" 219 | { 220 | cs = ansi_escape_start; 221 | } 222 | 223 | #line 224 "pr_ansi.h" 224 | { 225 | if ( p == pe ) 226 | goto _test_eof; 227 | switch ( cs ) 228 | { 229 | tr5: 230 | #line 48 "pr_ansi.rh" 231 | { 232 | cprint_doansi( info, prev, p+1-prev ); 233 | prev = p+1; 234 | } 235 | goto st0; 236 | st0: 237 | if ( ++p == pe ) 238 | goto _test_eof0; 239 | case 0: 240 | #line 241 "pr_ansi.h" 241 | if ( (*p) == 27 ) 242 | goto tr1; 243 | goto st0; 244 | tr1: 245 | #line 43 "pr_ansi.rh" 246 | { 247 | if( p > prev ) 248 | w( prev, p-prev ); 249 | prev = p; 250 | } 251 | goto st1; 252 | st1: 253 | if ( ++p == pe ) 254 | goto _test_eof1; 255 | case 1: 256 | #line 257 "pr_ansi.h" 257 | switch( (*p) ) { 258 | case 27: goto tr1; 259 | case 91: goto st2; 260 | } 261 | goto st0; 262 | st2: 263 | if ( ++p == pe ) 264 | goto _test_eof2; 265 | case 2: 266 | if ( (*p) == 27 ) 267 | goto tr1; 268 | if ( (*p) < 51 ) { 269 | if ( 48 <= (*p) && (*p) <= 50 ) 270 | goto st3; 271 | } else if ( (*p) > 52 ) { 272 | if ( 53 <= (*p) && (*p) <= 57 ) 273 | goto st3; 274 | } else 275 | goto st4; 276 | goto st0; 277 | st3: 278 | if ( ++p == pe ) 279 | goto _test_eof3; 280 | case 3: 281 | switch( (*p) ) { 282 | case 27: goto tr1; 283 | case 59: goto st2; 284 | case 109: goto tr5; 285 | } 286 | goto st0; 287 | st4: 288 | if ( ++p == pe ) 289 | goto _test_eof4; 290 | case 4: 291 | switch( (*p) ) { 292 | case 27: goto tr1; 293 | case 59: goto st2; 294 | case 109: goto tr5; 295 | } 296 | if ( 48 <= (*p) && (*p) <= 57 ) 297 | goto st3; 298 | goto st0; 299 | } 300 | _test_eof0: cs = 0; goto _test_eof; 301 | _test_eof1: cs = 1; goto _test_eof; 302 | _test_eof2: cs = 2; goto _test_eof; 303 | _test_eof3: cs = 3; goto _test_eof; 304 | _test_eof4: cs = 4; goto _test_eof; 305 | 306 | _test_eof: {} 307 | if ( p == eof ) 308 | { 309 | switch ( cs ) { 310 | case 0: 311 | case 1: 312 | case 2: 313 | case 3: 314 | case 4: 315 | #line 43 "pr_ansi.rh" 316 | { 317 | if( p > prev ) 318 | w( prev, p-prev ); 319 | prev = p; 320 | } 321 | break; 322 | #line 323 "pr_ansi.h" 323 | } 324 | } 325 | 326 | } 327 | 328 | #line 66 "pr_ansi.rh" 329 | 330 | (void)ansi_escape_en_main; 331 | } 332 | 333 | -------------------------------------------------------------------------------- /pr_ansi.rh: -------------------------------------------------------------------------------- 1 | /* 2 | * Compile (to pr_ansi.h): 3 | * $ ragel -C -G2 pr_ansi.rh 4 | * For graphviz output: 5 | * $ ragel -V -p pr_ansi.rh | dot -T png -o out.png 6 | */ 7 | 8 | 9 | %%{ 10 | machine term_support; 11 | main := ( 12 | "xterm" | 13 | "rxvt" | 14 | "screen" | 15 | "Eterm" | 16 | "ansi" 0 | 17 | "pcansi" 0 | 18 | "linux" 0 19 | ) @{ res = 1; fbreak; }; 20 | }%% 21 | 22 | 23 | static int terminal_supported( char const* p ) { 24 | int res = 0; 25 | int cs = 0; 26 | %%{ 27 | machine term_support; 28 | write data nofinal noerror; 29 | write init; 30 | write exec noend; 31 | }%% 32 | (void)term_support_en_main; 33 | return res; 34 | } 35 | 36 | 37 | %%{ 38 | machine ansi_escape; 39 | fg = "3" digit; 40 | bg = "4" digit; 41 | md = digit; 42 | opt = fg | bg | md; 43 | action flush { 44 | if( p > prev ) 45 | w( prev, p-prev ); 46 | prev = p; 47 | } 48 | action ansi { 49 | cprint_doansi( info, prev, p+1-prev ); 50 | prev = p+1; 51 | } 52 | esc = 27 >flush "[" opt ( ";" opt )* "m" @ansi; 53 | main := (any | esc)* %/flush ; 54 | }%% 55 | 56 | static void write_ansi( cprint_info* info, char const* p, size_t n ) { 57 | int cs = 0; 58 | char const* pe = p + n; 59 | char const* eof = pe; 60 | char const* prev = p; 61 | %%{ 62 | machine ansi_escape; 63 | write data nofinal noerror; 64 | write init; 65 | write exec; 66 | }%% 67 | (void)ansi_escape_en_main; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | local cprint = require( "cprint" ) 4 | 5 | cprint( "hello world!" ) 6 | cprint( "hello", "world!" ) 7 | cprint( "hello\027 world!" ) 8 | cprint( "hello\027[31 world!" ) 9 | cprint( "hello\027[31m world!" ) 10 | cprint( "hello\027[0m world!" ) 11 | cprint( "hello \027[31;44;1mworld\027[39;49;0m bla!" ) 12 | 13 | for i = 0, 7 do 14 | cprint( "\027[3"..i.."mwhoa!\027[1m highlighted!\027[0m" ) 15 | end 16 | 17 | --[[ 18 | for i = 0, 7 do 19 | for j = 0, 7 do 20 | for _,m in ipairs{ 0, 1, 7 } do 21 | cprint( "Wait for it: \027[3"..j..";4"..i..";"..m.. 22 | "mCOLOR\027[0m. cool, isn't it?" ) 23 | end 24 | end 25 | end 26 | --]] 27 | 28 | 29 | --------------------------------------------------------------------------------