1 | # lognestmonster Copyright (c) 2019 Joshua 'joshuas3' Stockin |
2 | # <https://github.com/JoshuaS3/lognestmonster/>. |
3 |
|
4 |
|
5 | # This file is part of lognestmonster. |
6 |
|
7 | # lognestmonster is free software: you can redistribute it and/or modify |
8 | # it under the terms of the GNU General Public License as published by |
9 | # the Free Software Foundation, either version 3 of the License, or |
10 | # (at your option) any later version. |
11 |
|
12 | # lognestmonster is distributed in the hope that it will be useful, |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | # GNU General Public License for more details. |
16 |
|
17 | # You should have received a copy of the GNU General Public License |
18 | # along with lognestmonster. If not, see <https://www.gnu.org/licenses/>. |
19 |
|
20 | import os |
21 | import sys |
22 | import time |
23 | from utils import * |
24 | from format import * |
25 | from text import * |
26 | from args import * |
27 | from parseargs import * |
28 | from read import * |
29 |
|
30 | class Parser: |
31 | screen = None |
32 | clines = None |
33 | ccols = None |
34 | lines = None |
35 | def __init__(self): |
36 | self.screen = curses_window() |
37 | self.get_size() |
38 |
|
39 | def get_size(self): |
40 | size = term_size() |
41 | self.clines = size[0] |
42 | self.ccols = size[1] |
43 |
|
44 | def redraw(self): |
45 | self.get_size() |
46 | curses_clear(self.screen) |
47 | l = 0 |
48 | for line in self.lines: |
49 | if l == self.clines: break |
50 | self.screen.move(l, 0) |
51 | line_len = 0 |
52 | for string in line: |
53 | # ("content string", "attributes string") |
54 | content = string[0] |
55 |
|
56 | self.screen.attrset(0) |
57 |
|
58 | try: |
59 | attr = string[1] |
60 | except: |
61 | attr = "" |
62 |
|
63 | if "RESET" in attr: # set curses attributes based on attributes string |
64 | self.screen.attron(curses.color_pair(0)) |
65 | else: |
66 | if "BLACK" in attr: |
67 | self.screen.attron(curses.color_pair(1)) |
68 | if "BLUE" in attr: |
69 | self.screen.attron(curses.color_pair(2)) |
70 | if "CYAN" in attr: |
71 | self.screen.attron(curses.color_pair(3)) |
72 | if "GREEN" in attr: |
73 | self.screen.attron(curses.color_pair(4)) |
74 | if "MAGENTA" in attr: |
75 | self.screen.attron(curses.color_pair(5)) |
76 | if "RED" in attr: |
77 | self.screen.attron(curses.color_pair(6)) |
78 | if "WHITE" in attr: |
79 | self.screen.attron(curses.color_pair(7)) |
80 | if "YELLOW" in attr: |
81 | self.screen.attron(curses.color_pair(8)) |
82 | if "BOLD" in attr: |
83 | self.screen.attron(curses.A_BOLD) |
84 | if "STANDOUT" in attr: |
85 | self.screen.attron(curses.A_STANDOUT) |
86 | if "UNDERLINE" in attr: |
87 | self.screen.attron(curses.A_UNDERLINE) |
88 |
|
89 | out = content[:self.ccols - line_len] |
90 | self.screen.addstr(out) |
91 | self.screen.attrset(0) |
92 | line_len += len(out) |
93 | l += 1 |
94 | curses_refresh(self.screen) |
95 |
|
96 | def get_input(self): |
97 | input = getch() |
98 | if input == UP: input = "up" |
99 | elif input == DOWN: input = "down" |
100 | elif input == LEFT: input = "left" |
101 | elif input == RIGHT: input = "right" |
102 | elif input == CTRLC: input = "exit" |
103 | else: input = input.decode("utf-8").lower() |
104 | return input |
105 |
|
106 | folder_name = "log_1565561768719" |
107 | divider = " | " |
108 | title = TITLE |
109 |
|
110 | def loop(self): |
111 | def tab(string, count=1): |
112 | return " "*count + string |
113 | input = None |
114 | try: |
115 | while True: |
116 | self.get_size() |
117 | screen_width = self.ccols |
118 |
|
119 | self.lines = [ |
120 | [(self.title + " - ", "RESET"), (self.folder_name, "BOLD YELLOW")], |
121 | "Size: 235 bytes | Timestamp: 1565561768719", |
122 | "7 Statements | 2 Events | 0 Unsaved Data Trees", |
123 | pad(margin("STATEMENT 5"), ":", screen_width), |
124 | "", |
125 | "[[LOG START]]", |
126 | "v 7 ITEMS", |
127 | tab("1565561768752 - INFO - INIT - HELLO"), |
128 | tab("1565561768752 - INFO - INIT - HELLO"), |
129 | tab("v 4 ITEMS"), |
130 | tab("1565561768752 - INFO - INIT - HELLO", 2), |
131 | tab("1565561768752 - INFO - INIT - HELLO", 2), |
132 | "", |
133 | [(tab("1565561768752 - INFO - INIT - HELLO", 2), "BOLD")], |
134 | "", |
135 | tab("1565561768752 - INFO - INIT - HELLO", 2), |
136 | tab("1565561768752 - INFO - INIT - HELLO"), |
137 | "", |
138 | str(input), |
139 | "[[LOG END]", |
140 | "", |
141 | "", |
142 | "", |
143 | pad(margin(CONTROLS_MESSAGE), ":", screen_width) |
144 | ] |
145 | self.redraw() |
146 |
|
147 | input = self.get_input() |
148 |
|
149 | if input == "exit" or input == "q": # exit program on Ctrl + C or `q` |
150 | break; |
151 | finally: |
152 | curses_reset() |
153 |
|
154 | def output_exit(error=None): |
155 | exitcode = 0 |
156 | output(VERSION_SHORT) |
157 | if error is not None: # print argument error is exists |
158 | output(TEXT_RED + "error: " + RESET + error) |
159 | exitcode = 1 |
160 | output(HELP_MESSAGE) |
161 | exit(exitcode) |
162 |
|
163 | def main(): |
164 | options = parseargs(sys.argv[1:]) |
165 |
|
166 | display_help = "help" in options |
167 | display_version = "version" in options |
168 | is_status = "status" in options |
169 |
|
170 | filter_errors = "errors" in options |
171 | filter_warnings = "warnings" in options |
172 | filter_info = "info" in options |
173 | filter_debug = "debug" in options |
174 | filter_verbose = "verbose" in options |
175 | filter_veryverbose = "veryverbose" in options |
176 |
|
177 | filter_after = "after" in options |
178 | filter_before = "before" in options |
179 | filter_tag = "tag" in options |
180 |
|
181 | screen_size = term_size() |
182 | clines = screen_size[0] |
183 | ccols = screen_size[1] |
184 |
|
185 | if display_help: |
186 | output(VERSION_SHORT) |
187 |
|
188 | output("usage: lognestmonster " + USAGE_MESSAGE) |
189 |
|
190 | output() |
191 |
|
192 | for paragraph in COMMAND_INFO.split("\n\n"): |
193 | output_lines(wrap(paragraph, ccols)) |
194 |
|
195 | output() |
196 |
|
197 | args = [] |
198 |
|
199 | div1 = int(ccols/3) |
200 | div2 = int(ccols/3*2) |
201 | for arg in ARGUMENT_OPTIONS: |
202 | arg_lines = [] |
203 | indicators = wrap(", ".join(ARGUMENT_OPTIONS[arg]["indicators"]), div1) |
204 | description = wrap(ARGUMENT_OPTIONS[arg]["description"], div2) |
205 | l1 = indicators[0] |
206 | z = 0 |
207 | for line in description: |
208 | try: |
209 | l1 = indicators[z] |
210 | except: |
211 | l1 = "" |
212 | z += 1 |
213 | l = columnize([(div1, l1), (div2, line)], ccols) |
214 | arg_lines.append(l) |
215 | args += arg_lines |
216 |
|
217 | output_lines(args) |
218 | output() |
219 | output(DESCRIPTION_PYTHON_VERSION) |
220 | return |
221 | elif display_version: |
222 | output(VERSION_MESSAGE) |
223 | return |
224 | elif len(sys.argv) == 1 or type(options) is str: # argument error or no args passed |
225 | if type(options) is str: # print argument error is exists |
226 | output_exit(options) |
227 | else: |
228 | output_exit() |
229 |
|
230 | positional = sys.argv[-1] |
231 | if positional is not "-" and os.path.isfile(positional) is not True and os.path.isdir(positional) is not True: |
232 | output_exit("file unknown '" + positional + "'") |
233 |
|
234 | if positional is "-": positional = "stdin" |
235 |
|
236 | if positional is "stdin": |
237 | fd = sys.stdin |
238 | else: |
239 | try: |
240 | fd = open(positional, "rb", buffering=8192) |
241 | except: |
242 | output_exit("unable to open file '" + positional + "'") |
243 |
|
244 | if not is_status: |
245 | p = Parser() |
246 | p.folder_name = positional |
247 | p.loop() |
248 | else: |
249 | r = Reader(fd) |
250 |
|
251 | filter_verbosity_levels = [] |
252 | if filter_errors: |
253 | filter_verbosity_levels.append(5) |
254 | if filter_warnings: |
255 | filter_verbosity_levels.append(4) |
256 | if filter_info: |
257 | filter_verbosity_levels.append(0) |
258 | if filter_debug: |
259 | filter_verbosity_levels.append(1) |
260 | if filter_verbose: |
261 | filter_verbosity_levels.append(2) |
262 | if filter_veryverbose: |
263 | filter_verbosity_levels.append(3) |
264 | if filter_verbosity_levels != []: |
265 | r.filter_verbosity = filter_verbosity_levels |
266 | r.filters = True |
267 | else: |
268 | r.filter_verbosity = [0, 1, 2, 3, 4, 5] |
269 |
|
270 | if filter_after: |
271 | try: |
272 | r.filter_time_start = int(options["after"][0]) |
273 | r.filters = True |
274 | except: |
275 | output_exit("expected int for flag --after, got '" + options["after"][0] + "'") |
276 | if filter_before: |
277 | try: |
278 | r.filter_time_end = int(options["before"][0]) |
279 | r.filters = True |
280 | except: |
281 | output_exit("expected int for flag --before, got '" + options["before"][0] + "'") |
282 |
|
283 | if filter_tag: |
284 | r.filter_tag = str(options["tag"][0]) |
285 | r.filters = True |
286 | if positional is not "stdin": r.size() |
287 | else: r.seekable = False |
288 | output("File scan in progress...") |
289 | s = time.time() |
290 | def update(): |
291 | if r.statement_count % 200 == 0: |
292 | output("{0} statements | {1} events | {2} bad bytes | {3}%".format(r.statement_count, r.event_count, r.bad_bytes, round((r.position/r.file_size)*1000)/10), end="\r") |
293 | r.onupdate(update) |
294 | r.scan() |
295 | output("{0} statements | {1} events | {2} bad bytes | {3}%".format(r.statement_count, r.event_count, r.bad_bytes, round((r.position/r.file_size)*1000)/10)) |
296 | output("Finished in {0} seconds".format(time.time() - s)) |
297 |
|
298 | fd.close() |
299 |
|
300 |
|
301 | if __name__ == "__main__": |
302 | main() |
303 |
|