├── .gitchangelog.rc ├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── CHANGELOG.rst ├── HISTORY.rst ├── LICENSE.txt ├── README.rst ├── cmddocs-md2ascii.png ├── cmddocs.py ├── cmddocs ├── __init__.py ├── articles.py ├── completions.py ├── rendering.py ├── utils.py └── version.py ├── release.sh ├── setup.py └── tests ├── conftest.py ├── test_delete.py ├── test_directory.py ├── test_edit.py ├── test_info.py ├── test_initializaton.py ├── test_list.py ├── test_log.py ├── test_misc.py ├── test_move.py ├── test_search.py ├── test_stats.py └── test_undo.py /.gitchangelog.rc: -------------------------------------------------------------------------------- 1 | ## 2 | ## Format 3 | ## 4 | ## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] 5 | ## 6 | ## Description 7 | ## 8 | ## ACTION is one of 'chg', 'fix', 'new' 9 | ## 10 | ## Is WHAT the change is about. 11 | ## 12 | ## 'chg' is for refactor, small improvement, cosmetic changes... 13 | ## 'fix' is for bug fixes 14 | ## 'new' is for new features, big improvement 15 | ## 16 | ## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc' 17 | ## 18 | ## Is WHO is concerned by the change. 19 | ## 20 | ## 'dev' is for developpers (API changes, refactors...) 21 | ## 'usr' is for final users (UI changes) 22 | ## 'pkg' is for packagers (packaging changes) 23 | ## 'test' is for testers (test only related changes) 24 | ## 'doc' is for doc guys (doc only changes) 25 | ## 26 | ## COMMIT_MSG is ... well ... the commit message itself. 27 | ## 28 | ## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' 29 | ## 30 | ## They are preceded with a '!' or a '@' (prefer the former, as the 31 | ## latter is wrongly interpreted in github.) Commonly used tags are: 32 | ## 33 | ## 'refactor' is obviously for refactoring code only 34 | ## 'minor' is for a very meaningless change (a typo, adding a comment) 35 | ## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) 36 | ## 'wip' is for partial functionality but complete subfunctionality. 37 | ## 38 | ## Example: 39 | ## 40 | ## new: usr: support of bazaar implemented 41 | ## chg: re-indentend some lines !cosmetic 42 | ## new: dev: updated code to be compatible with last version of killer lib. 43 | ## fix: pkg: updated year of licence coverage. 44 | ## new: test: added a bunch of test around user usability of feature X. 45 | ## fix: typo in spelling my name in comment. !minor 46 | ## 47 | ## Please note that multi-line commit message are supported, and only the 48 | ## first line will be considered as the "summary" of the commit message. So 49 | ## tags, and other rules only applies to the summary. The body of the commit 50 | ## message will be displayed in the changelog without reformatting. 51 | 52 | 53 | ## 54 | ## ``ignore_regexps`` is a line of regexps 55 | ## 56 | ## Any commit having its full commit message matching any regexp listed here 57 | ## will be ignored and won't be reported in the changelog. 58 | ## 59 | ignore_regexps = [ 60 | r'@minor', r'!minor', 61 | r'@cosmetic', r'!cosmetic', 62 | r'@refactor', r'!refactor', 63 | r'@wip', r'!wip', 64 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', 65 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', 66 | r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', 67 | ] 68 | 69 | 70 | ## ``section_regexps`` is a list of 2-tuples associating a string label and a 71 | ## list of regexp 72 | ## 73 | ## Commit messages will be classified in sections thanks to this. Section 74 | ## titles are the label, and a commit is classified under this section if any 75 | ## of the regexps associated is matching. 76 | ## 77 | section_regexps = [ 78 | ('Feature', [ 79 | r'^[fF]eature\s*:\s*([^\n]*)$', 80 | ]), 81 | ('Changes', [ 82 | r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 83 | ]), 84 | ('Fix', [ 85 | r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 86 | ]), 87 | ('Documentation', [ 88 | r'^[dD]ocumentation\s*:\s*([^\n]*)$', 89 | ]), 90 | 91 | ('Other', None ## Match all lines 92 | ), 93 | 94 | ] 95 | 96 | 97 | ## ``body_process`` is a callable 98 | ## 99 | ## This callable will be given the original body and result will 100 | ## be used in the changelog. 101 | ## 102 | ## Available constructs are: 103 | ## 104 | ## - any python callable that take one txt argument and return txt argument. 105 | ## 106 | ## - ReSub(pattern, replacement): will apply regexp substitution. 107 | ## 108 | ## - Indent(chars=" "): will indent the text with the prefix 109 | ## Please remember that template engines gets also to modify the text and 110 | ## will usually indent themselves the text if needed. 111 | ## 112 | ## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns 113 | ## 114 | ## - noop: do nothing 115 | ## 116 | ## - ucfirst: ensure the first letter is uppercase. 117 | ## (usually used in the ``subject_process`` pipeline) 118 | ## 119 | ## - final_dot: ensure text finishes with a dot 120 | ## (usually used in the ``subject_process`` pipeline) 121 | ## 122 | ## - strip: remove any spaces before or after the content of the string 123 | ## 124 | ## Additionally, you can `pipe` the provided filters, for instance: 125 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") 126 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') 127 | #body_process = noop 128 | body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip 129 | 130 | 131 | ## ``subject_process`` is a callable 132 | ## 133 | ## This callable will be given the original subject and result will 134 | ## be used in the changelog. 135 | ## 136 | ## Available constructs are those listed in ``body_process`` doc. 137 | subject_process = (strip | 138 | ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | 139 | ucfirst | final_dot) 140 | 141 | 142 | ## ``tag_filter_regexp`` is a regexp 143 | ## 144 | ## Tags that will be used for the changelog must match this regexp. 145 | ## 146 | tag_filter_regexp = r'^[0-9]+\.[0-9]+(\.[0-9]+)?$' 147 | 148 | 149 | ## ``unreleased_version_label`` is a string 150 | ## 151 | ## This label will be used as the changelog Title of the last set of changes 152 | ## between last valid tag and HEAD if any. 153 | unreleased_version_label = "%%version%% (unreleased)" 154 | 155 | 156 | ## ``output_engine`` is a callable 157 | ## 158 | ## This will change the output format of the generated changelog file 159 | ## 160 | ## Available choices are: 161 | ## 162 | ## - rest_py 163 | ## 164 | ## Legacy pure python engine, outputs ReSTructured text. 165 | ## This is the default. 166 | ## 167 | ## - mustache() 168 | ## 169 | ## Template name could be any of the available templates in 170 | ## ``templates/mustache/*.tpl``. 171 | ## Requires python package ``pystache``. 172 | ## Examples: 173 | ## - mustache("markdown") 174 | ## - mustache("restructuredtext") 175 | ## 176 | ## - makotemplate() 177 | ## 178 | ## Template name could be any of the available templates in 179 | ## ``templates/mako/*.tpl``. 180 | ## Requires python package ``mako``. 181 | ## Examples: 182 | ## - makotemplate("restructuredtext") 183 | ## 184 | 185 | output_engine = rest_py 186 | #output_engine = mustache("restructuredtext") 187 | #output_engine = mustache("markdown") 188 | #output_engine = makotemplate("restructuredtext") 189 | 190 | 191 | ## ``include_merge`` is a boolean 192 | ## 193 | ## This option tells git-log whether to include merge commits in the log. 194 | ## The default is to include them. 195 | include_merge = True 196 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.9 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | - name: Install dependencies 24 | run: | 25 | sudo apt-get install -y tree 26 | python -m pip install --upgrade pip 27 | pip install flake8 pytest pytest-cov 28 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 29 | pip install . 30 | - name: Lint with flake8 31 | run: | 32 | # stop the build if there are Python syntax errors or undefined names 33 | 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | - name: Test with pytest 38 | run: | 39 | PYTHONPATH=. py.test --cov=cmddocs 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | /cmddocs.egg-info/ 4 | /dist/ 5 | /build/ 6 | .cache/* 7 | .coverage 8 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.17.0 (2016-11-15) 5 | ------------------- 6 | 7 | - Python 3.5 compatibility in setup and tests. [Florian Baumann] 8 | 9 | - Python3 compatibility: raw_input => input. [lonetwin] 10 | 11 | - Python3 compatibility: relative imports. [lonetwin] 12 | 13 | - Python3 compatibility: File mode changes. [lonetwin] 14 | 15 | Use text mode instead of binary to avoid unnecesary bytes-to-string 16 | conversions 17 | 18 | - Python3 compatibility: subprocess stdout bytes => utf-8 strings. [lonetwin] 19 | 20 | - Python3 compatibility: ConfigParser => configparser. [lonetwin] 21 | 22 | - Badges. [Florian Baumann] 23 | 24 | - Codecoverage for tests. [Florian Baumann] 25 | 26 | - Codecoverage for tests. [Florian Baumann] 27 | 28 | 0.16.2 (2016-04-12) 29 | ------------------- 30 | 31 | - Version bump. [Florian Baumann] 32 | 33 | - Fix mail with no parameters #32. [Florian Baumann] 34 | 35 | 0.16.1 (2016-04-12) 36 | ------------------- 37 | 38 | - Version bump. [Florian Baumann] 39 | 40 | - Fix for editorflags. [Florian Baumann] 41 | 42 | - Changelog update. [Florian Baumann] 43 | 44 | 0.16.0 (2016-04-12) 45 | ------------------- 46 | 47 | Fix 48 | ~~~ 49 | 50 | - No [Colors] section is also okay for config parser now. #29. [Florian Baumann] 51 | 52 | - Catch error when accessing git objects with diff. [Florian Baumann] 53 | 54 | Documentation 55 | ~~~~~~~~~~~~~ 56 | 57 | - Documentation: Screenshot in Readme. [Florian Baumann] 58 | 59 | - Documentation: Screenshot in Readme. [Florian Baumann] 60 | 61 | - Documentation: Readme format update. [Florian Baumann] 62 | 63 | - Documentation: Screenshot in Readme. [Florian Baumann] 64 | 65 | - Documentation: Screenshot in Readme. [Florian Baumann] 66 | 67 | Other 68 | ~~~~~ 69 | 70 | - Version bump. [Florian Baumann] 71 | 72 | - Implemented editorflags #31. [Florian Baumann] 73 | 74 | - Merge branch 'master' of github.com:noqqe/cmddocs. [Florian Baumann] 75 | 76 | - Changelog update. [Florian Baumann] 77 | 78 | 0.15.0 (2015-12-13) 79 | ------------------- 80 | 81 | Feature 82 | ~~~~~~~ 83 | 84 | - Feature: Added more tests for dir, log, search. [Florian Baumann] 85 | 86 | - Feature: Added more tests. [Florian Baumann] 87 | 88 | Fix 89 | ~~~ 90 | 91 | - Status and Initialization tests. [Florian Baumann] 92 | 93 | - Fixed broken tree call for directories and added tests. [Florian Baumann] 94 | 95 | - Stats test. [Florian Baumann] 96 | 97 | - Catch exception when dir or file in list not found. [Florian Baumann] 98 | 99 | - Tests for pwd and properly change to datadir. [Florian Baumann] 100 | 101 | - Tests imporved. [Florian Baumann] 102 | 103 | - Make checks work in travis. [Florian Baumann] 104 | 105 | Documentation 106 | ~~~~~~~~~~~~~ 107 | 108 | - Documentation: Fixed directory checks. [Florian Baumann] 109 | 110 | - Documentation: more tests. [Florian Baumann] 111 | 112 | - Documentation: directory checks. [Florian Baumann] 113 | 114 | - Documentation: undo, version, status Tests. [Florian Baumann] 115 | 116 | - Documentation: new tests and new testenv. [Florian Baumann] 117 | 118 | - Documentation: info command tests. [Florian Baumann] 119 | 120 | - Documentation: Restructured Tests. [Florian Baumann] 121 | 122 | - Documentation: reformat readme. [Florian Baumann] 123 | 124 | - Documentation: Docs for each command in Readme. [Florian Baumann] 125 | 126 | - Documentation: new sections for changelog. [Florian Baumann] 127 | 128 | - Documentation: new help messages. [Florian Baumann] 129 | 130 | - Documentation: Readme updated. [Florian Baumann] 131 | 132 | Other 133 | ~~~~~ 134 | 135 | - Release version 0.15.0. [Florian Baumann] 136 | 137 | - Edit tests. [Florian Baumann] 138 | 139 | - Removed pypy build from travis. [Florian Baumann] 140 | 141 | - Added user and mail for tests. [Florian Baumann] 142 | 143 | - Fix readme. [Florian Baumann] 144 | 145 | - Fix readme. [Florian Baumann] 146 | 147 | - Markup fix. [Florian Baumann] 148 | 149 | - Applied pylint changes. [Florian Baumann] 150 | 151 | - Version bump. [Florian Baumann] 152 | 153 | 0.14.0 (2015-12-08) 154 | ------------------- 155 | 156 | Feature 157 | ~~~~~~~ 158 | 159 | - Feature: Configurable colors for md to ascii #22. [Florian Baumann] 160 | 161 | - Feature: Configure pager flags - #20. [Florian Baumann] 162 | 163 | Documentation 164 | ~~~~~~~~~~~~~ 165 | 166 | - Documentation: updated Readme for Pagerflags. [Florian Baumann] 167 | 168 | Other 169 | ~~~~~ 170 | 171 | - More robust config in completions. [Florian Baumann] 172 | 173 | - Referenced Changelog in README.rst. [Florian Baumann] 174 | 175 | - Now using gitchangelog for python to provide proper changelog. [Florian Baumann] 176 | 177 | 0.13.0 (2015-12-08) 178 | ------------------- 179 | 180 | - Implemented version command #21. [Florian Baumann] 181 | 182 | - Count files and ignore .git. [Florian Baumann] 183 | 184 | - Implemented stats command - fixes #24. [Florian Baumann] 185 | 186 | - Fixed diff function and help message. [Florian Baumann] 187 | 188 | - Created info command to display informations about an article. [Florian Baumann] 189 | 190 | - Comma code style for arguments. [Florian Baumann] 191 | 192 | - Fixed bugs in log and diff because of missing file extensions. [Florian Baumann] 193 | 194 | - More py3 removals. [Florian Baumann] 195 | 196 | 0.12.3 (2015-11-11) 197 | ------------------- 198 | 199 | - Reverted py3 compatibility. Its fucked. [Florian Baumann] 200 | 201 | - Tree as dep in test build. [Florian Baumann] 202 | 203 | - Config example mail in tests. [Florian Baumann] 204 | 205 | - Added more tests. [Florian Baumann] 206 | 207 | 0.12.2 (2015-11-10) 208 | ------------------- 209 | 210 | - Bugfix default-extension when creating a new file. [Florian Baumann] 211 | 212 | - Switch to pytest. [Florian Baumann] 213 | 214 | - Added test and some restructuring. [Florian Baumann] 215 | 216 | - Made .cmddocsrc a class parameter. [Florian Baumann] 217 | 218 | - Deleted cache. [Florian Baumann] 219 | 220 | - Cache dir ignore. [Florian Baumann] 221 | 222 | - Tests init. [Florian Baumann] 223 | 224 | - Gitpython is broken with 3.2. [Florian Baumann] 225 | 226 | - Fixes py3.1-py3.4 setup py. [Florian Baumann] 227 | 228 | - Removed requirements due to fully compatible py3 py2 pip. [Florian Baumann] 229 | 230 | - Testing travis. [Florian Baumann] 231 | 232 | 0.12.1 (2015-11-08) 233 | ------------------- 234 | 235 | - Fixes for python3 install with pip. [Florian Baumann] 236 | 237 | 0.12.0 (2015-11-08) 238 | ------------------- 239 | 240 | - Python 3 compatibility - fixes #17. [Florian Baumann] 241 | 242 | - Fixes #11 - Default Filetype introduced! [Florian Baumann] 243 | 244 | 0.11.0 (2015-11-08) 245 | ------------------- 246 | 247 | - Fixes #11 - Default Filetype introduced! [Florian Baumann] 248 | 249 | - Readme update. [Florian Baumann] 250 | 251 | - Mail function #14. [Florian Baumann] 252 | 253 | - Merge pull request #19 from agundy/master. [Florian Baumann] 254 | 255 | Added exception catch for log. 256 | 257 | - Added exception catch for log. [Aaron Gunderson] 258 | 259 | 0.10.6 (2015-06-14) 260 | ------------------- 261 | 262 | - Added handler for tree dependency. [Florian Baumann] 263 | 264 | - Catch missing tree, converted all print statements. [Florian Baumann] 265 | 266 | 0.10.5 (2015-06-06) 267 | ------------------- 268 | 269 | - Version bump. [Florian Baumann] 270 | 271 | - Crtl-c signal handling. [Florian Baumann] 272 | 273 | 0.10.4 (2015-06-06) 274 | ------------------- 275 | 276 | - Version bump. [Florian Baumann] 277 | 278 | - Bug fixes, print syntax, return values. [Florian Baumann] 279 | 280 | 0.10.3 (2015-06-06) 281 | ------------------- 282 | 283 | - Version bump. [Florian Baumann] 284 | 285 | - Catch datadir not existing error. [Florian Baumann] 286 | 287 | 0.10.2 (2015-06-06) 288 | ------------------- 289 | 290 | - Repo init fix. [Florian Baumann] 291 | 292 | - Mistune requirements. [Florian Baumann] 293 | 294 | 0.10.0 (2015-06-06) 295 | ------------------- 296 | 297 | - Version bump. [Florian Baumann] 298 | 299 | - Deleted setup. [Florian Baumann] 300 | 301 | - Long description for pypi. [Florian Baumann] 302 | 303 | - Fixed list items. [Florian Baumann] 304 | 305 | - Readme to rst. [Florian Baumann] 306 | 307 | - Added mistune to PROPERLY parse markdown to ascii. [Florian Baumann] 308 | 309 | - Added mistune to PROPERLY parse markdown to ascii. [Florian Baumann] 310 | 311 | - Created undo/revert. [Florian Baumann] 312 | 313 | - Updated readme. [Florian Baumann] 314 | 315 | - Added sane config default fallbacks #1. [Florian Baumann] 316 | 317 | - Color prompt now configurable. [Florian Baumann] 318 | 319 | - Catching errors when exec without valid config #13. [Florian Baumann] 320 | 321 | - Article name search implemented #12. [Florian Baumann] 322 | 323 | - Updated readme for pip. [Florian Baumann] 324 | 325 | 0.9.1 (2015-05-17) 326 | ------------------ 327 | 328 | - Fix long description. [Florian Baumann] 329 | 330 | - Moved license. [Florian Baumann] 331 | 332 | - Setup.cfg. [Florian Baumann] 333 | 334 | - Ignores. [Florian Baumann] 335 | 336 | - Pip preparations. [Florian Baumann] 337 | 338 | 0.9.0 (2015-05-17) 339 | ------------------ 340 | 341 | - Added setup.py. [Florian Baumann] 342 | 343 | - Added diff functionality. [Florian Baumann] 344 | 345 | - Moved utils to compeltions. [Florian Baumann] 346 | 347 | - Removed imports - thanks to pyflakes. [Florian Baumann] 348 | 349 | - More structure. [Florian Baumann] 350 | 351 | - Lol. [Florian Baumann] 352 | 353 | - Gitignore. [Florian Baumann] 354 | 355 | - Moved to package. [Florian Baumann] 356 | 357 | - Better presentation of path. [Florian Baumann] 358 | 359 | - Merge branch 'posativ-patch-3' [Florian Baumann] 360 | 361 | - Merged. [Florian Baumann] 362 | 363 | - Use subprocess instead of os.system with string replacement. [Martin Zimmermann] 364 | 365 | - T push origin master Merge branch 'posativ-patch-4' [Florian Baumann] 366 | 367 | - Merged. [Florian Baumann] 368 | 369 | - Fix undefined behavior, mis-used classmethods. [Martin Zimmermann] 370 | 371 | - Accidentially wrong mapped alias. [Florian Baumann] 372 | 373 | - Merge pull request #3 from posativ/patch-2. [Florian Baumann] 374 | 375 | simplify command declaration 376 | 377 | - Simplify command declaration. [Martin Zimmermann] 378 | 379 | Minor drawback: the docstring for aliases is no longer available 380 | (replaced with the actual function's docstring). 381 | 382 | - Merge pull request #2 from posativ/patch-1. [Florian Baumann] 383 | 384 | expanduser for configuration variables 385 | 386 | - Expanduser for configuration variables. [Martin Zimmermann] 387 | 388 | - Error handling for rm and fix for mv. [Florian Baumann] 389 | 390 | - Prompt in new structure. [Florian Baumann] 391 | 392 | - Repo referenced in functions. [Florian Baumann] 393 | 394 | - Merged. [Florian Baumann] 395 | 396 | - Bugfix cwd. [Florian Baumann] 397 | 398 | - Fixed cwd problem. [Florian Baumann] 399 | 400 | - More variable passing. [Florian Baumann] 401 | 402 | - Merge branch 'master' into noglobals. [Florian Baumann] 403 | 404 | - Replaced dumb try with if. [Florian Baumann] 405 | 406 | - First steps making config in class. [Florian Baumann] 407 | 408 | - Just renaming. [Florian Baumann] 409 | 410 | - Function definitions. [Florian Baumann] 411 | 412 | - Small fix. [Florian Baumann] 413 | 414 | - Added intro message configurable and readme update. [Florian Baumann] 415 | 416 | - Prompt configurable. [Florian Baumann] 417 | 418 | - Removed double check of datadir. [Florian Baumann] 419 | 420 | - Merge branch 'master' of github.com:noqqe/cmddocs. [Florian Baumann] 421 | 422 | - Update LICENSE.md. [Florian Baumann] 423 | 424 | - Pager and editor now configurable in config. [Florian Baumann] 425 | 426 | - Merge branch 'master' of github.com:noqqe/cmddocs. [Florian Baumann] 427 | 428 | - Added license. [Florian Baumann] 429 | 430 | - Embedding of asciinema does not work... :( added link instead. [Florian Baumann] 431 | 432 | - Make config usergeneric. [Florian Baumann] 433 | 434 | - Docs update and helptexts improvements. [Florian Baumann] 435 | 436 | - Fixes for list dir. [Florian Baumann] 437 | 438 | - Restructuring. [Florian Baumann] 439 | 440 | - Readme added. [Florian Baumann] 441 | 442 | - Configparser. [Florian Baumann] 443 | 444 | - Arg parsing into functions, better error handling. [Florian Baumann] 445 | 446 | - Better error handling. [Florian Baumann] 447 | 448 | - Added check for EDITOR and PAGER. [Florian Baumann] 449 | 450 | - Default commit message implemented. [Florian Baumann] 451 | 452 | - Log messages. [Florian Baumann] 453 | 454 | - Intelligent log function. [Florian Baumann] 455 | 456 | - View mode with header and codeblock highlight. [Florian Baumann] 457 | 458 | - Highlighted view mode. [Florian Baumann] 459 | 460 | - Added basic pager, view mode. [Florian Baumann] 461 | 462 | - Fix mv and colors for log. [Florian Baumann] 463 | 464 | - Added comments, move and delete functionality. [Florian Baumann] 465 | 466 | - Make cd able to switch to default. [Florian Baumann] 467 | 468 | - Stopped experimenting with python made tree-like output. [Florian Baumann] 469 | 470 | - Colored search. [Florian Baumann] 471 | 472 | - Working search. [Florian Baumann] 473 | 474 | - Var replacement and datadir. [Florian Baumann] 475 | 476 | - Path completion for all functions. [Florian Baumann] 477 | 478 | - Fix dir not found message. [Florian Baumann] 479 | 480 | - Added 'safe' cd function. [Florian Baumann] 481 | 482 | - Implemented search function.. start.. [Florian Baumann] 483 | 484 | - Log improvements and list replacement. [Florian Baumann] 485 | 486 | - Huge steps, we make. [Florian Baumann] 487 | 488 | - L can now take arguments. [Florian Baumann] 489 | 490 | - Completion without .git directory. [Florian Baumann] 491 | 492 | - Added completion to list. [Florian Baumann] 493 | 494 | - Fixed edit with new subdirs. [Florian Baumann] 495 | 496 | - Init. [Florian Baumann] 497 | 498 | 499 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 5 | %%version%% (unreleased) 6 | ------------------------ 7 | - Release: 1.0.3. [Florian Baumann] 8 | - Fix. [Florian Baumann] 9 | - Changelog: 1.0.2. [Florian Baumann] 10 | - Release: 1.0.2. [Florian Baumann] 11 | - Fix changelog. [Florian Baumann] 12 | - Release: 1.0.1. [Florian Baumann] 13 | - Fix release script. [Florian Baumann] 14 | - Changelog: 1.0.0. [Florian Baumann] 15 | 16 | 17 | 1.0.0 (2017-02-13) 18 | ------------------ 19 | - Release: 1.0.0. [Florian Baumann] 20 | - Fix setup again. [Florian Baumann] 21 | - Fix setup. [Florian Baumann] 22 | - Release script. [Florian Baumann] 23 | - Changelog + version bump. [Florian Baumann] 24 | 25 | 26 | 0.17.0 (2016-11-15) 27 | ------------------- 28 | - Python 3.5 compatibility in setup and tests. [Florian Baumann] 29 | - Python3 compatibility: raw_input => input. [lonetwin] 30 | - Python3 compatibility: relative imports. [lonetwin] 31 | - Python3 compatibility: File mode changes. [lonetwin] 32 | 33 | Use text mode instead of binary to avoid unnecesary bytes-to-string 34 | conversions 35 | - Python3 compatibility: subprocess stdout bytes => utf-8 strings. 36 | [lonetwin] 37 | - Python3 compatibility: ConfigParser => configparser. [lonetwin] 38 | - Badges. [Florian Baumann] 39 | - Codecoverage for tests. [Florian Baumann] 40 | - Codecoverage for tests. [Florian Baumann] 41 | 42 | 43 | 0.16.2 (2016-04-12) 44 | ------------------- 45 | - Version bump. [Florian Baumann] 46 | - Fix mail with no parameters #32. [Florian Baumann] 47 | 48 | 49 | 0.16.1 (2016-04-12) 50 | ------------------- 51 | - Version bump. [Florian Baumann] 52 | - Fix for editorflags. [Florian Baumann] 53 | - Changelog update. [Florian Baumann] 54 | 55 | 56 | 0.16.0 (2016-04-12) 57 | ------------------- 58 | 59 | Fix 60 | ~~~ 61 | - No [Colors] section is also okay for config parser now. #29. [Florian 62 | Baumann] 63 | - Catch error when accessing git objects with diff. [Florian Baumann] 64 | 65 | Documentation 66 | ~~~~~~~~~~~~~ 67 | - Documentation: Screenshot in Readme. [Florian Baumann] 68 | - Documentation: Screenshot in Readme. [Florian Baumann] 69 | - Documentation: Readme format update. [Florian Baumann] 70 | - Documentation: Screenshot in Readme. [Florian Baumann] 71 | - Documentation: Screenshot in Readme. [Florian Baumann] 72 | 73 | Other 74 | ~~~~~ 75 | - Version bump. [Florian Baumann] 76 | - Implemented editorflags #31. [Florian Baumann] 77 | - Merge branch 'master' of github.com:noqqe/cmddocs. [Florian Baumann] 78 | - Changelog update. [Florian Baumann] 79 | 80 | 81 | 0.15.0 (2015-12-13) 82 | ------------------- 83 | 84 | Feature 85 | ~~~~~~~ 86 | - Feature: Added more tests for dir, log, search. [Florian Baumann] 87 | - Feature: Added more tests. [Florian Baumann] 88 | 89 | Fix 90 | ~~~ 91 | - Status and Initialization tests. [Florian Baumann] 92 | - Fixed broken tree call for directories and added tests. [Florian 93 | Baumann] 94 | - Stats test. [Florian Baumann] 95 | - Catch exception when dir or file in list not found. [Florian Baumann] 96 | - Tests for pwd and properly change to datadir. [Florian Baumann] 97 | - Tests imporved. [Florian Baumann] 98 | - Make checks work in travis. [Florian Baumann] 99 | 100 | Documentation 101 | ~~~~~~~~~~~~~ 102 | - Documentation: Fixed directory checks. [Florian Baumann] 103 | - Documentation: more tests. [Florian Baumann] 104 | - Documentation: directory checks. [Florian Baumann] 105 | - Documentation: undo, version, status Tests. [Florian Baumann] 106 | - Documentation: new tests and new testenv. [Florian Baumann] 107 | - Documentation: info command tests. [Florian Baumann] 108 | - Documentation: Restructured Tests. [Florian Baumann] 109 | - Documentation: reformat readme. [Florian Baumann] 110 | - Documentation: Docs for each command in Readme. [Florian Baumann] 111 | - Documentation: new sections for changelog. [Florian Baumann] 112 | - Documentation: new help messages. [Florian Baumann] 113 | - Documentation: Readme updated. [Florian Baumann] 114 | 115 | Other 116 | ~~~~~ 117 | - Release version 0.15.0. [Florian Baumann] 118 | - Edit tests. [Florian Baumann] 119 | - Removed pypy build from travis. [Florian Baumann] 120 | - Added user and mail for tests. [Florian Baumann] 121 | - Fix readme. [Florian Baumann] 122 | - Fix readme. [Florian Baumann] 123 | - Markup fix. [Florian Baumann] 124 | - Applied pylint changes. [Florian Baumann] 125 | - Version bump. [Florian Baumann] 126 | 127 | 128 | 0.14.0 (2015-12-08) 129 | ------------------- 130 | 131 | Feature 132 | ~~~~~~~ 133 | - Feature: Configurable colors for md to ascii #22. [Florian Baumann] 134 | - Feature: Configure pager flags - #20. [Florian Baumann] 135 | 136 | Documentation 137 | ~~~~~~~~~~~~~ 138 | - Documentation: updated Readme for Pagerflags. [Florian Baumann] 139 | 140 | Other 141 | ~~~~~ 142 | - More robust config in completions. [Florian Baumann] 143 | - Referenced Changelog in README.rst. [Florian Baumann] 144 | - Now using gitchangelog for python to provide proper changelog. 145 | [Florian Baumann] 146 | 147 | 148 | 0.13.0 (2015-12-08) 149 | ------------------- 150 | - Implemented version command #21. [Florian Baumann] 151 | - Count files and ignore .git. [Florian Baumann] 152 | - Implemented stats command - fixes #24. [Florian Baumann] 153 | - Fixed diff function and help message. [Florian Baumann] 154 | - Created info command to display informations about an article. 155 | [Florian Baumann] 156 | - Comma code style for arguments. [Florian Baumann] 157 | - Fixed bugs in log and diff because of missing file extensions. 158 | [Florian Baumann] 159 | - More py3 removals. [Florian Baumann] 160 | 161 | 162 | 0.12.3 (2015-11-11) 163 | ------------------- 164 | - Reverted py3 compatibility. Its fucked. [Florian Baumann] 165 | - Tree as dep in test build. [Florian Baumann] 166 | - Config example mail in tests. [Florian Baumann] 167 | - Added more tests. [Florian Baumann] 168 | 169 | 170 | 0.12.2 (2015-11-10) 171 | ------------------- 172 | - Bugfix default-extension when creating a new file. [Florian Baumann] 173 | - Switch to pytest. [Florian Baumann] 174 | - Added test and some restructuring. [Florian Baumann] 175 | - Made .cmddocsrc a class parameter. [Florian Baumann] 176 | - Deleted cache. [Florian Baumann] 177 | - Cache dir ignore. [Florian Baumann] 178 | - Tests init. [Florian Baumann] 179 | - Gitpython is broken with 3.2. [Florian Baumann] 180 | - Fixes py3.1-py3.4 setup py. [Florian Baumann] 181 | - Removed requirements due to fully compatible py3 py2 pip. [Florian 182 | Baumann] 183 | - Testing travis. [Florian Baumann] 184 | 185 | 186 | 0.12.1 (2015-11-08) 187 | ------------------- 188 | - Fixes for python3 install with pip. [Florian Baumann] 189 | 190 | 191 | 0.12.0 (2015-11-08) 192 | ------------------- 193 | - Python 3 compatibility - fixes #17. [Florian Baumann] 194 | - Fixes #11 - Default Filetype introduced! [Florian Baumann] 195 | 196 | 197 | 0.11.0 (2015-11-08) 198 | ------------------- 199 | - Fixes #11 - Default Filetype introduced! [Florian Baumann] 200 | - Readme update. [Florian Baumann] 201 | - Mail function #14. [Florian Baumann] 202 | - Merge pull request #19 from agundy/master. [Florian Baumann] 203 | 204 | Added exception catch for log. 205 | - Added exception catch for log. [Aaron Gunderson] 206 | 207 | 208 | 0.10.6 (2015-06-14) 209 | ------------------- 210 | - Added handler for tree dependency. [Florian Baumann] 211 | - Catch missing tree, converted all print statements. [Florian Baumann] 212 | 213 | 214 | 0.10.5 (2015-06-06) 215 | ------------------- 216 | - Version bump. [Florian Baumann] 217 | - Crtl-c signal handling. [Florian Baumann] 218 | 219 | 220 | 0.10.4 (2015-06-06) 221 | ------------------- 222 | - Version bump. [Florian Baumann] 223 | - Bug fixes, print syntax, return values. [Florian Baumann] 224 | 225 | 226 | 0.10.3 (2015-06-06) 227 | ------------------- 228 | - Version bump. [Florian Baumann] 229 | - Catch datadir not existing error. [Florian Baumann] 230 | 231 | 232 | 0.10.2 (2015-06-06) 233 | ------------------- 234 | - Repo init fix. [Florian Baumann] 235 | - Mistune requirements. [Florian Baumann] 236 | 237 | 238 | 0.10.0 (2015-06-06) 239 | ------------------- 240 | - Version bump. [Florian Baumann] 241 | - Deleted setup. [Florian Baumann] 242 | - Long description for pypi. [Florian Baumann] 243 | - Fixed list items. [Florian Baumann] 244 | - Readme to rst. [Florian Baumann] 245 | - Added mistune to PROPERLY parse markdown to ascii. [Florian Baumann] 246 | - Added mistune to PROPERLY parse markdown to ascii. [Florian Baumann] 247 | - Created undo/revert. [Florian Baumann] 248 | - Updated readme. [Florian Baumann] 249 | - Added sane config default fallbacks #1. [Florian Baumann] 250 | - Color prompt now configurable. [Florian Baumann] 251 | - Catching errors when exec without valid config #13. [Florian Baumann] 252 | - Article name search implemented #12. [Florian Baumann] 253 | - Updated readme for pip. [Florian Baumann] 254 | 255 | 256 | 0.9.1 (2015-05-17) 257 | ------------------ 258 | - Fix long description. [Florian Baumann] 259 | - Moved license. [Florian Baumann] 260 | - Setup.cfg. [Florian Baumann] 261 | - Ignores. [Florian Baumann] 262 | - Pip preparations. [Florian Baumann] 263 | 264 | 265 | 0.9.0 (2015-05-17) 266 | ------------------ 267 | - Added setup.py. [Florian Baumann] 268 | - Added diff functionality. [Florian Baumann] 269 | - Moved utils to compeltions. [Florian Baumann] 270 | - Removed imports - thanks to pyflakes. [Florian Baumann] 271 | - More structure. [Florian Baumann] 272 | - Lol. [Florian Baumann] 273 | - Gitignore. [Florian Baumann] 274 | - Moved to package. [Florian Baumann] 275 | - Better presentation of path. [Florian Baumann] 276 | - Merge branch 'posativ-patch-3' [Florian Baumann] 277 | - Merged. [Florian Baumann] 278 | - Use subprocess instead of os.system with string replacement. [Martin 279 | Zimmermann] 280 | - T push origin master Merge branch 'posativ-patch-4' [Florian Baumann] 281 | - Merged. [Florian Baumann] 282 | - Fix undefined behavior, mis-used classmethods. [Martin Zimmermann] 283 | - Accidentially wrong mapped alias. [Florian Baumann] 284 | - Merge pull request #3 from posativ/patch-2. [Florian Baumann] 285 | 286 | simplify command declaration 287 | - Simplify command declaration. [Martin Zimmermann] 288 | 289 | Minor drawback: the docstring for aliases is no longer available 290 | (replaced with the actual function's docstring). 291 | - Merge pull request #2 from posativ/patch-1. [Florian Baumann] 292 | 293 | expanduser for configuration variables 294 | - Expanduser for configuration variables. [Martin Zimmermann] 295 | - Error handling for rm and fix for mv. [Florian Baumann] 296 | - Prompt in new structure. [Florian Baumann] 297 | - Repo referenced in functions. [Florian Baumann] 298 | - Merged. [Florian Baumann] 299 | - Bugfix cwd. [Florian Baumann] 300 | - Fixed cwd problem. [Florian Baumann] 301 | - More variable passing. [Florian Baumann] 302 | - Merge branch 'master' into noglobals. [Florian Baumann] 303 | - Replaced dumb try with if. [Florian Baumann] 304 | - First steps making config in class. [Florian Baumann] 305 | - Just renaming. [Florian Baumann] 306 | - Function definitions. [Florian Baumann] 307 | - Small fix. [Florian Baumann] 308 | - Added intro message configurable and readme update. [Florian Baumann] 309 | - Prompt configurable. [Florian Baumann] 310 | - Removed double check of datadir. [Florian Baumann] 311 | - Merge branch 'master' of github.com:noqqe/cmddocs. [Florian Baumann] 312 | - Update LICENSE.md. [Florian Baumann] 313 | - Pager and editor now configurable in config. [Florian Baumann] 314 | - Merge branch 'master' of github.com:noqqe/cmddocs. [Florian Baumann] 315 | - Added license. [Florian Baumann] 316 | - Embedding of asciinema does not work... :( added link instead. 317 | [Florian Baumann] 318 | - Make config usergeneric. [Florian Baumann] 319 | - Docs update and helptexts improvements. [Florian Baumann] 320 | - Fixes for list dir. [Florian Baumann] 321 | - Restructuring. [Florian Baumann] 322 | - Readme added. [Florian Baumann] 323 | - Configparser. [Florian Baumann] 324 | - Arg parsing into functions, better error handling. [Florian Baumann] 325 | - Better error handling. [Florian Baumann] 326 | - Added check for EDITOR and PAGER. [Florian Baumann] 327 | - Default commit message implemented. [Florian Baumann] 328 | - Log messages. [Florian Baumann] 329 | - Intelligent log function. [Florian Baumann] 330 | - View mode with header and codeblock highlight. [Florian Baumann] 331 | - Highlighted view mode. [Florian Baumann] 332 | - Added basic pager, view mode. [Florian Baumann] 333 | - Fix mv and colors for log. [Florian Baumann] 334 | - Added comments, move and delete functionality. [Florian Baumann] 335 | - Make cd able to switch to default. [Florian Baumann] 336 | - Stopped experimenting with python made tree-like output. [Florian 337 | Baumann] 338 | - Colored search. [Florian Baumann] 339 | - Working search. [Florian Baumann] 340 | - Var replacement and datadir. [Florian Baumann] 341 | - Path completion for all functions. [Florian Baumann] 342 | - Fix dir not found message. [Florian Baumann] 343 | - Added 'safe' cd function. [Florian Baumann] 344 | - Implemented search function.. start.. [Florian Baumann] 345 | - Log improvements and list replacement. [Florian Baumann] 346 | - Huge steps, we make. [Florian Baumann] 347 | - L can now take arguments. [Florian Baumann] 348 | - Completion without .git directory. [Florian Baumann] 349 | - Added completion to list. [Florian Baumann] 350 | - Fixed edit with new subdirs. [Florian Baumann] 351 | - Init. [Florian Baumann] 352 | 353 | 354 | 355 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Florian Baumann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://travis-ci.org/noqqe/cmddocs.svg?branch=master 2 | :target: https://travis-ci.org/noqqe/cmddocs 3 | 4 | .. image:: https://codecov.io/gh/noqqe/cmddocs/branch/master/graph/badge.svg 5 | :target: https://codecov.io/gh/noqqe/cmddocs 6 | 7 | cmddocs 8 | ======= 9 | 10 | ``cmddocs`` is an interactive commandline wiki. It 11 | lets you easily maintain your docs/cheetsheets/notes using: 12 | 13 | - Plain Text Files 14 | - Write ``markdown`` in your local Editor 15 | - View in your local Pager 16 | - Versioning with ``git`` 17 | 18 | cmddocs is like a framework around your plaintext files. 19 | 20 | Why ? 21 | ----- 22 | 23 | I kind of started ``cmddocs`` because I couldn't find something like 24 | this on the internet. Here's my usecase. Im working as a DevOps guy 25 | being in touch with various types of software, languages, tools, 26 | operating systems and databases. To remember all those things I need a 27 | place to store commands, workflows and short howtos. 28 | 29 | Most of the software I use (and love) runs on a OpenBSD Box on the 30 | internet and are commandline-based. These are 31 | `mutt `__, 32 | `taskwarrior `__, 33 | `jrnl `__, 34 | `weechat `__ and so on...But i was missing a tool 35 | for documentation. 36 | 37 | After switching to just plaintext files using 38 | ``vim`` and ``git`` it was also a bit annoying. So I wrote 39 | ``cmddocs`` to make it easier for me to handle my plaintext files. 40 | 41 | Markdown Rendering 42 | ------------------ 43 | 44 | ``cmddocs`` uses the `mistune `__ lexer to 45 | wrap markdown with ansi control sequences instead of html tags. 46 | 47 | It looks something like this. 48 | 49 | .. image:: https://raw.github.com/noqqe/cmddocs/master/cmddocs-md2ascii.png 50 | 51 | Demo 52 | ---- 53 | 54 | To give you an idea what it looks/feels like I created a short terminal 55 | recording. 56 | 57 | `asciinema cmddocs demo `__ 58 | 59 | Installation 60 | ------------ 61 | 62 | :: 63 | 64 | pip install cmddocs 65 | 66 | Also make sure you have `tree` installed. 67 | 68 | 69 | Configuration 70 | ------------- 71 | 72 | Create ``.cmddocsrc`` file in your $HOME with the following content 73 | (adjust to your needs): 74 | 75 | :: 76 | 77 | [General] 78 | Datadir = /home/noqqe/Docs 79 | Default_Commit_Message = small changes 80 | Excludedir = .git/ 81 | Editor = /usr/local/bin/vim 82 | # EditorFlags = -C 83 | Pager = /usr/bin/less 84 | PagerFlags = -fr 85 | Prompt = cmddocs> 86 | Promptcolor = 37 87 | Intro_Message = cmddocs - press ? for help 88 | Mail = mail@example.org 89 | Default_Extension = md 90 | 91 | [Colors] 92 | Header12 = 37 93 | Header345 = 37 94 | Codeblock = 92 95 | 96 | The only required option is "Datadir", everything else will be guessed 97 | or defaults to a sane default value. Once you start cmddocs.py the CLI 98 | will be shown. Use ``help`` for commands. 99 | 100 | Quick Start 101 | ----------- 102 | 103 | At first, create a very minimal config, like 104 | 105 | :: 106 | 107 | [General] 108 | Datadir = /home/noqqe/Docs 109 | Editor = /usr/local/bin/vim 110 | Pager = /usr/bin/less 111 | 112 | Then you can start using cmddocs. 113 | 114 | :: 115 | 116 | $ cmddocs 117 | cmddocs - press ? for help 118 | cmddocs> help 119 | 120 | Documented commands (type help ): 121 | ======================================== 122 | EOF delete e help list mail pwd search undo 123 | cd diff edit info log move revert stats version 124 | d dirs exit l ls mv rm status view 125 | 126 | cmddocs> help l 127 | 128 | Show files in current working dir 129 | 130 | cmddocs> help log 131 | 132 | Show git logs of your docs. 133 | 134 | Usage: log # default loglines: 10) 135 | log 20 # show 20 loglines 136 | log 20 article # show log for specific article 137 | log databases/mongodb 3 # same 138 | 139 | Command Documentation 140 | --------------------- 141 | 142 | ``cd`` 143 | ------ 144 | 145 | Change directory 146 | 147 | :: 148 | 149 | Usage: 150 | cd Programming/ 151 | cd 152 | 153 | ``delete``, ``rm`` 154 | ------------------ 155 | 156 | Delete an article 157 | 158 | :: 159 | 160 | Usage: 161 | delete databases/mongodb 162 | rm databases/mssql 163 | 164 | 165 | ``dirs``, ``d`` 166 | --------------- 167 | 168 | Show directories in current working dir 169 | 170 | :: 171 | 172 | Usage: 173 | dirs 174 | d 175 | dirs Databases/ 176 | 177 | 178 | ``e``, ``edit`` 179 | --------------- 180 | 181 | Edit or create new article. 182 | 183 | :: 184 | 185 | Usage: 186 | edit databases/mongodb 187 | edit intro 188 | 189 | 190 | 191 | ``list``, ``l``, ``ls`` 192 | ----------------------- 193 | 194 | Show files in current working dir 195 | 196 | :: 197 | 198 | Usage: 199 | list 200 | l 201 | list Databases/ 202 | 203 | 204 | ``move``, ``mv`` 205 | ---------------- 206 | 207 | Move an article to a new location 208 | 209 | :: 210 | 211 | Usage: 212 | move databases/mongodb databases/MongoDB 213 | move life/foo notes/foo 214 | mv life/foo notes/foo 215 | 216 | ``view`` 217 | -------- 218 | 219 | View an article. Creates temporary file with converted markdown to 220 | ansi colored output. Opens your PAGER. (Only less supported atm) 221 | 222 | :: 223 | 224 | Usage: 225 | view databases/mongodb 226 | view intro 227 | 228 | ``mail`` 229 | -------- 230 | 231 | Mail an article to a friend 232 | 233 | :: 234 | 235 | Usage: 236 | mail databases/mongodb 237 | Recipient: mail@example.net 238 | 239 | mail programming/r/loops 240 | mail intro 241 | 242 | ``pwd`` 243 | ------- 244 | 245 | Show current directory 246 | 247 | :: 248 | 249 | Usage: 250 | pwd 251 | 252 | ``search`` 253 | ---------- 254 | 255 | Search for keyword in current directory 256 | 257 | :: 258 | 259 | Usage: 260 | search mongodb 261 | search foo 262 | 263 | ``undo``, ``revert`` 264 | ------------------- 265 | 266 | You can revert your changes (use revert from git) 267 | 268 | :: 269 | 270 | 271 | Usage: 272 | undo HEAD 273 | undo 355f375 274 | 275 | Will ask for confirmation. 276 | 277 | ``diff`` 278 | -------- 279 | 280 | Show git diffs between files and commits 281 | 282 | :: 283 | 284 | Usage: 285 | diff 7 # show diff for last 7 changes 286 | diff 1 article # show diff for last change to article 287 | diff # show last 5 diffs 288 | 289 | ``info`` 290 | -------- 291 | 292 | Show infos for an article 293 | 294 | :: 295 | 296 | Usage: 297 | info article 298 | info Databases/mongodb 299 | Created: 2014-01-18 11:18:03 +0100 300 | Updated: 2015-10-23 14:14:44 +0200 301 | Commits: 26 302 | Lines: 116 303 | Words: 356 304 | Characters: 2438 305 | 306 | ``log`` 307 | -------- 308 | 309 | Show git logs of your docs. 310 | 311 | :: 312 | 313 | Usage: 314 | log # default loglines: 10) 315 | log 20 # show 20 loglines 316 | log 20 article # show log for specific article 317 | log databases/mongodb 3 # same 318 | 319 | ``status`` 320 | ---------- 321 | 322 | Show git repo status of your docs 323 | 324 | :: 325 | 326 | Usage: 327 | status 328 | 329 | ``stats`` 330 | --------- 331 | 332 | Calculate some statistics on your docs 333 | 334 | :: 335 | 336 | Usage: 337 | stats 338 | 339 | ``exit``, ``EOF`` 340 | ----------------- 341 | 342 | Exit cmddocs 343 | 344 | :: 345 | 346 | Usage: 347 | exit 348 | 349 | 350 | ``help`` 351 | -------- 352 | 353 | List available commands with "help" or detailed help with "help cmd". 354 | 355 | ``version`` 356 | ----------- 357 | 358 | Show version of cmddocs 359 | 360 | :: 361 | 362 | Usage: 363 | version 364 | 365 | 366 | Changelog 367 | --------- 368 | 369 | See Changelog_. 370 | 371 | .. _Changelog: https://github.com/noqqe/cmddocs/blob/master/CHANGELOG.rst 372 | 373 | License 374 | ------- 375 | 376 | See License_. 377 | 378 | .. _License: https://github.com/noqqe/cmddocs/blob/master/License.txt 379 | 380 | -------------------------------------------------------------------------------- /cmddocs-md2ascii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noqqe/cmddocs/5cc61ec36e153c380f81b60717931eb8ff428b98/cmddocs-md2ascii.png -------------------------------------------------------------------------------- /cmddocs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | if __name__ == '__main__': 6 | Cmddocs(conf="~/.cmddocsrc").cmdloop() 7 | -------------------------------------------------------------------------------- /cmddocs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ cmddocs Class """ 3 | 4 | import os 5 | import cmd 6 | import sys 7 | import signal 8 | import configparser 9 | import git 10 | import pkg_resources 11 | from cmddocs.articles import * 12 | from cmddocs.completions import * 13 | from cmddocs.version import __version__ 14 | 15 | class Cmddocs(cmd.Cmd): 16 | """ Basic commandline interface class """ 17 | 18 | def __init__(self, conf="~/.cmddocsrc"): 19 | """ 20 | Initialize the class 21 | Inherit from Cmd 22 | Read config, initialize Datadir, create Prompt 23 | """ 24 | cmd.Cmd.__init__(self) 25 | self.reset = '\033[0m' 26 | self.read_config(self, conf) 27 | self.initialize_docs(self) 28 | self.prompt = '\033[1m\033[' + self.promptcol + 'm' + self.prompt + " " + self.reset 29 | self.do_cd(self.datadir) 30 | 31 | def read_config(self, sconf, conf): 32 | """ 33 | All Config Options being read and defaulting 34 | """ 35 | 36 | self.colors = {} 37 | config = configparser.ConfigParser() 38 | 39 | if not config.read(os.path.expanduser(conf)): 40 | print("Error: your config %s could not be read" % conf) 41 | exit(1) 42 | 43 | try: 44 | self.datadir = os.path.expanduser(config.get("General", "Datadir")) 45 | except configparser.NoOptionError: 46 | print("Error: Please set a Datadir in %s" % conf) 47 | exit(1) 48 | 49 | try: 50 | self.exclude = os.path.expanduser(config.get("General", "Excludedir")) 51 | except configparser.NoOptionError: 52 | self.exclude = os.path.expanduser('.git/') 53 | 54 | try: 55 | self.default_commit_msg = config.get("General", "Default_Commit_Message") 56 | except configparser.NoOptionError: 57 | self.default_commit_msg = "small changes" 58 | 59 | try: 60 | self.editor = config.get("General", "Editor") 61 | except configparser.NoOptionError: 62 | if os.environ.get('EDITOR') is not None: 63 | self.editor = os.environ.get('EDITOR') 64 | else: 65 | print("Error: Could not find usable editor.") 66 | print("Please specify one in config or set EDITOR in your \ 67 | OS Environment") 68 | exit(1) 69 | 70 | try: 71 | self.pager = config.get("General", "Pager") 72 | except configparser.NoOptionError: 73 | if os.environ.get('PAGER') is not None: 74 | self.editor = os.environ.get('PAGER') 75 | else: 76 | print("Error: Could not find usable Pager.") 77 | print("Please specify one in config or set PAGER in your\ 78 | OS Environment") 79 | exit(1) 80 | 81 | try: 82 | self.pagerflags = config.get("General", "PagerFlags") 83 | except configparser.NoOptionError: 84 | self.pagerflags = False 85 | 86 | try: 87 | self.editorflags = config.get("General", "EditorFlags") 88 | except configparser.NoOptionError: 89 | self.editorflags = False 90 | 91 | try: 92 | self.prompt = config.get("General", "Prompt") 93 | except configparser.NoOptionError: 94 | self.prompt = "cmddocs>" 95 | 96 | try: 97 | self.promptcol = config.get("General", "Promptcolor") 98 | except configparser.NoOptionError: 99 | self.promptcol = "37" 100 | 101 | try: 102 | self.intro = config.get("General", "Intro_Message") 103 | except configparser.NoOptionError: 104 | self.intro = "cmddocs - press ? for help" 105 | 106 | try: 107 | self.mailfrom = config.get("General", "Mail") 108 | except configparser.NoOptionError: 109 | self.mailfrom = "nobody" 110 | 111 | try: 112 | self.extension = config.get("General", "Default_Extension") 113 | except configparser.NoOptionError: 114 | self.extension = "md" 115 | 116 | try: 117 | self.colors['h1'] = config.get("Colors", "Header12") 118 | except (configparser.NoOptionError, configparser.NoSectionError): 119 | self.colors['h1'] = "37" 120 | 121 | try: 122 | self.colors['h2'] = config.get("Colors", "Header345") 123 | except (configparser.NoOptionError, configparser.NoSectionError): 124 | self.colors['h2'] = "92" 125 | 126 | try: 127 | self.colors['code'] = config.get("Colors", "Codeblock") 128 | except (configparser.NoOptionError, configparser.NoSectionError): 129 | self.colors['code'] = "92" 130 | 131 | return 132 | 133 | def initialize_docs(self, docs): 134 | """ Read or initialize git repository """ 135 | try: 136 | self.repo = git.Repo(self.datadir) 137 | except git.exc.NoSuchPathError: 138 | print("Error: Specified datadir %s does not exist" % self.datadir) 139 | exit(1) 140 | except git.exc.InvalidGitRepositoryError: 141 | self.repo = git.Repo.init(self.datadir) 142 | try: 143 | self.repo.git.add(".") 144 | self.repo.git.commit(m=" init") 145 | except git.exc.GitCommandError: 146 | pass 147 | print("Successfully created and initialized empty repo at %s" % self.datadir) 148 | # Change to datadir 149 | try: 150 | os.chdir(self.datadir) 151 | self.cwd = os.getcwd() 152 | except OSError: 153 | print("Error: Switching to Datadir %s not possible" % self.datadir) 154 | exit(1) 155 | 156 | def do_list(self, dir): 157 | """ 158 | Show files in current working dir 159 | 160 | Usage: 161 | list 162 | l 163 | list Databases/ 164 | """ 165 | if not dir: 166 | dir = "." 167 | return list_articles(dir, self.extension) 168 | 169 | do_l = do_list 170 | do_ls = do_list 171 | 172 | def do_dirs(self, dir): 173 | """ 174 | Show directories in current working dir 175 | 176 | Usage: 177 | dirs 178 | d 179 | dirs Databases/ 180 | """ 181 | if not dir: 182 | dir = "." 183 | return list_directories(dir) 184 | 185 | do_d = do_dirs 186 | 187 | def do_cd(self, dir): 188 | """ 189 | Change directory 190 | 191 | Usage: 192 | cd Programming/ 193 | cd 194 | """ 195 | change_directory(dir, self.datadir) 196 | 197 | def do_pwd(self, line): 198 | """ 199 | Show current directory 200 | 201 | Usage: 202 | pwd 203 | """ 204 | print(os.path.relpath(os.getcwd(), self.datadir)) 205 | 206 | def do_edit(self, article, test=False): 207 | """ 208 | Edit or create new article. 209 | 210 | Usage: 211 | edit databases/mongodb 212 | edit intro 213 | """ 214 | return edit_article(article, os.getcwd(), self.editor, self.repo, 215 | self.default_commit_msg, self.extension, test, self.editorflags) 216 | 217 | do_e = do_edit 218 | 219 | def do_view(self, article): 220 | """ 221 | View an article. Creates temporary file with converted markdown to 222 | ansi colored output. Opens your PAGER. (Only less supported atm) 223 | 224 | Usage: 225 | view databases/mongodb 226 | view intro 227 | """ 228 | return view_article(article, os.getcwd(), self.pager, self.extension, 229 | self.pagerflags, self.colors) 230 | 231 | def do_mail(self, article): 232 | """ 233 | Mail an article to a friend 234 | 235 | Usage: 236 | mail databases/mongodb 237 | Recipient: mail@example.net 238 | 239 | mail programming/r/loops 240 | mail intro 241 | """ 242 | return mail_article(article, os.getcwd(), self.mailfrom, self.extension) 243 | 244 | def do_delete(self, article): 245 | """ 246 | Delete an article 247 | 248 | Usage: 249 | delete databases/mongodb 250 | rm databases/mssql 251 | """ 252 | delete_article(article, os.getcwd(), self.repo, self.extension) 253 | 254 | do_rm = do_delete 255 | 256 | def do_move(self, args): 257 | """ 258 | Move an article to a new location 259 | 260 | Usage: 261 | move databases/mongodb databases/MongoDB 262 | move life/foo notes/foo 263 | mv life/foo notes/foo 264 | """ 265 | move_article(os.getcwd(), args, self.repo, self.extension) 266 | 267 | do_mv = do_move 268 | 269 | def do_search(self, keyword): 270 | """ 271 | Search for keyword in current directory 272 | 273 | Usage: 274 | search mongodb 275 | search foo 276 | """ 277 | print(search_article(keyword, os.getcwd(), self.datadir, 278 | self.exclude)) 279 | 280 | def do_status(self, line): 281 | """ 282 | Show git repo status of your docs 283 | 284 | Usage: 285 | status 286 | 287 | """ 288 | print(self.repo.git.status()) 289 | 290 | def do_log(self, args): 291 | """ 292 | Show git logs of your docs. 293 | 294 | Usage: 295 | log # default loglines: 10) 296 | log 20 # show 20 loglines 297 | log 20 article # show log for specific article 298 | log databases/mongodb 3 # same 299 | """ 300 | show_log(args, self.repo, self.extension) 301 | 302 | def do_info(self, article): 303 | """ 304 | Show infos for an article 305 | 306 | Usage: 307 | info article 308 | info Databases/mongodb 309 | Created: 2014-01-18 11:18:03 +0100 310 | Updated: 2015-10-23 14:14:44 +0200 311 | Commits: 26 312 | Lines: 116 313 | Words: 356 314 | Characters: 2438 315 | """ 316 | info_article(article, os.getcwd(), self.repo, self.extension) 317 | 318 | def do_diff(self, args): 319 | """ 320 | Show git diffs between files and commits 321 | 322 | Usage: 323 | diff 7 # show diff for last 7 changes 324 | diff 1 article # show diff for last change to article 325 | diff # show last 5 diffs 326 | """ 327 | show_diff(args, self.repo, self.extension) 328 | 329 | def do_undo(self, args): 330 | """ 331 | You can revert your changes (use revert from git) 332 | 333 | Usage: 334 | undo HEAD 335 | undo 355f375 336 | 337 | Will ask for confirmation. 338 | """ 339 | undo_change(args, self.repo) 340 | 341 | def do_stats(self, args): 342 | """ 343 | Calculate some statistics on your docs 344 | 345 | Usage: 346 | stats 347 | 348 | """ 349 | show_stats(args, self.repo, self.datadir) 350 | 351 | def do_version(self, args): 352 | """ 353 | Show version of cmddocs 354 | 355 | Usage: 356 | version 357 | 358 | """ 359 | print("cmddocs %s" % __version__) 360 | 361 | 362 | do_revert = do_undo 363 | 364 | ### exit 365 | def do_exit(self, args): 366 | """ 367 | Exit cmddocs 368 | 369 | Usage: 370 | exit 371 | """ 372 | return True 373 | 374 | do_EOF = do_exit 375 | 376 | ### completions 377 | complete_l = path_complete 378 | complete_ls = path_complete 379 | complete_list = path_complete 380 | 381 | complete_d = path_complete 382 | complete_dirs = path_complete 383 | 384 | complete_view = path_complete 385 | complete_cd = path_complete 386 | 387 | complete_e = path_complete 388 | complete_edit = path_complete 389 | 390 | complete_rm = path_complete 391 | complete_delete = path_complete 392 | 393 | complete_mail = path_complete 394 | complete_mv = path_complete 395 | complete_move = path_complete 396 | 397 | complete_log = path_complete 398 | complete_info = path_complete 399 | 400 | def ctrlc(sig, frame): 401 | """ Handle Interrupts """ 402 | print("\n") 403 | sys.exit(0) 404 | 405 | signal.signal(signal.SIGINT, ctrlc) 406 | 407 | def main(): 408 | """ Call loop method """ 409 | Cmddocs().cmdloop() 410 | 411 | if __name__ == '__main__': 412 | main() 413 | -------------------------------------------------------------------------------- /cmddocs/articles.py: -------------------------------------------------------------------------------- 1 | """ Article class """ 2 | 3 | import os 4 | import re 5 | import subprocess 6 | import socket 7 | import smtplib 8 | import tempfile 9 | import datetime 10 | from builtins import input 11 | from email.mime.text import MIMEText 12 | import mistune 13 | import git 14 | from cmddocs.utils import * 15 | from cmddocs.rendering import md_to_ascii 16 | 17 | 18 | # Function definitions 19 | def list_articles(dir, extension): 20 | "lists all articles in current dir and below" 21 | try: 22 | DEVNULL = open(os.devnull, 'wb') 23 | listing = subprocess.check_output(["tree", dir], stderr=DEVNULL) 24 | listing = remove_fileextension(listing.decode('utf-8'), extension) 25 | print(listing) 26 | except OSError: 27 | print("Error: tree not installed") 28 | return False 29 | except subprocess.CalledProcessError: 30 | print("Error: File or Directory not found") 31 | 32 | def list_directories(dir): 33 | "lists all directories in current dir and below" 34 | try: 35 | DEVNULL = open(os.devnull, 'wb') 36 | listing = subprocess.check_output(["tree", "-d", dir], stderr=DEVNULL) 37 | print(listing.decode('utf-8')) 38 | except OSError: 39 | print("Error: tree not installed") 40 | return False 41 | except subprocess.CalledProcessError: 42 | print("Error: File or Directory not found") 43 | 44 | def change_directory(dir, datadir): 45 | "changes directory" 46 | d = os.path.join(os.getcwd(), dir) 47 | 48 | # dont cd out of datadir 49 | if datadir not in d: 50 | d = datadir 51 | 52 | # if empty, switch to datadir 53 | if not dir: 54 | d = datadir 55 | 56 | # switch to dir 57 | try: 58 | os.chdir(d) 59 | return d 60 | except OSError: 61 | print("Error: Directory %s not found" % dir) 62 | 63 | def edit_article(article, directory, editor, repo, default_commit_msg, extension, test, editorflags): 64 | """edit an article within your docs""" 65 | # set paths 66 | a = add_fileextension(article, extension) 67 | a = os.path.join(directory, a) 68 | d = os.path.dirname(a) 69 | 70 | # create dir(s) 71 | if not os.path.isdir(d): 72 | try: 73 | os.makedirs(d) 74 | except OSError: 75 | print("Error: Creation of path %s is not possible" % d) 76 | return False 77 | 78 | # start editor 79 | if test is False: 80 | try: 81 | if editorflags is False: 82 | subprocess.call([editor, a]) 83 | else: 84 | subprocess.call([editor, editorflags, a]) 85 | except OSError: 86 | print("Error: '%s' No such file or directory" % editor) 87 | return False 88 | else: 89 | try: 90 | with open(a, "a") as fp: 91 | content = "TEST CHANGE" 92 | fp.write(content) 93 | except OSError: 94 | print("Error: '%s' No such file or directory" % editor) 95 | 96 | 97 | # commit into git 98 | try: 99 | repo.git.add(a) 100 | if repo.is_dirty(): 101 | if test is False: 102 | try: 103 | msg = input("Commit message: ") 104 | if not msg: 105 | msg = default_commit_msg 106 | except OSError: 107 | print("Error: Could not create commit") 108 | else: 109 | msg = default_commit_msg 110 | print("automatic change done") 111 | try: 112 | repo.git.commit(m=msg) 113 | except OSError: 114 | print("Error: Could not create commit") 115 | 116 | else: 117 | print("Nothing to commit") 118 | except (OSError, git.exc.GitCommandError) as e: 119 | print("Error: Could not create commit") 120 | 121 | 122 | 123 | def info_article(article, dir, repo, extension): 124 | "get info for an article within your docs" 125 | a = add_fileextension(article, extension) 126 | a = os.path.join(dir, a) 127 | 128 | # Create commit list 129 | try: 130 | commits = repo.git.log(a, follow=True, format="%H") 131 | commits = commits.split() 132 | n = len(commits) 133 | except git.exc.GitCommandError: 134 | print("Error: File not found") 135 | return False 136 | 137 | # Article create date 138 | created = repo.git.show(commits[n-1], quiet=True, pretty="format:%ci") 139 | print("Created: %s" % created) 140 | 141 | # Article last updated 142 | updated = repo.git.show(commits[0], quiet=True, pretty="format:%ci") 143 | print("Updated: %s" % updated) 144 | 145 | # Number of commits 146 | print("Commits: %s" % n) 147 | 148 | # Collect textual informations 149 | num_lines = 0 150 | num_words = 0 151 | num_chars = 0 152 | 153 | with open(a, 'r') as f: 154 | for line in f: 155 | words = line.split() 156 | 157 | num_lines += 1 158 | num_words += len(words) 159 | num_chars += len(line) 160 | 161 | # Print textual informations 162 | print("Lines: %s" % num_lines) 163 | print("Words: %s" % num_words) 164 | print("Characters: %s" % num_chars) 165 | 166 | def mail_article(article, dir, mailfrom, extension): 167 | "mail an article to a friend" 168 | a = add_fileextension(article, extension) 169 | a = os.path.join(dir, a) 170 | 171 | # Create a text/plain message 172 | try: 173 | fp = open(a, 'r') 174 | msg = MIMEText(fp.read()) 175 | fp.close() 176 | except IOError: 177 | print("Error: Please specify a document") 178 | return False 179 | 180 | 181 | mailto = input("Recipient: ") 182 | msg['Subject'] = article 183 | msg['From'] = mailfrom 184 | msg['To'] = mailto 185 | 186 | # Send the message via our own SMTP server, but don't include the 187 | # envelope header. 188 | s = smtplib.SMTP() 189 | try: 190 | s.connect() 191 | s.sendmail(mailfrom, [mailto], msg.as_string()) 192 | s.quit() 193 | except socket.error: 194 | print("Error: Apparently no mailer running on your system.") 195 | print("Error: Could not connect to localhost:25") 196 | return False 197 | except smtplib.SMTPRecipientsRefused: 198 | print("Error: Invalid recipient or sender") 199 | return False 200 | 201 | 202 | 203 | def view_article(article, dir, pager, extension, pagerflags, colors): 204 | "view an article within your docs" 205 | a = add_fileextension(article, extension) 206 | a = os.path.join(dir, a) 207 | # read original file 208 | try: 209 | article = open(a, "r") 210 | except IOError: 211 | print("Error: Could not find %s" % article) 212 | return False 213 | 214 | content = article.read() 215 | article.close() 216 | 217 | # hand everything over to mistune lexer 218 | with tempfile.NamedTemporaryFile(delete=False, mode='w') as tmp: 219 | md = mistune.Markdown(renderer=md_to_ascii(colors)) 220 | tmp.write(md.render(content)) 221 | 222 | # start pager and cleanup tmp file afterwards 223 | # also parse flags for local pager 224 | try: 225 | if pagerflags is False: 226 | subprocess.call([pager, tmp.name]) 227 | else: 228 | subprocess.call([pager, pagerflags, tmp.name]) 229 | except OSError: 230 | print("Error: '%s' No such file or directory" % pager) 231 | 232 | try: 233 | os.remove(tmp.name) 234 | except OSError: 235 | print("Error: Could not remove %s" % tmp.name) 236 | 237 | def delete_article(article, dir, repo, extension): 238 | """ delete an article """ 239 | a = add_fileextension(article, extension) 240 | a = os.path.join(dir, a) 241 | try: 242 | repo.git.rm(a) 243 | repo.git.commit(m="%s deleted" % article) 244 | print("%s deleted" % article) 245 | except: 246 | if os.path.isdir(a): 247 | try: 248 | os.rmdir(a) 249 | print("Removed directory %s which was not under version control" % a) 250 | except OSError: 251 | print("Could not remove %s - its maybe not empty" % a) 252 | else: 253 | try: 254 | os.remove(a) 255 | print("Removed file %s which was not under version control" % a) 256 | except OSError: 257 | print("File %s could not be removed" % a) 258 | return 259 | 260 | def move_article(dir, args, repo, extension): 261 | "move an article from source to destination" 262 | args = args.split() 263 | if len(args) != 2: 264 | print("Invalid usage\nUse: mv source dest") 265 | return False 266 | 267 | a = os.path.join(dir, args[0]) 268 | a = add_fileextension(a, extension) 269 | e = os.path.join(dir, args[1]) 270 | e = add_fileextension(e, extension) 271 | d = os.path.dirname(e) 272 | 273 | # create dir(s) 274 | if not os.path.isdir(d): 275 | os.makedirs(d) 276 | 277 | # move file in git and commit 278 | try: 279 | repo.git.mv(a, e) 280 | repo.git.commit(m="Moved %s to %s" % (a, e)) 281 | print("Moved %s to %s" % (a, e)) 282 | except git.exc.GitCommandError: 283 | print("Error: File could not be moved") 284 | 285 | def search_article(keyword, directory, datadir, exclude): 286 | """ 287 | Search for a keyword in every article within your current directory and 288 | below. Much like recursive grep. 289 | """ 290 | c = 0 291 | r = re.compile(keyword) 292 | print("Articles:") 293 | for dirpath, dirs, files in os.walk(directory): 294 | dirs[:] = [d for d in dirs if d not in exclude] 295 | for fname in files: 296 | path = os.path.join(dirpath, fname) 297 | if r.search(path) is not None: 298 | print("* \033[92m%s\033[39m" % 299 | os.path.relpath(path, datadir)) 300 | c = c + 1 301 | print("Content:") 302 | for dirpath, dirs, files in os.walk(directory): 303 | dirs[:] = [d for d in dirs if d not in exclude] 304 | for fname in files: 305 | path = os.path.join(dirpath, fname) 306 | f = open(path, "rt") 307 | for i, line in enumerate(f): 308 | if r.search(line): 309 | c = c + 1 310 | print("* \033[92m%s\033[39m: %s" % (os.path.relpath(path, datadir), 311 | line.rstrip('\n'))) 312 | return "Results: %s" % c 313 | 314 | def show_diff(args, repo, extension): 315 | """ 316 | Shows diffs for files or whole article directory 317 | """ 318 | colorization = "always" 319 | unifiedopt = "0" 320 | 321 | args = args.split() 322 | if len(args) > 1: 323 | if os.path.isfile(os.path.join(os.getcwd(), add_fileextension(args[1], extension))): 324 | # diff 7 article 325 | try: 326 | print(repo.git.diff('HEAD~'+args[0], add_fileextension(args[1], extension), 327 | unified=unifiedopt, color=colorization)) 328 | except git.exc.GitCommandError: 329 | print("Error: Not a valid git commit reference") 330 | else: 331 | print("Error: Wrong Usage. See help diff") 332 | elif len(args) == 1: 333 | # diff 7 334 | try: 335 | print(repo.git.diff('HEAD~'+args[0], 336 | unified=unifiedopt, color=colorization)) 337 | except git.exc.GitCommandError: 338 | print("Error: Not a valid git commit reference") 339 | else: 340 | try: 341 | print(repo.git.diff('HEAD~1', unified="0", color="always")) 342 | except git.exc.GitCommandError: 343 | print("Error: Not a valid git commit reference") 344 | 345 | def show_log(args, repo, extension): 346 | """ 347 | Show latest git logs with specified number of entries and maybe for a 348 | specific file. 349 | """ 350 | args = args.split() 351 | format = "format:%C(blue)%h %Cgreen%C(bold)%ad %Creset%s" 352 | dateformat = "short" 353 | 354 | if len(args) >= 1: 355 | if os.path.isfile(os.path.join(os.getcwd(), add_fileextension(args[0], extension))): 356 | file = add_fileextension(args[0], extension) 357 | 358 | # Command: log Article 12 359 | try: 360 | count = args[1] 361 | print("Last %s commits for %s" % (count, file)) 362 | print(repo.git.log(file, pretty=format, n=count, 363 | date=dateformat, follow=True)) 364 | # Command: log Article 365 | except IndexError: 366 | count = 10 367 | print("Last %s commits for %s" % (count, file)) 368 | print(repo.git.log(file, pretty=format, n=count, 369 | date=dateformat, follow=True)) 370 | except git.exc.GitCommandError: 371 | print("Error: git command resulted in an error") 372 | else: 373 | count = args[0] 374 | # Command: log 12 Article 375 | try: 376 | file = add_fileextension(args[1], extension) 377 | print("Last %s commits for %s" % (count, file)) 378 | print(repo.git.log(file, pretty=format, n=count, 379 | date=dateformat, follow=True)) 380 | # Command: log 12 381 | except IndexError: 382 | print("Last %s commits" % count) 383 | print(repo.git.log(pretty=format, n=count, 384 | date=dateformat)) 385 | except git.exc.GitCommandError: 386 | print("Error: git command resulted in an error") 387 | 388 | # Command: log 389 | elif len(args) == 0: 390 | count = 10 391 | print("Last %s commits" % count) 392 | try: 393 | print(repo.git.log(pretty=format, n=count, date=dateformat)) 394 | except git.exc.GitCommandError: 395 | print("Error: git may not be configured on your system.") 396 | 397 | def undo_change(args, repo): 398 | """ 399 | You can revert your changes (use revert from git) 400 | """ 401 | args = args.split() 402 | if len(args) == 1: 403 | try: 404 | print(repo.git.show(args[0], '--oneline', '--patience')) 405 | msg = input("\nDo you really want to undo this? (y/n): ") 406 | if msg == "y": 407 | repo.git.revert(args[0], '--no-edit') 408 | 409 | except git.exc.GitCommandError: 410 | print("Error: Could not find given commit reference") 411 | 412 | def show_stats(args, repo, datadir): 413 | """ 414 | Show some statistics and other informations 415 | on your repos 416 | """ 417 | # get time series of commits 418 | commits = repo.git.log(format="%ci") 419 | commits = commits.split('\n') 420 | n = len(commits) 421 | 422 | print("Newest Commit: %s" % commits[0]) 423 | print("Oldest Commit: %s" % commits[n-1]) 424 | print("Number of Commits: %s" % n) 425 | 426 | # Calculate Repo age 427 | f = commits[n-1].split() 428 | today = datetime.datetime.today() 429 | first = datetime.datetime.strptime(f[0], "%Y-%m-%d") 430 | days = today - first 431 | print("Repository Age: %s" % days.days) 432 | 433 | # Calculate commits per day 434 | cpd = float(days.days) / n 435 | print("Average Commits per Day: %s" % cpd) 436 | 437 | # Calculate size of docs 438 | folder_size = 0 439 | num_lines = 0 440 | num_words = 0 441 | num_chars = 0 442 | num_files = 0 443 | for (path, dirs, files) in os.walk(datadir): 444 | for file in files: 445 | filename = os.path.join(path, file) 446 | if ".git/" not in filename: 447 | num_files += 1 448 | folder_size += os.path.getsize(filename) 449 | with open(filename, 'r') as f: 450 | for line in f: 451 | words = line.split() 452 | num_lines += 1 453 | num_words += len(words) 454 | num_chars += len(line) 455 | 456 | print("Size of your Docs: %0.1f MB" % (folder_size/(1024*1024.0))) 457 | print("Total Articles: %s" % num_files) 458 | print("Total Lines: %s" % num_lines) 459 | print("Total Words: %s" % num_words) 460 | print("Total Characters: %s" % num_chars) 461 | -------------------------------------------------------------------------------- /cmddocs/completions.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | from os.path import expanduser 4 | from .utils import * 5 | 6 | def path_complete(self, text, line, begidx, endidx): 7 | """ 8 | Path completition function used in various places for tab completion 9 | when using cmd 10 | """ 11 | arg = line.split()[1:] 12 | 13 | # this is a workaround to get default extension into the completion function 14 | # may (hopefully) gets replaced. 15 | try: 16 | config = configparser.ConfigParser() 17 | if not config.read(expanduser("~/.cmddocsrc")): 18 | print("Error: your config %s could not be read" % conf) 19 | exit(1) 20 | extension = config.get("General", "Default_Extension") 21 | except configparser.NoOptionError: 22 | self.extension = "md" 23 | 24 | if not arg: 25 | completions = os.listdir('./') 26 | completions[:] = [d for d in completions if d not in self.exclude] 27 | else: 28 | dir, part, base = arg[-1].rpartition('/') 29 | if part == '': 30 | dir = './' 31 | elif dir == '': 32 | dir = '/' 33 | 34 | completions = [] 35 | for f in os.listdir(dir): 36 | if f.startswith(base): 37 | if os.path.isfile(os.path.join(dir, f)): 38 | f = remove_fileextension(f, extension) 39 | completions.append(f) 40 | else: 41 | completions.append(f+'/') 42 | return completions 43 | -------------------------------------------------------------------------------- /cmddocs/rendering.py: -------------------------------------------------------------------------------- 1 | """ Class to render ascii from md """ 2 | import mistune 3 | 4 | class md_to_ascii(mistune.Renderer): 5 | """ md_to_ascii class """ 6 | 7 | def __init__(self, colors): 8 | mistune.Renderer.__init__(self) 9 | self.colors = colors 10 | 11 | # Pagelayout 12 | def block_code(self, code, lang): 13 | return '\n\033[' + self.colors['code'] + 'm%s\033[0m\n' % code.strip() 14 | def header(self, text, level, raw=None): 15 | if level == 2 or level == 1: 16 | head = '\n\033[4m\033[1m\033[' + self.colors['h1'] + 'm%s\033[0m\n' % text.strip() 17 | else: 18 | head = '\n\033[1m\033[' + self.colors['h2'] + 'm%s\033[0m\n' % text.strip() 19 | return head 20 | def block_quote(self, text): 21 | return '\n%s\n' % text.strip() 22 | def block_html(self, html): 23 | return '\n%s\n' % html.strip() 24 | def hrule(self): 25 | return '---' 26 | def list(self, body, ordered=True): 27 | return '\n%s' % body 28 | def list_item(self, text): 29 | return '* %s \n' % text.strip() 30 | def paragraph(self, text): 31 | return '\n%s\n' % text.strip() 32 | def table(self, header, body): 33 | return '\n%s\n' % body.strip() 34 | def table_row(self, content): 35 | return '\n%s\n' % content.strip() 36 | def table_cell(self, content, **flags): 37 | return '\n%s\n' % content.strip() 38 | 39 | # Inline Tags 40 | def double_emphasis(self, text): 41 | return '%s' % text.strip() 42 | def emphasis(self, text): 43 | return '%s' % text.strip() 44 | def codespan(self, text): 45 | return '\n%s\n' % text.strip() 46 | def linebreak(self): 47 | return '\n' 48 | def strikethrough(self, text): 49 | return '%s' % text.strip() 50 | def text(self, text): 51 | return '%s' % text.strip() 52 | def autolink(self, link, is_email=False): 53 | return '%s' % link.strip() 54 | def link(self, link, title, text): 55 | return '%s (%s)' % (text.strip(), link.strip()) 56 | def image(self, src, title, text): 57 | return '%s' % src.strip() 58 | def inline_html(self, html): 59 | return '%s' % html.strip() 60 | def newline(self): 61 | return '\n' 62 | def footnote_ref(self, key, index): 63 | return '%s, [%s]' % (key.strip(), index.strip()) 64 | def footnote_item(self, key, text): 65 | return '%s, [%s]' % (text.strip(), key.strip()) 66 | def footnotes(self, text): 67 | return '%s' % text.strip() 68 | -------------------------------------------------------------------------------- /cmddocs/utils.py: -------------------------------------------------------------------------------- 1 | """ File Extension Add/Remove Util""" 2 | 3 | import re 4 | 5 | def add_fileextension(article, extension): 6 | "add file extension to article" 7 | 8 | article = article + '.' + extension 9 | 10 | return article 11 | 12 | def remove_fileextension(article, extension): 13 | "remove file extension" 14 | 15 | extension = r'\.' + extension + '$' 16 | article = re.sub(extension, "", article, flags=re.M) 17 | 18 | return article 19 | -------------------------------------------------------------------------------- /cmddocs/version.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import get_distribution 2 | __version__ = get_distribution('cmddocs').version 3 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # conf 5 | VERFILE=setup.py 6 | 7 | if [[ $1 != "major" ]] && [[ $1 != "minor" ]] && [[ $1 != "patch" ]]; then 8 | echo wrong usage. use major/minor/patch as first argument 9 | exit 1 10 | fi 11 | 12 | function get_cur_vers { 13 | grep '^version = "' $VERFILE | awk -F\" '{print $2}' 14 | } 15 | 16 | echo bump version 17 | bumpversion --current-version $(get_cur_vers) $1 $VERFILE 18 | 19 | v=$(get_cur_vers) 20 | 21 | echo adding local file 22 | git add $VERFILE 23 | 24 | echo commit 25 | git commit -m "Release: $v" 26 | 27 | echo tagging.. 28 | git tag v${v} 29 | 30 | echo creating history.rst 31 | gitchangelog > HISTORY.rst 32 | git add HISTORY.rst 33 | 34 | echo committing history 35 | git commit -m "Changelog: $v" 36 | 37 | echo pushing.. 38 | git push --tags origin master 39 | 40 | echo release on pypi 41 | python setup.py sdist upload -r pypi 42 | 43 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Always prefer setuptools over distutils 2 | from setuptools import setup, find_packages 3 | # To use a consistent encoding 4 | from codecs import open 5 | import os 6 | import sys 7 | 8 | # file read helper 9 | def read_from_file(path): 10 | if os.path.exists(path): 11 | with open(path,"rb","utf-8") as input: 12 | return input.read() 13 | 14 | version = "1.0.3" 15 | 16 | setup( 17 | name='cmddocs', 18 | 19 | # Versions should comply with PEP440. For a discussion on single-sourcing 20 | # the version across setup.py and the project code, see 21 | # https://packaging.python.org/en/latest/single_source_version.html 22 | version=version, 23 | 24 | description='An interactive commandline interface for your personal docs using python, Cmd, git and markdown', 25 | long_description=read_from_file('README.rst'), 26 | 27 | # The project's main homepage. 28 | url='https://github.com/noqqe/cmddocs', 29 | 30 | # Author details 31 | author='Florian Baumann', 32 | author_email='flo@noqqe.de', 33 | 34 | # Choose your license 35 | license='MIT', 36 | 37 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 38 | classifiers=[ 39 | 'Development Status :: 4 - Beta', 40 | 'Topic :: Utilities', 41 | 'Topic :: Terminals', 42 | 'License :: OSI Approved :: MIT License', 43 | 'Programming Language :: Python :: 2.7', 44 | 'Programming Language :: Python :: 3.5', 45 | ], 46 | 47 | keywords='markdown wiki commandline git', 48 | packages=find_packages(), 49 | zip_safe=True, 50 | install_requires=['future', 'gitpython', 'configparser', 'mistune'], 51 | 52 | entry_points={ 53 | 'console_scripts': [ 54 | 'cmddocs=cmddocs:main', 55 | ], 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import pytest 4 | import git 5 | 6 | @pytest.fixture(scope="module") 7 | def demoenv(): 8 | """ 9 | Initializes a test environment to make 10 | sure all tests can be applied properly 11 | """ 12 | 13 | # init demo dir 14 | d = tempfile.mkdtemp(dir="/tmp/", prefix="demodocs-") 15 | 16 | doc = """ 17 | # Header 1 18 | 19 | This is some test content 20 | hopefully no one will ever read this 21 | 22 | ## Header 2 23 | 24 | py.test rocks... 25 | 26 | ### Header 3 27 | 28 | there will be some codeblock 29 | 30 | ``` 31 | foo=$(echo foo) 32 | echo foo | sed -i 's#foo#bar#' 33 | ``` 34 | 35 | #### Header 4 36 | 37 | Test Test test 38 | 39 | ##### Header 5 40 | 41 | This is some test content 42 | hopefully no one will ever read this 43 | """ 44 | 45 | # create test dirs 46 | for x in range(1, 4): 47 | x = str(x) 48 | os.mkdir(d + "/dir" + x) 49 | 50 | # create test files 51 | for x in range(1, 6): 52 | x = str(x) 53 | f = open(d + "/testfile" + x + ".md", "a") 54 | f.write("Test " + x) 55 | f.close() 56 | 57 | for x in range(1, 4): 58 | x = str(x) 59 | f = open(d + "/dir1/testfile" + x + ".md", "a") 60 | f.write(doc) 61 | f.close() 62 | 63 | 64 | repo = git.Repo.init(d) 65 | repo.git.config("user.email", "mail@example.net") 66 | repo.git.config("user.name", "Charlie Root") 67 | repo.git.add(".") 68 | repo.git.commit(m=" init") 69 | 70 | # create new content 71 | for x in range(1, 4): 72 | x = str(x) 73 | f = open(d + "/dir2/testfile" + x + ".md", "a") 74 | f.write(doc) 75 | f.close() 76 | 77 | # create 2nd commit 78 | repo.git.add(".") 79 | repo.git.commit(m=" 2nd commit") 80 | 81 | # create new content 82 | for x in range(1, 4): 83 | x = str(x) 84 | f = open(d + "/dir3/testfile" + x + ".md", "a") 85 | f.write(doc) 86 | f.close() 87 | 88 | # create 3rd commit 89 | repo.git.add(".") 90 | repo.git.commit(m=" 3rd commit") 91 | 92 | # create test config 93 | confpath = tempfile.mkdtemp(dir="/tmp/", prefix="demodocsconf-") 94 | config = open(confpath + "/config", "a") 95 | 96 | # initialize test config 97 | content = """ 98 | [General] 99 | Datadir = %s 100 | Default_Commit_Message = small changes 101 | Excludedir = .git/ 102 | Editor = /usr/local/bin/vim 103 | Pager = /usr/bin/less 104 | Prompt = cmddocs> 105 | Promptcolor = 37 106 | Intro_Message = cmddocs - press ? for help 107 | Mail = mail@example.com 108 | Default_Extension = md 109 | 110 | [Colors] 111 | Header12 = 37 112 | Header345 = 37 113 | Codeblock = 92 114 | """ % d 115 | 116 | config.write(content) 117 | config.close() 118 | 119 | c = config.name 120 | print(d) 121 | 122 | return c, d 123 | 124 | 125 | @pytest.fixture(scope="module") 126 | def emptyenv(): 127 | """ 128 | Initializes a test environment to make 129 | sure all tests can be applied properly 130 | """ 131 | 132 | # init demo dir 133 | d = tempfile.mkdtemp(dir="/tmp/", prefix="demodocs-") 134 | 135 | repo = git.Repo.init(d) 136 | repo.git.config("user.email", "mail@example.net") 137 | repo.git.config("user.name", "Charlie Root") 138 | 139 | # create test config 140 | confpath = tempfile.mkdtemp(dir="/tmp/", prefix="demodocsconf-") 141 | config = open(confpath + "/config", "a") 142 | 143 | # initialize test config 144 | content = """ 145 | [General] 146 | Datadir = %s 147 | Editor = /usr/bin/vi 148 | Pager = /usr/bin/less 149 | 150 | [Colors] 151 | Header12 = 37 152 | Header345 = 37 153 | Codeblock = 92 154 | """ % d 155 | 156 | config.write(content) 157 | config.close() 158 | 159 | c = config.name 160 | print(d) 161 | 162 | return c, d 163 | -------------------------------------------------------------------------------- /tests/test_delete.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | def test_do_delete_edit(demoenv, capsys): 6 | c, d = demoenv 7 | Cmddocs(c).do_edit("testfile1", test=True) 8 | out, err = capsys.readouterr() 9 | assert out.startswith("automatic change done") 10 | 11 | def test_do_delete_edit_commitmsg(demoenv, capsys): 12 | c, d = demoenv 13 | Cmddocs(c).do_log("") 14 | out, err = capsys.readouterr() 15 | assert "small changes" in out 16 | 17 | def test_do_delete(demoenv, capsys): 18 | c, d = demoenv 19 | Cmddocs(c).do_delete("testfile1") 20 | out, err = capsys.readouterr() 21 | assert out == "testfile1 deleted\n" 22 | 23 | def test_do_delete_log_commitmsg(demoenv, capsys): 24 | c, d = demoenv 25 | Cmddocs(c).do_log("") 26 | out, err = capsys.readouterr() 27 | assert "testfile1 deleted\n" in out 28 | -------------------------------------------------------------------------------- /tests/test_directory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | def test_do_pwd(demoenv, capsys): 6 | c, d = demoenv 7 | Cmddocs(c).do_pwd(d) 8 | out, err = capsys.readouterr() 9 | assert ".\n" in out 10 | 11 | def test_do_cd(demoenv, capsys): 12 | c, d = demoenv 13 | Cmddocs(c).do_cd("dir2") 14 | out, err = capsys.readouterr() 15 | assert "" in out 16 | 17 | def test_do_cd_fail(demoenv, capsys): 18 | c, d = demoenv 19 | Cmddocs(c).do_cd("dir7") 20 | out, err = capsys.readouterr() 21 | assert out == "Error: Directory dir7 not found\n" 22 | 23 | def test_do_dirs_fail(demoenv, capsys): 24 | c, d = demoenv 25 | Cmddocs(c).do_dirs("dir7") 26 | out, err = capsys.readouterr() 27 | assert out.startswith("dir7 [error opening dir]\n\n0 directories\n\n") 28 | 29 | def test_do_dirs_subdir(demoenv, capsys): 30 | c, d = demoenv 31 | Cmddocs(c).do_dirs("dir1") 32 | out, err = capsys.readouterr() 33 | assert out.startswith("dir1") 34 | 35 | def test_do_dirs_pwd(demoenv, capsys): 36 | c, d = demoenv 37 | Cmddocs(c).do_dirs(".") 38 | out, err = capsys.readouterr() 39 | assert out.endswith('\n3 directories\n\n') 40 | -------------------------------------------------------------------------------- /tests/test_edit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | def test_do_edit(demoenv, capsys): 6 | c, d = demoenv 7 | Cmddocs(c).do_edit("testfile1", test=True) 8 | out, err = capsys.readouterr() 9 | assert out.startswith("automatic change done") 10 | 11 | def test_do_edit_log_commitmsg(demoenv, capsys): 12 | c, d = demoenv 13 | Cmddocs(c).do_log("testfile1") 14 | out, err = capsys.readouterr() 15 | assert "small changes" in out 16 | 17 | def test_do_e(demoenv, capsys): 18 | c, d = demoenv 19 | Cmddocs(c).do_e("testfile1", test=True) 20 | out, err = capsys.readouterr() 21 | assert out.startswith("automatic change done") 22 | -------------------------------------------------------------------------------- /tests/test_info.py: -------------------------------------------------------------------------------- 1 | from cmddocs import Cmddocs 2 | 3 | def test_do_info_start(demoenv, capsys): 4 | c, d = demoenv 5 | Cmddocs(c).do_info("testfile1") 6 | out, err = capsys.readouterr() 7 | assert out.startswith("Created:") 8 | 9 | def test_do_info_end(demoenv, capsys): 10 | c, d = demoenv 11 | Cmddocs(c).do_info("testfile1") 12 | out, err = capsys.readouterr() 13 | assert "Characters" in out 14 | 15 | def test_do_info_charcount(demoenv, capsys): 16 | c, d = demoenv 17 | Cmddocs(c).do_info("testfile2") 18 | out, err = capsys.readouterr() 19 | assert "Characters: 6" in out 20 | 21 | def test_do_info_linecount(demoenv, capsys): 22 | c, d = demoenv 23 | Cmddocs(c).do_info("testfile1") 24 | out, err = capsys.readouterr() 25 | assert "Lines: 1" in out 26 | 27 | def test_do_info_wordcount(demoenv, capsys): 28 | c, d = demoenv 29 | Cmddocs(c).do_info("testfile1") 30 | out, err = capsys.readouterr() 31 | assert "Words: 2" in out 32 | 33 | def test_do_info_commitcount(demoenv, capsys): 34 | c, d = demoenv 35 | Cmddocs(c).do_info("testfile1") 36 | out, err = capsys.readouterr() 37 | assert "Commits: 1" in out 38 | -------------------------------------------------------------------------------- /tests/test_initializaton.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | def test_do_initializaton(emptyenv, capsys): 6 | c, d = emptyenv 7 | Cmddocs(c).do_status("test") 8 | out, err = capsys.readouterr() 9 | assert "On branch master" in out 10 | -------------------------------------------------------------------------------- /tests/test_list.py: -------------------------------------------------------------------------------- 1 | 2 | from cmddocs import Cmddocs 3 | 4 | def test_do_list_end(demoenv, capsys): 5 | c, d = demoenv 6 | Cmddocs(c).do_list(d) 7 | out, err = capsys.readouterr() 8 | assert out.endswith("14 files\n\n") 9 | 10 | def test_do_list_start(demoenv, capsys): 11 | c, d = demoenv 12 | Cmddocs(c).do_list(d) 13 | out, err = capsys.readouterr() 14 | assert "tmp/demodocs" in out 15 | 16 | def test_do_l_start(demoenv, capsys): 17 | c, d = demoenv 18 | Cmddocs(c).do_l(d) 19 | out, err = capsys.readouterr() 20 | assert "tmp/demodocs" in out 21 | 22 | def test_do_list_fail(demoenv, capsys): 23 | c, d = demoenv 24 | Cmddocs(c).do_list("NEVEREVER") 25 | out, err = capsys.readouterr() 26 | assert out.startswith("NEVEREVER [error opening dir]") 27 | 28 | def test_do_l_fail(demoenv, capsys): 29 | c, d = demoenv 30 | Cmddocs(c).do_l("NEVEREVER") 31 | out, err = capsys.readouterr() 32 | assert out.startswith("NEVEREVER [error opening dir]") 33 | -------------------------------------------------------------------------------- /tests/test_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | def test_do_log(demoenv, capsys): 6 | c, d = demoenv 7 | Cmddocs(c).do_log("10") 8 | out, err = capsys.readouterr() 9 | assert out.startswith("Last 10 commits\n") 10 | 11 | def test_do_log_10_file(demoenv, capsys): 12 | c, d = demoenv 13 | Cmddocs(c).do_log("10 testfile1") 14 | out, err = capsys.readouterr() 15 | assert out.startswith("Last 10 commits for testfile1.md\n") 16 | 17 | def test_do_log_20_file(demoenv, capsys): 18 | c, d = demoenv 19 | Cmddocs(c).do_log("20 testfile1") 20 | out, err = capsys.readouterr() 21 | assert out.startswith("Last 20 commits for testfile1.md\n") 22 | 23 | def test_do_log_file(demoenv, capsys): 24 | c, d = demoenv 25 | Cmddocs(c).do_log("testfile2") 26 | out, err = capsys.readouterr() 27 | assert out.startswith("Last 10 commits for testfile2.md\n") 28 | 29 | def test_do_log_commitmsg(demoenv, capsys): 30 | c, d = demoenv 31 | Cmddocs(c).do_log("testfile2") 32 | out, err = capsys.readouterr() 33 | assert out.endswith("init\n") 34 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | def test_do_exit(demoenv): 6 | c, d = demoenv 7 | assert Cmddocs(c).do_exit('exit') == True 8 | 9 | def test_do_help(demoenv, capsys): 10 | c, d = demoenv 11 | Cmddocs(c).do_help('exit') 12 | out, err = capsys.readouterr() 13 | assert out.startswith("\n Exit cmddocs\n") 14 | 15 | def test_do_status(demoenv, capsys): 16 | c, d = demoenv 17 | Cmddocs(c).do_status('test') 18 | out, err = capsys.readouterr() 19 | assert "On branch master" in out 20 | 21 | def test_do_version(demoenv, capsys): 22 | c, d = demoenv 23 | Cmddocs(c).do_version('test') 24 | out, err = capsys.readouterr() 25 | assert out.startswith('cmddocs ') 26 | -------------------------------------------------------------------------------- /tests/test_move.py: -------------------------------------------------------------------------------- 1 | 2 | from cmddocs import Cmddocs 3 | 4 | def test_do_mv_fail(demoenv, capsys): 5 | c, d = demoenv 6 | Cmddocs(c).do_mv("testfileX testfileX") 7 | out, err = capsys.readouterr() 8 | assert out == ("Error: File could not be moved\n") 9 | 10 | def test_do_mv_start(demoenv, capsys): 11 | c, d = demoenv 12 | Cmddocs(c).do_mv("testfile1 testfileX") 13 | out, err = capsys.readouterr() 14 | movemsg = "%s/testfile1.md" % d 15 | assert movemsg in out 16 | 17 | def test_do_mv_ends(demoenv, capsys): 18 | c, d = demoenv 19 | Cmddocs(c).do_mv("testfileX testfile1") 20 | out, err = capsys.readouterr() 21 | assert out.endswith("testfile1.md\n") 22 | 23 | def test_do_mv_commitcheck(demoenv, capsys): 24 | c, d = demoenv 25 | Cmddocs(c).do_log("testfile1") 26 | out, err = capsys.readouterr() 27 | assert "testfileX.md" in out 28 | -------------------------------------------------------------------------------- /tests/test_search.py: -------------------------------------------------------------------------------- 1 | from cmddocs import Cmddocs 2 | 3 | def test_do_search(demoenv, capsys): 4 | c, d = demoenv 5 | Cmddocs(c).do_search("test") 6 | out, err = capsys.readouterr() 7 | assert out.startswith("Articles:") 8 | 9 | def test_do_search_results(demoenv, capsys): 10 | c, d = demoenv 11 | Cmddocs(c).do_search("test") 12 | out, err = capsys.readouterr() 13 | assert out.endswith("Results: 50\n") 14 | 15 | def test_do_search_content(demoenv, capsys): 16 | c, d = demoenv 17 | Cmddocs(c).do_search("test") 18 | out, err = capsys.readouterr() 19 | assert "Content:" in out 20 | 21 | def test_do_search_noresults(demoenv, capsys): 22 | c, d = demoenv 23 | Cmddocs(c).do_search("SOMETHINGTHATWILLNEVERBEFOUND") 24 | out, err = capsys.readouterr() 25 | assert out == "Articles:\nContent:\nResults: 0\n" 26 | -------------------------------------------------------------------------------- /tests/test_stats.py: -------------------------------------------------------------------------------- 1 | from cmddocs import Cmddocs 2 | 3 | def test_do_stats_start(demoenv, capsys): 4 | c, d = demoenv 5 | Cmddocs(c).do_stats("test") 6 | out, err = capsys.readouterr() 7 | assert out.startswith("Newest Commit:") 8 | 9 | def test_do_stats_end(demoenv, capsys): 10 | c, d = demoenv 11 | Cmddocs(c).do_stats("test") 12 | out, err = capsys.readouterr() 13 | assert "Characters" in out 14 | 15 | def test_do_stats_charcount(demoenv, capsys): 16 | c, d = demoenv 17 | Cmddocs(c).do_stats("test") 18 | out, err = capsys.readouterr() 19 | assert "Characters: 2892" in out 20 | 21 | #def test_do_stats_linecount(demoenv, capsys): 22 | # c, d = demoenv 23 | # Cmddocs(c).do_stats("test") 24 | # out, err = capsys.readouterr() 25 | # assert "Lines: 35" in out 26 | 27 | def test_do_stats_articlecount(demoenv, capsys): 28 | c, d = demoenv 29 | Cmddocs(c).do_stats("test") 30 | out, err = capsys.readouterr() 31 | assert "Articles: 14" in out 32 | -------------------------------------------------------------------------------- /tests/test_undo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cmddocs import Cmddocs 4 | 5 | def test_do_undo_fail(demoenv, capsys): 6 | c, d = demoenv 7 | Cmddocs(c).do_undo('test') 8 | out, err = capsys.readouterr() 9 | assert out == "Error: Could not find given commit reference\n" 10 | 11 | def test_do_revert_fail(demoenv, capsys): 12 | c, d = demoenv 13 | Cmddocs(c).do_revert('test') 14 | out, err = capsys.readouterr() 15 | assert out == "Error: Could not find given commit reference\n" 16 | 17 | #def test_do_undo_head(demoenv, capsys): 18 | # c, d = demoenv 19 | # Cmddocs(c).do_undo('HEAD') 20 | # out, err = capsys.readouterr() 21 | # assert out == "Error: Could not find given commit reference\n" 22 | --------------------------------------------------------------------------------