├── .gitignore
├── .gitmodules
├── LICENSE
├── Makefile
├── README.md
├── build_insert_statement.h
├── getword.h
├── getwords.h
├── getwordsregex.h
├── options.h
├── pcre_split.c
├── pcre_split.h
├── process_line.h
├── sqlbong.c
├── sqlbong.cfdg
├── sqlbong.png
├── test.csv
└── test.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | sqlbong
2 | sqlbong.dSYM/
3 | sqlpipe
4 | sqlpipe.dSYM/
5 | *.o
6 | *.h.gch
7 | *.db
8 | usage.h
9 | *.zip
10 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "pcre_split"]
2 | path = pcre_split
3 | url = https://github.com/sordina/pcre_split.git
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Lyndon Maydwell
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CLANG_OPTS = -I /usr/local/include -L /usr/local/lib
2 | Compiler = clang
3 | Options = -lm -Wall pcre_split.c sqlbong.c -lpcre -lsqlite3 -o sqlbong
4 |
5 | # Targets
6 |
7 | sqlbong: pcre_split/src usage.h *.c *.h
8 | ${Compiler} ${CLANG_OPTS} ${Options}
9 |
10 | .PHONY: install
11 | install: sqlbong
12 | cp sqlbong /usr/local/bin/
13 |
14 | .PHONY: uninstall
15 | uninstall:
16 | rm /usr/local/bin/sqlbong
17 |
18 | .PHONY: debug
19 | debug: clean usage.h
20 | ${Compiler} ${CLANG_OPTS} -ggdb -DDEBUG=1 ${Options}
21 |
22 | .PHONY: clean
23 | clean:
24 | rm -rf sqlbong *.o sql*SYM/ usage.h
25 |
26 | .PHONY: test
27 | test: sqlbong runtests
28 |
29 | .PHONY: testdebug
30 | testdebug: debug runtests
31 |
32 | pcre_split/src:
33 | git submodule update --init --recursive
34 |
35 | .PHONY: upload
36 | upload: sqlbong
37 | $(eval zipfile := $(shell echo "sqlbong-0.1.0-`git rev-parse --short HEAD`-`sw_vers -productName | sed 's/ //g'`-`sw_vers -productVersion`-`sw_vers -buildVersion`.zip"))
38 | zip $(zipfile) sqlbong README.md
39 | s3cmd put --acl-public $(zipfile) s3://sordina.binaries/$(zipfile)
40 | rm $(zipfile)
41 |
42 | usage.h:
43 | # Convert README.md to a printing function
44 | echo "#ifndef USAGEH" > usage.h
45 | echo "#define USAGEH" >> usage.h
46 | echo "void usage() {" >> usage.h
47 | cat README.md | sed 's/["%\\]/\\&/g;s/^/printf("/;s/$$/\\n");/' | grep -v '
> usage.h
48 | echo "}" >> usage.h
49 | echo "#endif" >> usage.h
50 |
51 | .PHONY: runtests
52 | runtests:
53 | # Multi column data
54 | (echo 111 222 333; echo 444 555 666; echo 777 888 999) | ./sqlbong "select c1, c2 from data"
55 | # Increasing columns
56 | (echo 1; echo 1 2; echo 1 2 3; echo 1 2 3 4) | ./sqlbong "select c1, c2, c4 from data"
57 | # Decreasing columns
58 | (echo 1; echo 1 2; echo 1 2 3; echo 1 2; echo 1) | ./sqlbong "select c1, c3, c2 from data"
59 | # Blank lines
60 | (echo 1 3 2 4; echo; echo 1 2 3) | ./sqlbong "select c2 from data"
61 | # Long words
62 | (echo ksjdhfklsjdhfksjhdfkjshdl; \
63 | echo abdominogenital; \
64 | echo skfdjlhasldkfhlaskdjfhlaksdjhflkjahs; \
65 | echo foooooooooooofoo fooooof fooooooooooooo) | ./sqlbong "select c1, c2 from data"
66 | # Not sure why it's throwing an error, but:
67 | echo "95585 ttys002 0:03.11 vim sqlbong.c" | ./sqlbong "select * from data"
68 | # Lots of words
69 | echo a b c d e f g h i j k l m n o p q r s t u v w x y | ./sqlbong "select * from data"
70 | echo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ./sqlbong "select c15 from data" | grep 15
71 | (echo 1_3_2_4; echo 5___6__7_8) | ./sqlbong -d '_+' "select c1,c2 from data"
72 | ./sqlbong -d , 'select * from data' < test.csv > /dev/null
73 | (echo 1 2 3; echo 1 2) | ./sqlbong -d ' ' 'select * from data' | wc -l | grep 2
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | SQLBong
3 | =======
4 |
5 |
6 | Parse columnized output with SQL - Thanks to the power of [SQLite3](http://www.sqlite.org/).
7 |
8 | The SQL syntax accepted is completely determined by what SQLite3 supports.
9 |
10 | Why the name? Because `SQLPipe` was taken.
11 |
12 |
13 |
14 | ## Alternatives:
15 |
16 | *
17 | *
18 | *
19 | *
20 | *
21 | *
22 | *
23 | *
24 |
25 | ## Examples:
26 |
27 | > ps | sqlbong "select * from data"
28 |
29 | Acts like cat, but normalises the delimiters to one space - Passthrough.
30 |
31 | > cat data.txt | sqlbong -f foo.db
32 |
33 | Send columnized data from data.txt into an sqlite database foo.db
34 |
35 | Server> nc -l 1234 | sqlbong -f out.db
36 |
37 | Client> nc 127.0.0.1 1234
38 | hello world
39 | how are
40 | you ?
41 |
42 | Server> sqlite3 out.db ".dump"
43 | PRAGMA foreign_keys=OFF;
44 | BEGIN TRANSACTION;
45 | CREATE TABLE data (c1, c2);
46 | INSERT INTO "data" VALUES('hello','world');
47 | INSERT INTO "data" VALUES('how','are');
48 | INSERT INTO "data" VALUES('you','?');
49 | COMMIT;
50 |
51 | Log output to a database on a remote server.
52 |
53 | > cat test.txt | sqlbong "select c2 from data limit 2" "select c1, c3, c2 from data where c2 > '4' order by c3 desc"
54 |
55 | ### test.txt:
56 |
57 | 1 2 3
58 | 2 3 4
59 | 3 4 5
60 | 4 5 6
61 | 5 6 7
62 | 6 7 8
63 |
64 | ### Output:
65 |
66 | 2
67 | 3
68 | 6 8 7
69 | 5 7 6
70 | 4 6 5
71 |
72 | Details:
73 | --------
74 |
75 | * The table used is called 'data'
76 | * There are as many columns in 'data' as the maximum number of columns in the input data
77 | * The columns are labled (c1 .. c\)
78 | * Data is stored as 'text' type (useful to remember for comparisons, numeric operations)
79 |
80 | Known Bugs
81 | ----------
82 |
83 | * Not all memory is freed correctly
84 |
85 | To Do
86 | -----
87 |
88 | * Add options for reusing a database
89 | * Check out for faster table creation
90 |
91 | Usage
92 | -----
93 |
94 | Accepts data on STDIN, takes SQL statements as arguments (make sure you quote them) and outputs data selected on STDOUT.
95 |
96 | Usage:
97 |
98 | sqlbong [-h] [-f ] [-d ] *
99 |
100 | Options:
101 |
102 | -h Help
103 | -d Regex delimiter
104 | -f On disk database (defaults to in-memory)
105 |
106 | SQL:
107 |
108 | Columns - c1..cn
109 | Table - data
110 |
111 |
112 | Binaries
113 | --------
114 |
115 | *
116 | *
117 | *
118 | *
119 |
--------------------------------------------------------------------------------
/build_insert_statement.h:
--------------------------------------------------------------------------------
1 | #ifndef BUILD_INSERTH
2 | #define BUILD_INSERTH
3 |
4 | #include
5 | #include
6 |
7 | char* insert(int num) {
8 |
9 | #ifdef DEBUG
10 | printf("Inside Builder - Number of items: [%d].\n", num);
11 | #endif
12 |
13 | // TODO: This is quite fragile be careful if modifying
14 | const char dstmt[] = "insert into data()";
15 | const char vstmt[] = " values()";
16 | const int dlen = strlen(dstmt);
17 | const int vlen = strlen(vstmt);
18 | const int boiler_len = dlen + vlen;
19 | const int dyn_factor = 1 + (int) log10(num);
20 | const int dyn_len = dyn_factor * num; // represents the size of the column-number text (minus formatting) (overshoots estimate by log(n) factor)
21 | char* dyn_part = malloc(sizeof(char) * (dyn_factor + 3)); // constant is 'c' ',' '\0'
22 | char* result = malloc(sizeof(char) * (boiler_len + (4 * num) + dyn_len) + 2);
23 | char* p = result; // movable pointer for printing
24 |
25 | memcpy(p, dstmt, dlen * sizeof(char));
26 | p += dlen - 1;
27 |
28 | int i;
29 | for(i = 1; i <= num; i++) {
30 | sprintf(dyn_part, "c%d,", i);
31 | sprintf(p, "%s", dyn_part);
32 |
33 | p += ((int) log10(i)) + 1 + 2; // constant is the length of 'c' ',' the first digit, and one more to advance to next position (where '\0' is)
34 | }
35 | p[-1] = ')';
36 |
37 | memcpy(p, vstmt, vlen * sizeof(char));
38 | p += vlen - 1;
39 |
40 | for(i = 0; i < num; i++) {
41 | *p = '?'; p++;
42 | *p = ','; p++;
43 | }
44 |
45 | p[-1] = ')';
46 | p[ 0] = '\0';
47 | p[ 1] = '\0';
48 | p[ 2] = '\0';
49 | // TODO: Check UTF8 fix
50 |
51 | #ifdef DEBUG
52 | printf("Inside build_insert_statement - Created result [%s].\n", result);
53 | #endif
54 |
55 | free(dyn_part);
56 |
57 | return result;
58 | }
59 |
60 | #endif
61 |
--------------------------------------------------------------------------------
/getword.h:
--------------------------------------------------------------------------------
1 | #ifndef GETWORDH
2 | #define GETWORDH 1
3 |
4 | #include
5 | #include
6 |
7 | char* getword(char delim, char** line) {
8 |
9 | // Skip leading delimiters
10 | while(**line == delim) {
11 | #ifdef DEBUG
12 | printf("Inside getword - removing leading delimiter.\n");
13 | #endif
14 | (*line)++;
15 | }
16 |
17 | char* word_start = *line;
18 | char* word_end = *line - 1;
19 |
20 | // Is this an empty line?
21 | if((!line) || **line == EOF || **line == '\n') { return NULL; }
22 |
23 | // Proceed to the end of the word
24 | while (**line && **line != EOF && **line != '\n' && **line != delim) {
25 | word_end = *line;
26 | (*line)++;
27 | }
28 |
29 | int word_length = 1 + word_end - word_start;
30 |
31 | #if DEBUG
32 | printf("Inside getword - word length: [%d] characters.\n", word_length);
33 | #endif
34 |
35 | if(word_length < 1) {
36 | #ifdef DEBUG
37 | printf("Inside getword - Blank word.\n");
38 | #endif
39 | return NULL;
40 | }
41 |
42 | char* result = malloc(sizeof(char) * (word_length + 1));
43 |
44 | memcpy(result, word_start, word_length);
45 |
46 | result[word_length] = '\0';
47 |
48 | return result;
49 | }
50 |
51 | #endif
52 |
--------------------------------------------------------------------------------
/getwords.h:
--------------------------------------------------------------------------------
1 | #ifndef GETWORDSH
2 | #define GETWORDSH 1
3 |
4 | #include "getword.h"
5 |
6 | char** getwords(char* line, int* numwords) { // TODO: Take a size param to make this safe
7 |
8 | char* word;
9 | int cap = 2; // Start with 2 word limit
10 | char** result = malloc(cap * sizeof(char*));
11 |
12 | *numwords = 0;
13 |
14 | while((word = getword(' ', &line))) {
15 |
16 | #if DEBUG
17 | printf("Inside getwords - Got word: [%s], rest of line: [%s]\n", word, line);
18 | #endif
19 |
20 | if(*numwords >= cap / 2) {
21 | cap = (cap + 2) * 2; // TODO: Why does this need to be cap + 2 ?
22 | result = realloc(result, cap * sizeof(char*));
23 | #ifdef DEBUG
24 | printf("Inside getwords - Reallocated.\n");
25 | #endif
26 | }
27 |
28 | #ifdef DEBUG
29 | printf("Inside getwords - Numwords: %d, Cap: %d\n", *numwords, cap);
30 | #endif
31 |
32 | result[*numwords] = word;
33 |
34 | #ifdef DEBUG
35 | printf("Inside getwords - Assigned word to list.\n");
36 | #endif
37 |
38 | (*numwords)++;
39 | }
40 |
41 | if(0 == *numwords) {
42 | #ifdef DEBUG
43 | printf("Inside getwords - No words\n");
44 | #endif
45 | free(result);
46 | return NULL;
47 | }
48 |
49 | #ifdef DEBUG
50 | printf("Inside getwords - Finished getting words: %d\n", *numwords);
51 | #endif
52 |
53 | return result;
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/getwordsregex.h:
--------------------------------------------------------------------------------
1 | #ifndef GETWORDSREGEX
2 | #define GETWORDSREGEX
3 |
4 | #include "pcre_split/inc/pcre_split.h"
5 |
6 | char** getwordsregex(char* delimiter, char* line, int* numwords) {
7 | split_t* tokens = pcre_split(delimiter, line);
8 | split_t* start = tokens;
9 |
10 | #ifdef DEBUG
11 | pcre_split_print(tokens);
12 | #endif
13 |
14 | *numwords = 0;
15 |
16 | while(start) {
17 | (*numwords)++;
18 | start = start->next;
19 | }
20 |
21 | start = tokens;
22 |
23 | char** result = malloc(sizeof(char*) * (*numwords));
24 | char** resultp = result;
25 |
26 | while(start) {
27 | *resultp = start->string;
28 | resultp++;
29 | start = start->next;
30 | }
31 |
32 | return result;
33 | }
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/options.h:
--------------------------------------------------------------------------------
1 | #ifndef OPTIONSH
2 | #define OPTIONSH
3 |
4 | #include
5 | #include
6 | #include "usage.h"
7 |
8 | typedef struct globalArgs_t {
9 | char* file;
10 | int num;
11 | char* delimiter; // TODO: Useful option
12 | // int overwrite; // TODO: Useful option
13 | // char* table_name; // TODO: Useful option
14 | } globalArgs;
15 |
16 | globalArgs getOpts(int argc, char** argv) {
17 |
18 | #ifdef DEBUG
19 | printf("Inside getOpts - Argc [%d].\n", argc);
20 | #endif
21 |
22 | int i;
23 | globalArgs result;
24 |
25 | result.file = NULL;
26 | result.num = 0;
27 | result.delimiter = NULL;
28 | // result.table_name = NULL
29 | // result.overwrite = 1;
30 |
31 | // NOTE: Incrementing i by 2 as all flags take a param
32 | for(i = 0; i < argc; i++) {
33 | #ifdef DEBUG
34 | printf("Inside getOpts - Arg %d - [%s].\n", i, argv[i]);
35 | #endif
36 | if(0 == strcmp("-f", argv[i])) {
37 | result.file = argv[i+1];
38 | result.num += 2;
39 | i++;
40 | } else
41 | if(0 == strcmp("-d", argv[i])) {
42 | result.delimiter = argv[i+1];
43 | result.num += 2;
44 | i++;
45 | } else
46 | if(0 == strcmp("-h",argv[i])) {
47 | usage();
48 | exit(1);
49 | }
50 | }
51 |
52 | return result;
53 | }
54 |
55 | #endif
56 |
--------------------------------------------------------------------------------
/pcre_split.c:
--------------------------------------------------------------------------------
1 | pcre_split/src/pcre_split.c
--------------------------------------------------------------------------------
/pcre_split.h:
--------------------------------------------------------------------------------
1 | pcre_split/inc/pcre_split.h
--------------------------------------------------------------------------------
/process_line.h:
--------------------------------------------------------------------------------
1 | #ifndef PROCESS_LINEH
2 | #define PROCESS_LINEH
3 |
4 | #include
5 | #include
6 |
7 | #include "getwords.h"
8 | #include "build_insert_statement.h"
9 |
10 | // Dangerous and stupid, but we're already doing this stringy stuff, so let's do it anyway!
11 | void remove_newlines(char* line) {
12 | char* p = line;
13 | while(*p) {
14 | if(*p == '\n') {
15 | *p = 0;
16 | break;
17 | }
18 | p++;
19 | }
20 | }
21 |
22 | void process_line(char* line, int* columns, sqlite3* db, globalArgs options) {
23 |
24 | int return_code;
25 | char* zErrMsg;
26 | int numwords;
27 | char command[400];
28 |
29 | remove_newlines(line);
30 |
31 | #ifdef DEBUG
32 | printf("Inside process_line - Line: %s\n", line);
33 | #endif
34 |
35 | char** words;
36 |
37 | if(options.delimiter) {
38 | words = getwordsregex(options.delimiter, line, &numwords);
39 | } else {
40 | words = getwords(line, &numwords);
41 | }
42 |
43 | #ifdef DEBUG
44 | if( numwords == 0 || ! words ) { fprintf(stderr, "No words on this line.\n"); }
45 | #endif
46 |
47 | #ifdef DEBUG
48 | int j;
49 | for(j = 0; j < numwords; j++) {
50 | printf("Inside process_line - Word: [%s]\n", words[j]);
51 | }
52 | #endif
53 |
54 | // Adjust table size if needed
55 | if(numwords > *columns) {
56 |
57 | while(*columns < numwords) {
58 | (*columns)++;
59 | sprintf(command, "alter table data add column c%d", *columns);
60 |
61 | #ifdef DEBUG
62 | printf("Inside process_line - Adding new column with command [%s]\n", command);
63 | #endif
64 |
65 | // TODO: Ensure that this is okay
66 | return_code = sqlite3_exec(db, command, NULL, NULL, &zErrMsg);
67 | HANDLE_ERROR(return_code);
68 | }
69 | }
70 |
71 | if(numwords < 1) {
72 | return_code = sqlite3_exec(db, "insert into data(c1) values(NULL)", NULL, NULL, &zErrMsg);
73 | HANDLE_ERROR(return_code);
74 | return;
75 | }
76 |
77 | char* insert_statement = insert(numwords);
78 |
79 | #ifdef DEBUG
80 | printf("Inside process_line - Created insert statement [%s].\n", insert_statement);
81 | #endif
82 |
83 | sqlite3_stmt *stmt;
84 |
85 | if ( sqlite3_prepare_v2( db, insert_statement, -1, &stmt, 0 ) != SQLITE_OK ) {
86 | fprintf(stderr, "Could not prepare statement [%s]\n.", insert_statement);
87 | exit(1);
88 | }
89 |
90 | #ifdef DEBUG
91 | printf("Prepared insert statement.\n");
92 | #endif
93 |
94 | int i;
95 | for(i = 0; i < numwords; i++) {
96 |
97 | // SQLITE_TRANSIENT allows words to be freed
98 | if ( sqlite3_bind_text( stmt, i+1, words[i], -1 /* length of text */, SQLITE_TRANSIENT ) != SQLITE_OK ) {
99 | fprintf(stderr,"\nCould not bind text [%s].\n", words[i]);
100 | exit(1);
101 | }
102 |
103 | #ifdef DEBUG
104 | printf("Bound word [%s].\n", words[i]);
105 | #endif
106 | // TODO: Need to find a way to free the regex_split struct
107 | //
108 | // The fact that this isn't being freed may cause a memory-leak proportional to the
109 | // number of words bound in the query... Which shouldn't be much.
110 | //
111 | if(! options.delimiter) {
112 | free(words[i]);
113 | }
114 |
115 | }
116 | free(words); // words is no longer used
117 |
118 | #ifdef DEBUG
119 | printf("Freed words.\n");
120 | #endif
121 |
122 | if (sqlite3_step(stmt) != SQLITE_DONE) {
123 | fprintf(stderr,"\nCould not step (execute) stmt.\n");
124 | exit(1);
125 | }
126 |
127 | sqlite3_finalize(stmt);
128 | free(insert_statement);
129 | }
130 |
131 | #endif
132 |
--------------------------------------------------------------------------------
/sqlbong.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #define HANDLE_ERROR(rc) \
5 | if( rc!=SQLITE_OK ){ \
6 | fprintf(stderr, "SQL error: %s\n", zErrMsg); \
7 | sqlite3_free(zErrMsg); \
8 | exit(1); \
9 | }
10 |
11 | #include "getwords.h"
12 | #include "getwordsregex.h"
13 | #include "build_insert_statement.h"
14 | #include "options.h"
15 | #include "process_line.h"
16 |
17 | const char sep_default = ' ';
18 |
19 | static int callback(void* userArg, int count, char** columns, char** column_names){
20 |
21 | #ifdef DEBUG
22 | printf("Columns in query: %d\n", count);
23 | #endif
24 |
25 | int i;
26 | for(i=0; i] [-d ] *\n", argv[0]);
55 | exit(1);
56 | }
57 |
58 | argv++;
59 |
60 | // TODO: Fixme
61 | globalArgs options = getOpts(argc, argv);
62 |
63 | // Create a database in memory
64 | if(options.file) {
65 | rc = sqlite3_open(options.file, &db);
66 | } else {
67 | rc = sqlite3_open(":memory:", &db);
68 | }
69 |
70 | // Create a table with an inital 9 columns. Why 9? Because.
71 | rc = sqlite3_exec(db, "create table data (c1)", NULL, NULL, &zErrMsg);
72 | HANDLE_ERROR(rc);
73 |
74 | // Parse stdin, expanding table where necessary
75 | while(fgets(line, line_buffer_length, stdin)) { process_line(line, &columns, db, options); }
76 |
77 | // Run all queries
78 | for( i=0 + options.num; i <= argc; i++ ) {
79 |
80 | #ifdef DEBUG
81 | printf("Query: %s\n", argv[i]);
82 | printf("Results:\n");
83 | #endif
84 |
85 | rc = sqlite3_exec(db, argv[i], callback, NULL, &zErrMsg);
86 | HANDLE_ERROR(rc);
87 | }
88 |
89 | sqlite3_close(db);
90 | return 0;
91 | }
92 |
--------------------------------------------------------------------------------
/sqlbong.cfdg:
--------------------------------------------------------------------------------
1 | startshape bong
2 |
3 | CF::Background = [a -1]
4 |
5 | shape bong {
6 | TRIANGLE []
7 | SQUARE [s 0.4 1.3 y 1.3 r 10 x -0.2]
8 | CIRCLE [s 1.2 y 0.4]
9 | SQUARE [s 1 0.1 x 0.3 y 0.5 r 40]
10 | TRIANGLE [s 0.2 x 0.7 y 0.83 r 10]
11 | S [b 1 s 0.7 y 1.5 r 10 x -0.24]
12 | Q [b 1 s 0.6 y 0.8 r 10 x -0.1]
13 | L [b 1 s 0.8 y 0.15 r 10 x -0.05]
14 | }
15 |
16 | path S {
17 |
18 | MOVETO ( 0.14174454828660435 , 0.5 )
19 |
20 |
21 | LINETO ( -0.14797507788161993 , 0.38161993769470404 )
22 | LINETO ( -0.2881619937694704 , 0.13239875389408098 )
23 | LINETO ( -0.16043613707165108 , -0.08566978193146417 )
24 | LINETO ( 0.09813084112149532 , -0.14174454828660435 )
25 | LINETO ( 0.12305295950155763 , -0.2102803738317757 )
26 | LINETO ( 0.06386292834890965 , -0.29439252336448596 )
27 | LINETO ( -0.24143302180685358 , -0.34735202492211836 )
28 | LINETO ( -0.2383177570093458 , -0.5 )
29 | LINETO ( 0.19470404984423675 , -0.43146417445482865 )
30 | LINETO ( 0.2881619937694704 , -0.24454828660436137 )
31 | LINETO ( 0.27258566978193144 , -0.06386292834890965 )
32 | LINETO ( 0.08878504672897196 , 0.020249221183800622 )
33 | LINETO ( -0.08878504672897196 , 0.0514018691588785 )
34 | LINETO ( -0.13239875389408098 , 0.19781931464174454 )
35 | LINETO ( 0.05763239875389408 , 0.3099688473520249 )
36 | LINETO ( 0.16978193146417445 , 0.35358255451713394 )
37 |
38 |
39 | CLOSEPOLY()
40 | FILL []
41 |
42 | }
43 |
44 | path Q {
45 |
46 | MOVETO ( -0.3260135135135135 , 0.2939189189189189 )
47 |
48 |
49 | LINETO ( -0.17736486486486486 , 0.47635135135135137 )
50 | LINETO ( 0.16722972972972974 , 0.5 )
51 | LINETO ( 0.33614864864864863 , 0.28378378378378377 )
52 | LINETO ( 0.3564189189189189 , -0.14189189189189189 )
53 | LINETO ( 0.28547297297297297 , -0.26013513513513514 )
54 | LINETO ( 0.3766891891891892 , -0.35135135135135137 )
55 | LINETO ( 0.44425675675675674 , -0.3547297297297297 )
56 | LINETO ( 0.44425675675675674 , -0.46959459459459457 )
57 | LINETO ( 0.31925675675675674 , -0.44932432432432434 )
58 | LINETO ( 0.21452702702702703 , -0.3581081081081081 )
59 | LINETO ( 0.10641891891891891 , -0.4864864864864865 )
60 | LINETO ( -0.18412162162162163 , -0.5 )
61 | LINETO ( -0.38344594594594594 , -0.35135135135135137 )
62 | LINETO ( -0.44425675675675674 , 0.030405405405405407 )
63 | LINETO ( -0.36317567567567566 , 0.13851351351351351 )
64 | LINETO ( -0.2719594594594595 , 0.10472972972972973 )
65 | LINETO ( -0.2956081081081081 , -0.037162162162162164 )
66 | LINETO ( -0.21114864864864866 , -0.23648648648648649 )
67 | LINETO ( -0.05574324324324324 , -0.3141891891891892 )
68 | LINETO ( 0.07939189189189189 , -0.24324324324324326 )
69 | LINETO ( 0.015202702702702704 , -0.16891891891891891 )
70 | LINETO ( 0.11317567567567567 , -0.08445945945945946 )
71 | LINETO ( 0.18412162162162163 , -0.16216216216216217 )
72 | LINETO ( 0.22128378378378377 , -0.060810810810810814 )
73 | LINETO ( 0.17398648648648649 , 0.23648648648648649 )
74 | LINETO ( 0.03885135135135135 , 0.3141891891891892 )
75 | LINETO ( -0.15033783783783783 , 0.2939189189189189 )
76 | LINETO ( -0.22466216216216217 , 0.22635135135135134 )
77 |
78 |
79 | CLOSEPOLY()
80 | FILL []
81 |
82 | }
83 |
84 |
85 | path L {
86 |
87 | MOVETO ( -0.3953488372093023 , 0.42441860465116277 )
88 |
89 |
90 | LINETO ( -0.38953488372093026 , -0.47093023255813954 )
91 | LINETO ( 0.3953488372093023 , -0.5 )
92 | LINETO ( 0.3866279069767442 , -0.22093023255813954 )
93 | LINETO ( -0.13662790697674418 , -0.25872093023255816 )
94 | LINETO ( -0.21220930232558138 , 0.5 )
95 |
96 |
97 | CLOSEPOLY()
98 | FILL []
99 |
100 | }
--------------------------------------------------------------------------------
/sqlbong.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sordina/SQLBong/153d6f4b1d1c204a7b52ae8eae95f86ab8fcdbe6/sqlbong.png
--------------------------------------------------------------------------------
/test.txt:
--------------------------------------------------------------------------------
1 | 1 2 3
2 | 2 3 4
3 | 3 4 5
4 | 4 5 6
5 | 5 6 7
6 | 6 7 8
7 |
--------------------------------------------------------------------------------