Skip to content

Commit

Permalink
Code review: 268880043: Separated table view from CLI tool log2timeli…
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimmetz committed Dec 31, 2015
1 parent a0b760b commit 4ec808b
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 242 deletions.
2 changes: 1 addition & 1 deletion config/dpkg/changelog
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ python-plaso (1.3.1-1) unstable; urgency=low

* Auto-generated

-- Log2Timeline <[email protected]> Tue, 29 Sep 2015 22:18:57 +0200
-- Log2Timeline <[email protected]> Thu, 01 Oct 2015 21:45:47 +0200
8 changes: 8 additions & 0 deletions docs/plaso.analysis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ plaso.analysis.chrome_extension module
:undoc-members:
:show-inheritance:

plaso.analysis.definitions module
---------------------------------

.. automodule:: plaso.analysis.definitions
:members:
:undoc-members:
:show-inheritance:

plaso.analysis.file_hashes module
---------------------------------

Expand Down
8 changes: 8 additions & 0 deletions docs/plaso.cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ plaso.cli.tools module
:undoc-members:
:show-inheritance:

plaso.cli.views module
----------------------

.. automodule:: plaso.cli.views
:members:
:undoc-members:
:show-inheritance:


Module contents
---------------
Expand Down
2 changes: 1 addition & 1 deletion plaso/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
__version__ = '1.3.1'

VERSION_DEV = True
VERSION_DATE = '20150929'
VERSION_DATE = '20151001'


def GetVersion():
Expand Down
6 changes: 2 additions & 4 deletions plaso/analysis/chrome_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,15 @@ def __init__(self, incoming_queue):
"""
super(ChromeExtensionPlugin, self).__init__(incoming_queue)

# Saved list of already looked up extensions.
self._extensions = {}
self._results = {}
self.plugin_type = self.TYPE_REPORT

# TODO: see if these can be moved to arguments passed to ExamineEvent
# or some kind of state object.
self._sep = None
self._user_paths = None

# Saved list of already looked up extensions.
self._extensions = {}

def _GetChromeWebStorePage(self, extension_id):
"""Retrieves the page for the extension from the Chrome store website.
Expand Down
16 changes: 16 additions & 0 deletions plaso/analysis/definitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
"""This file contains the definitions for analysis plugins."""

# All the possible analysis plugin types.

# Plugin that detects anomalies.
PLUGIN_TYPE_ANOMALY = 1

# Plugin that calculating statistical information.
PLUGIN_TYPE_STATISTICS = 2

# Plugin that annotates (or tags) event objects.
PLUGIN_TYPE_ANNOTATION = 3

# Pluging that provides summary information.
PLUGIN_TYPE_REPORT = 4
10 changes: 2 additions & 8 deletions plaso/analysis/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from collections import defaultdict

from plaso.analysis import definitions
from plaso.engine import queue
from plaso.lib import timelib
from plaso.lib import errors
Expand Down Expand Up @@ -47,13 +48,6 @@ class AnalysisPlugin(queue.ItemQueueConsumer):
# should be able to run during the extraction phase.
ENABLE_IN_EXTRACTION = False

# All the possible report types.
TYPE_ANOMALY = 1 # Plugin that is inspecting events for anomalies.
TYPE_STATISTICS = 2 # Statistical calculations.
TYPE_ANNOTATION = 3 # Inspecting events with the primary purpose of
# annotating or tagging them.
TYPE_REPORT = 4 # Inspecting events to provide a summary information.

# A flag to indicate that this plugin takes a long time to compile a report.
LONG_RUNNING_PLUGIN = False

