[
  {
    "path": ".gitignore",
    "content": "/.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",
    "content": "<component name=\"ProjectDictionaryState\">\n  <dictionary name=\"Jacksgong\">\n    <words>\n      <w>dalvik</w>\n      <w>dest</w>\n      <w>dreamtobe</w>\n      <w>ducktype</w>\n      <w>dumpsys</w>\n      <w>gids</w>\n      <w>jacksgong</w>\n      <w>jakewharton</w>\n      <w>logcat</w>\n      <w>okcat</w>\n      <w>pids</w>\n      <w>popen</w>\n      <w>proc</w>\n      <w>prog</w>\n      <w>sharkey</w>\n      <w>stdin</w>\n      <w>vdiwef</w>\n    </words>\n  </dictionary>\n</component>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"PyPep8Inspection\" enabled=\"false\" level=\"WEAK WARNING\" enabled_by_default=\"false\" />\n  </profile>\n</component>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".travis.yml",
    "content": "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\ninstall:\n        #  - travis_retry pip install okcat\n  - bash debug.sh\nscript:\n  - okcat help\n  - cd demo-conf\n  - okcat -y=demo-config demo-log-file.log\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 1.4.0\n\n2024-02-27\n\n- Feature: Support `ignore-tag-list` and `ignore-msg-list` configuration to ignore messages by defined\n\n# 1.3.0\n\n2020-08-13\n\n- Fix: fix on the special timestamp format stackoverflow issue temporary\n- Fix: fix decode utf-8 failed\n\n# 1.1.9\n\n2019-08-23\n\n- Feature: Implement 'from' keyword from config yaml file means append rather than overwrite.\n\n# 1.1.7\n\n2019-01-11\n\n- Fix: Fix wrong config field on the `log-line-regex` or `adb-log-line-regex` from 'data' to the correct one 'date'\n\n# 1.1.6\n\n2018-06-26\n\n- Fix: fix encode issue on python3 - by [Ryfthink](https://github.com/Ryfthink)\n\n# 1.1.5\n\n2018-06-24\n\n- Fix: some dvices adb lost connection, closes #9 - by [Ryfthink](https://github.com/Ryfthink)\n\n# 1.1.4\n\n2018-05-24\n\n- Feat: support 'from' keyword to let yml config file extends from exist yml file\n\n# 1.1.3\n\n2017-12-01\n\n- Feat: support combine and parse multiple log-files once time\n\n# 1.1.2\n\n2017-11-17\n\n- Fix: fix unicode decode error on setup on windows system closes #4\n\n# 1.1.1\n\n2017-10-10\n\n- Feat: show tips instead of crash when user don't provide config-file name to parse log file. Closes #2\n\n# 1.1.0\n\n2017-09-27\n\n- Fix: fix import file failed on python 3.x\n\n# 1.0.9\n\n2017-09-16\n\n- Fix: missing parentheses in call to 'print' error occurred on python 3.x Closes #1\n\n# 1.0.8\n\n2017-09-04\n\n- Feat: handle the case of adb connection is lost when using adb logcat\n\n# 1.0.7\n\n2017-09-03\n\n- Enhance: print each line when it has been parsed immediately rather than waiting for parsing whole file to handle case of large log file\n\n# 1.0.6\n\n2017-09-1\n\n- Fix: cover the case of there is no 'level' keyword on `log-line-regex` case.\n- Enhance: add `help` param on okcat, such as `okcat help`.\n- Enhance: support `--hide-same-tags` param\n- Fix: output all log when `log-line-regex` can't parse\n- Fix: handle case of only message is valid\n- Fix: fix print non-match content when the log can't match regex\n- Fix: fix the default adb regex may wrong for some special case\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright (C) 2017 Jacksgong(jacksgong.com)\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "# Include the license file\ninclude LICENSE.txt\ninclude README.md \n"
  },
  {
    "path": "README-zh.md",
    "content": "# OkCat\n\n![](https://img.shields.io/badge/log-any%20format-orange.svg)\n![](https://img.shields.io/badge/log-android-orange.svg)\n![](https://img.shields.io/badge/log-ios-orange.svg)\n![](https://img.shields.io/badge/log-backend-orange.svg)\n![](https://img.shields.io/badge/license-Apache2-blue.svg)\n[![](https://img.shields.io/badge/readme-English-blue.svg)](https://github.com/Jacksgong/okcat)\n[![](https://img.shields.io/badge/readme-中文-blue.svg)](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)\n[![](https://img.shields.io/badge/pip-v1.4.0%20okcat-yellow.svg)](https://pypi.org/project/OkCat/1.4.0/)\n[![Build Status](https://travis-ci.org/Jacksgong/okcat.svg?branch=master)](https://travis-ci.org/Jacksgong/okcat)\n\n强大的日志处理组件。\n\n[English Doc](https://github.com/Jacksgong/okcat)\n\n- 你可以定义任意的日志正则表达式，来适配任意格式的日志，可以将其用于iOS、Android、后端等等。\n- ADB Logcat部分是基于JakeWharton的PID Cat，并且适配了各类OkCat的新特性 。\n\nAndrdoid工程师查看ADB Logcat最简单的使用:\n\n```shell\nokcat 包名\n```\n\n## 特性\n\n> 最主要的特性是：你可以为不同的日志定义自己的正则表达式，以此适配各种类型的日志处理。\n\n- 高亮一些关键字\n![](https://github.com/Jacksgong/okcat/raw/master/arts/highlight-demo.png)\n- 转译日志内容\n![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-msg-demo.png)\n- 转译Tag\n![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-tag-demo.png)\n- 隐藏一些日志\n![](https://github.com/Jacksgong/okcat/raw/master/arts/hide-msg-demo.png)\n- 对连续的日志进行分割\n![](https://github.com/Jacksgong/okcat/raw/master/arts/separate-demo.png)\n- 忽略符合规则日志\n`以提供的字段开头的日志将会被直接忽略` 或者 `满足以提供的TAG的日志将会被直接忽略`\n\n## 如何安装\n\n```shell\nsudo pip install okcat\n```\n\n如果你还没有安装`pip`，你需要先安装`pip`:\n\n1. `brew install python`\n2. `sudo easy_install pip`\n\n如果你想要升级:\n\n```\nsudo pip install okcat --upgrade\n```\n\n## 如何使用\n\n---\n\n#### 最简单的测试\n\n1. 下载[filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml)在当前目录，或者移动到`~/.okcat/`目录中\n3. 运行这个[Filedownloader-Demo](https://github.com/lingochamp/FileDownloader)项目中的demo项目，并运行到你的Android手机上，然后将手机连接电脑\n4. 执行: `okcat -y=filedownloader`\n5. 此时日志就会根据[filedownloader.yml](https://github.com/Jacksgong/okcat/raw/master/demo-conf/filedownloader.yml)的配置输出了\n\n![](https://github.com/Jacksgong/okcat/raw/master/arts/demo.png)\n\n---\n\n#### 1. 定义你的配置文件(`.yml`)\n\n你可以在`~/.okcat/`目录下创建你的yaml格式的配置文件，如果`~/.okcat`文件夹不存在先创建该文件夹；当然也可以直接在执行命令的当前目录创建yaml格式的配置文件。\n文件名字可以是任何你想要的名字，在执行`okcat`的时候可以通过`-y=文件名`的形式，告知okcat想要应用的是哪个文件名的配置文件，okcat会默认在当前目录找，找不到会在`~/.okcat`目录下进行查找。\n\n下面是配置文件的案例，里面列出了目前支持的所有的配置，当然你不需要配置所有的特性，只需要配置你需要的即可。\n\n```yml\n# 继承存在的其他yml的配置(不需要`.yml`后缀)\nfrom: exist-yml-file-name\n\n# 定义连线手机进行ADB处理时，需要过滤的包名；\n# 如果不使用Android的ADB功能，便不需要配置\npackage: com.liulishuo.filedownloader.demo\n\n# 配置对于一行日志的正则表达式，目前支持正则出date、time、level、tag、process、thread、message\n# 不过不一定要全部提供，至少需要提供一个message\n# 如log-line-regex: 'message=\"(.\\S*)\"'\nlog-line-regex: 'date,time,process,thread,level,tag,message = \"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): *(.*?)$\"'\n\n# 在Android的ADB的情况下，我们是使用adb logcat -v brief -v threadtime\n# 一般情况下不需要adb-log-line-regex配置，我们已经有很完善的这块的正则，但是如果对这个需要特别定制便可以使用以下定制\n# adb-log-line-regex: 'date,time,process,thread,level,tag,message=\"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): *(.*?)$\"'\n\n# 分割正则列表\n# 可以提供多个正则表达式，对日志进行分割\nseparator-regex-list:\n  # 对满足以下正则的那行日志开始进行分割，并且以([^\\]]*)的内容作为分割的标题\n  - 'call start Url\\[([^\\]]*)\\]'\n\n# 标签关键字\n# 如果不提供tag-keyword-list将会显示所有日志\n# 如果如下提供了tag-keyword-list将会过滤日志，只显示tag中包含了这里列出关键字的日志\ntag-keyword-list:\n  - 'FileDownloader'\n\n# 内容转译表\n# 如果日志message中由表中key开头，将会使用彩色的文字在该message开头加上表中的value\ntrans-msg-map:\n  # 如这个例子:\n  # 原message: 'filedownloader:lifecycle:over xxx'\n  # 转译后: '| 任务结束 | filedownloader:lifecycle:over xxx' 其中的'任务结束'会使用彩色的文字显示\n  'filedownloader:lifecycle:over': '任务结束'\n  'fetch date with': '开始拉取'\n\n# 标签转译表\n# 如果日志tag中包含表中key开头，将会使用彩色背景的文字在该message开头加上表中的value\ntrans-tag-map:\n  # 如这个例子:\n  # 原输出: 'FileDownloader.DownloadTaskHunter  xxx'\n  # 转译后: 'FileDownloader.DownloadTaskHunter [状态切换] xxx' 其中'[状态切换]'会使用彩色背景\n  'DownloadTaskHunter': '[状态切换]'\n  'ConnectTask': '[请求]'\n\n# 隐藏消息列表\n# 对以以下内容开头并且message长度小于100的内功进行灰色显示处理，在视觉上进行隐藏\nhide-msg-list:\n  # 这里案例因为心跳日志是非常频繁的日志，通常没有什么问题，因此将其着灰色\n  - 'notify progress'\n  - '~~~callback'\n\n# 高亮列表\n# 对message中的以下内容，背景进行彩色处理使其高亮\nhighlight-list:\n  - 'Path['\n  - 'Url['\n  - 'Tag['\n  - 'range['\n  \n# 忽略日志列表\n# 以提供的字段开头的日志将会被直接忽略\nignore-msg-list:\n  - 'log start with this will be ignored'\n\n# 忽略日志TAG列表\n# 当所在日志的 TAG 在该列表中时会被直接忽略\nignore-tag-list:\n  - 'tagToBeIgnored'\n```\n\n#### 2. 执行\n\n> okcat的使用非常的简单。\n\n如果你需要处理运行中App在Logcat的输出，只需要执行:\n\n```shell\nokcat -y=your-conf-name\n```\n\n如果你需要解析任意格式的日志，只需要执行:\n\n```shell\nokcat -y=your-conf-name your-log-path1 your-log-path2 your-log-path3 ... \n```\n\n> 小技巧: 你在终端中使用`Command + K`来刷新当前回话中的所有内容，以此快速启动新的okcat解析，而不用再另外创建一个新的会话。\n\n## 我的终端的风格配置\n\n如果你想要适配和上面截图一样的终端风格，非常简单:\n\n- 首先，请使用[powerlevel9k](https://github.com/bhilburn/powerlevel9k)主题(正如Powerlevel9k文档提到的安装Powerlevel9k主题，并且安装Powerline字体).\n- 其次，请配置[iTerm2-Neutron](https://github.com/Ch4s3/iTerm2-Neutron)色系.\n- 最后, 请配置ini的shell(如果你使用的是zsh，只需要添加下列代码到`~/.zshrc`文件中):\n```\nPOWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)\nPOWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status time)\nPOWERLEVEL9K_TIME_FORMAT=\"%D{%H:%M:%S}\"\nPOWERLEVEL9K_NODE_VERSION_BACKGROUND='022'\nPOWERLEVEL9K_SHORTEN_DIR_LENGTH=2\n```\n\n## LICENSE\n\n```\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n"
  },
  {
    "path": "README.md",
    "content": "# OkCat\n\n![](https://img.shields.io/badge/log-any%20format-orange.svg)\n![](https://img.shields.io/badge/log-android-orange.svg)\n![](https://img.shields.io/badge/log-ios-orange.svg)\n![](https://img.shields.io/badge/log-backend-orange.svg)\n![](https://img.shields.io/badge/license-Apache2-blue.svg)\n[![](https://img.shields.io/badge/readme-English-blue.svg)](https://github.com/Jacksgong/okcat)\n[![](https://img.shields.io/badge/readme-中文-blue.svg)](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)\n[![](https://img.shields.io/badge/pip-v1.4.0%20okcat-yellow.svg)](https://pypi.org/project/OkCat/1.4.0/)\n[![Build Status](https://travis-ci.org/Jacksgong/okcat.svg?branch=master)](https://travis-ci.org/Jacksgong/okcat)\n\nAn powerful log processor.\n\n[中文文档](https://github.com/Jacksgong/okcat/blob/master/README-zh.md)\n\n- The adb logcat handler is just update to JakeWharton's nice pidcat and I adapt it for more features.\n- You can using this log processor with define you own `log-line-regex` and it can work for any log: iOS, Android, Backend, etc.  \n\n## Features\n\n> The most important feature is you can define any regex for any kind of log.\n\n- highlight some keywords\n![](https://github.com/Jacksgong/okcat/raw/master/arts/highlight-demo.png)\n- trans msgs to some words\n![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-msg-demo.png)\n- trans tags to some words\n![](https://github.com/Jacksgong/okcat/raw/master/arts/trans-tag-demo.png)\n- hide msg on logs\n![](https://github.com/Jacksgong/okcat/raw/master/arts/hide-msg-demo.png)\n- separate logs\n![](https://github.com/Jacksgong/okcat/raw/master/arts/separate-demo.png)\n- ignore msg on logs:\n`when you provide such list, the msg start with provided msg will be ignored to printed`\n- ignore tag on logs:\n`when you provide such list, the tag in the list will be ignored to printed`\n\n## How to Install\n\n```shell\nsudo pip install okcat\n```\n\nIf you has not installed `pip` yet, you need to install it first:\n\n1. `brew install python`\n2. `sudo easy_install pip`\n\nIf you want to upgrade:\n\n```shell\nsudo pip install okcat --upgrade\n```\n\n## How to Use\n\n---\n\n#### Simplest test\n\n1. 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\n2. Running: run the demo project on [Filedownloader](https://github.com/lingochamp/FileDownloader) repo to your Android phone and connect your Phone to computer\n3. Execute: `okcat -y=filedownloader`\n4. Done: now, you can checkout the colored logs on terminal, enjoy~\n\n![](https://github.com/Jacksgong/okcat/raw/master/arts/demo.png)\n\n---\n\n#### 1. Define your config file(`.yml`)\n\nYou 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.\n\nthe 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.\n\n```yml\n# extends from exist yml file (provide only filename without `.yml` extension)\n# from: exist-yml-name\n\n# we will filter out logs with the provided package (name)\n# this 'package' keyword is just using for android adb logcat\npackage: com.liulishuo.filedownloader.demo\n\n# this 'log-line-regex' is just a regex for one line log\n# now we support keyword: 'date' 'time' 'level' 'tag' 'process' 'thread' 'message'\n# you don't have to provide all keyword, but you have to provide at least the 'message'\n# such as: 'message=\"(\\S*)\"'\nlog-line-regex: 'date,time,process,thread,level,tag,message = \"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): *(.*?)$\"'\n\n# on the case of filter logs from Android adb logcat, we using 'adb logcat -v brief -v threadtime' command to obtain logcat\n# in the normal case you don't need ot provide this config, because there is a perfect one on the okcat internal\n# but if you want to customize the regex log from adb logcat, it's free to define it such below\n# adb-log-line-regex: 'date,time,process,thread,level,tag,message=\"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): *(.*?)$\"'\n\n# separator regex list\n# you can provide multiple regex to separate serial logs\nseparator-regex-list:\n  # 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\n  - 'call start Url\\[([^\\]]*)\\]'\n\n# tag keyword list\n# this list keyword is using for filter out which log need to be output\n# 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\ntag-keyword-list:\n  - 'FileDownloader'\n\n# translate message map\n# 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\ntrans-msg-map:\n  # such as this case:\n  # origin message: 'filedownloader:lifecycle:over xxx'\n  # after translate: '| Task OVER | filedownloader:lifecycle:over xxx'\n  'filedownloader:lifecycle:over': 'Task OVER'\n  'fetch data with': 'Start Fetch'\n\n# translate tag map\n# 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\ntrans-tag-map:\n  # such as this case:\n  # origin message: 'FileDownloader.DownloadTaskHunter  xxx'\n  # after translate: 'FileDownloader.DownloadTaskHunter [Status Change] xxx'\n  'DownloadTaskHunter': '[Status Change]'\n  'ConnectTask': '[Request]'\n\n# hide message list\n# 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\nhide-msg-list:\n  # here we hide message start with 'notify progress' and '~~~callback' because it is too frequently to output and useless in most case\n  - 'notify progress'\n  - '~~~callback'\n\n# highlight list\n# if any value on the 'highlight-list' display on any message, the background of the value word would be colored to highlight it\nhighlight-list:\n  - 'Path['\n  - 'Url['\n  - 'Tag['\n  - 'range['\n\n# ignore message list\n# when you provide such list, the msg start with provided msg will be ignored to printed \nignore-msg-list:\n  - 'log start with this will be ignored' \n\n# ignore tag list\n# when you provide such list, the tag in the list will be ignored to printed\nignore-tag-list:\n  - 'tagToBeIgnored'\n```\n\n#### 2. Execute\n\nYou can just parse logcat from running adb:\n\n```shell\nokcat -y=your-conf-name\n```\n\nYou also can parse your log file through:\n\n```shell\nokcat -y=your-conf-name your-log-path1 your-log-path2 your-log-path3 ... \n```\n\nSimplest case for any developer:\n\n```shell\nokcat your.package.name\n```\n\n> 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.\n\n## My Terminal Config\n\nIf you want to adapter the same theme like screenshot above, it's very easy:\n\n- Firstly, please use [powerlevel9k](https://github.com/bhilburn/powerlevel9k) theme(Install the Powerlevel9k Theme and Powerline Fonts as the powerlevel9k repo readme doc said).\n- Secondly, please config the [iTerm2-Neutron](https://github.com/Ch4s3/iTerm2-Neutron) color scheme.\n- Thirdly, please config your shell(If you are using zsh, just add following code to the `~/.zshrc` file):\n```\nPOWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)\nPOWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status time)\nPOWERLEVEL9K_TIME_FORMAT=\"%D{%H:%M:%S}\"\nPOWERLEVEL9K_NODE_VERSION_BACKGROUND='022'\nPOWERLEVEL9K_SHORTEN_DIR_LENGTH=2\n```\n\n## Dev\n\nImport to PyCharm, and Set the Project Structure:\n\n![](https://github.com/Jacksgong/okcat/raw/master/arts/pycharm-build.jpg)\n\n## LICENSE\n\n```\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n"
  },
  {
    "path": "debug.sh",
    "content": "#!/bin/bash\npip uninstall okcat --yes\npython3 setup.py install\n"
  },
  {
    "path": "demo-conf/demo-config.yml",
    "content": "log-line-regex: 'date,time,process,thread,level,tag,message = \"(\\S*) *(\\S*) *(\\d*) *(\\d*) ([A-Z]) ([^:]*): (.*?)$\"'\n\ntrans-msg-map:\n  'Unknown': '未知'\n\ntrans-tag-map:\n  'SystemServiceManager': '[System Service Manager]'\n  'RescueParty': '[恢复部分]'\n\nhighlight-list:\n  - 'complete'\n"
  },
  {
    "path": "demo-conf/demo-log-file.log",
    "content": "--------- beginning of system\n06-26 15:18:59.546  1513  1513 I vold    : Vold 3.0 (the awakening) firing up\n06-26 15:18:59.546  1513  1513 V vold    : Detected support for: ext4 vfat\n06-26 15:18:59.551  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop5: No such device or address\n06-26 15:18:59.581  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop1: No such device or address\n06-26 15:18:59.621  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop6: No such device or address\n06-26 15:18:59.662  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop2: No such device or address\n06-26 15:18:59.700  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop4: No such device or address\n06-26 15:18:59.810  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop0: No such device or address\n06-26 15:18:59.841  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop7: No such device or address\n06-26 15:18:59.880  1513  1513 W vold    : Failed to LOOP_GET_STATUS64 /dev/block/loop3: No such device or address\n06-26 15:18:59.915  1513  1513 D vold    : VoldNativeService::start() completed OK\n06-26 15:18:59.932  1513  1517 D vold    : e4crypt_init_user0\n06-26 15:18:59.933  1513  1517 D vold    : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 1\n06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/system/users/0\n06-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\n06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/misc/profiles/cur/0\n06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/system_de/0\n06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/misc_de/0\n06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/vendor_de/0\n06-26 15:18:59.933  1513  1517 D vold    : Preparing: /data/user_de/0\n06-26 15:18:59.934  1513  1517 V vold    : /system/bin/vold_prepare_subdirs\n06-26 15:18:59.935  1513  1517 V vold    :     prepare\n06-26 15:18:59.935  1513  1517 V vold    :     0\n06-26 15:18:59.935  1513  1517 V vold    :     1\n06-26 15:18:59.949  1513  1517 D vold    : e4crypt_unlock_user_key 0 serial=0 token_present=0\n06-26 15:18:59.949  1513  1517 E vold    : Failed to chmod /data/system_ce/0: No such file or directory\n06-26 15:18:59.949  1513  1517 E vold    : Failed to chmod /data/misc_ce/0: No such file or directory\n06-26 15:18:59.949  1513  1517 E vold    : Failed to chmod /data/media/0: No such file or directory\n06-26 15:19:00.225  1513  1590 I Cryptfs : cryptfs_check_passwd\n06-26 15:19:00.227  1513  1590 D Cryptfs : crypt_ftr->fs_size = 1638400\n06-26 15:19:00.227  1513  1590 I Cryptfs : Using scrypt for cryptfs KDF\n06-26 15:19:00.393  1572  1572 I android.hardware.wifi@1.0-service: Wifi Hal is booting up...\n06-26 15:19:00.813  1513  1590 I Cryptfs : Extra parameters for dm_crypt: 1 allow_discards\n06-26 15:19:00.822  1513  1516 D vold    : Disk at 252:0 changed\n06-26 15:19:00.978  1513  1590 I Cryptfs : Password matches\n06-26 15:19:00.980  1513  1590 D Cryptfs : test_mount_encrypted_fs(): Master key saved\n06-26 15:19:00.990  1513  1590 I vold    : List of Keymaster HALs found:\n06-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\n06-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\n06-26 15:19:00.990  1513  1590 D Cryptfs : Password is default - restarting filesystem\n06-26 15:19:01.032  1513  1590 D Cryptfs : unmounting /data succeeded\n06-26 15:19:01.036  1513  1590 I vold    : [libfs_mgr]superblock s_max_mnt_count:65535,/dev/block/dm-0\n06-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\n06-26 15:19:01.315  1513  1590 I vold    : [libfs_mgr]check_fs(): mount(/dev/block/dm-0,/data,ext4)=0: Success\n06-26 15:19:01.354  1513  1590 I vold    : [libfs_mgr]check_fs(): unmount(/data) succeeded\n06-26 15:19:01.355  1513  1590 I vold    : [libfs_mgr]Running /system/bin/e2fsck on /dev/block/dm-0\n06-26 15:19:01.601  1513  1590 I vold    : [libfs_mgr]e2fsck returned status 0x100\n06-26 15:19:01.612  1513  1590 I vold    : [libfs_mgr]__mount(source=/dev/block/dm-0,target=/data,type=ext4)=0: Success\n06-26 15:19:01.612  1513  1590 D Cryptfs : Just triggered post_fs_data\n06-26 15:19:01.649  1513  1515 D vold    : e4crypt_init_user0\n06-26 15:19:01.649  1513  1515 D vold    : e4crypt_prepare_user_storage for volume null, user 0, serial 0, flags 1\n06-26 15:19:01.649  1513  1515 D vold    : Preparing: /data/system/users/0\n06-26 15:19:01.649  1513  1515 D vold    : Preparing: /data/misc/profiles/cur/0\n06-26 15:19:01.650  1513  1515 D vold    : Preparing: /data/system_de/0\n06-26 15:19:01.651  1513  1515 D vold    : Preparing: /data/misc_de/0\n06-26 15:19:01.652  1513  1515 D vold    : Preparing: /data/vendor_de/0\n06-26 15:19:01.652  1513  1515 D vold    : Preparing: /data/user_de/0\n06-26 15:19:01.653  1513  1515 V vold    : /system/bin/vold_prepare_subdirs\n06-26 15:19:01.653  1513  1515 V vold    :     prepare\n06-26 15:19:01.653  1513  1515 V vold    :     \n06-26 15:19:01.653  1513  1515 V vold    :     0\n06-26 15:19:01.653  1513  1515 V vold    :     1\n06-26 15:19:01.667  1513  1515 D vold    : e4crypt_unlock_user_key 0 serial=0 token_present=0\n06-26 15:19:01.691  1513  1590 D Cryptfs : post_fs_data done\n06-26 15:19:01.692  1513  1590 D Cryptfs : Just triggered restart_framework\n06-26 15:19:01.987  1694  1694 I wificond: wificond is starting up...\n06-26 15:19:02.140  1685  1685 I installd: installd firing up\n06-26 15:19:03.232  1681  1681 D Zygote32Timing: BeginIcuCachePinning took to complete: 46ms\n06-26 15:19:03.721  1681  1681 D Zygote32Timing: PreloadClasses took to complete: 489ms\n06-26 15:19:03.829  1681  1681 D Zygote32Timing: PreloadResources took to complete: 107ms\n06-26 15:19:03.864  1681  1681 D Zygote32Timing: ZygotePreload took to complete: 679ms\n06-26 15:19:03.873  1681  1681 D Zygote32Timing: PostZygoteInitGC took to complete: 9ms\n06-26 15:19:03.873  1681  1681 D Zygote32Timing: ZygoteInit took to complete: 694ms\n06-26 15:19:04.001  1819  1819 I SystemServer: InitBeforeStartServices\n06-26 15:19:04.002  1819  1819 I SystemServer: Entered the Android system server!\n06-26 15:19:04.162  1819  1819 D SystemServerTiming: InitBeforeStartServices took to complete: 161ms\n06-26 15:19:04.162  1819  1819 I SystemServer: StartServices\n06-26 15:19:04.162  1819  1819 I SystemServer: Reading configuration...\n06-26 15:19:04.162  1819  1819 I SystemServer: ReadingSystemConfig\n06-26 15:19:04.164  1819  1819 D SystemServerTiming: ReadingSystemConfig took to complete: 2ms\n06-26 15:19:04.164  1819  1819 I SystemServer: StartInstaller\n06-26 15:19:04.164  1819  1819 I SystemServiceManager: Starting com.android.server.pm.Installer\n06-26 15:19:04.165  1819  1832 D SystemServerInitThreadPool: Started executing ReadingSystemConfig\n06-26 15:19:04.169  1819  1819 D SystemServerTiming: StartInstaller took to complete: 5ms\n06-26 15:19:04.169  1819  1819 I SystemServer: DeviceIdentifiersPolicyService\n06-26 15:19:04.169  1819  1819 I SystemServiceManager: Starting com.android.server.os.DeviceIdentifiersPolicyService\n06-26 15:19:04.171  1819  1819 D SystemServerTiming: DeviceIdentifiersPolicyService took to complete: 2ms\n06-26 15:19:04.171  1819  1819 I SystemServer: StartActivityManager\n06-26 15:19:04.171  1819  1819 I SystemServiceManager: Starting com.android.server.am.ActivityManagerService$Lifecycle\n06-26 15:19:04.197  1819  1819 I ActivityManager: Memory class: 384\n06-26 15:19:04.207  1819  1832 D SystemServerInitThreadPool: Finished executing ReadingSystemConfig\n06-26 15:19:04.216  1819  1819 D BatteryStatsImpl: Reading daily items from /data/system/batterystats-daily.xml\n06-26 15:19:04.230  1819  1840 E BatteryExternalStatsWorker: no controller energy info supplied for telephony\n06-26 15:19:04.233  1819  1840 I KernelUidCpuFreqTimeReader: mPerClusterTimesAvailable=false\n06-26 15:19:04.235  1819  1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/time_in_state\n06-26 15:19:04.235  1819  1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/concurrent_active_time\n06-26 15:19:04.236  1819  1840 W KernelCpuProcReader: File not exist: /proc/uid_cpupower/concurrent_policy_time\n06-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)\n06-26 15:19:04.236  1819  1840 W KernelMemoryBandwidthStats: No kernel memory bandwidth stats available\n06-26 15:19:04.252  1819  1819 W AppOps  : Unknown attribute in 'op' tag: n\n06-26 15:19:04.256  1819  1819 I chatty  : uid=1000 system_server identical 163 lines\n06-26 15:19:04.256  1819  1819 W AppOps  : Unknown attribute in 'op' tag: n\n06-26 15:19:04.266  1819  1819 I IntentFirewall: Read new rules (A:0 B:0 S:0)\n06-26 15:19:04.277  1819  1819 D AppOps  : AppOpsService published\n06-26 15:19:04.277  1819  1819 D SystemServerTiming: StartActivityManager took to complete: 106ms\n06-26 15:19:04.277  1819  1819 I SystemServer: StartPowerManager\n06-26 15:19:04.277  1819  1819 I SystemServiceManager: Starting com.android.server.power.PowerManagerService\n06-26 15:19:04.288  1819  1819 D SystemServerTiming: StartPowerManager took to complete: 11ms\n06-26 15:19:04.288  1819  1819 I SystemServer: InitPowerManagement\n06-26 15:19:04.290  1819  1819 D SystemServerTiming: InitPowerManagement took to complete: 2ms\n06-26 15:19:04.290  1819  1819 I SystemServer: StartRecoverySystemService\n06-26 15:19:04.290  1819  1819 I SystemServiceManager: Starting com.android.server.RecoverySystemService\n06-26 15:19:04.291  1819  1819 D SystemServerTiming: StartRecoverySystemService took to complete: 1ms\n06-26 15:19:04.293  1819  1819 W RescueParty: Failed to determine if device was on USB\n06-26 15:19:04.293  1819  1819 W RescueParty: java.io.FileNotFoundException: /sys/class/android_usb/android0/state (No such file or directory)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at java.io.FileInputStream.open0(Native Method)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at java.io.FileInputStream.open(FileInputStream.java:231)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at java.io.FileInputStream.<init>(FileInputStream.java:165)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at android.os.FileUtils.readTextFile(FileUtils.java:514)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.RescueParty.isUsbActive(RescueParty.java:348)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.RescueParty.isDisabled(RescueParty.java:88)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.RescueParty.noteBoot(RescueParty.java:107)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.SystemServer.startBootstrapServices(SystemServer.java:587)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.SystemServer.run(SystemServer.java:429)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.server.SystemServer.main(SystemServer.java:294)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at java.lang.reflect.Method.invoke(Native Method)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)\n06-26 15:19:04.293  1819  1819 W RescueParty:   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)\n06-26 15:19:04.296  1819  1819 W RescueParty: Noticed 1 events for UID 0 in last 7 sec\n06-26 15:19:04.296  1819  1819 I SystemServer: StartLightsService\n06-26 15:19:04.296  1819  1819 I SystemServiceManager: Starting com.android.server.lights.LightsService\n06-26 15:19:04.297  1819  1819 D SystemServerTiming: StartLightsService took to complete: 1ms\n06-26 15:19:04.297  1819  1819 I SystemServer: StartSidekickService\n06-26 15:19:04.297  1819  1819 D SystemServerTiming: StartSidekickService took to complete: 0ms\n06-26 15:19:04.297  1819  1819 I SystemServer: StartDisplayManager\n06-26 15:19:04.297  1819  1819 I SystemServiceManager: Starting com.android.server.display.DisplayManagerService\n06-26 15:19:04.301  1819  1819 D SystemServerTiming: StartDisplayManager took to complete: 4ms\n06-26 15:19:04.301  1819  1819 I SystemServer: WaitForDisplay\n06-26 15:19:04.301  1819  1819 I SystemServiceManager: Starting phase 100\n"
  },
  {
    "path": "demo-conf/filedownloader.yml",
    "content": "# we will filter out logs with the provided package (name)\n# this 'package' keyword is just using for android adb logcat\npackage: com.liulishuo.filedownloader.demo\n\n# this 'log-line-regex' is just a regex for one line log\n# now we support keyword: 'date' 'time' 'level' 'tag' 'process' 'thread' 'message'\n# you don't have to provide all keyword, but you have to provide at least the 'message'\n# such as: 'message=\"(\\S*)\"'\nlog-line-regex: 'date,time,level,tag,process,thread,message = \"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): *(.*?)$\"'\n\n# on the case of filter logs from Android adb logcat, we using 'adb logcat -v brief -v threadtime' command to obtain logcat\n# in the normal case you don't need ot provide this config, because there is a perfect one on the okcat internal\n# but if you want to customize the regex log from adb logcat, it's free to define it such below\n# adb-log-line-regex: 'date,time,process,thread,level,tag,message=\"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): *(.*?)$\"'\n\n# separator regex list\n# you can provide multiple regex to separate serial logs\nseparator-regex-list: \n  # 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\n  - 'call start Url\\[([^\\]]*)\\]'\n\n# tag keyword list\n# this list keyword is using for filter out which log need to be output\n# 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\ntag-keyword-list:\n  - 'FileDownloader'\n\n# translate message map\n# 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\ntrans-msg-map:\n  # such as this case:\n  # origin message: 'filedownloader:lifecycle:over xxx'\n  # after translate: '| Task OVER | filedownloader:lifecycle:over xxx'\n  'filedownloader:lifecycle:over': 'Task OVER'\n  'fetch data with': 'Start Fetch'\n\n# translate tag map\n# 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\ntrans-tag-map:\n  # such as this case:\n  # origin message: 'FileDownloader.DownloadTaskHunter  xxx'\n  # after translate: 'FileDownloader.DownloadTaskHunter [Status Change] xxx'\n  'DownloadTaskHunter': '[Status Change]'\n  'ConnectTask': '[Request]'\n\n# hide message list\n# 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\nhide-msg-list:\n  # here we hide message start with 'notify progress' and '~~~callback' because it is too frequently to output and useless in most case\n  - 'notify progress'\n  - '~~~callback'\n\n# highlight list\n# if any value on the 'highlight-list' display on any message, the background of the value word would be colored to highlight it\nhighlight-list:\n  - 'Path['\n  - 'Url['\n  - 'Tag['\n  - 'range['\n"
  },
  {
    "path": "okcat/__init__.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nimport argparse\nfrom sys import argv\n\nfrom okcat.adb import Adb\nfrom okcat.helper import LOG_LEVELS, is_path\nfrom okcat.logfile_parser import LogFileParser\nfrom okcat.terminalcolor import print_tips, print_blue, print_warn, print_header, print_exit\n\n__author__ = 'JacksGong'\n__version__ = '1.4.0'\n__description__ = 'This python script used for combine several Android projects to one project.'\n\n\ndef main():\n    print(\"-------------------------------------------------------\")\n    print(\"                  OkCat v\" + __version__)\n    print(\"\")\n    print(\"Thanks for using okcat! Now, the doc is available on: \")\n    print_blue(\"        https://github.com/Jacksgong/okcat\")\n    print(\"\")\n    print(\"                   Have Fun!\")\n    print(\"-------------------------------------------------------\")\n\n    parser = argparse.ArgumentParser(description='Filter logcat by package name')\n    parser.add_argument('package_or_path', nargs='*',\n                        help='This can be Application package name(s) or log file path(if the file from path is exist)')\n    parser.add_argument('-y', '--yml_file_name', dest='yml', help='Using yml file you config on ~/.okcat folder')\n    parser.add_argument('--hide-same-tags', dest='hide_same_tags', action='store_true',\n                        help='Do not display the same tag name')\n\n    # following args are just for parser\n    # parser.add_argument('-k', '--keyword', dest='keyword', action='append', help='You can filter you care about log by this keyword(s)')\n\n    # following args are just for adb\n    parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=23,\n                        help='Width of log tag')\n    parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS + LOG_LEVELS.lower(),\n                        default='V', help='Minimum level to be displayed')\n    parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='Color garbage collection')\n    parser.add_argument('--current', dest='current_app', action='store_true',\n                        help='Filter logcat by current running app')\n    parser.add_argument('-s', '--serial', dest='device_serial', help='Device serial number (adb -s option)')\n    parser.add_argument('-d', '--device', dest='use_device', action='store_true',\n                        help='Use first device for log input (adb -d option)')\n    parser.add_argument('-e', '--emulator', dest='use_emulator', action='store_true',\n                        help='Use first emulator for log input (adb -e option)')\n    parser.add_argument('-c', '--clear', dest='clear_logcat', action='store_true',\n                        help='Clear the entire log before running')\n    parser.add_argument('-t', '--tag', dest='tag', action='append', help='Filter output by specified tag(s)')\n    parser.add_argument('-tk', '--tag_keywords', dest='tag_keywords', action='append',\n                        help='Filter output by specified tag keyword(s)')\n    parser.add_argument('-i', '--ignore-tag', dest='ignored_tag', action='append',\n                        help='Filter output by ignoring specified tag(s)')\n    parser.add_argument('-a', '--all', dest='all', action='store_true', default=False,\n                        help='Print all log messages')\n\n    # help\n    if len(argv) == 2 and argv[1] == 'help':\n        exit()\n\n    args = parser.parse_args()\n\n    file_paths = []\n    candidate_path = args.package_or_path\n    for path in candidate_path:\n        if is_path(path):\n            file_paths.append(path)\n\n    if file_paths:\n        if args.yml is None:\n            print(\"\")\n            print_exit(\"Please using '-y=conf-name' to provide config file to parse this log file.\")\n            print(\"The config file is very very simple! More detail about config file please move to : https://github.com/Jacksgong/okcat\")\n            print(\"\")\n            print(\"-------------------------------------------------------\")\n            exit()\n\n        parser = LogFileParser(file_paths, args.hide_same_tags)\n        parser.setup(args.yml)\n        parser.process()\n    else:\n        is_interrupt_by_user = False\n\n        _adb = Adb()\n        _adb.setup(args)\n        try:\n            _adb.loop()\n        except KeyboardInterrupt:\n            is_interrupt_by_user = True\n\n        if not is_interrupt_by_user:\n            print_warn('ADB CONNECTION IS LOST.')\n"
  },
  {
    "path": "okcat/adb.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nfrom os.path import exists\n\nfrom okcat.confloader import ConfLoader\nfrom okcat.helper import LOG_LEVELS_MAP, get_conf_path, handle_home_case, print_unicode\nfrom okcat.logprocessor import LogProcessor, indent_wrap\nfrom okcat.logregex import LogRegex\nfrom okcat.terminalcolor import termcolor, RED, RESET, YELLOW, GREEN, colorize, WHITE, allocate_color\n\n__author__ = 'JacksGong'\n\n# Script to highlight adb logcat output for console\n# Originally written by Jeff Sharkey, http://jsharkey.org/\n# Piping detection and popen() added by other Android team members\n# Package filtering and output improvements by Jake Wharton, http://jakewharton.com\n# Package adapt for okcat by Jacks Gong, https://jacksgong.com\n\nimport sys\nimport re\nimport subprocess\nfrom subprocess import PIPE, STDOUT\n\n# noinspection Annotator\nPID_LINE = re.compile(r'^\\w+\\s+(\\w+)\\s+\\w+\\s+\\w+\\s+\\w+\\s+\\w+\\s+\\w+\\s+\\w\\s([\\w|\\.|\\/]+)')\nPID_START = re.compile(r'^.*: Start proc ([a-zA-Z0-9._:]+) for ([a-z]+ [^:]+): pid=(\\d+) uid=(\\d+) gids=(.*)$')\nPID_START_5_1 = re.compile(r'^.*: Start proc (\\d+):([a-zA-Z0-9._:]+)/[a-z0-9]+ for (.*)$')\nPID_START_DALVIK = re.compile(\n    r'^E/dalvikvm\\(\\s*(\\d+)\\): >>>>> ([a-zA-Z0-9._:]+) \\[ userId:0 \\| appId:(\\d+) \\]$')\nPID_KILL = re.compile(r'^Killing (\\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$')\nPID_LEAVE = re.compile(r'^No longer want ([a-zA-Z0-9._:]+) \\(pid (\\d+)\\): .*$')\nPID_DEATH = re.compile(r'^Process ([a-zA-Z0-9._:]+) \\(pid (\\d+)\\) has died.?$')\n\nADB_LOG_REGEX_EXP = 'date,time,process,thread,level,tag,message=\"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): *(.*?)$\"'\n\nBUG_LINE = re.compile(r'.*nativeGetEnabledTags.*')\nBACKTRACE_LINE = re.compile(r'^#(.*?)pc\\s(.*?)$')\nRULES = {\n    # StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1\n    re.compile(r'^(StrictMode policy violation)(; ~duration=)(\\d+ ms)')\n    : r'%s\\1%s\\2%s\\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET),\n}\n\n\nclass Adb:\n    all = None\n    min_level = None\n    package_name = None\n    tag = None\n    header_size = None\n    ignored_tag = None\n\n    log_regex = None\n    catchall_package = None\n    named_processes = None\n    pids = None\n\n    adb = None\n    processor = None\n\n    def __init__(self):\n        pass\n\n    def setup(self, args):\n        self.processor = LogProcessor(args.hide_same_tags)\n\n        self.min_level = LOG_LEVELS_MAP[args.min_level.upper()]\n        self.all = args.all\n        self.ignored_tag = args.ignored_tag\n        self.tag = args.tag\n\n        self.package_name = args.package_or_path\n        self.processor.setup_condition(tag_keywords=args.tag_keywords)\n        if args.yml is not None:\n            conf_file_path = get_conf_path(args.yml)\n            if not exists(handle_home_case(conf_file_path)):\n                exit('you provide conf file path: ' + conf_file_path + ' is not exist!')\n\n            conf_loader = ConfLoader()\n            conf_loader.load(conf_file_path)\n\n            yml_package = conf_loader.get_package()\n            if yml_package is not None:\n                self.package_name.append(yml_package)\n\n            yml_adb_log_regex = conf_loader.get_adb_log_line_regex()\n            if yml_adb_log_regex is not None:\n                self.log_regex = LogRegex(yml_adb_log_regex)\n\n            self.processor.setup_condition(tag_keywords=conf_loader.get_tag_keyword_list())\n            self.processor.setup_trans(trans_msg_map=conf_loader.get_trans_msg_map(),\n                                       trans_tag_map=conf_loader.get_trans_tag_map(),\n                                       hide_msg_list=conf_loader.get_hide_msg_list())\n            self.processor.setup_ignore(ignore_msg_list=conf_loader.get_ignore_msg_list(),\n                                        ignore_tag_list=conf_loader.get_ignore_tag_list())\n            self.processor.setup_highlight(highlight_list=conf_loader.get_highlight_list())\n            self.processor.setup_separator(separator_rex_list=conf_loader.get_separator_regex_list())\n\n        if self.log_regex is None:\n            self.log_regex = LogRegex(ADB_LOG_REGEX_EXP)\n\n        base_adb_command = ['adb']\n        if args.device_serial:\n            base_adb_command.extend(['-s', args.device_serial])\n        if args.use_device:\n            base_adb_command.append('-d')\n        if args.use_emulator:\n            base_adb_command.append('-e')\n\n        if args.current_app:\n            system_dump_command = base_adb_command + [\"shell\", \"dumpsys\", \"activity\", \"activities\"]\n            system_dump = subprocess.Popen(system_dump_command, stdout=PIPE, stderr=PIPE).communicate()[0]\n            running_package_name = re.search(\".*TaskRecord.*A[= ]([^ ^}]*)\", system_dump).group(1)\n            self.package_name.append(running_package_name)\n\n        if len(self.package_name) == 0:\n            self.all = True\n\n        # Store the names of packages for which to match all processes.\n        self.catchall_package = list(filter(lambda package: package.find(\":\") == -1, self.package_name))\n        # Store the name of processes to match exactly.\n        named_processes = filter(lambda package: package.find(\":\") != -1, self.package_name)\n        # Convert default process names from <package>: (cli notation) to <package> (android notation) in the exact names match group.\n        self.named_processes = map(lambda package: package if package.find(\":\") != len(package) - 1 else package[:-1],\n                                   named_processes)\n\n        self.header_size = args.tag_width + 1 + 3 + 1  # space, level, space\n\n        # Only enable GC coloring if the user opted-in\n        if args.color_gc:\n            # GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms\n            key = re.compile(\n                r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed <?\\d+.)(, \\d+% free \\d+./\\d+., )(paused \\d+ms(?:\\+\\d+ms)?)')\n            val = r'\\1%s\\2%s\\3%s\\4%s' % (termcolor(GREEN), RESET, termcolor(YELLOW), RESET)\n\n            RULES[key] = val\n\n        adb_command = base_adb_command[:]\n        adb_command.append('logcat')\n        adb_command.extend(['-v', 'brief'])\n        adb_command.extend(['-v', 'threadtime'])\n\n        # Clear log before starting logcat\n        if args.clear_logcat:\n            adb_clear_command = list(adb_command)\n            adb_clear_command.append('-c')\n            adb_clear = subprocess.Popen(adb_clear_command)\n\n            while adb_clear.poll() is None:\n                pass\n\n        if sys.stdin.isatty():\n            self.adb = subprocess.Popen(adb_command, stdout=PIPE, stderr=STDOUT, stdin=PIPE)\n        else:\n            self.adb = FakeStdinProcess()\n\n        self.pids = set()\n\n        ps_command = base_adb_command + ['shell', 'ps']\n        ps_pid = subprocess.Popen(ps_command, stdin=PIPE, stdout=PIPE, stderr=PIPE)\n        while True:\n            try:\n                line = ps_pid.stdout.readline().decode('utf-8', 'replace').strip()\n            except KeyboardInterrupt:\n                break\n            if len(line) == 0:\n                break\n\n            pid_match = PID_LINE.match(line)\n            if pid_match is not None:\n                pid = pid_match.group(1)\n                proc = pid_match.group(2)\n                if proc in self.catchall_package:\n                    self.pids.add(pid)\n\n    def loop(self):\n        app_pid = None\n\n        while self.adb.poll() is None:\n            try:\n                line = self.adb.stdout.readline()\n            except KeyboardInterrupt:\n                break\n            if len(line) == 0:\n                break\n\n            line = line.decode('utf-8', 'replace').strip()\n            if len(line) == 0:\n                continue\n\n            bug_line = BUG_LINE.match(line)\n            if bug_line is not None:\n                continue\n\n            date, time, level, tag, owner, thread, message = self.log_regex.parse(line)\n            if message is None:\n                # print 'message is none with %s' % line\n                continue\n\n            tag = tag.strip()\n            start = parse_start_proc(line)\n            if start:\n                line_package, target, line_pid, line_uid, line_gids = start\n                if self.match_packages(line_package):\n                    self.pids.add(line_pid)\n\n                    app_pid = line_pid\n\n                    linebuf = '\\n'\n                    linebuf += colorize(' ' * (self.header_size - 1), bg=WHITE)\n                    linebuf += indent_wrap(' Process %s created for %s\\n' % (line_package, target))\n                    linebuf += colorize(' ' * (self.header_size - 1), bg=WHITE)\n                    linebuf += ' PID: %s   UID: %s   GIDs: %s' % (line_pid, line_uid, line_gids)\n                    linebuf += '\\n'\n                    print(linebuf)\n\n            dead_pid, dead_pname = self.parse_death(tag, message)\n            if dead_pid:\n                self.pids.remove(dead_pid)\n                linebuf = '\\n'\n                linebuf += colorize(' ' * (self.header_size - 1), bg=RED)\n                linebuf += ' Process %s (PID: %s) ended' % (dead_pname, dead_pid)\n                linebuf += '\\n'\n                print(linebuf)\n\n            # Make sure the backtrace is printed after a native crash\n            if tag == 'DEBUG':\n                bt_line = BACKTRACE_LINE.match(message.lstrip())\n                if bt_line is not None:\n                    message = message.lstrip()\n                    owner = app_pid\n\n            # print '%s %s %s' % (owner, self.pids, tag)\n            if not self.all and owner not in self.pids:\n                continue\n            if level in LOG_LEVELS_MAP and LOG_LEVELS_MAP[level] < self.min_level:\n                continue\n            if self.ignored_tag and tag_in_tags_regex(tag, self.ignored_tag):\n                continue\n            if self.tag and not tag_in_tags_regex(tag, self.tag):\n                continue\n\n            msg_key, linebuf, match_precondition = self.processor.process_decode_content(line, time, level, tag, owner,\n                                                                                         thread,\n                                                                                         message)\n            if not match_precondition or linebuf is None:\n                continue\n\n            if msg_key is not None:\n                print('')\n                print_unicode(u''.join(colorize(msg_key + \": \", fg=allocate_color(msg_key))).encode('utf-8').lstrip())\n\n            print_unicode(u''.join(linebuf).encode('utf-8').lstrip())\n\n    def match_packages(self, token):\n        if len(self.package_name) == 0:\n            return True\n        if token in self.named_processes:\n            return True\n        index = token.find(':')\n        return (token in self.catchall_package) if index == -1 else (token[:index] in self.catchall_package)\n\n    def parse_death(self, _tag, _message):\n        if _tag != 'ActivityManager':\n            return None, None\n        kill = PID_KILL.match(_message)\n        if kill:\n            _pid = kill.group(1)\n            package_line = kill.group(2)\n            if self.match_packages(package_line) and _pid in self.pids:\n                return _pid, package_line\n        leave = PID_LEAVE.match(_message)\n        if leave:\n            _pid = leave.group(2)\n            package_line = leave.group(1)\n            if self.match_packages(package_line) and _pid in self.pids:\n                return _pid, package_line\n        death = PID_DEATH.match(_message)\n        if death:\n            _pid = death.group(2)\n            package_line = death.group(1)\n            if self.match_packages(package_line) and _pid in self.pids:\n                return _pid, package_line\n        return None, None  # This is a ducktype of the subprocess.Popen object\n\n\nclass FakeStdinProcess:\n    def __init__(self):\n        self.stdout = sys.stdin\n\n    @staticmethod\n    def poll():\n        return None\n\n\ndef parse_start_proc(_line):\n    _start = PID_START_5_1.match(_line)\n    if _start is not None:\n        _line_pid, _line_package, _target = _start.groups()\n        return _line_package, _target, _line_pid, '', ''\n    _start = PID_START.match(_line)\n    if _start is not None:\n        _line_package, _target, _line_pid, _line_uid, _line_gids = _start.groups()\n        return _line_package, _target, _line_pid, _line_uid, _line_gids\n    _start = PID_START_DALVIK.match(_line)\n    if _start is not None:\n        _line_pid, _line_package, _line_uid = _start.groups()\n        return _line_package, '', _line_pid, _line_uid, ''\n    return None\n\n\ndef tag_in_tags_regex(_tag, tags):\n    return any(re.match(r'^' + t + r'$', _tag) for t in map(str.strip, tags))\n"
  },
  {
    "path": "okcat/confloader.py",
    "content": "#!/usr/bin/env python\n# coding: utf-8\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\n\nimport yaml\n\nfrom okcat.helper import handle_home_case, get_conf_path\n\n__author__ = 'JacksGong'\n\n\nclass ConfLoader:\n    yml_conf = None\n    from_yml_conf = None\n\n    def __init__(self):\n        pass\n\n    def load(self, yml_file_path):\n        with open(handle_home_case(yml_file_path), 'r') as stream:\n            try:\n                self.yml_conf = yaml.load(stream, yaml.SafeLoader)\n                from_conf_path = self.get_from()\n                if from_conf_path is not None:\n                    self.from_yml_conf = ConfLoader()\n                    self.from_yml_conf.load(get_conf_path(from_conf_path))\n\n                    # print(u'find yml configuration on %s:' % yml_file_path)\n                    # self.dump()\n\n            except yaml.YAMLError as exc:\n                print(exc)\n\n    def get_from(self):\n        return self.get_value('from')\n\n    def get_package(self):\n        return self.get_value('package')\n\n    def get_tag_keyword_list(self):\n        return self.get_value('tag-keyword-list')\n\n    def get_trans_msg_map(self):\n        return self.get_value('trans-msg-map')\n\n    def get_trans_tag_map(self):\n        return self.get_value('trans-tag-map')\n\n    def get_hide_msg_list(self):\n        return self.get_value('hide-msg-list')\n\n    def get_ignore_msg_list(self):\n        return self.get_value('ignore-msg-list')\n\n    def get_ignore_tag_list(self):\n        return self.get_value('ignore-tag-list')\n\n    def get_highlight_list(self):\n        return self.get_value('highlight-list')\n\n    def get_log_line_regex(self):\n        return self.get_value('log-line-regex')\n\n    def get_adb_log_line_regex(self):\n        return self.get_value('adb-log-line-regex')\n\n    def get_separator_regex_list(self):\n        return self.get_value('separator-regex-list')\n\n    def get_value(self, keyword):\n        if keyword not in self.yml_conf or self.yml_conf[keyword] is None:\n            if keyword != 'from' and self.from_yml_conf is not None:\n                return self.from_yml_conf.get_value(keyword)\n            else:\n                return None\n\n        origin = self.yml_conf[keyword]\n        if self.from_yml_conf is not None and self.from_yml_conf.get_value(keyword) is not None:\n            values  = self.from_yml_conf.get_value(keyword)\n            if type(origin) == list:\n                origin.extend(values)\n            if type(origin) == dict:\n                origin.update(values)\n\n        return origin\n\n    def dump(self):\n        print('from: %s' % self.get_from())\n        print('package: %s' % self.get_package())\n        print('log-line-regex: %s' % self.get_log_line_regex())\n        print('adb-log-line-regex: %s' % self.get_adb_log_line_regex())\n        self.dump_list('tag-keyword-list')\n        self.dump_unicode_map('trans-msg-map')\n        self.dump_unicode_map('trans-tag-map')\n        self.dump_list('hide-msg-list')\n        self.dump_list('ignore-msg-list')\n        self.dump_list('ignore-tag-list')\n        self.dump_list('highlight-list')\n        self.dump_list('separator-regex-list')\n\n    def dump_unicode_map(self, map_key):\n        unicode_map = self.get_value(map_key)\n        if unicode_map is None:\n            print('%s: None' % map_key)\n        else:\n            print('%s:' % map_key)\n            for key in unicode_map:\n                print(u'    \"%s\" : \"%s\"' % (key, unicode_map[key]))\n\n    def dump_list(self, list_key):\n        cur_list = self.get_value(list_key)\n        if cur_list is None:\n            print('%s: None' % list_key)\n        else:\n            print('%s: ' % list_key)\n            for value in cur_list:\n                print('    - %s' % value)\n"
  },
  {
    "path": "okcat/helper.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nimport re\nimport sys\n\nfrom os import environ, getcwd\n\nfrom os.path import exists\n\n__author__ = 'JacksGong'\n\nLOG_LEVELS = 'VDIWEF'\nLOG_LEVELS_MAP = dict([(LOG_LEVELS[i], i) for i in range(len(LOG_LEVELS))])\n\nNO_HOME_PATH = re.compile(r'~/(.*)')\nHOME_PATH = environ['HOME']\n\n\n# get the home case path\ndef handle_home_case(path):\n    path = path.strip()\n    if path.startswith('~/'):\n        path = HOME_PATH + '/' + NO_HOME_PATH.match(path).groups()[0]\n    return path\n\n\ndef is_path(path):\n    if path.startswith('/') or path.startswith('~/') or path.startswith('./'):\n        return True\n\n    if exists(path):\n        return True\n    return False\n\n\ndef get_conf_path(conf_name):\n    if not conf_name.endswith('.yml'):\n        conf_name = conf_name + '.yml'\n\n    cur_path_yml = '%s/%s' % (getcwd(), conf_name)\n    if exists(cur_path_yml):\n        result = cur_path_yml\n    else:\n        result = '~/.okcat/' + conf_name\n\n    print('using config on %s' % result)\n    return result\n\n\ndef print_unicode(line):\n    if sys.version_info >= (3, 0):\n        print(bytes.decode(line))\n    else:\n        print(line)\n\ndef line_rstrip(line):\n    if sys.version_info >= (3, 0):\n        return line.rstrip()\n    else:\n        try:\n            return line.decode('utf-8').rstrip()\n        except UnicodeDecodeError:\n            return line.rstrip()"
  },
  {
    "path": "okcat/logfile_parser.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nimport re\nfrom os.path import exists\n\nfrom okcat.confloader import ConfLoader\nfrom okcat.helper import get_conf_path, print_unicode\nfrom okcat.logprocessor import LogProcessor\nfrom okcat.terminalcolor import colorize, allocate_color\n\nTIME_REGEX = r'\\d{2}-\\d{2} .*\\d{2}:\\d{2}:\\d{2}\\.\\d+'\n\n\nclass LogFileParser:\n    filePaths = []\n    valid = False\n    processor = None\n    hideSameTags = None\n    logStreams = []\n    cacheLines = []\n    lineTimes = []\n\n    def __init__(self, file_paths, hide_same_tags):\n        self.filePaths = file_paths\n        self.hideSameTags = hide_same_tags\n\n    def setup(self, yml_file_name):\n        for path in self.filePaths:\n            if not exists(path):\n                exit(\"log path: %s is not exist!\" % path)\n        self.processor = LogProcessor(self.hideSameTags)\n\n        loader = ConfLoader()\n        loader.load(get_conf_path(yml_file_name))\n\n        self.processor.setup_trans(trans_msg_map=loader.get_trans_msg_map(),\n                                   trans_tag_map=loader.get_trans_tag_map(),\n                                   hide_msg_list=loader.get_hide_msg_list())\n        self.processor.setup_separator(separator_rex_list=loader.get_separator_regex_list())\n        self.processor.setup_highlight(highlight_list=loader.get_highlight_list())\n        self.processor.setup_condition(tag_keywords=loader.get_tag_keyword_list())\n        log_line_regex = loader.get_log_line_regex()\n        if log_line_regex is None:\n            log_line_regex = 'date,time,process,thread,level,tag,message = \"(.\\S*) *(.\\S*) *(\\d*) *(\\d*) *([A-Z]) *([^:]*): (.*?)$\"'\n            print(\"you don't provide 'log_line-regex' for parse each line on file, so we use this one as default:\")\n            print(log_line_regex + \"\\n\")\n        self.processor.setup_regex_parser(regex_exp=log_line_regex)\n\n    def color_line(self, line):\n        msg_key, line_buf, match_precondition = self.processor.process(line)\n\n        if not match_precondition:\n            return\n\n        if msg_key is not None:\n            print('')\n            print_unicode(u''.join(colorize(msg_key + \": \", fg=allocate_color(msg_key))).encode('utf-8').lstrip())\n\n        print_unicode(u''.join(line_buf).encode('utf-8').lstrip())\n\n    def popup_cache_line(self, popup_index):\n        need_read_stream = self.logStreams[popup_index]\n        new_line = need_read_stream.readline()\n        if new_line:\n            match_result = re.search(TIME_REGEX, new_line)\n            if match_result:\n                self.lineTimes.insert(popup_index, match_result.group())\n                self.cacheLines.insert(popup_index, new_line)\n            else:\n                self.color_line(new_line)\n                self.popup_cache_line(popup_index)\n        else:\n            need_read_stream.close()\n            self.logStreams.pop(popup_index)\n\n    def process(self):\n        origin_index = 0\n        for path in self.filePaths:\n            stream = open(path, \"r\")\n            self.logStreams.append(stream)\n            self.popup_cache_line(origin_index)\n            origin_index += 1\n\n        while self.cacheLines:\n            min_index = self.lineTimes.index(min(self.lineTimes))\n            self.lineTimes.pop(min_index)\n            selected_line = self.cacheLines.pop(min_index)\n            self.color_line(selected_line)\n            self.popup_cache_line(min_index)\n"
  },
  {
    "path": "okcat/logprocessor.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nimport re\n\nfrom okcat.helper import line_rstrip\nfrom okcat.logregex import LogRegex\nfrom okcat.logseparator import LogSeparator\nfrom okcat.terminalcolor import allocate_color, colorize, TAGTYPES, termcolor, BLACK, RESET\nfrom okcat.trans import Trans\n\n__author__ = 'JacksGong'\n\nTIME_WIDTH = 12\nTHREAD_WIDTH = 12\nTAG_WIDTH = 23\n\nwidth = -1\n# noinspection PyBroadException\ntry:\n    # Get the current terminal width\n    import fcntl, termios, struct\n\n    h, width = struct.unpack('hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0)))\nexcept:\n    pass\n\nheader_size = TAG_WIDTH + 1 + 3 + 1  # space, level, space\n\n\ndef indent_wrap(message):\n    return message\n\n\ndef keywords_regex(content, keywords):\n    return any(re.match(r'.*' + t + r'.*', content) for t in map(str.strip, keywords))\n\n\nclass LogProcessor:\n    hide_same_tags = None\n    trans = None\n    tag_keywords = None\n    line_keywords = None\n    separator = None\n    regex_parser = None\n    highlight_list = None\n    # target_time = None\n\n    ignore_msg_list = None\n    ignore_tag_list = None\n\n    # tmp\n    last_msg_key = None\n    last_tag = None\n    pre_line_match = True\n\n    def __init__(self, hide_same_tags):\n        self.hide_same_tags = hide_same_tags\n\n    def setup_trans(self, trans_msg_map, trans_tag_map, hide_msg_list):\n        self.trans = Trans(trans_msg_map, trans_tag_map, hide_msg_list)\n\n    def setup_ignore(self, ignore_msg_list, ignore_tag_list):\n        self.ignore_msg_list = ignore_msg_list\n        self.ignore_tag_list = ignore_tag_list\n\n    def setup_separator(self, separator_rex_list):\n        if separator_rex_list is not None:\n            self.separator = LogSeparator(separator_rex_list)\n\n    def setup_highlight(self, highlight_list):\n        self.highlight_list = highlight_list\n\n    def setup_condition(self, tag_keywords, line_keywords=None):\n        self.tag_keywords = tag_keywords\n        self.line_keywords = line_keywords\n\n    def setup_regex_parser(self, regex_exp):\n        self.regex_parser = LogRegex(regex_exp)\n\n    def process(self, origin_line):\n        origin_line = line_rstrip(origin_line)\n\n        if len(origin_line.strip()) <= 0:\n            return None, None, False\n\n        if self.regex_parser is None:\n            return None, None, False\n\n        date, time, level, tag, process, thread, message = self.regex_parser.parse(origin_line)\n        if message is None:\n            message = origin_line\n\n        return self.process_decode_content(origin_line, time, level, tag, process, thread, message)\n\n    # noinspection PyUnusedLocal\n    def process_decode_content(self, line, time, level, tag, process, thread, message):\n\n        match_condition = True\n\n        # filter\n        if self.tag_keywords is not None and tag is not None:\n            if not keywords_regex(tag, self.tag_keywords):\n                match_condition = False\n                self.pre_line_match = False\n            else:\n                self.pre_line_match = True\n\n\n\n        if self.line_keywords is not None:\n            if not keywords_regex(line, self.line_keywords):\n                match_condition = False\n                self.pre_line_match = False\n            else:\n                self.pre_line_match = True\n\n        if match_condition and tag is None and not self.pre_line_match:\n            match_condition = False\n\n        # if 'special world' in line:\n        #     match_precondition = True\n\n        if self.ignore_msg_list is not None:\n            for ignore_msg in self.ignore_msg_list:\n                if message.startswith(ignore_msg):\n                    match_condition = False\n\n        if self.ignore_tag_list is not None:\n            if tag in self.ignore_tag_list:\n                match_condition = False\n\n        if not match_condition:\n            return None, None, None\n\n        msgkey = None\n        # the handled current line\n        linebuf = ''\n\n        # time\n        if time is not None:\n            time = time[-TIME_WIDTH:].rjust(TIME_WIDTH)\n            linebuf += time\n            linebuf += ' '\n        elif self.regex_parser.is_contain_time():\n            linebuf += ' ' * TIME_WIDTH\n            linebuf += ' '\n\n        # thread\n        if thread is not None:\n            thread = thread.strip()\n            thread = thread[-THREAD_WIDTH:].rjust(THREAD_WIDTH)\n            linebuf += thread\n            linebuf += ' '\n        elif self.regex_parser.is_contain_thread():\n            linebuf += ' ' * THREAD_WIDTH\n            linebuf += ' '\n\n        # tag\n        if tag is not None and (not self.hide_same_tags or tag != self.last_tag):\n            self.last_tag = tag\n            tag = tag.strip()\n            color = allocate_color(tag)\n            tag = tag.strip()\n            tag = tag[-TAG_WIDTH:].rjust(TAG_WIDTH)\n            linebuf += colorize(tag, fg=color)\n            linebuf += ' '\n        elif self.regex_parser.is_contain_tag():\n            linebuf += ' ' * TAG_WIDTH\n            linebuf += ' '\n\n        # level\n        if level is not None:\n            if level in TAGTYPES:\n                linebuf += TAGTYPES[level]\n            else:\n                linebuf += ' ' + level + ' '\n            linebuf += ' '\n        elif self.regex_parser.is_contain_level():\n            linebuf += ' '\n            linebuf += ' '\n\n        # message\n        # -separator\n        if self.separator is not None:\n            msgkey = self.separator.process(message)\n\n        # -trans\n        if self.trans is not None:\n            message = self.trans.trans_msg(message)\n            message = self.trans.hide_msg(message)\n            message = self.trans.trans_tag(tag, message)\n\n        if self.highlight_list is not None:\n            for highlight in self.highlight_list:\n                if highlight in message:\n                    message = message.replace(highlight,\n                                              termcolor(fg=BLACK, bg=allocate_color(highlight)) + highlight + RESET)\n\n        linebuf += message\n\n        return msgkey, linebuf, match_condition\n"
  },
  {
    "path": "okcat/logregex.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\n\nimport re\n\nfrom okcat.terminalcolor import print_warn\n\n__author__ = 'jacks.gong'\n\n# val = 'date,time,process,thread,level,tag,message = \"(.\\S*) (.\\S*) (\\d*) (\\d*) ([V|I|D|W|E]) ([^:]*): (.*)\"'\nREGEX_EXP_RE = re.compile(r\"([^ =]*) *= *[\\\"|'](.*)[\\\"|']\")\nALL_SUPPORT_KEY = [\"date\", \"time\", \"process\", \"thread\", \"level\", \"tag\", \"message\"]\nDEPRECATED_DATE_KEY = \"data\"\n\nclass LogRegex:\n    key_order = list()\n\n    regex = None\n\n    def __init__(self, regex_exp):\n        keys, regex = REGEX_EXP_RE.match(regex_exp).groups()\n        process_key_order = keys.split(',')\n\n        self.regex = re.compile(r\"%s\" % regex)\n        for key in process_key_order:\n            key = key.strip()\n            if key in ALL_SUPPORT_KEY:\n                self.key_order.append(key)\n            elif key == DEPRECATED_DATE_KEY:\n                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'\")\n                self.key_order.append('date')\n            else:\n                print_warn(\"not support key[%s] only support: %s\" % (key, ALL_SUPPORT_KEY))\n\n        print(\"find regex: \" + self.key_order.__str__() + \" with \" + regex)\n\n    def parse(self, line):\n        date = None\n        time = None\n        process = None\n        thread = None\n        level = None\n        tag = None\n        message = None\n\n        values = self.regex.match(line)\n\n        if values is None:\n            return date, time, level, tag, process, thread, message\n\n        # print(values.groups().__str__())\n        i = 0\n        for value in values.groups():\n            key = self.key_order[i]\n            i += 1\n            if key == \"date\":\n                date = value\n            elif key == \"time\":\n                time = value\n            elif key == \"process\":\n                process = value\n            elif key == \"thread\":\n                thread = value\n            elif key == \"level\":\n                level = value\n            elif key == \"tag\":\n                tag = value\n            elif key == \"message\":\n                message = value\n\n        return date, time, level, tag, process, thread, message\n\n    contain_date = None\n    contain_time = None\n    contain_thread = None\n    contain_tag = None\n    contain_level = None\n\n    def is_contain_date(self):\n        if self.contain_date is None:\n            self.contain_date = self.is_contain_key(\"date\")\n        return self.contain_date\n\n    def is_contain_time(self):\n        if self.contain_time is None:\n            self.contain_time = self.is_contain_key(\"time\")\n        return self.contain_time\n\n    def is_contain_thread(self):\n        if self.contain_thread is None:\n            self.contain_thread = self.is_contain_key(\"thread\")\n        return self.contain_thread\n\n    def is_contain_tag(self):\n        if self.contain_tag is None:\n            self.contain_tag = self.is_contain_key(\"tag\")\n        return self.contain_tag\n\n    def is_contain_level(self):\n        if self.contain_level is None:\n            self.contain_level = self.is_contain_key(\"level\")\n        return self.contain_level\n\n    def is_contain_key(self, key):\n        return key in self.key_order\n"
  },
  {
    "path": "okcat/logseparator.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nimport re\n\n__author__ = 'JacksGong'\n\n\nclass LogSeparator:\n    separator_rex_list = list()\n    pre_separate_key = None\n\n    def __init__(self, separator_rex_list):\n        for regex_string in separator_rex_list:\n            self.separator_rex_list.append(re.compile(r'%s' % regex_string))\n\n    def process(self, msg):\n        key = None\n        for regex in self.separator_rex_list:\n            matched_obj = regex.match(msg)\n            if matched_obj is not None:\n                key = matched_obj.groups()[0]\n                break\n\n        if self.pre_separate_key is None:\n            if key is None:\n                key = \"unknown\"\n            self.pre_separate_key = key\n            return key\n        elif key is not None and self.pre_separate_key != key:\n            return key\n        else:\n            return None\n"
  },
  {
    "path": "okcat/terminalcolor.py",
    "content": "#!/usr/bin/env python\n# coding: utf-8\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\n\n__author__ = 'jacks.gong'\n\n\nclass BashColors:\n    def __init__(self):\n        pass\n\n    HEADER = '\\033[95m'\n    BLUE = '\\033[94m'\n    GREEN = '\\033[92m'\n    WARNING = '\\033[93m'\n    FAIL = '\\033[91m'\n    END = '\\033[0m'\n    BOLD = '\\033[1m'\n    UNDERLINE = '\\033[4m'\n\n\ndef print_header(msg):\n    print(BashColors.HEADER + msg + BashColors.END)\n\n\ndef print_exit(msg):\n    print(BashColors.FAIL + msg + BashColors.END)\n\n\ndef print_error(msg):\n    print(BashColors.FAIL + msg + BashColors.END)\n\n\ndef print_tips(msg):\n    print(BashColors.UNDERLINE + msg + BashColors.END)\n\n\ndef print_warn(msg):\n    print(BashColors.WARNING + msg + BashColors.END)\n\n\ndef print_key(msg):\n    print(BashColors.GREEN + msg + BashColors.END)\n\n\ndef print_blue(msg):\n    print(BashColors.BLUE + msg + BashColors.END)\n\n\ndef print_content_tips(msg):\n    msg = BashColors.UNDERLINE + msg + BashColors.END\n    print(msg)\n    return msg + \"\\n\"\n\n\ndef print_content_header(msg):\n    msg = BashColors.HEADER + msg + BashColors.END\n    print_content(msg)\n    return msg + \"\\n\"\n\n\ndef print_content(msg):\n    print(msg)\n    return msg\n\n\n# -------------- color -----------------------------\nRESET = '\\033[0m'\nBLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)\n\n\ndef termcolor(fg=None, bg=None):\n    codes = []\n    if fg is not None: codes.append('3%d' % fg)\n    if bg is not None: codes.append('10%d' % bg)\n    return '\\033[%sm' % ';'.join(codes) if codes else ''\n\n\ndef colorize(message, fg=None, bg=None):\n    if fg is None:\n        if bg == BLACK:\n            fg = WHITE\n        else:\n            fg = BLACK\n\n    return termcolor(fg, bg) + message + RESET\n\n\nTAGTYPES = {\n    'V': colorize(' V ', fg=WHITE, bg=BLACK),\n    'D': colorize(' D ', fg=BLACK, bg=BLUE),\n    'I': colorize(' I ', fg=BLACK, bg=GREEN),\n    'W': colorize(' W ', fg=BLACK, bg=YELLOW),\n    'E': colorize(' E ', fg=BLACK, bg=RED),\n    'F': colorize(' F ', fg=BLACK, bg=RED),\n}\n\n# for random color\nKNOWN_TAGS = {\n    'dalvikvm': WHITE,\n    'Process': WHITE,\n    'ActivityManager': WHITE,\n    'ActivityThread': WHITE,\n    'AndroidRuntime': CYAN,\n    'jdwp': WHITE,\n    'StrictMode': WHITE,\n    'DEBUG': YELLOW,\n}\nLAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]\nLOG_FLOW_KEY_LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]\n\n\ndef allocate_color(_key, loop_color=LAST_USED):\n    if _key not in KNOWN_TAGS:\n        KNOWN_TAGS[_key] = loop_color[0]\n\n    _color = KNOWN_TAGS[_key]\n    if _color in LAST_USED:\n        loop_color.remove(_color)\n        loop_color.append(_color)\n    return _color\n"
  },
  {
    "path": "okcat/trans.py",
    "content": "#!/usr/bin/env python\n# coding: utf-8\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nfrom okcat.terminalcolor import colorize, allocate_color, BLACK\n\n__author__ = 'JacksGong'\n\n\nclass Trans:\n    trans_msg_map = None\n    trans_tag_map = None\n    hide_msg_list = None\n\n    def __init__(self, trans_msg_map, trans_tag_map, hide_msg_list):\n        self.trans_msg_map = trans_msg_map\n        self.trans_tag_map = trans_tag_map\n        self.hide_msg_list = hide_msg_list\n\n    def trans_msg(self, msg):\n        if self.trans_msg_map is None:\n            return msg\n\n        for key in self.trans_msg_map:\n            if msg.startswith(key):\n                value = self.trans_msg_map[key]\n                return u'| %s | %s' % (colorize(value, fg=allocate_color(value)), msg)\n\n        return msg\n\n    def trans_tag(self, tag, msg):\n        if self.trans_tag_map is None or tag is None:\n            return msg\n\n        for key in self.trans_tag_map:\n            if key in tag:\n                prefix = self.trans_tag_map[key]\n                return u'%s %s' % (colorize(prefix, bg=allocate_color(prefix)), msg)\n\n        return msg\n\n    def hide_msg(self, msg):\n        if self.hide_msg_list is None:\n            return msg\n\n        # print(\"get hide msg list: %s and len(%d)\" % (self.hide_msg_list, len(msg)))\n        # if msg.__len__() > 100:\n        #     return msg\n\n        for gray_msg in self.hide_msg_list:\n            if msg.startswith(gray_msg):\n                return colorize(msg, fg=BLACK)\n\n        return msg\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/python -u\n\n\"\"\"\nCopyright (C) 2017 Jacksgong(jacksgong.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\"\"\"\nfrom setuptools import setup, find_packages\n\n# Get the long description from the README file\n# noinspection PyArgumentList\nsetup(\n    name=\"OkCat\",\n    version=\"1.4.0\",\n    packages=find_packages(exclude=['demo-conf', 'arts']),\n\n    # Project uses reStructuredText, so ensure that the docutils get\n    # installed or upgraded on the target machine\n    install_requires=['PyYAML>=3.12'],\n\n    # metadata for upload to PyPI\n    author=\"Jacksgong\",\n    author_email=\"igzhenjie@gmail.com\",\n    description=\"An powerful log processor\",\n    long_description='More detail please move to https://github.com/Jacksgong/okcat',\n    license=\"Apache2\",\n    keywords=\"okcat log 'log processor' 'log filter'\",\n    url=\"https://github.com/Jacksgong/okcat\",\n\n    # See https://pypi.python.org/pypi?%3Aaction=list_classifiers\n    classifiers=[\n        # How mature is this project? Common values are\n        #   3 - Alpha\n        #   4 - Beta\n        #   5 - Production/Stable\n        'Development Status :: 5 - Production/Stable',\n\n        # Pick your license as you wish (should match \"license\" above)\n        'License :: OSI Approved :: Apache Software License',\n\n        # Specify the Python versions you support here. In particular, ensure\n        # that you indicate whether you support Python 2, Python 3 or both.\n        'Programming Language :: Python :: 2.7',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.3',\n        'Programming Language :: Python :: 3.4',\n        'Programming Language :: Python :: 3.5',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3.9',\n    ],\n    entry_points={\n        'console_scripts': [\n            'okcat=okcat:main'\n        ]\n    }\n)\n"
  }
]