Full Code of Jacksgong/okcat for AI

master 12e0631a93e5 cached
25 files
90.8 KB
25.6k tokens
86 symbols
1 requests
Download .txt
Repository: Jacksgong/okcat
Branch: master
Commit: 12e0631a93e5
Files: 25
Total size: 90.8 KB

Directory structure:
gitextract_s8579sb1/

├── .gitignore
├── .idea/
│   ├── dictionaries/
│   │   └── Jacksgong.xml
│   ├── inspectionProfiles/
│   │   └── Project_Default.xml
│   └── vcs.xml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.txt
├── MANIFEST.in
├── README-zh.md
├── README.md
├── debug.sh
├── demo-conf/
│   ├── demo-config.yml
│   ├── demo-log-file.log
│   └── filedownloader.yml
├── okcat/
│   ├── __init__.py
│   ├── adb.py
│   ├── confloader.py
│   ├── helper.py
│   ├── logfile_parser.py
│   ├── logprocessor.py
│   ├── logregex.py
│   ├── logseparator.py
│   ├── terminalcolor.py
│   └── trans.py
└── setup.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/.idea/workspace.xml
/.idea/misc.xml
/.idea/okcat.iml
/.idea/modules.xml
*.pyc
*.egg-info
/dist
/build
.DS_Store

================================================
FILE: .idea/dictionaries/Jacksgong.xml
================================================
<component name="ProjectDictionaryState">
  <dictionary name="Jacksgong">
    <words>
      <w>dalvik</w>
      <w>dest</w>
      <w>dreamtobe</w>
      <w>ducktype</w>
      <w>dumpsys</w>
      <w>gids</w>
      <w>jacksgong</w>
      <w>jakewharton</w>
      <w>logcat</w>
      <w>okcat</w>
      <w>pids</w>
      <w>popen</w>
      <w>proc</w>
      <w>prog</w>
      <w>sharkey</w>
      <w>stdin</w>
      <w>vdiwef</w>
    </words>
  </dictionary>
</component>

================================================
FILE: .idea/inspectionProfiles/Project_Default.xml
================================================
<component name="InspectionProjectProfileManager">
  <profile version="1.0">
    <option name="myName" value="Project Default" />
    <inspection_tool class="PyPep8Inspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
  </profile>
</component>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>

================================================
FILE: .travis.yml
================================================
sudo: false
language: python
python:
  - "2.7"
  - "3.3"
  - "3.4"
  - "3.5"
  - "3.6"
  - "3.7"
  - "3.8"
  - "3.9"

install:
        #  - travis_retry pip install okcat
  - bash debug.sh
script:
  - okcat help
  - cd demo-conf
  - okcat -y=demo-config demo-log-file.log


================================================
FILE: CHANGELOG.md
================================================
# 1.4.0

2024-02-27

- Feature: Support `ignore-tag-list` and `ignore-msg-list` configuration to ignore messages by defined

# 1.3.0

2020-08-13

- Fix: fix on the special timestamp format stackoverflow issue temporary
- Fix: fix decode utf-8 failed

# 1.1.9

2019-08-23

- Feature: Implement 'from' keyword from config yaml file means append rather than overwrite.

# 1.1.7

2019-01-11

- Fix: Fix wrong config field on the `log-line-regex` or `adb-log-line-regex` from 'data' to the correct one 'date'

# 1.1.6

2018-06-26