Expand All @@ -64,7 +58,7 @@ def __init__(self, incoming_queue):
incoming_queue: A queue that is used to listen to incoming events.
"""
super(AnalysisPlugin, self).__init__(incoming_queue)
self.plugin_type = self.TYPE_REPORT
self.plugin_type = definitions.PLUGIN_TYPE_REPORT

def _ConsumeItem(self, item, analysis_mediator=None, **kwargs):
"""Consumes an item callback for ConsumeItems.
Expand Down
32 changes: 24 additions & 8 deletions plaso/analysis/manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""This file contains the analysis plugin manager class."""

from plaso.analysis import definitions
from plaso.lib import errors


Expand All @@ -9,6 +10,18 @@ class AnalysisPluginManager(object):

_plugin_classes = {}

_PLUGIN_TYPE_STRINGS = {
definitions.PLUGIN_TYPE_ANNOTATION: (
u'Annotation/Tagging plugin'),
definitions.PLUGIN_TYPE_ANOMALY: (
u'Anomaly plugin'),
definitions.PLUGIN_TYPE_REPORT: (
u'Summary/Report plugin'),
definitions.PLUGIN_TYPE_STATISTICS: (
u'Statistics plugin')
}
_PLUGIN_TYPE_STRINGS.setdefault(u'Unknown type')

@classmethod
def DeregisterPlugin(cls, plugin_class):
"""Deregisters an analysis plugin class.
Expand Down Expand Up @@ -48,17 +61,20 @@ def GetAllPluginInformation(cls, show_all=True):
plugin names. The default is True.
Returns:
A sorted list of tuples containing the name, docstring and type of each
analysis plugin.
A sorted list of tuples containing the name, docstring and type string
of each analysis plugin.
"""
results = []
for cls_obj in cls._plugin_classes.itervalues():
# TODO: Use a specific description variable, not the docstring.
doc_string, _, _ = cls_obj.__doc__.partition(u'\n')
for plugin_class in iter(cls._plugin_classes.values()):
plugin_object = plugin_class(None)
if not show_all and not plugin_class.ENABLE_IN_EXTRACTION:
continue

obj = cls_obj(None)
if show_all or cls_obj.ENABLE_IN_EXTRACTION:
results.append((obj.plugin_name, doc_string, obj.plugin_type))
# TODO: Use a specific description variable, not the docstring.
doc_string, _, _ = plugin_class.__doc__.partition(u'\n')
type_string = cls._PLUGIN_TYPE_STRINGS.get(plugin_object.plugin_type)
information_tuple = (plugin_object.plugin_name, doc_string, type_string)
results.append(information_tuple)

return sorted(results)

Expand Down
3 changes: 1 addition & 2 deletions plaso/analysis/windows_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,8 @@ def __init__(self, incoming_queue):
incoming_queue: A queue to read events from.
"""
super(WindowsServicesPlugin, self).__init__(incoming_queue)
self._service_collection = WindowsServiceCollection()
self.plugin_type = interface.AnalysisPlugin.TYPE_REPORT
self._output_format = u'text'
self._service_collection = WindowsServiceCollection()

def ExamineEvent(self, analysis_mediator, event_object, **kwargs):
"""Analyzes an event_object and creates Windows Services as required.
Expand Down
72 changes: 10 additions & 62 deletions plaso/cli/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import plaso
from plaso.lib import errors
from plaso.cli import views

import pytz

Expand All @@ -36,10 +37,10 @@ def __init__(self, input_reader=None, output_writer=None):
"""Initializes the CLI tool object.
Args:
input_reader: the input reader (instance of InputReader).
input_reader: optional input reader (instance of InputReader).
The default is None which indicates the use of the stdin
input reader.
output_writer: the output writer (instance of OutputWriter).
output_writer: optional output writer (instance of OutputWriter).
The default is None which indicates the use of the stdout
output writer.
"""
Expand Down Expand Up @@ -242,15 +243,18 @@ def AddTimezoneOption(self, argument_group):

def ListTimeZones(self):
"""Lists the timezones."""
self.PrintHeader(u'Zones')
max_length = 0
for timezone_name in pytz.all_timezones:
if len(timezone_name) > max_length:
max_length = len(timezone_name)

utc_date_time = datetime.datetime.utcnow()

self.PrintColumnValue(u'Timezone', u'UTC Offset', column_width=max_length)
table_view = views.CLITableView(
self._output_writer, column_width=max_length)

table_view.PrintHeader(u'Zones')
table_view.PrintRow(u'Timezone', u'UTC Offset')
for timezone_name in pytz.all_timezones:
local_timezone = pytz.timezone(timezone_name)

Expand All @@ -263,9 +267,9 @@ def ListTimeZones(self):
_, _, diff = local_date_string.rpartition(u'-')
diff_string = u'-{0:s}'.format(diff)

self.PrintColumnValue(timezone_name, diff_string, column_width=max_length)
table_view.PrintRow(timezone_name, diff_string)

self.PrintSeparatorLine()
table_view.PrintFooter()

def ParseOptions(self, options):
"""Parses tool specific options.
Expand All @@ -275,62 +279,6 @@ def ParseOptions(self, options):
"""
self._ParseInformationalOptions(options)

def PrintColumnValue(self, name, description, column_width=25):
"""Prints a value with a name and description aligned to the column width.
Args:
name: the name.
description: the description.
column_width: optional column width. The default is 25.
"""
line_length = self._LINE_LENGTH - column_width - 3

# The format string of the first line of the column value.
primary_format_string = u'{{0:>{0:d}s}} : {{1:s}}\n'.format(column_width)

# The format string of successive lines of the column value.
secondary_format_string = u'{{0:<{0:d}s}}{{1:s}}\n'.format(
column_width + 3)

if len(description) < line_length:
self._output_writer.Write(primary_format_string.format(name, description))
return

# Split the description in words.
words = description.split()

current = 0

lines = []
word_buffer = []
for word in words:
current += len(word) + 1
if current >= line_length:
current = len(word)
lines.append(u' '.join(word_buffer))
word_buffer = [word]
else:
word_buffer.append(word)
lines.append(u' '.join(word_buffer))

# Print the column value on multiple lines.
self._output_writer.Write(primary_format_string.format(name, lines[0]))
for line in lines[1:]:
self._output_writer.Write(secondary_format_string.format(u'', line))

def PrintHeader(self, text, character=u'*'):
"""Prints the header as a line with centered text.
Args:
text: The header text.
character: Optional header line character. The default is '*'.
"""
self._output_writer.Write(u'\n')

format_string = u'{{0:{0:s}^{1:d}}}\n'.format(character, self._LINE_LENGTH)
header_string = format_string.format(u' {0:s} '.format(text))
self._output_writer.Write(header_string)

def PrintSeparatorLine(self):
"""Prints a separator line."""
self._output_writer.Write(u'-' * self._LINE_LENGTH)
Expand Down
94 changes: 94 additions & 0 deletions plaso/cli/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""The CLI view classes."""


class CLITableView(object):
"""Class that implements a 2 column command line table view."""

# The maximum width of the table in number of characters.
# The standard width of Windows cmd.exe is 80 characters.
_MAXIMUM_WIDTH = 80

_HEADER_FORMAT_STRING = u'{{0:*^{0:d}}}\n'.format(_MAXIMUM_WIDTH)

def __init__(self, output_writer, column_width=25):
"""Initializes the command line table view object.
Args:
output_writer: the output writer (instance of OutputWriter).
The default is None which indicates the use of the stdout
output writer.
column_width: optional column width, which cannot be smaller than 0 or
larger than the maximum width.
Raises:
ValueError: if the column width is out of bounds.
"""
if column_width < 0 or column_width > self._MAXIMUM_WIDTH:
raise ValueError(u'Column width value out of bounds.')

super(CLITableView, self).__init__()
self._column_width = column_width
self._output_writer = output_writer

def PrintFooter(self):
"""Prints the footer."""
self._output_writer.Write(u'-' * self._MAXIMUM_WIDTH)
self._output_writer.Write(u'\n')

def PrintHeader(self, text):
"""Prints the header as a line with centered text.
Args:
text: The header text.
"""
self._output_writer.Write(u'\n')

text = u' {0:s} '.format(text)
header_string = self._HEADER_FORMAT_STRING.format(text)
self._output_writer.Write(header_string)

def PrintRow(self, first_column, second_column):
"""Prints a row of 2 column values aligned to the column width.
Args:
first_column: the first column value.
second_column: the second column value.
"""
maximum_row_width = self._MAXIMUM_WIDTH - self._column_width - 3

# The format string of the first line of the column value.
primary_format_string = u'{{0:>{0:d}s}} : {{1:s}}\n'.format(
self._column_width)

# The format string of successive lines of the column value.
secondary_format_string = u'{{0:<{0:d}s}}{{1:s}}\n'.format(
self._column_width + 3)

if len(second_column) < maximum_row_width:
self._output_writer.Write(primary_format_string.format(
first_column, second_column))
return

# Split the column value in words.
words = second_column.split()

current = 0

lines = []
word_buffer = []
for word in words:
current += len(word) + 1
if current >= maximum_row_width:
current = len(word)
lines.append(u' '.join(word_buffer))
word_buffer = [word]
else:
word_buffer.append(word)
lines.append(u' '.join(word_buffer))

# Print the column value on multiple lines.
self._output_writer.Write(primary_format_string.format(
first_column, lines[0]))
for line in lines[1:]:
self._output_writer.Write(secondary_format_string.format(u'', line))
Loading

0 comments on commit 4ec808b

Please sign in to comment.