- Fix: fix encode issue on python3 - by [Ryfthink](https://github.com/Ryfthink)

# 1.1.5

2018-06-24

- Fix: some dvices adb lost connection, closes #9 - by [Ryfthink](https://github.com/Ryfthink)

# 1.1.4

2018-05-24

- Feat: support 'from' keyword to let yml config file extends from exist yml file

# 1.1.3

2017-12-01

- Feat: support combine and parse multiple log-files once time

# 1.1.2

2017-11-17

- Fix: fix unicode decode error on setup on windows system closes #4

# 1.1.1

2017-10-10

- Feat: show tips instead of crash when user don't provide config-file name to parse log file. Closes #2

# 1.1.0

2017-09-27

- Fix: fix import file failed on python 3.x

# 1.0.9

2017-09-16

- Fix: missing parentheses in call to 'print' error occurred on python 3.x Closes #1

# 1.0.8

2017-09-04

- Feat: handle the case of adb connection is lost when using adb logcat

# 1.0.7

2017-09-03

- Enhance: print each line when it has been parsed immediately rather than waiting for parsing whole file to handle case of large log file

# 1.0.6

2017-09-1

- Fix: cover the case of there is no 'level' keyword on `log-line-regex` case.
- Enhance: add `help` param on okcat, such as `okcat help`.
- Enhance: support `--hide-same-tags` param
- Fix: output all log when `log-line-regex` can't parse
- Fix: handle case of only message is valid
- Fix: fix print non-match content when the log can't match regex
- Fix: fix the default adb regex may wrong for some special case


================================================
FILE: LICENSE.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright (C) 2017 Jacksgong(jacksgong.com)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: MANIFEST.in
================================================
# Include the license file
include LICENSE.txt
include README.md 


================================================
FILE: README-zh.md
================================================
# OkCat

![](https://img.shields.io/badge/log-any%20format-orange.svg)
![](https://img.shields.io/badge/log-android-orange.svg)
![](https://img.shields.io/badge/log-ios-orange.svg)
![](https://img.shields.io/badge/log-backend-orange.svg)
![](https://img.shields.io/badge/license-Apache2-blue.svg)
[![](https://img.shields.io/badge/readme-English-blue.svg)](https://github.com/Jacksgong/okcat)
[![](https://img.shields.io/badge/readme-中文-blue.svg)](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)
[![](https://img.shields.io/badge/pip-v1.4.0%20okcat-yellow.svg)](https://pypi.org/project/OkCat/1.4.0/)
[![Build Status](https://travis-ci.org/Jacksgong/okcat.svg?branch=master)](https://travis-ci.org/Jacksgong/okcat)

强大的日志处理组件。

[English Doc](https://github.com/Jacksgong/okcat)

- 你可以定义任意的日志正则表达式,来适配任意格式的日志,可以将其用于iOS、Android、后端等等。
- ADB Logcat部分是基于JakeWharton的PID Cat,并且适配了各类OkCat的新特性 。

Andrdoid工程师查看ADB Logcat最简单的使用:

```shell
okcat 包名
```

## 特性

> 最主要的特性是:你可以为不同的日志定义自己的正则表达式,以此适配各种类型的日志处理。

- 高亮一些关键字
![](https://github.com/Jacksgong/okcat/raw/master/arts/highlight-demo.png)
- 转译日志内容
![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-msg-demo.png)
- 转译Tag
![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-tag-demo.png)
- 隐藏一些日志
![](https://github.com/Jacksgong/okcat/raw/master/arts/hide-msg-demo.png)
- 对连续的日志进行分割
![](https://github.com/Jacksgong/okcat/raw/master/arts/separate-demo.png)
- 忽略符合规则日志
`以提供的字段开头的日志将会被直接忽略` 或者 `满足以提供的TAG的日志将会被直接忽略`

## 如何安装

```shell
sudo pip install okcat
```

如果你还没有安装`pip`,你需要先安装`pip`:

1. `brew install python`
2. `sudo easy_install pip`

如果你想要升级:

```
sudo pip install okcat --upgrade
```

## 如何使用

---

#### 最简单的测试

1. 下载[filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml)在当前目录,或者移动到`~/.okcat/`目录中
3. 运行这个[Filedownloader-Demo](https://github.com/lingochamp/FileDownloader)项目中的demo项目,并运行到你的Android手机上,然后将手机连接电脑
4. 执行: `okcat -y=filedownloader`
5. 此时日志就会根据[filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml)的配置输出了

![](https://github.com/Jacksgong/okcat/raw/master/arts/demo.png)

---

#### 1. 定义你的配置文件(`.yml`)

你可以在`~/.okcat/`目录下创建你的yaml格式的配置文件,如果`~/.okcat`文件夹不存在先创建该文件夹;当然也可以直接在执行命令的当前目录创建yaml格式的配置文件。
文件名字可以是任何你想要的名字,在执行`okcat`的时候可以通过`-y=文件名`的形式,告知okcat想要应用的是哪个文件名的配置文件,okcat会默认在当前目录找,找不到会在`~/.okcat`目录下进行查找。

下面是配置文件的案例,里面列出了目前支持的所有的配置,当然你不需要配置所有的特性,只需要配置你需要的即可。

```yml
# 继承存在的其他yml的配置(不需要`.yml`后缀)
from: exist-yml-file-name

# 定义连线手机进行ADB处理时,需要过滤的包名;
# 如果不使用Android的ADB功能,便不需要配置
package: com.liulishuo.filedownloader.demo

# 配置对于一行日志的正则表达式,目前支持正则出date、time、level、tag、process、thread、message
# 不过不一定要全部提供,至少需要提供一个message
# 如log-line-regex: 'message="(.\S*)"'
log-line-regex: 'date,time,process,thread,level,tag,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'

# 在Android的ADB的情况下,我们是使用adb logcat -v brief -v threadtime
# 一般情况下不需要adb-log-line-regex配置,我们已经有很完善的这块的正则,但是如果对这个需要特别定制便可以使用以下定制
# adb-log-line-regex: 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'

# 分割正则列表
# 可以提供多个正则表达式,对日志进行分割
separator-regex-list:
  # 对满足以下正则的那行日志开始进行分割,并且以([^\]]*)的内容作为分割的标题
  - 'call start Url\[([^\]]*)\]'

# 标签关键字
# 如果不提供tag-keyword-list将会显示所有日志
# 如果如下提供了tag-keyword-list将会过滤日志,只显示tag中包含了这里列出关键字的日志
tag-keyword-list:
  - 'FileDownloader'

# 内容转译表
# 如果日志message中由表中key开头,将会使用彩色的文字在该message开头加上表中的value
trans-msg-map:
  # 如这个例子:
  # 原message: 'filedownloader:lifecycle:over xxx'
  # 转译后: '| 任务结束 | filedownloader:lifecycle:over xxx' 其中的'任务结束'会使用彩色的文字显示
  'filedownloader:lifecycle:over': '任务结束'
  'fetch date with': '开始拉取'

# 标签转译表
# 如果日志tag中包含表中key开头,将会使用彩色背景的文字在该message开头加上表中的value
trans-tag-map:
  # 如这个例子:
  # 原输出: 'FileDownloader.DownloadTaskHunter  xxx'
  # 转译后: 'FileDownloader.DownloadTaskHunter [状态切换] xxx' 其中'[状态切换]'会使用彩色背景
  'DownloadTaskHunter': '[状态切换]'
  'ConnectTask': '[请求]'

# 隐藏消息列表
# 对以以下内容开头并且message长度小于100的内功进行灰色显示处理,在视觉上进行隐藏
hide-msg-list:
  # 这里案例因为心跳日志是非常频繁的日志,通常没有什么问题,因此将其着灰色
  - 'notify progress'
  - '~~~callback'

# 高亮列表
# 对message中的以下内容,背景进行彩色处理使其高亮
highlight-list:
  - 'Path['
  - 'Url['
  - 'Tag['
  - 'range['
  
# 忽略日志列表
# 以提供的字段开头的日志将会被直接忽略
ignore-msg-list:
  - 'log start with this will be ignored'

# 忽略日志TAG列表
# 当所在日志的 TAG 在该列表中时会被直接忽略
ignore-tag-list:
  - 'tagToBeIgnored'
```

#### 2. 执行

> okcat的使用非常的简单。

如果你需要处理运行中App在Logcat的输出,只需要执行:

```shell
okcat -y=your-conf-name
```

如果你需要解析任意格式的日志,只需要执行:

```shell
okcat -y=your-conf-name your-log-path1 your-log-path2 your-log-path3 ... 
```

> 小技巧: 你在终端中使用`Command + K`来刷新当前回话中的所有内容,以此快速启动新的okcat解析,而不用再另外创建一个新的会话。

## 我的终端的风格配置

如果你想要适配和上面截图一样的终端风格,非常简单:

- 首先,请使用[powerlevel9k](https://github.com/bhilburn/powerlevel9k)主题(正如Powerlevel9k文档提到的安装Powerlevel9k主题,并且安装Powerline字体).
- 其次,请配置[iTerm2-Neutron](https://github.com/Ch4s3/iTerm2-Neutron)色系.
- 最后, 请配置ini的shell(如果你使用的是zsh,只需要添加下列代码到`~/.zshrc`文件中):
```
POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)
POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status time)
POWERLEVEL9K_TIME_FORMAT="%D{%H:%M:%S}"
POWERLEVEL9K_NODE_VERSION_BACKGROUND='022'
POWERLEVEL9K_SHORTEN_DIR_LENGTH=2
```

## LICENSE

```
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```


================================================
FILE: README.md
================================================
# OkCat

![](https://img.shields.io/badge/log-any%20format-orange.svg)
![](https://img.shields.io/badge/log-android-orange.svg)
![](https://img.shields.io/badge/log-ios-orange.svg)
![](https://img.shields.io/badge/log-backend-orange.svg)
![](https://img.shields.io/badge/license-Apache2-blue.svg)
[![](https://img.shields.io/badge/readme-English-blue.svg)](https://github.com/Jacksgong/okcat)
[![](https://img.shields.io/badge/readme-中文-blue.svg)](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)
[![](https://img.shields.io/badge/pip-v1.4.0%20okcat-yellow.svg)](https://pypi.org/project/OkCat/1.4.0/)
[![Build Status](https://travis-ci.org/Jacksgong/okcat.svg?branch=master)](https://travis-ci.org/Jacksgong/okcat)

An powerful log processor.

[中文文档](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)

- The adb logcat handler is just update to JakeWharton's nice pidcat and I adapt it for more features.
- You can using this log processor with define you own `log-line-regex` and it can work for any log: iOS, Android, Backend, etc.  

## Features

> The most important feature is you can define any regex for any kind of log.

- highlight some keywords
![](https://github.com/Jacksgong/okcat/raw/master/arts/highlight-demo.png)
- trans msgs to some words
![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-msg-demo.png)
- trans tags to some words
![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-tag-demo.png)
- hide msg on logs
![](https://github.com/Jacksgong/okcat/raw/master/arts/hide-msg-demo.png)
- separate logs
![](https://github.com/Jacksgong/okcat/raw/master/arts/separate-demo.png)
- ignore msg on logs:
`when you provide such list, the msg start with provided msg will be ignored to printed`
- ignore tag on logs:
`when you provide such list, the tag in the list will be ignored to printed`

## How to Install

```shell
sudo pip install okcat
```

If you has not installed `pip` yet, you need to install it first:

1. `brew install python`
2. `sudo easy_install pip`

If you want to upgrade:

```shell
sudo pip install okcat --upgrade
```

## How to Use

---

#### Simplest test

1. Download: download [filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml) to the current folder or move to the `~/.okcat/` folder
2. Running: run the demo project on [Filedownloader](https://github.com/lingochamp/FileDownloader) repo to your Android phone and connect your Phone to computer
3. Execute: `okcat -y=filedownloader`
4. Done: now, you can checkout the colored logs on terminal, enjoy~

![](https://github.com/Jacksgong/okcat/raw/master/arts/demo.png)

---

#### 1. Define your config file(`.yml`)

You can create your own `.yaml` file as config file on `~/.okcat/` folder or the current folder you will execute `okcat` command, and the filename is free to choose, when you execute the okcat, we will ask you the configure file name you want to apply.

the following is demo of config file, Of course, you don't have to provide all configs such below, if you think which one is needed, just config that one.

```yml
# extends from exist yml file (provide only filename without `.yml` extension)
# from: exist-yml-name

# we will filter out logs with the provided package (name)
# this 'package' keyword is just using for android adb logcat
package: com.liulishuo.filedownloader.demo

# this 'log-line-regex' is just a regex for one line log
# now we support keyword: 'date' 'time' 'level' 'tag' 'process' 'thread' 'message'
# you don't have to provide all keyword, but you have to provide at least the 'message'
# such as: 'message="(\S*)"'
log-line-regex: 'date,time,process,thread,level,tag,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'

# on the case of filter logs from Android adb logcat, we using 'adb logcat -v brief -v threadtime' command to obtain logcat
# in the normal case you don't need ot provide this config, because there is a perfect one on the okcat internal
# but if you want to customize the regex log from adb logcat, it's free to define it such below
# adb-log-line-regex: 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'

# separator regex list
# you can provide multiple regex to separate serial logs
separator-regex-list:
  # on this case, if one line log match 'call start Url\[([^\]]*)\]' regex we will separate logs with \n and output a indie line with the '([^\]]*)' value as the title of separate
  - 'call start Url\[([^\]]*)\]'

# tag keyword list
# this list keyword is using for filter out which log need to be output
# all provided keyword will be using for compare with each line tag, if a line with tag not contain any keyword on 'tag-keyword-list' it will be ignore to output
tag-keyword-list:
  - 'FileDownloader'

# translate message map
# if a message on a line start with provide keyword on the 'trans-msg-map' we will add the value of the keyword on the start of the message, and the word of value will be colored to highlight it
trans-msg-map:
  # such as this case:
  # origin message: 'filedownloader:lifecycle:over xxx'
  # after translate: '| Task OVER | filedownloader:lifecycle:over xxx'
  'filedownloader:lifecycle:over': 'Task OVER'
  'fetch data with': 'Start Fetch'

# translate tag map
# if a tag on a line contain provide keyword on the 'trans-tag-map' we will add the value of the keyword on the start of the message, and the background of the value word will be colored to highlight it
trans-tag-map:
  # such as this case:
  # origin message: 'FileDownloader.DownloadTaskHunter  xxx'
  # after translate: 'FileDownloader.DownloadTaskHunter [Status Change] xxx'
  'DownloadTaskHunter': '[Status Change]'
  'ConnectTask': '[Request]'

# hide message list
# if a message on a line start with provide value on the 'hide-msg-list` and the length of the message is less than 100 word, it would be colored with gray to hide
hide-msg-list:
  # here we hide message start with 'notify progress' and '~~~callback' because it is too frequently to output and useless in most case
  - 'notify progress'
  - '~~~callback'

# highlight list
# if any value on the 'highlight-list' display on any message, the background of the value word would be colored to highlight it
highlight-list:
  - 'Path['
  - 'Url['
  - 'Tag['
  - 'range['

# ignore message list
# when you provide such list, the msg start with provided msg will be ignored to printed 
ignore-msg-list:
  - 'log start with this will be ignored' 

# ignore tag list
# when you provide such list, the tag in the list will be ignored to printed
ignore-tag-list:
  - 'tagToBeIgnored'
```

#### 2. Execute

You can just parse logcat from running adb:

```shell
okcat -y=your-conf-name
```

You also can parse your log file through:

```shell
okcat -y=your-conf-name your-log-path1 your-log-path2 your-log-path3 ... 
```

Simplest case for any developer:

```shell
okcat your.package.name
```

> Tips: You can use `command + k` on Terminal to flush all content on the session and start a new okcat parse instead of creating anthor new session.

## My Terminal Config

If you want to adapter the same theme like screenshot above, it's very easy:

- Firstly, please use [powerlevel9k](https://github.com/bhilburn/powerlevel9k) theme(Install the Powerlevel9k Theme and Powerline Fonts as the powerlevel9k repo readme doc said).
- Secondly, please config the [iTerm2-Neutron](https://github.com/Ch4s3/iTerm2-Neutron) color scheme.
- Thirdly, please config your shell(If you are using zsh, just add following code to the `~/.zshrc` file):
```
POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)
POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status time)
POWERLEVEL9K_TIME_FORMAT="%D{%H:%M:%S}"
POWERLEVEL9K_NODE_VERSION_BACKGROUND='022'
POWERLEVEL9K_SHORTEN_DIR_LENGTH=2
```

## Dev

Import to PyCharm, and Set the Project Structure:

![](https://github.com/Jacksgong/okcat/raw/master/arts/pycharm-build.jpg)

## LICENSE

```
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```


================================================
FILE: debug.sh
================================================
#!/bin/bash
pip uninstall okcat --yes
python3 setup.py install


================================================
FILE: demo-conf/demo-config.yml
================================================
log-line-regex: 'date,time,process,thread,level,tag,message = "(\S*) *(\S*) *(\d*) *(\d*) ([A-Z]) ([^:]*): (.*?)$"'

trans-msg-map:
  'Unknown': '未知'

trans-tag-map:
  'SystemServiceManager': '[System Service Manager]'
  'RescueParty': '[恢复部分]'

highlight-list:
  - 'complete'


================================================
FILE: demo-conf/demo-log-file.log
================================================
--------- beginning of system
06-26 15:18:59.546  1513  1513 I vold    : Vold 3.0 (the awakening) firing up
06-26 15:18:59.546  1513  1513 V vold    : Detected support for: ext4 vfat
06-26 15:18:59.551  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop5: No such device or address
06-26 15:18:59.581  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop1: No such device or address
06-26 15:18:59.621  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop6: No such device or address
06-26 15:18:59.662  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop2: No such device or address
06-26 15:18:59.700  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop4: No such device or address
06-26 15:18:59.810  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop0: No such device or address
06-26 15:18:59.841  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop7: No such device or address
06-26 15:18:59.880  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop3: No such device or address
06-26 15:18:59.915  1513  1513 D vold    : VoldNativeService::start() completed OK
06-26 15:18:59.932  1513  1517 D vold    : e4crypt_init_user0
06-26 15:18:59.933  1513  1517 D vold    : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 1
06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/system/users/0
06-26 15:18:59.933  1513  1516 I vold    : Found disk at /devices/pci0000:00/0000:00:08.0/virtio5/block/vdf but delaying scan due to secure keyguard
06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/misc/profiles/cur/0
06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/system_de/0
06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/misc_de/0
06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/vendor_de/0
06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/user_de/0
06-26 15:18:59.934  1513  1517 V vold    : /system/bin/vold_prepare_subdirs
06-26 15:18:59.935  1513  1517 V vold    :     prepare
06-26 15:18:59.935  1513  1517 V vold    :     0
06-26 15:18:59.935  1513  1517 V vold    :     1
06-26 15:18:59.949  1513  1517 D vold    : e4crypt_unlock_user_key 0 serial=0 token_present=0
06-26 15:18:59.949  1513  1517 E vold    : Failed to chmod /data/system_ce/0: No such file or directory
06-26 15:18:59.949  1513  1517 E vold    : Failed to chmod /data/misc_ce/0: No such file or directory
06-26 15:18:59.949  1513  1517 E vold    : Failed to chmod /data/media/0: No such file or directory
06-26 15:19:00.225  1513  1590 I Cryptfs : cryptfs_check_passwd
06-26 15:19:00.227  1513  1590 D Cryptfs : crypt_ftr->fs_size = 1638400
06-26 15:19:00.227  1513  1590 I Cryptfs : Using scrypt for cryptfs KDF
06-26 15:19:00.393  1572  1572 I android.hardware.wifi@1.0-service: Wifi Hal is booting up...
06-26 15:19:00.813  1513  1590 I Cryptfs : Extra parameters for dm_crypt: 1 allow_discards
06-26 15:19:00.822  1513  1516 D vold    : Disk at 252:0 changed
06-26 15:19:00.978  1513  1590 I Cryptfs : Password matches
06-26 15:19:00.980  1513  1590 D Cryptfs : test_mount_encrypted_fs(): Master key saved
06-26 15:19:00.990  1513  1590 I vold    : List of Keymaster HALs found:
06-26 15:19:00.990  1513  1590 I vold    : Keymaster HAL #1: SoftwareKeymasterDevice from Google SecurityLevel: SOFTWARE HAL : android.hardware.keymaster@3.0::IKeymasterDevice instance default
06-26 15:19:00.990  1513  1590 I vold    : Using SoftwareKeymasterDevice from Google for encryption.  Security level: SOFTWARE, HAL: android.hardware.keymaster@3.0::IKeymasterDevice/default
06-26 15:19:00.990  1513  1590 D Cryptfs : Password is default - restarting filesystem
06-26 15:19:01.032  1513  1590 D Cryptfs : unmounting /data succeeded
06-26 15:19:01.036  1513  1590 I vold    : [libfs_mgr]superblock s_max_mnt_count:65535,/dev/block/dm-0
06-26 15:19:01.037  1513  1590 I vold    : [libfs_mgr]Filesystem on /dev/block/dm-0 was not cleanly shutdown; state flags: 0x1, incompat feature flags: 0x46
06-26 15:19:01.315  1513  1590 I vold    : [libfs_mgr]check_fs(): mount(/dev/block/dm-0,/data,ext4)=0: Success
06-26 15:19:01.354  1513  1590 I vold    : [libfs_mgr]check_fs(): unmount(/data) succeeded
06-26 15:19:01.355  1513  1590 I vold    : [libfs_mgr]Running /system/bin/e2fsck on /dev/block/dm-0
06-26 15:19:01.601  1513  1590 I vold    : [libfs_mgr]e2fsck returned status 0x100
06-26 15:19:01.612  1513  1590 I vold    : [libfs_mgr]__mount(source=/dev/block/dm-0,target=/data,type=ext4)=0: Success
06-26 15:19:01.612  1513  1590 D Cryptfs : Just triggered post_fs_data
06-26 15:19:01.649  1513  1515 D vold    : e4crypt_init_user0
06-26 15:19:01.649  1513  1515 D vold    : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 1
06-26 15:19:01.649  1513  1515 D vold    : Preparing: /data/system/users/0
06-26 15:19:01.649  1513  1515 D vold    : Preparing: /data/misc/profiles/cur/0
06-26 15:19:01.650  1513  1515 D vold    : Preparing: /data/system_de/0
06-26 15:19:01.651  1513  1515 D vold    : Preparing: /data/misc_de/0
06-26 15:19:01.652  1513  1515 D vold    : Preparing: /data/vendor_de/0
06-26 15:19:01.652  1513  1515 D vold    : Preparing: /data/user_de/0
06-26 15:19:01.653  1513  1515 V vold    : /system/bin/vold_prepare_subdirs
06-26 15:19:01.653  1513  1515 V vold    :     prepare
06-26 15:19:01.653  1513  1515 V vold    :     
06-26 15:19:01.653  1513  1515 V vold    :     0
06-26 15:19:01.653  1513  1515 V vold    :     1
06-26 15:19:01.667  1513  1515 D vold    : e4crypt_unlock_user_key 0 serial=0 token_present=0
06-26 15:19:01.691  1513  1590 D Cryptfs : post_fs_data done
06-26 15:19:01.692  1513  1590 D Cryptfs : Just triggered restart_framework
06-26 15:19:01.987  1694  1694 I wificond: wificond is starting up...
06-26 15:19:02.140  1685  1685 I installd: installd firing up
06-26 15:19:03.232  1681  1681 D Zygote32Timing: BeginIcuCachePinning took to complete: 46ms
06-26 15:19:03.721  1681  1681 D Zygote32Timing: PreloadClasses took to complete: 489ms
06-26 15:19:03.829  1681  1681 D Zygote32Timing: PreloadResources took to complete: 107ms
06-26 15:19:03.864  1681  1681 D Zygote32Timing: ZygotePreload took to complete: 679ms
06-26 15:19:03.873  1681  1681 D Zygote32Timing: PostZygoteInitGC took to complete: 9ms
06-26 15:19:03.873  1681  1681 D Zygote32Timing: ZygoteInit took to complete: 694ms
06-26 15:19:04.001  1819  1819 I SystemServer: InitBeforeStartServices
06-26 15:19:04.002  1819  1819 I SystemServer: Entered the Android system server!
06-26 15:19:04.162  1819  1819 D SystemServerTiming: InitBeforeStartServices took to complete: 161ms
06-26 15:19:04.162  1819  1819 I SystemServer: StartServices
06-26 15:19:04.162  1819  1819 I SystemServer: Reading configuration...
06-26 15:19:04.162  1819  1819 I SystemServer: ReadingSystemConfig
06-26 15:19:04.164  1819  1819 D SystemServerTiming: ReadingSystemConfig took to complete: 2ms
06-26 15:19:04.164  1819  1819 I SystemServer: StartInstaller
06-26 15:19:04.164  1819  1819 I SystemServiceManager: Starting com.android.server.pm.Installer
06-26 15:19:04.165  1819  1832 D SystemServerInitThreadPool: Started executing ReadingSystemConfig
06-26 15:19:04.169  1819  1819 D SystemServerTiming: StartInstaller took to complete: 5ms
06-26 15:19:04.169  1819  1819 I SystemServer: DeviceIdentifiersPolicyService
06-26 15:19:04.169  1819  1819 I SystemServiceManager: Starting com.android.server.os.DeviceIdentifiersPolicyService
06-26 15:19:04.171  1819  1819 D SystemServerTiming: DeviceIdentifiersPolicyService took to complete: 2ms
06-26 15:19:04.171  1819  1819 I SystemServer: StartActivityManager
06-26 15:19:04.171  1819  1819 I SystemServiceManager: Starting com.android.server.am.ActivityManagerService$Lifecycle
06-26 15:19:04.197  1819  1819 I ActivityManager: Memory class: 384
06-26 15:19:04.207  1819  1832 D SystemServerInitThreadPool: Finished executing ReadingSystemConfig
06-26 15:19:04.216  1819  1819 D BatteryStatsImpl: Reading daily items from /data/system/batterystats-daily.xml
06-26 15:19:04.230  1819  1840 E BatteryExternalStatsWorker: no controller energy info supplied for telephony
06-26 15:19:04.233  1819  1840 I KernelUidCpuFreqTimeReader: mPerClusterTimesAvailable=false
06-26 15:19:04.235  1819  1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/time_in_state
06-26 15:19:04.235  1819  1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/concurrent_active_time
06-26 15:19:04.236  1819  1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/concurrent_policy_time
06-26 15:19:04.236  1819  1840 E KernelCpuSpeedReader: Failed to read cpu-freq: /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state (No such file or directory)
06-26 15:19:04.236  1819  1840 W KernelMemoryBandwidthStats: No kernel memory bandwidth stats available
06-26 15:19:04.252  1819  1819 W AppOps  : Unknown attribute in 'op' tag: n
06-26 15:19:04.256  1819  1819 I chatty  : uid=1000 system_server identical 163 lines
06-26 15:19:04.256  1819  1819 W AppOps  : Unknown attribute in 'op' tag: n
06-26 15:19:04.266  1819  1819 I IntentFirewall: Read new rules (A:0 B:0 S:0)
06-26 15:19:04.277  1819  1819 D AppOps  : AppOpsService published
06-26 15:19:04.277  1819  1819 D SystemServerTiming: StartActivityManager took to complete: 106ms
06-26 15:19:04.277  1819  1819 I SystemServer: StartPowerManager
06-26 15:19:04.277  1819  1819 I SystemServiceManager: Starting com.android.server.power.PowerManagerService
06-26 15:19:04.288  1819  1819 D SystemServerTiming: StartPowerManager took to complete: 11ms
06-26 15:19:04.288  1819  1819 I SystemServer: InitPowerManagement
06-26 15:19:04.290  1819  1819 D SystemServerTiming: InitPowerManagement took to complete: 2ms
06-26 15:19:04.290  1819  1819 I SystemServer: StartRecoverySystemService
06-26 15:19:04.290  1819  1819 I SystemServiceManager: Starting com.android.server.RecoverySystemService
06-26 15:19:04.291  1819  1819 D SystemServerTiming: StartRecoverySystemService took to complete: 1ms
06-26 15:19:04.293  1819  1819 W RescueParty: Failed to determine if device was on USB
06-26 15:19:04.293  1819  1819 W RescueParty: java.io.FileNotFoundException: /sys/class/android_usb/android0/state (No such file or directory)
06-26 15:19:04.293  1819  1819 W RescueParty:   at java.io.FileInputStream.open0(Native Method)
06-26 15:19:04.293  1819  1819 W RescueParty:   at java.io.FileInputStream.open(FileInputStream.java:231)
06-26 15:19:04.293  1819  1819 W RescueParty:   at java.io.FileInputStream.<init>(FileInputStream.java:165)
06-26 15:19:04.293  1819  1819 W RescueParty:   at android.os.FileUtils.readTextFile(FileUtils.java:514)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.RescueParty.isUsbActive(RescueParty.java:348)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.RescueParty.isDisabled(RescueParty.java:88)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.RescueParty.noteBoot(RescueParty.java:107)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.SystemServer.startBootstrapServices(SystemServer.java:587)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.SystemServer.run(SystemServer.java:429)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.SystemServer.main(SystemServer.java:294)
06-26 15:19:04.293  1819  1819 W RescueParty:   at java.lang.reflect.Method.invoke(Native Method)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)
06-26 15:19:04.296  1819  1819 W RescueParty: Noticed 1 events for UID 0 in last 7 sec
06-26 15:19:04.296  1819  1819 I SystemServer: StartLightsService
06-26 15:19:04.296  1819  1819 I SystemServiceManager: Starting com.android.server.lights.LightsService
06-26 15:19:04.297  1819  1819 D SystemServerTiming: StartLightsService took to complete: 1ms
06-26 15:19:04.297  1819  1819 I SystemServer: StartSidekickService
06-26 15:19:04.297  1819  1819 D SystemServerTiming: StartSidekickService took to complete: 0ms
06-26 15:19:04.297  1819  1819 I SystemServer: StartDisplayManager
06-26 15:19:04.297  1819  1819 I SystemServiceManager: Starting com.android.server.display.DisplayManagerService
06-26 15:19:04.301  1819  1819 D SystemServerTiming: StartDisplayManager took to complete: 4ms
06-26 15:19:04.301  1819  1819 I SystemServer: WaitForDisplay
06-26 15:19:04.301  1819  1819 I SystemServiceManager: Starting phase 100


================================================
FILE: demo-conf/filedownloader.yml
================================================
# we will filter out logs with the provided package (name)
# this 'package' keyword is just using for android adb logcat
package: com.liulishuo.filedownloader.demo

# this 'log-line-regex' is just a regex for one line log
# now we support keyword: 'date' 'time' 'level' 'tag' 'process' 'thread' 'message'
# you don't have to provide all keyword, but you have to provide at least the 'message'
# such as: 'message="(\S*)"'
log-line-regex: 'date,time,level,tag,process,thread,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'

# on the case of filter logs from Android adb logcat, we using 'adb logcat -v brief -v threadtime' command to obtain logcat
# in the normal case you don't need ot provide this config, because there is a perfect one on the okcat internal
# but if you want to customize the regex log from adb logcat, it's free to define it such below
# adb-log-line-regex: 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'

# separator regex list
# you can provide multiple regex to separate serial logs
separator-regex-list: 
  # on this case, if one line log match 'call start Url\[([^\]]*)\]' regex we will separate logs with \n and output a indie line with the '([^\]]*)' value as the title of separate
  - 'call start Url\[([^\]]*)\]'

# tag keyword list
# this list keyword is using for filter out which log need to be output
# all provided keyword will be using for compare with each line tag, if a line with tag not contain any keyword on 'tag-keyword-list' it will be ignore to output
tag-keyword-list:
  - 'FileDownloader'

# translate message map
# if a message on a line start with provide keyword on the 'trans-msg-map' we will add the value of the keyword on the start of the message, and the word of value will be colored to highlight it
trans-msg-map:
  # such as this case:
  # origin message: 'filedownloader:lifecycle:over xxx'
  # after translate: '| Task OVER | filedownloader:lifecycle:over xxx'
  'filedownloader:lifecycle:over': 'Task OVER'
  'fetch data with': 'Start Fetch'

# translate tag map
# if a tag on a line contain provide keyword on the 'trans-tag-map' we will add the value of the keyword on the start of the message, and the background of the value word will be colored to highlight it
trans-tag-map:
  # such as this case:
  # origin message: 'FileDownloader.DownloadTaskHunter  xxx'
  # after translate: 'FileDownloader.DownloadTaskHunter [Status Change] xxx'
  'DownloadTaskHunter': '[Status Change]'
  'ConnectTask': '[Request]'

# hide message list
# if a message on a line start with provide value on the 'hide-msg-list` and the length of the message is less than 100 word, it would be colored with gray to hide
hide-msg-list:
  # here we hide message start with 'notify progress' and '~~~callback' because it is too frequently to output and useless in most case
  - 'notify progress'
  - '~~~callback'

# highlight list
# if any value on the 'highlight-list' display on any message, the background of the value word would be colored to highlight it
highlight-list:
  - 'Path['
  - 'Url['
  - 'Tag['
  - 'range['


================================================
FILE: okcat/__init__.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import argparse
from sys import argv

from okcat.adb import Adb
from okcat.helper import LOG_LEVELS, is_path
from okcat.logfile_parser import LogFileParser
from okcat.terminalcolor import print_tips, print_blue, print_warn, print_header, print_exit

__author__ = 'JacksGong'
__version__ = '1.4.0'
__description__ = 'This python script used for combine several Android projects to one project.'


def main():
    print("-------------------------------------------------------")
    print("                  OkCat v" + __version__)
    print("")
    print("Thanks for using okcat! Now, the doc is available on: ")
    print_blue("        https://github.com/Jacksgong/okcat")
    print("")
    print("                   Have Fun!")
    print("-------------------------------------------------------")

    parser = argparse.ArgumentParser(description='Filter logcat by package name')
    parser.add_argument('package_or_path', nargs='*',
                        help='This can be Application package name(s) or log file path(if the file from path is exist)')
    parser.add_argument('-y', '--yml_file_name', dest='yml', help='Using yml file you config on ~/.okcat folder')
    parser.add_argument('--hide-same-tags', dest='hide_same_tags', action='store_true',
                        help='Do not display the same tag name')

    # following args are just for parser
    # parser.add_argument('-k', '--keyword', dest='keyword', action='append', help='You can filter you care about log by this keyword(s)')

    # following args are just for adb
    parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=23,
                        help='Width of log tag')
    parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS + LOG_LEVELS.lower(),
                        default='V', help='Minimum level to be displayed')
    parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='Color garbage collection')
    parser.add_argument('--current', dest='current_app', action='store_true',
                        help='Filter logcat by current running app')
    parser.add_argument('-s', '--serial', dest='device_serial', help='Device serial number (adb -s option)')
    parser.add_argument('-d', '--device', dest='use_device', action='store_true',
                        help='Use first device for log input (adb -d option)')
    parser.add_argument('-e', '--emulator', dest='use_emulator', action='store_true',
                        help='Use first emulator for log input (adb -e option)')
    parser.add_argument('-c', '--clear', dest='clear_logcat', action='store_true',
                        help='Clear the entire log before running')
    parser.add_argument('-t', '--tag', dest='tag', action='append', help='Filter output by specified tag(s)')
    parser.add_argument('-tk', '--tag_keywords', dest='tag_keywords', action='append',
                        help='Filter output by specified tag keyword(s)')
    parser.add_argument('-i', '--ignore-tag', dest='ignored_tag', action='append',
                        help='Filter output by ignoring specified tag(s)')
    parser.add_argument('-a', '--all', dest='all', action='store_true', default=False,
                        help='Print all log messages')

    # help
    if len(argv) == 2 and argv[1] == 'help':
        exit()

    args = parser.parse_args()

    file_paths = []
    candidate_path = args.package_or_path
    for path in candidate_path:
        if is_path(path):
            file_paths.append(path)

    if file_paths:
        if args.yml is None:
            print("")
            print_exit("Please using '-y=conf-name' to provide config file to parse this log file.")
            print("The config file is very very simple! More detail about config file please move to : https://github.com/Jacksgong/okcat")
            print("")
            print("-------------------------------------------------------")
            exit()

        parser = LogFileParser(file_paths, args.hide_same_tags)
        parser.setup(args.yml)
        parser.process()
    else:
        is_interrupt_by_user = False

        _adb = Adb()
        _adb.setup(args)
        try:
            _adb.loop()
        except KeyboardInterrupt:
            is_interrupt_by_user = True

        if not is_interrupt_by_user:
            print_warn('ADB CONNECTION IS LOST.')


================================================
FILE: okcat/adb.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from os.path import exists

from okcat.confloader import ConfLoader
from okcat.helper import LOG_LEVELS_MAP, get_conf_path, handle_home_case, print_unicode
from okcat.logprocessor import LogProcessor, indent_wrap
from okcat.logregex import LogRegex
from okcat.terminalcolor import termcolor, RED, RESET, YELLOW, GREEN, colorize, WHITE, allocate_color

__author__ = 'JacksGong'

# Script to highlight adb logcat output for console
# Originally written by Jeff Sharkey, http://jsharkey.org/
# Piping detection and popen() added by other Android team members
# Package filtering and output improvements by Jake Wharton, http://jakewharton.com
# Package adapt for okcat by Jacks Gong, https://jacksgong.com

import sys
import re
import subprocess
from subprocess import PIPE, STDOUT

# noinspection Annotator
PID_LINE = re.compile(r'^\w+\s+(\w+)\s+\w+\s+\w+\s+\w+\s+\w+\s+\w+\s+\w\s([\w|\.|\/]+)')
PID_START = re.compile(r'^.*: Start proc ([a-zA-Z0-9._:]+) for ([a-z]+ [^:]+): pid=(\d+) uid=(\d+) gids=(.*)$')
PID_START_5_1 = re.compile(r'^.*: Start proc (\d+):([a-zA-Z0-9._:]+)/[a-z0-9]+ for (.*)$')
PID_START_DALVIK = re.compile(
    r'^E/dalvikvm\(\s*(\d+)\): >>>>> ([a-zA-Z0-9._:]+) \[ userId:0 \| appId:(\d+) \]$')
PID_KILL = re.compile(r'^Killing (\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$')
PID_LEAVE = re.compile(r'^No longer want ([a-zA-Z0-9._:]+) \(pid (\d+)\): .*$')
PID_DEATH = re.compile(r'^Process ([a-zA-Z0-9._:]+) \(pid (\d+)\) has died.?$')

ADB_LOG_REGEX_EXP = 'date,time,process,thread,level,tag,message="(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): *(.*?)$"'

BUG_LINE = re.compile(r'.*nativeGetEnabledTags.*')
BACKTRACE_LINE = re.compile(r'^#(.*?)pc\s(.*?)$')
RULES = {
    # StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1
    re.compile(r'^(StrictMode policy violation)(; ~duration=)(\d+ ms)')
    : r'%s\1%s\2%s\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET),
}


class Adb:
    all = None
    min_level = None
    package_name = None
    tag = None
    header_size = None
    ignored_tag = None

    log_regex = None
    catchall_package = None
    named_processes = None
    pids = None

    adb = None
    processor = None

    def __init__(self):
        pass

    def setup(self, args):
        self.processor = LogProcessor(args.hide_same_tags)

        self.min_level = LOG_LEVELS_MAP[args.min_level.upper()]
        self.all = args.all
        self.ignored_tag = args.ignored_tag
        self.tag = args.tag

        self.package_name = args.package_or_path
        self.processor.setup_condition(tag_keywords=args.tag_keywords)
        if args.yml is not None:
            conf_file_path = get_conf_path(args.yml)
            if not exists(handle_home_case(conf_file_path)):
                exit('you provide conf file path: ' + conf_file_path + ' is not exist!')

            conf_loader = ConfLoader()
            conf_loader.load(conf_file_path)

            yml_package = conf_loader.get_package()
            if yml_package is not None:
                self.package_name.append(yml_package)

            yml_adb_log_regex = conf_loader.get_adb_log_line_regex()
            if yml_adb_log_regex is not None:
                self.log_regex = LogRegex(yml_adb_log_regex)

            self.processor.setup_condition(tag_keywords=conf_loader.get_tag_keyword_list())
            self.processor.setup_trans(trans_msg_map=conf_loader.get_trans_msg_map(),
                                       trans_tag_map=conf_loader.get_trans_tag_map(),
                                       hide_msg_list=conf_loader.get_hide_msg_list())
            self.processor.setup_ignore(ignore_msg_list=conf_loader.get_ignore_msg_list(),
                                        ignore_tag_list=conf_loader.get_ignore_tag_list())
            self.processor.setup_highlight(highlight_list=conf_loader.get_highlight_list())
            self.processor.setup_separator(separator_rex_list=conf_loader.get_separator_regex_list())

        if self.log_regex is None:
            self.log_regex = LogRegex(ADB_LOG_REGEX_EXP)

        base_adb_command = ['adb']
        if args.device_serial:
            base_adb_command.extend(['-s', args.device_serial])
        if args.use_device:
            base_adb_command.append('-d')
        if args.use_emulator:
            base_adb_command.append('-e')

        if args.current_app:
            system_dump_command = base_adb_command + ["shell", "dumpsys", "activity", "activities"]
            system_dump = subprocess.Popen(system_dump_command, stdout=PIPE, stderr=PIPE).communicate()[0]
            running_package_name = re.search(".*TaskRecord.*A[= ]([^ ^}]*)", system_dump).group(1)
            self.package_name.append(running_package_name)

        if len(self.package_name) == 0:
            self.all = True

        # Store the names of packages for which to match all processes.
        self.catchall_package = list(filter(lambda package: package.find(":") == -1, self.package_name))
        # Store the name of processes to match exactly.
        named_processes = filter(lambda package: package.find(":") != -1, self.package_name)
        # Convert default process names from <package>: (cli notation) to <package> (android notation) in the exact names match group.
        self.named_processes = map(lambda package: package if package.find(":") != len(package) - 1 else package[:-1],
                                   named_processes)

        self.header_size = args.tag_width + 1 + 3 + 1  # space, level, space

        # Only enable GC coloring if the user opted-in
        if args.color_gc:
            # GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms
            key = re.compile(
                r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed <?\d+.)(, \d+% free \d+./\d+., )(paused \d+ms(?:\+\d+ms)?)')
            val = r'\1%s\2%s\3%s\4%s' % (termcolor(GREEN), RESET, termcolor(YELLOW), RESET)

            RULES[key] = val

        adb_command = base_adb_command[:]
        adb_command.append('logcat')
        adb_command.extend(['-v', 'brief'])
        adb_command.extend(['-v', 'threadtime'])

        # Clear log before starting logcat
        if args.clear_logcat:
            adb_clear_command = list(adb_command)
            adb_clear_command.append('-c')
            adb_clear = subprocess.Popen(adb_clear_command)

            while adb_clear.poll() is None:
                pass

        if sys.stdin.isatty():
            self.adb = subprocess.Popen(adb_command, stdout=PIPE, stderr=STDOUT, stdin=PIPE)
        else:
            self.adb = FakeStdinProcess()

        self.pids = set()

        ps_command = base_adb_command + ['shell', 'ps']
        ps_pid = subprocess.Popen(ps_command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
        while True:
            try:
                line = ps_pid.stdout.readline().decode('utf-8', 'replace').strip()
            except KeyboardInterrupt:
                break
            if len(line) == 0:
                break

            pid_match = PID_LINE.match(line)
            if pid_match is not None:
                pid = pid_match.group(1)
                proc = pid_match.group(2)
                if proc in self.catchall_package:
                    self.pids.add(pid)

    def loop(self):
        app_pid = None

        while self.adb.poll() is None:
            try:
                line = self.adb.stdout.readline()
            except KeyboardInterrupt:
                break
            if len(line) == 0:
                break

            line = line.decode('utf-8', 'replace').strip()
            if len(line) == 0:
                continue

            bug_line = BUG_LINE.match(line)
            if bug_line is not None:
                continue

            date, time, level, tag, owner, thread, message = self.log_regex.parse(line)
            if message is None:
                # print 'message is none with %s' % line
                continue

            tag = tag.strip()
            start = parse_start_proc(line)
            if start:
                line_package, target, line_pid, line_uid, line_gids = start
                if self.match_packages(line_package):
                    self.pids.add(line_pid)

                    app_pid = line_pid

                    linebuf = '\n'
                    linebuf += colorize(' ' * (self.header_size - 1), bg=WHITE)
                    linebuf += indent_wrap(' Process %s created for %s\n' % (line_package, target))
                    linebuf += colorize(' ' * (self.header_size - 1), bg=WHITE)
                    linebuf += ' PID: %s   UID: %s   GIDs: %s' % (line_pid, line_uid, line_gids)
                    linebuf += '\n'
                    print(linebuf)

            dead_pid, dead_pname = self.parse_death(tag, message)
            if dead_pid:
                self.pids.remove(dead_pid)
                linebuf = '\n'
                linebuf += colorize(' ' * (self.header_size - 1), bg=RED)
                linebuf += ' Process %s (PID: %s) ended' % (dead_pname, dead_pid)
                linebuf += '\n'
                print(linebuf)

            # Make sure the backtrace is printed after a native crash
            if tag == 'DEBUG':
                bt_line = BACKTRACE_LINE.match(message.lstrip())
                if bt_line is not None:
                    message = message.lstrip()
                    owner = app_pid

            # print '%s %s %s' % (owner, self.pids, tag)
            if not self.all and owner not in self.pids:
                continue
            if level in LOG_LEVELS_MAP and LOG_LEVELS_MAP[level] < self.min_level:
                continue
            if self.ignored_tag and tag_in_tags_regex(tag, self.ignored_tag):
                continue
            if self.tag and not tag_in_tags_regex(tag, self.tag):
                continue

            msg_key, linebuf, match_precondition = self.processor.process_decode_content(line, time, level, tag, owner,
                                                                                         thread,
                                                                                         message)
            if not match_precondition or linebuf is None:
                continue

            if msg_key is not None:
                print('')
                print_unicode(u''.join(colorize(msg_key + ": ", fg=allocate_color(msg_key))).encode('utf-8').lstrip())

            print_unicode(u''.join(linebuf).encode('utf-8').lstrip())

    def match_packages(self, token):
        if len(self.package_name) == 0:
            return True
        if token in self.named_processes:
            return True
        index = token.find(':')
        return (token in self.catchall_package) if index == -1 else (token[:index] in self.catchall_package)

    def parse_death(self, _tag, _message):
        if _tag != 'ActivityManager':
            return None, None
        kill = PID_KILL.match(_message)
        if kill:
            _pid = kill.group(1)
            package_line = kill.group(2)
            if self.match_packages(package_line) and _pid in self.pids:
                return _pid, package_line
        leave = PID_LEAVE.match(_message)
        if leave:
            _pid = leave.group(2)
            package_line = leave.group(1)
            if self.match_packages(package_line) and _pid in self.pids:
                return _pid, package_line
        death = PID_DEATH.match(_message)
        if death:
            _pid = death.group(2)
            package_line = death.group(1)
            if self.match_packages(package_line) and _pid in self.pids:
                return _pid, package_line
        return None, None  # This is a ducktype of the subprocess.Popen object


class FakeStdinProcess:
    def __init__(self):
        self.stdout = sys.stdin

    @staticmethod
    def poll():
        return None


def parse_start_proc(_line):
    _start = PID_START_5_1.match(_line)
    if _start is not None:
        _line_pid, _line_package, _target = _start.groups()
        return _line_package, _target, _line_pid, '', ''
    _start = PID_START.match(_line)
    if _start is not None:
        _line_package, _target, _line_pid, _line_uid, _line_gids = _start.groups()
        return _line_package, _target, _line_pid, _line_uid, _line_gids
    _start = PID_START_DALVIK.match(_line)
    if _start is not None:
        _line_pid, _line_package, _line_uid = _start.groups()
        return _line_package, '', _line_pid, _line_uid, ''
    return None


def tag_in_tags_regex(_tag, tags):
    return any(re.match(r'^' + t + r'$', _tag) for t in map(str.strip, tags))


================================================
FILE: okcat/confloader.py
================================================
#!/usr/bin/env python
# coding: utf-8

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import yaml

from okcat.helper import handle_home_case, get_conf_path

__author__ = 'JacksGong'


class ConfLoader:
    yml_conf = None
    from_yml_conf = None

    def __init__(self):
        pass

    def load(self, yml_file_path):
        with open(handle_home_case(yml_file_path), 'r') as stream:
            try:
                self.yml_conf = yaml.load(stream, yaml.SafeLoader)
                from_conf_path = self.get_from()
                if from_conf_path is not None:
                    self.from_yml_conf = ConfLoader()
                    self.from_yml_conf.load(get_conf_path(from_conf_path))

                    # print(u'find yml configuration on %s:' % yml_file_path)
                    # self.dump()

            except yaml.YAMLError as exc:
                print(exc)

    def get_from(self):
        return self.get_value('from')

    def get_package(self):
        return self.get_value('package')

    def get_tag_keyword_list(self):
        return self.get_value('tag-keyword-list')

    def get_trans_msg_map(self):
        return self.get_value('trans-msg-map')

    def get_trans_tag_map(self):
        return self.get_value('trans-tag-map')

    def get_hide_msg_list(self):
        return self.get_value('hide-msg-list')

    def get_ignore_msg_list(self):
        return self.get_value('ignore-msg-list')

    def get_ignore_tag_list(self):
        return self.get_value('ignore-tag-list')

    def get_highlight_list(self):
        return self.get_value('highlight-list')

    def get_log_line_regex(self):
        return self.get_value('log-line-regex')

    def get_adb_log_line_regex(self):
        return self.get_value('adb-log-line-regex')

    def get_separator_regex_list(self):
        return self.get_value('separator-regex-list')

    def get_value(self, keyword):
        if keyword not in self.yml_conf or self.yml_conf[keyword] is None:
            if keyword != 'from' and self.from_yml_conf is not None:
                return self.from_yml_conf.get_value(keyword)
            else:
                return None

        origin = self.yml_conf[keyword]
        if self.from_yml_conf is not None and self.from_yml_conf.get_value(keyword) is not None:
            values  = self.from_yml_conf.get_value(keyword)
            if type(origin) == list:
                origin.extend(values)
            if type(origin) == dict:
                origin.update(values)

        return origin

    def dump(self):
        print('from: %s' % self.get_from())
        print('package: %s' % self.get_package())
        print('log-line-regex: %s' % self.get_log_line_regex())
        print('adb-log-line-regex: %s' % self.get_adb_log_line_regex())
        self.dump_list('tag-keyword-list')
        self.dump_unicode_map('trans-msg-map')
        self.dump_unicode_map('trans-tag-map')
        self.dump_list('hide-msg-list')
        self.dump_list('ignore-msg-list')
        self.dump_list('ignore-tag-list')
        self.dump_list('highlight-list')
        self.dump_list('separator-regex-list')

    def dump_unicode_map(self, map_key):
        unicode_map = self.get_value(map_key)
        if unicode_map is None:
            print('%s: None' % map_key)
        else:
            print('%s:' % map_key)
            for key in unicode_map:
                print(u'    "%s" : "%s"' % (key, unicode_map[key]))

    def dump_list(self, list_key):
        cur_list = self.get_value(list_key)
        if cur_list is None:
            print('%s: None' % list_key)
        else:
            print('%s: ' % list_key)
            for value in cur_list:
                print('    - %s' % value)


================================================
FILE: okcat/helper.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re
import sys

from os import environ, getcwd

from os.path import exists

__author__ = 'JacksGong'

LOG_LEVELS = 'VDIWEF'
LOG_LEVELS_MAP = dict([(LOG_LEVELS[i], i) for i in range(len(LOG_LEVELS))])

NO_HOME_PATH = re.compile(r'~/(.*)')
HOME_PATH = environ['HOME']


# get the home case path
def handle_home_case(path):
    path = path.strip()
    if path.startswith('~/'):
        path = HOME_PATH + '/' + NO_HOME_PATH.match(path).groups()[0]
    return path


def is_path(path):
    if path.startswith('/') or path.startswith('~/') or path.startswith('./'):
        return True

    if exists(path):
        return True
    return False


def get_conf_path(conf_name):
    if not conf_name.endswith('.yml'):
        conf_name = conf_name + '.yml'

    cur_path_yml = '%s/%s' % (getcwd(), conf_name)
    if exists(cur_path_yml):
        result = cur_path_yml
    else:
        result = '~/.okcat/' + conf_name

    print('using config on %s' % result)
    return result


def print_unicode(line):
    if sys.version_info >= (3, 0):
        print(bytes.decode(line))
    else:
        print(line)

def line_rstrip(line):
    if sys.version_info >= (3, 0):
        return line.rstrip()
    else:
        try:
            return line.decode('utf-8').rstrip()
        except UnicodeDecodeError:
            return line.rstrip()

================================================
FILE: okcat/logfile_parser.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re
from os.path import exists

from okcat.confloader import ConfLoader
from okcat.helper import get_conf_path, print_unicode
from okcat.logprocessor import LogProcessor
from okcat.terminalcolor import colorize, allocate_color

TIME_REGEX = r'\d{2}-\d{2} .*\d{2}:\d{2}:\d{2}\.\d+'


class LogFileParser:
    filePaths = []
    valid = False
    processor = None
    hideSameTags = None
    logStreams = []
    cacheLines = []
    lineTimes = []

    def __init__(self, file_paths, hide_same_tags):
        self.filePaths = file_paths
        self.hideSameTags = hide_same_tags

    def setup(self, yml_file_name):
        for path in self.filePaths:
            if not exists(path):
                exit("log path: %s is not exist!" % path)
        self.processor = LogProcessor(self.hideSameTags)

        loader = ConfLoader()
        loader.load(get_conf_path(yml_file_name))

        self.processor.setup_trans(trans_msg_map=loader.get_trans_msg_map(),
                                   trans_tag_map=loader.get_trans_tag_map(),
                                   hide_msg_list=loader.get_hide_msg_list())
        self.processor.setup_separator(separator_rex_list=loader.get_separator_regex_list())
        self.processor.setup_highlight(highlight_list=loader.get_highlight_list())
        self.processor.setup_condition(tag_keywords=loader.get_tag_keyword_list())
        log_line_regex = loader.get_log_line_regex()
        if log_line_regex is None:
            log_line_regex = 'date,time,process,thread,level,tag,message = "(.\S*) *(.\S*) *(\d*) *(\d*) *([A-Z]) *([^:]*): (.*?)$"'
            print("you don't provide 'log_line-regex' for parse each line on file, so we use this one as default:")
            print(log_line_regex + "\n")
        self.processor.setup_regex_parser(regex_exp=log_line_regex)

    def color_line(self, line):
        msg_key, line_buf, match_precondition = self.processor.process(line)

        if not match_precondition:
            return

        if msg_key is not None:
            print('')
            print_unicode(u''.join(colorize(msg_key + ": ", fg=allocate_color(msg_key))).encode('utf-8').lstrip())

        print_unicode(u''.join(line_buf).encode('utf-8').lstrip())

    def popup_cache_line(self, popup_index):
        need_read_stream = self.logStreams[popup_index]
        new_line = need_read_stream.readline()
        if new_line:
            match_result = re.search(TIME_REGEX, new_line)
            if match_result:
                self.lineTimes.insert(popup_index, match_result.group())
                self.cacheLines.insert(popup_index, new_line)
            else:
                self.color_line(new_line)
                self.popup_cache_line(popup_index)
        else:
            need_read_stream.close()
            self.logStreams.pop(popup_index)

    def process(self):
        origin_index = 0
        for path in self.filePaths:
            stream = open(path, "r")
            self.logStreams.append(stream)
            self.popup_cache_line(origin_index)
            origin_index += 1

        while self.cacheLines:
            min_index = self.lineTimes.index(min(self.lineTimes))
            self.lineTimes.pop(min_index)
            selected_line = self.cacheLines.pop(min_index)
            self.color_line(selected_line)
            self.popup_cache_line(min_index)


================================================
FILE: okcat/logprocessor.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re

from okcat.helper import line_rstrip
from okcat.logregex import LogRegex
from okcat.logseparator import LogSeparator
from okcat.terminalcolor import allocate_color, colorize, TAGTYPES, termcolor, BLACK, RESET
from okcat.trans import Trans

__author__ = 'JacksGong'

TIME_WIDTH = 12
THREAD_WIDTH = 12
TAG_WIDTH = 23

width = -1
# noinspection PyBroadException
try:
    # Get the current terminal width
    import fcntl, termios, struct

    h, width = struct.unpack('hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0)))
except:
    pass

header_size = TAG_WIDTH + 1 + 3 + 1  # space, level, space


def indent_wrap(message):
    return message


def keywords_regex(content, keywords):
    return any(re.match(r'.*' + t + r'.*', content) for t in map(str.strip, keywords))


class LogProcessor:
    hide_same_tags = None
    trans = None
    tag_keywords = None
    line_keywords = None
    separator = None
    regex_parser = None
    highlight_list = None
    # target_time = None

    ignore_msg_list = None
    ignore_tag_list = None

    # tmp
    last_msg_key = None
    last_tag = None
    pre_line_match = True

    def __init__(self, hide_same_tags):
        self.hide_same_tags = hide_same_tags

    def setup_trans(self, trans_msg_map, trans_tag_map, hide_msg_list):
        self.trans = Trans(trans_msg_map, trans_tag_map, hide_msg_list)

    def setup_ignore(self, ignore_msg_list, ignore_tag_list):
        self.ignore_msg_list = ignore_msg_list
        self.ignore_tag_list = ignore_tag_list

    def setup_separator(self, separator_rex_list):
        if separator_rex_list is not None:
            self.separator = LogSeparator(separator_rex_list)

    def setup_highlight(self, highlight_list):
        self.highlight_list = highlight_list

    def setup_condition(self, tag_keywords, line_keywords=None):
        self.tag_keywords = tag_keywords
        self.line_keywords = line_keywords

    def setup_regex_parser(self, regex_exp):
        self.regex_parser = LogRegex(regex_exp)

    def process(self, origin_line):
        origin_line = line_rstrip(origin_line)

        if len(origin_line.strip()) <= 0:
            return None, None, False

        if self.regex_parser is None:
            return None, None, False

        date, time, level, tag, process, thread, message = self.regex_parser.parse(origin_line)
        if message is None:
            message = origin_line

        return self.process_decode_content(origin_line, time, level, tag, process, thread, message)

    # noinspection PyUnusedLocal
    def process_decode_content(self, line, time, level, tag, process, thread, message):

        match_condition = True

        # filter
        if self.tag_keywords is not None and tag is not None:
            if not keywords_regex(tag, self.tag_keywords):
                match_condition = False
                self.pre_line_match = False
            else:
                self.pre_line_match = True



        if self.line_keywords is not None:
            if not keywords_regex(line, self.line_keywords):
                match_condition = False
                self.pre_line_match = False
            else:
                self.pre_line_match = True

        if match_condition and tag is None and not self.pre_line_match:
            match_condition = False

        # if 'special world' in line:
        #     match_precondition = True

        if self.ignore_msg_list is not None:
            for ignore_msg in self.ignore_msg_list:
                if message.startswith(ignore_msg):
                    match_condition = False

        if self.ignore_tag_list is not None:
            if tag in self.ignore_tag_list:
                match_condition = False

        if not match_condition:
            return None, None, None

        msgkey = None
        # the handled current line
        linebuf = ''

        # time
        if time is not None:
            time = time[-TIME_WIDTH:].rjust(TIME_WIDTH)
            linebuf += time
            linebuf += ' '
        elif self.regex_parser.is_contain_time():
            linebuf += ' ' * TIME_WIDTH
            linebuf += ' '

        # thread
        if thread is not None:
            thread = thread.strip()
            thread = thread[-THREAD_WIDTH:].rjust(THREAD_WIDTH)
            linebuf += thread
            linebuf += ' '
        elif self.regex_parser.is_contain_thread():
            linebuf += ' ' * THREAD_WIDTH
            linebuf += ' '

        # tag
        if tag is not None and (not self.hide_same_tags or tag != self.last_tag):
            self.last_tag = tag
            tag = tag.strip()
            color = allocate_color(tag)
            tag = tag.strip()
            tag = tag[-TAG_WIDTH:].rjust(TAG_WIDTH)
            linebuf += colorize(tag, fg=color)
            linebuf += ' '
        elif self.regex_parser.is_contain_tag():
            linebuf += ' ' * TAG_WIDTH
            linebuf += ' '

        # level
        if level is not None:
            if level in TAGTYPES:
                linebuf += TAGTYPES[level]
            else:
                linebuf += ' ' + level + ' '
            linebuf += ' '
        elif self.regex_parser.is_contain_level():
            linebuf += ' '
            linebuf += ' '

        # message
        # -separator
        if self.separator is not None:
            msgkey = self.separator.process(message)

        # -trans
        if self.trans is not None:
            message = self.trans.trans_msg(message)
            message = self.trans.hide_msg(message)
            message = self.trans.trans_tag(tag, message)

        if self.highlight_list is not None:
            for highlight in self.highlight_list:
                if highlight in message:
                    message = message.replace(highlight,
                                              termcolor(fg=BLACK, bg=allocate_color(highlight)) + highlight + RESET)

        linebuf += message

        return msgkey, linebuf, match_condition


================================================
FILE: okcat/logregex.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import re

from okcat.terminalcolor import print_warn

__author__ = 'jacks.gong'

# val = 'date,time,process,thread,level,tag,message = "(.\S*) (.\S*) (\d*) (\d*) ([V|I|D|W|E]) ([^:]*): (.*)"'
REGEX_EXP_RE = re.compile(r"([^ =]*) *= *[\"|'](.*)[\"|']")
ALL_SUPPORT_KEY = ["date", "time", "process", "thread", "level", "tag", "message"]
DEPRECATED_DATE_KEY = "data"

class LogRegex:
    key_order = list()

    regex = None

    def __init__(self, regex_exp):
        keys, regex = REGEX_EXP_RE.match(regex_exp).groups()
        process_key_order = keys.split(',')

        self.regex = re.compile(r"%s" % regex)
        for key in process_key_order:
            key = key.strip()
            if key in ALL_SUPPORT_KEY:
                self.key_order.append(key)
            elif key == DEPRECATED_DATE_KEY:
                print_warn("please change 'data' to 'date' because this wrong word has been fixed on the current version, for the temporary we treat it as 'date'")
                self.key_order.append('date')
            else:
                print_warn("not support key[%s] only support: %s" % (key, ALL_SUPPORT_KEY))

        print("find regex: " + self.key_order.__str__() + " with " + regex)

    def parse(self, line):
        date = None
        time = None
        process = None
        thread = None
        level = None
        tag = None
        message = None

        values = self.regex.match(line)

        if values is None:
            return date, time, level, tag, process, thread, message

        # print(values.groups().__str__())
        i = 0
        for value in values.groups():
            key = self.key_order[i]
            i += 1
            if key == "date":
                date = value
            elif key == "time":
                time = value
            elif key == "process":
                process = value
            elif key == "thread":
                thread = value
            elif key == "level":
                level = value
            elif key == "tag":
                tag = value
            elif key == "message":
                message = value

        return date, time, level, tag, process, thread, message

    contain_date = None
    contain_time = None
    contain_thread = None
    contain_tag = None
    contain_level = None

    def is_contain_date(self):
        if self.contain_date is None:
            self.contain_date = self.is_contain_key("date")
        return self.contain_date

    def is_contain_time(self):
        if self.contain_time is None:
            self.contain_time = self.is_contain_key("time")
        return self.contain_time

    def is_contain_thread(self):
        if self.contain_thread is None:
            self.contain_thread = self.is_contain_key("thread")
        return self.contain_thread

    def is_contain_tag(self):
        if self.contain_tag is None:
            self.contain_tag = self.is_contain_key("tag")
        return self.contain_tag

    def is_contain_level(self):
        if self.contain_level is None:
            self.contain_level = self.is_contain_key("level")
        return self.contain_level

    def is_contain_key(self, key):
        return key in self.key_order


================================================
FILE: okcat/logseparator.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import re

__author__ = 'JacksGong'


class LogSeparator:
    separator_rex_list = list()
    pre_separate_key = None

    def __init__(self, separator_rex_list):
        for regex_string in separator_rex_list:
            self.separator_rex_list.append(re.compile(r'%s' % regex_string))

    def process(self, msg):
        key = None
        for regex in self.separator_rex_list:
            matched_obj = regex.match(msg)
            if matched_obj is not None:
                key = matched_obj.groups()[0]
                break

        if self.pre_separate_key is None:
            if key is None:
                key = "unknown"
            self.pre_separate_key = key
            return key
        elif key is not None and self.pre_separate_key != key:
            return key
        else:
            return None


================================================
FILE: okcat/terminalcolor.py
================================================
#!/usr/bin/env python
# coding: utf-8

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

__author__ = 'jacks.gong'


class BashColors:
    def __init__(self):
        pass

    HEADER = '\033[95m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    END = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'


def print_header(msg):
    print(BashColors.HEADER + msg + BashColors.END)


def print_exit(msg):
    print(BashColors.FAIL + msg + BashColors.END)


def print_error(msg):
    print(BashColors.FAIL + msg + BashColors.END)


def print_tips(msg):
    print(BashColors.UNDERLINE + msg + BashColors.END)


def print_warn(msg):
    print(BashColors.WARNING + msg + BashColors.END)


def print_key(msg):
    print(BashColors.GREEN + msg + BashColors.END)


def print_blue(msg):
    print(BashColors.BLUE + msg + BashColors.END)


def print_content_tips(msg):
    msg = BashColors.UNDERLINE + msg + BashColors.END
    print(msg)
    return msg + "\n"


def print_content_header(msg):
    msg = BashColors.HEADER + msg + BashColors.END
    print_content(msg)
    return msg + "\n"


def print_content(msg):
    print(msg)
    return msg


# -------------- color -----------------------------
RESET = '\033[0m'
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)


def termcolor(fg=None, bg=None):
    codes = []
    if fg is not None: codes.append('3%d' % fg)
    if bg is not None: codes.append('10%d' % bg)
    return '\033[%sm' % ';'.join(codes) if codes else ''


def colorize(message, fg=None, bg=None):
    if fg is None:
        if bg == BLACK:
            fg = WHITE
        else:
            fg = BLACK

    return termcolor(fg, bg) + message + RESET


TAGTYPES = {
    'V': colorize(' V ', fg=WHITE, bg=BLACK),
    'D': colorize(' D ', fg=BLACK, bg=BLUE),
    'I': colorize(' I ', fg=BLACK, bg=GREEN),
    'W': colorize(' W ', fg=BLACK, bg=YELLOW),
    'E': colorize(' E ', fg=BLACK, bg=RED),
    'F': colorize(' F ', fg=BLACK, bg=RED),
}

# for random color
KNOWN_TAGS = {
    'dalvikvm': WHITE,
    'Process': WHITE,
    'ActivityManager': WHITE,
    'ActivityThread': WHITE,
    'AndroidRuntime': CYAN,
    'jdwp': WHITE,
    'StrictMode': WHITE,
    'DEBUG': YELLOW,
}
LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
LOG_FLOW_KEY_LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]


def allocate_color(_key, loop_color=LAST_USED):
    if _key not in KNOWN_TAGS:
        KNOWN_TAGS[_key] = loop_color[0]

    _color = KNOWN_TAGS[_key]
    if _color in LAST_USED:
        loop_color.remove(_color)
        loop_color.append(_color)
    return _color


================================================
FILE: okcat/trans.py
================================================
#!/usr/bin/env python
# coding: utf-8

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from okcat.terminalcolor import colorize, allocate_color, BLACK

__author__ = 'JacksGong'


class Trans:
    trans_msg_map = None
    trans_tag_map = None
    hide_msg_list = None

    def __init__(self, trans_msg_map, trans_tag_map, hide_msg_list):
        self.trans_msg_map = trans_msg_map
        self.trans_tag_map = trans_tag_map
        self.hide_msg_list = hide_msg_list

    def trans_msg(self, msg):
        if self.trans_msg_map is None:
            return msg

        for key in self.trans_msg_map:
            if msg.startswith(key):
                value = self.trans_msg_map[key]
                return u'| %s | %s' % (colorize(value, fg=allocate_color(value)), msg)

        return msg

    def trans_tag(self, tag, msg):
        if self.trans_tag_map is None or tag is None:
            return msg

        for key in self.trans_tag_map:
            if key in tag:
                prefix = self.trans_tag_map[key]
                return u'%s %s' % (colorize(prefix, bg=allocate_color(prefix)), msg)

        return msg

    def hide_msg(self, msg):
        if self.hide_msg_list is None:
            return msg

        # print("get hide msg list: %s and len(%d)" % (self.hide_msg_list, len(msg)))
        # if msg.__len__() > 100:
        #     return msg

        for gray_msg in self.hide_msg_list:
            if msg.startswith(gray_msg):
                return colorize(msg, fg=BLACK)

        return msg


================================================
FILE: setup.py
================================================
#!/usr/bin/python -u

"""
Copyright (C) 2017 Jacksgong(jacksgong.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from setuptools import setup, find_packages

# Get the long description from the README file
# noinspection PyArgumentList
setup(
    name="OkCat",
    version="1.4.0",
    packages=find_packages(exclude=['demo-conf', 'arts']),

    # Project uses reStructuredText, so ensure that the docutils get
    # installed or upgraded on the target machine
    install_requires=['PyYAML>=3.12'],

    # metadata for upload to PyPI
    author="Jacksgong",
    author_email="igzhenjie@gmail.com",
    description="An powerful log processor",
    long_description='More detail please move to https://github.com/Jacksgong/okcat',
    license="Apache2",
    keywords="okcat log 'log processor' 'log filter'",
    url="https://github.com/Jacksgong/okcat",

    # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
    classifiers=[
        # How mature is this project? Common values are
        #   3 - Alpha
        #   4 - Beta
        #   5 - Production/Stable
        'Development Status :: 5 - Production/Stable',

        # Pick your license as you wish (should match "license" above)
        'License :: OSI Approved :: Apache Software License',

        # Specify the Python versions you support here. In particular, ensure
        # that you indicate whether you support Python 2, Python 3 or both.
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
    ],
    entry_points={
        'console_scripts': [
            'okcat=okcat:main'
        ]
    }
)
Download .txt
gitextract_s8579sb1/

├── .gitignore
├── .idea/
│   ├── dictionaries/
│   │   └── Jacksgong.xml
│   ├── inspectionProfiles/
│   │   └── Project_Default.xml
│   └── vcs.xml
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.txt
├── MANIFEST.in
├── README-zh.md
├── README.md
├── debug.sh
├── demo-conf/
│   ├── demo-config.yml
│   ├── demo-log-file.log
│   └── filedownloader.yml
├── okcat/
│   ├── __init__.py
│   ├── adb.py
│   ├── confloader.py
│   ├── helper.py
│   ├── logfile_parser.py
│   ├── logprocessor.py
│   ├── logregex.py
│   ├── logseparator.py
│   ├── terminalcolor.py
│   └── trans.py
└── setup.py
Download .txt
SYMBOL INDEX (86 symbols across 10 files)

FILE: okcat/__init__.py
  function main (line 31) | def main():

FILE: okcat/adb.py
  class Adb (line 60) | class Adb:
    method __init__ (line 76) | def __init__(self):
    method setup (line 79) | def setup(self, args):
    method loop (line 191) | def loop(self):
    method match_packages (line 270) | def match_packages(self, token):
    method parse_death (line 278) | def parse_death(self, _tag, _message):
  class FakeStdinProcess (line 302) | class FakeStdinProcess:
    method __init__ (line 303) | def __init__(self):
    method poll (line 307) | def poll():
  function parse_start_proc (line 311) | def parse_start_proc(_line):
  function tag_in_tags_regex (line 327) | def tag_in_tags_regex(_tag, tags):

FILE: okcat/confloader.py
  class ConfLoader (line 27) | class ConfLoader:
    method __init__ (line 31) | def __init__(self):
    method load (line 34) | def load(self, yml_file_path):
    method get_from (line 49) | def get_from(self):
    method get_package (line 52) | def get_package(self):
    method get_tag_keyword_list (line 55) | def get_tag_keyword_list(self):
    method get_trans_msg_map (line 58) | def get_trans_msg_map(self):
    method get_trans_tag_map (line 61) | def get_trans_tag_map(self):
    method get_hide_msg_list (line 64) | def get_hide_msg_list(self):
    method get_ignore_msg_list (line 67) | def get_ignore_msg_list(self):
    method get_ignore_tag_list (line 70) | def get_ignore_tag_list(self):
    method get_highlight_list (line 73) | def get_highlight_list(self):
    method get_log_line_regex (line 76) | def get_log_line_regex(self):
    method get_adb_log_line_regex (line 79) | def get_adb_log_line_regex(self):
    method get_separator_regex_list (line 82) | def get_separator_regex_list(self):
    method get_value (line 85) | def get_value(self, keyword):
    method dump (line 102) | def dump(self):
    method dump_unicode_map (line 116) | def dump_unicode_map(self, map_key):
    method dump_list (line 125) | def dump_list(self, list_key):

FILE: okcat/helper.py
  function handle_home_case (line 35) | def handle_home_case(path):
  function is_path (line 42) | def is_path(path):
  function get_conf_path (line 51) | def get_conf_path(conf_name):
  function print_unicode (line 65) | def print_unicode(line):
  function line_rstrip (line 71) | def line_rstrip(line):

FILE: okcat/logfile_parser.py
  class LogFileParser (line 29) | class LogFileParser:
    method __init__ (line 38) | def __init__(self, file_paths, hide_same_tags):
    method setup (line 42) | def setup(self, yml_file_name):
    method color_line (line 64) | def color_line(self, line):
    method popup_cache_line (line 76) | def popup_cache_line(self, popup_index):
    method process (line 91) | def process(self):

FILE: okcat/logprocessor.py
  function indent_wrap (line 45) | def indent_wrap(message):
  function keywords_regex (line 49) | def keywords_regex(content, keywords):
  class LogProcessor (line 53) | class LogProcessor:
    method __init__ (line 71) | def __init__(self, hide_same_tags):
    method setup_trans (line 74) | def setup_trans(self, trans_msg_map, trans_tag_map, hide_msg_list):
    method setup_ignore (line 77) | def setup_ignore(self, ignore_msg_list, ignore_tag_list):
    method setup_separator (line 81) | def setup_separator(self, separator_rex_list):
    method setup_highlight (line 85) | def setup_highlight(self, highlight_list):
    method setup_condition (line 88) | def setup_condition(self, tag_keywords, line_keywords=None):
    method setup_regex_parser (line 92) | def setup_regex_parser(self, regex_exp):
    method process (line 95) | def process(self, origin_line):
    method process_decode_content (line 111) | def process_decode_content(self, line, time, level, tag, process, thre...

FILE: okcat/logregex.py
  class LogRegex (line 30) | class LogRegex:
    method __init__ (line 35) | def __init__(self, regex_exp):
    method parse (line 52) | def parse(self, line):
    method is_contain_date (line 94) | def is_contain_date(self):
    method is_contain_time (line 99) | def is_contain_time(self):
    method is_contain_thread (line 104) | def is_contain_thread(self):
    method is_contain_tag (line 109) | def is_contain_tag(self):
    method is_contain_level (line 114) | def is_contain_level(self):
    method is_contain_key (line 119) | def is_contain_key(self, key):

FILE: okcat/logseparator.py
  class LogSeparator (line 23) | class LogSeparator:
    method __init__ (line 27) | def __init__(self, separator_rex_list):
    method process (line 31) | def process(self, msg):

FILE: okcat/terminalcolor.py
  class BashColors (line 23) | class BashColors:
    method __init__ (line 24) | def __init__(self):
  function print_header (line 37) | def print_header(msg):
  function print_exit (line 41) | def print_exit(msg):
  function print_error (line 45) | def print_error(msg):
  function print_tips (line 49) | def print_tips(msg):
  function print_warn (line 53) | def print_warn(msg):
  function print_key (line 57) | def print_key(msg):
  function print_blue (line 61) | def print_blue(msg):
  function print_content_tips (line 65) | def print_content_tips(msg):
  function print_content_header (line 71) | def print_content_header(msg):
  function print_content (line 77) | def print_content(msg):
  function termcolor (line 87) | def termcolor(fg=None, bg=None):
  function colorize (line 94) | def colorize(message, fg=None, bg=None):
  function allocate_color (line 128) | def allocate_color(_key, loop_color=LAST_USED):

FILE: okcat/trans.py
  class Trans (line 24) | class Trans:
    method __init__ (line 29) | def __init__(self, trans_msg_map, trans_tag_map, hide_msg_list):
    method trans_msg (line 34) | def trans_msg(self, msg):
    method trans_tag (line 45) | def trans_tag(self, tag, msg):
    method hide_msg (line 56) | def hide_msg(self, msg):
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (100K chars).
[
  {
    "path": ".gitignore",
    "chars": 112,
    "preview": "/.idea/workspace.xml\n/.idea/misc.xml\n/.idea/okcat.iml\n/.idea/modules.xml\n*.pyc\n*.egg-info\n/dist\n/build\n.DS_Store"
  },
  {
    "path": ".idea/dictionaries/Jacksgong.xml",
    "chars": 469,
    "preview": "<component name=\"ProjectDictionaryState\">\n  <dictionary name=\"Jacksgong\">\n    <words>\n      <w>dalvik</w>\n      <w>dest<"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "chars": 268,
    "preview": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project De"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 180,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": ".travis.yml",
    "chars": 272,
    "preview": "sudo: false\nlanguage: python\npython:\n  - \"2.7\"\n  - \"3.3\"\n  - \"3.4\"\n  - \"3.5\"\n  - \"3.6\"\n  - \"3.7\"\n  - \"3.8\"\n  - \"3.9\"\n\nin"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1993,
    "preview": "# 1.4.0\n\n2024-02-27\n\n- Feature: Support `ignore-tag-list` and `ignore-msg-list` configuration to ignore messages by defi"
  },
  {
    "path": "LICENSE.txt",
    "chars": 11359,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "MANIFEST.in",
    "chars": 66,
    "preview": "# Include the license file\ninclude LICENSE.txt\ninclude README.md \n"
  },
  {
    "path": "README-zh.md",
    "chars": 5728,
    "preview": "# OkCat\n\n![](https://img.shields.io/badge/log-any%20format-orange.svg)\n![](https://img.shields.io/badge/log-android-oran"
  },
  {
    "path": "README.md",
    "chars": 8546,
    "preview": "# OkCat\n\n![](https://img.shields.io/badge/log-any%20format-orange.svg)\n![](https://img.shields.io/badge/log-android-oran"
  },
  {
    "path": "debug.sh",
    "chars": 63,
    "preview": "#!/bin/bash\npip uninstall okcat --yes\npython3 setup.py install\n"
  },
  {
    "path": "demo-conf/demo-config.yml",
    "chars": 277,
    "preview": "log-line-regex: 'date,time,process,thread,level,tag,message = \"(\\S*) *(\\S*) *(\\d*) *(\\d*) ([A-Z]) ([^:]*): (.*?)$\"'\n\ntra"
  },
  {
    "path": "demo-conf/demo-log-file.log",
    "chars": 12682,
    "preview": "--------- beginning of system\n06-26 15:18:59.546  1513  1513 I vold    : Vold 3.0 (the awakening) firing up\n06-26 15:18:"
  },
  {
    "path": "demo-conf/filedownloader.yml",
    "chars": 3136,
    "preview": "# we will filter out logs with the provided package (name)\n# this 'package' keyword is just using for android adb logcat"
  },
  {
    "path": "okcat/__init__.py",
    "chars": 4997,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  },
  {
    "path": "okcat/adb.py",
    "chars": 13290,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  },
  {
    "path": "okcat/confloader.py",
    "chars": 4242,
    "preview": "#!/usr/bin/env python\n# coding: utf-8\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache Licens"
  },
  {
    "path": "okcat/helper.py",
    "chars": 1929,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  },
  {
    "path": "okcat/logfile_parser.py",
    "chars": 3953,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  },
  {
    "path": "okcat/logprocessor.py",
    "chars": 6591,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  },
  {
    "path": "okcat/logregex.py",
    "chars": 3794,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  },
  {
    "path": "okcat/logseparator.py",
    "chars": 1421,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  },
  {
    "path": "okcat/terminalcolor.py",
    "chars": 3161,
    "preview": "#!/usr/bin/env python\n# coding: utf-8\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache Licens"
  },
  {
    "path": "okcat/trans.py",
    "chars": 2043,
    "preview": "#!/usr/bin/env python\n# coding: utf-8\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache Licens"
  },
  {
    "path": "setup.py",
    "chars": 2444,
    "preview": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (t"
  }
]

About this extraction

This page contains the full source code of the Jacksgong/okcat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (90.8 KB), approximately 25.6k tokens, and a symbol index with 86 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!