diff --git a/.travis.yml b/.travis.yml
index 6a195a7c..7693027e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,8 +3,17 @@ python:
- "2.7"
- "3.3"
- "3.4"
+ - "3.5"
+ - "3.6"
+ - "3.6-dev"
+ - "3.7-dev"
+ - "nightly"
install:
- "pip install -e ."
+<<<<<<< HEAD
+=======
+ - "pip install pycrypto>=2.6"
+>>>>>>> master
- "pip install -q behave"
# command to run tests
script:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c400b8c..546e6833 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,9 @@ Changelog
### 1.9 (July 21, 2014)
+* __1.9.8__ Fixes a problem with temporary files on windows
+* __1.9.7__ Fixes writing non-ascii entries on the prompt
+* __1.9.6__ Fuzzy time parsing improvements (thanks to @pcarranza)
* __1.9.5__ Multi-word tags for DayOne Journals
* __1.9.4__ Fixed: Order of journal entries in file correct after --edit'ing
* __1.9.3__ Fixed: Tags at the beginning of lines
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d4bbb29f..4a342df2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,6 +3,9 @@ Contributing
If you use jrnl, you can totally make my day by just saying "thanks for the code" or by [tweeting about jrnl](https://twitter.com/intent/tweet?text=Write+your+memoirs+on+the+command+line.+Like+a+boss.+%23jrnl&url=http%3A%2F%2Fmaebert.github.io%2Fjrnl&via=maebert). It's your chance to make a programmer happy today! If you have a minute or two, let me know what you use jrnl for and how, it'll help me to make it even better. If you blog about jrnl, I'll send you a post card!
+> # Important:
+> ### Please develop new features against the `2.0-rc1` branch. PRs to the `master` branch will not get merged until version 2.0 is released.
+
Docs & Typos
------------
@@ -12,7 +15,7 @@ If you find a typo or a mistake in the docs, just fix it right away and send a p
Bugs
----
-They unfortunately happen. Specifically, I don't have a Windows machine to test on, so expect a few rough spots. If you found a bug, please [open a new issue](https://www.github.com/maebert/jrnl/issues/new) and describe it as well as possible. If you're a programmer and have a little time time spare, go ahead, fork the code and fix bugs you spot, it'll be much appreciated!
+They unfortunately happen. Specifically, I don't have a Windows machine to test on, so expect a few rough spots. If you found a bug, please [open a new issue](https://www.github.com/maebert/jrnl/issues/new) and describe it as well as possible. If you're a programmer and have a little time to spare, go ahead, fork the code and fix bugs you spot, it'll be much appreciated!
Feature requests and ideas
diff --git a/Makefile b/Makefile
index 56eb4598..49982715 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,5 @@
+.PHONY: clean docs
+
# A Makefile for commands I run frequently:
clean:
diff --git a/README.md b/README.md
index 4ea95240..9eb52515 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
jrnl [](https://travis-ci.org/maebert/jrnl) [](https://pypi.python.org/pypi/jrnl/) [](https://pypi.python.org/pypi/jrnl/)
====
-_For news on updates or to get help, [read the docs](http://maebert.github.io/jrnl), follow [@maebert](https://twitter.com/maebert) or [submit an issue](https://github.com/maebert/jrnl/issues/new) on Github._
+_For news on updates or to get help, [read the docs](http://maebert.github.io/jrnl/overview.html), follow [@maebert](https://twitter.com/maebert) or [submit an issue](https://github.com/maebert/jrnl/issues/new) on Github._
*jrnl* is a simple journal application for your command line. Journals are stored as human readable plain text files - you can put them into a Dropbox folder for instant syncing and you can be assured that your journal will still be readable in 2050, when all your fancy iPad journal applications will long be forgotten.
@@ -30,6 +30,9 @@ If you just call `jrnl`, you will be prompted to compose your entry - but you ca
Installation
------------
+Install _jrnl_ from source:
+
+ sudo python setup.py install
Install _jrnl_ using pip:
@@ -39,3 +42,6 @@ Or, if you want the option to encrypt your journal,
pip install jrnl[encrypted]
+Alternatively, on OS X with [Homebrew](http://brew.sh/) installed:
+
+ brew install jrnl
diff --git a/docs/_themes/jrnl/static/img/favicon-152.png b/docs/_themes/jrnl/static/img/favicon-152.png
index ac658d9c..539d40cc 100644
Binary files a/docs/_themes/jrnl/static/img/favicon-152.png and b/docs/_themes/jrnl/static/img/favicon-152.png differ
diff --git a/docs/_themes/jrnl/static/img/favicon.ico b/docs/_themes/jrnl/static/img/favicon.ico
index 7c9c2c1e..d6197b35 100644
Binary files a/docs/_themes/jrnl/static/img/favicon.ico and b/docs/_themes/jrnl/static/img/favicon.ico differ
diff --git a/docs/_themes/jrnl/static/img/logo.png b/docs/_themes/jrnl/static/img/logo.png
index 1ea79cf1..900ebac6 100644
Binary files a/docs/_themes/jrnl/static/img/logo.png and b/docs/_themes/jrnl/static/img/logo.png differ
diff --git a/docs/_themes/jrnl/static/img/logo@2x.png b/docs/_themes/jrnl/static/img/logo@2x.png
index 9cc3d76b..2ef28d1a 100644
Binary files a/docs/_themes/jrnl/static/img/logo@2x.png and b/docs/_themes/jrnl/static/img/logo@2x.png differ
diff --git a/docs/_themes/jrnl/static/less/docs.less b/docs/_themes/jrnl/static/less/docs.less
index eb12bb48..da817702 100644
--- a/docs/_themes/jrnl/static/less/docs.less
+++ b/docs/_themes/jrnl/static/less/docs.less
@@ -114,7 +114,7 @@ a:hover, a:active
background: desaturate(lighten(@terminal,10), 10);
pre {color: white;}
}
-.highlight-python
+.highlight-python, .highlight-sh
{
.terminal;
pre
@@ -223,12 +223,12 @@ div.footer
{
body:not(.landing){
padding-top: 130px;
- .highlight-output,.highlight-python, .highlight-javascript
+ .highlight-output, .highlight-python, .highlight-javascript, .highlight-sh
{
width: auto;
max-width: 500px;
}
- .highlight-python
+ .highlight-python, .highlight-sh
{
pre { margin: -10px 0 10px 0;}
&:before
diff --git a/docs/advanced.rst b/docs/advanced.rst
index 827b8b0b..76141f6b 100644
--- a/docs/advanced.rst
+++ b/docs/advanced.rst
@@ -90,12 +90,11 @@ You can configure _jrnl_ to use with multiple journals (eg. ``private`` and ``wo
}
}
-The ``default`` journal gets created the first time you start _jrnl_. Now you can access the ``work`` journal by using ``jrnl work`` instead of ``jrnl``, eg. ::
+The ``default`` journal gets created the first time you start _jrnl_. Now you can access the ``work`` journal by using ``jrnl work`` instead of ``jrnl``, eg.
+
+.. code-block:: sh
jrnl work at 10am: Meeting with @Steve
-
-::
-
jrnl work -n 3
will both use ``~/work.txt``, while ``jrnl -n 3`` will display the last three entries from ``~/journal.txt`` (and so does ``jrnl default -n 3``).
diff --git a/docs/encryption.rst b/docs/encryption.rst
index 73128898..e7558db3 100644
--- a/docs/encryption.rst
+++ b/docs/encryption.rst
@@ -7,11 +7,15 @@ Encrypting and decrypting
-------------------------
-If you don't choose to encrypt your file when you run `jrnl` for the first time, you can encrypt your existing journal file or change its password using ::
+If you don't choose to encrypt your file when you run `jrnl` for the first time, you can encrypt your existing journal file or change its password using
+
+.. code-block:: sh
jrnl --encrypt
-If it is already encrypted, you will first be asked for the current password. You can then enter a new password and your plain journal will replaced by the encrypted file. Conversely, ::
+If it is already encrypted, you will first be asked for the current password. You can then enter a new password and your plain journal will replaced by the encrypted file. Conversely,
+
+.. code-block:: sh
jrnl --decrypt
@@ -28,20 +32,53 @@ If you don't initially store the password in the keychain but decide to do so at
A note on security
------------------
+<<<<<<< HEAD
While jrnl follows best practices, true security is an illusion. Specifically, jrnl will leave traces in your memory and your shell history -- it's meant to keep journals secure in transit, for example when storing it on an `untrusted `_ services such as Dropbox. If you're concerned about security, disable history logging for journal in your ``.bashrc`` ::
HISTIGNORE="jrnl *:"
+=======
+While jrnl follows best practises, true security is an illusion. Specifically, jrnl will leave traces in your memory and your shell history -- it's meant to keep journals secure in transit, for example when storing it on an `untrusted `_ services such as Dropbox. If you're concerned about security, disable history logging for journal in your ``.bashrc``
+
+.. code-block:: sh
+
+ HISTIGNORE="$HISTIGNORE:jrnl *"
+
+If you are using zsh instead of bash, you can get the same behaviour adding this to your ``zshrc``
+
+.. code-block:: sh
+
+ setopt HIST_IGNORE_SPACE
+ alias jrnl=" jrnl"
+>>>>>>> master
Manual decryption
-----------------
-Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. So, to decrypt a journal file in python, run::
+Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. Here's a Python script that you can use to decrypt your journal
+
+.. code-block:: python
+
+ #!/usr/bin/env python3
+
+ import argparse
+ from Crypto.Cipher import AES
+ import getpass
+ import hashlib
+ import sys
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("filepath", help="journal file to decrypt")
+ args = parser.parse_args()
+
+ pwd = getpass.getpass()
+ key = hashlib.sha256(pwd.encode('utf-8')).digest()
+
+ with open(args.filepath, 'rb') as f:
+ ciphertext = f.read()
+
+ crypto = AES.new(key, AES.MODE_CBC, ciphertext[:16])
+ plain = crypto.decrypt(ciphertext[16:])
+ plain = plain.strip(plain[-1:])
+ plain = plain.decode("utf-8")
+ print(plain)
- import hashlib, Crypto.Cipher
- key = hashlib.sha256(my_password).digest()
- with open("my_journal.txt") as f:
- cipher = f.read()
- crypto = AES.new(key, AES.MODE_CBC, iv = cipher[:16])
- plain = crypto.decrypt(cipher[16:])
- plain = plain.strip(plain[-1])
- plain = plain.decode("utf-8")
diff --git a/docs/export.rst b/docs/export.rst
index 8b16348e..92ffdf24 100644
--- a/docs/export.rst
+++ b/docs/export.rst
@@ -6,7 +6,9 @@ Import and Export
Tag export
----------
-With::
+With
+
+.. code-block:: sh
jrnl --tags
@@ -15,7 +17,7 @@ you'll get a list of all tags you used in your journal, sorted by most frequent.
List of all entries
-------------------
-::
+.. code-block:: sh
jrnl --short
@@ -24,7 +26,9 @@ Will only display the date and title of each entry.
JSON export
-----------
-Can do::
+Can do
+
+.. code-block:: sh
jrnl --export json
@@ -33,7 +37,9 @@ Why not create a `beautiful timeline `_ of your jour
Markdown export
---------------
-Use::
+Use
+
+.. code-block:: sh
jrnl --export markdown
@@ -42,7 +48,7 @@ Markdown is a simple markup language that is human readable and can be used to b
Text export
-----------
-::
+.. code-block:: sh
jrnl --export text
@@ -60,11 +66,15 @@ Why anyone would want to export stuff to XML is beyond me, but here you go.
Export to files
---------------
-You can specify the output file of your exported journal using the `-o` argument::
+You can specify the output file of your exported journal using the `-o` argument
+
+.. code-block:: sh
jrnl --export md -o journal.md
-The above command will generate a file named `journal.md`. If the `-o` argument is a directory, jrnl will export each entry into an individual file::
+The above command will generate a file named `journal.md`. If the `-o` argument is a directory, jrnl will export each entry into an individual file
+
+.. code-block:: sh
jrnl --export json -o my_entries/
diff --git a/docs/installation.rst b/docs/installation.rst
index 1cabd58a..ce33433e 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -6,13 +6,37 @@ Getting started
Installation
------------
-Install *jrnl* using pip ::
+On OS X, the easiest way to install *jrnl* is using `Homebrew `_
+
+.. code-block:: sh
+
+ brew install jrnl
+
+On other platforms, install *jrnl* using pip
+
+.. code-block:: sh
pip install jrnl
+<<<<<<< HEAD
Alternatively, on OS X with [Homebrew](http://brew.sh/) installed:
brew install jrnl
+=======
+Or, if you want the option to encrypt your journal,
+
+.. code-block:: sh
+
+ pip install jrnl[encrypted]
+
+to install the dependencies for encrypting journals as well.
+
+.. note::
+
+ Installing the encryption library, `pycrypto`, requires a `gcc` compiler. For this reason, jrnl will not install `pycrypto` unless explicitly told so like this. You can `install PyCrypto manually `_ first or install it with ``pip install pycrypto`` if you have a `gcc` compiler.
+
+ Also note that when using zsh, the correct syntax is ``pip install "jrnl[encrypted]"`` (note the quotes).
+>>>>>>> master
The first time you run ``jrnl`` you will be asked where your journal file should be created and whether you wish to encrypt it.
@@ -20,7 +44,9 @@ The first time you run ``jrnl`` you will be asked where your journal file should
Quickstart
----------
-to make a new entry, just type::
+to make a new entry, just type
+
+.. code-block:: sh
jrnl yesterday: Called in sick. Used the time to clean the house and spent 4h on writing my book.
diff --git a/docs/recipes.rst b/docs/recipes.rst
index e1aef9a9..85c6b4a6 100644
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -9,7 +9,9 @@ Recipes
Co-occurrence of tags
~~~~~~~~~~~~~~~~~~~~~
-If I want to find out how often I mentioned my flatmates Alberto and Melo in the same entry, I run ::
+If I want to find out how often I mentioned my flatmates Alberto and Melo in the same entry, I run
+
+.. code-block:: sh
jrnl @alberto --tags | grep @melo
@@ -18,7 +20,9 @@ And will get something like ``@melo: 9``, meaning there are 9 entries where both
Combining filters
~~~~~~~~~~~~~~~~~
-You can do things like ::
+You can do things like
+
+.. code-block:: sh
jrnl @fixed -starred -n 10 -until "jan 2013" --short
@@ -27,11 +31,15 @@ To get a short summary of the 10 most recent, favourited entries before January
Statistics
~~~~~~~~~~
-How much did I write last year? ::
+How much did I write last year?
+
+.. code-block:: sh
jrnl -from "jan 1 2013" -until "dec 31 2013" | wc -w
-Will give you the number of words you wrote in 2013. How long is my average entry? ::
+Will give you the number of words you wrote in 2013. How long is my average entry?
+
+.. code-block:: sh
expr $(jrnl --export text | wc -w) / $(jrnl --short | wc -l)
@@ -40,7 +48,9 @@ This will first get the total number of words in the journal and divide it by th
Importing older files
~~~~~~~~~~~~~~~~~~~~~
-If you want to import a file as an entry to jrnl, you can just do ``jrnl < entry.ext``. But what if you want the modification date of the file to be the date of the entry in jrnl? Try this ::
+If you want to import a file as an entry to jrnl, you can just do ``jrnl < entry.ext``. But what if you want the modification date of the file to be the date of the entry in jrnl? Try this
+
+.. code-block:: sh
echo `stat -f %Sm -t '%d %b %Y at %H:%M: ' entry.txt` `cat entry.txt` | jrnl
@@ -108,10 +118,17 @@ On OS X, you can use the fabulous `iA Writer `_ to
.. code-block:: javascript
- "editor": "open -b jp.informationarchitects.WriterForMacOSX -Wn"
+ "editor": "open -b pro.writer.mac -Wn"
What does this do? ``open -b ...`` opens a file using the application identified by the bundle identifier (a unique string for every app out there). ``-Wn`` tells the application to wait until it's closed before passing back control, and to use a new instance of the application.
+If the ``pro.writer.mac`` bundle identifier is not found on your system, you can find the right string to use by inspecting iA Writer's ``Info.plist`` file in your shell:
+
+.. code-block:: sh
+
+ $ grep -A 1 CFBundleIdentifier /Applications/iA\ Writer.app/Contents/Info.plist
+ CFBundleIdentifier
+ pro.writer.mac
Notepad++ on Windows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/usage.rst b/docs/usage.rst
index f9cdb60c..f0ff87bd 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -10,7 +10,9 @@ We intentionally break a convention on command line arguments: all arguments sta
Listing Journals
----------------
-You can list the journals accessible by jrnl::
+You can list the journals accessible by jrnl
+
+.. code-block:: sh
jrnl -ls
@@ -19,7 +21,9 @@ The journals displayed correspond to those specified in the jrnl configuration f
Composing Entries
-----------------
-Composing mode is entered by either starting ``jrnl`` without any arguments -- which will prompt you to write an entry or launch your editor -- or by just writing an entry on the prompt, such as::
+Composing mode is entered by either starting ``jrnl`` without any arguments -- which will prompt you to write an entry or launch your editor -- or by just writing an entry on the prompt, such as
+
+.. code-block:: sh
jrnl today at 3am: I just met Steve Buscemi in a bar! He looked funny.
@@ -28,7 +32,9 @@ Composing mode is entered by either starting ``jrnl`` without any arguments -- w
Most shell contains a certain number of reserved characters, such as ``#`` and ``*``. Unbalanced quotes, parenthesis, and so on will also get into the way of your editing. For writing longer entries, just enter ``jrnl`` and hit ``return``. Only then enter the text of your journal entry. Alternatively, :doc:`use an external editor `).
-You can also import an entry directly from a file::
+You can also import an entry directly from a file
+
+.. code-block:: sh
jrnl < my_entry.txt
@@ -48,7 +54,9 @@ Timestamps that work:
Starring entries
~~~~~~~~~~~~~~~~
-To mark an entry as a favourite, simply "star" it::
+To mark an entry as a favourite, simply "star" it
+
+.. code-block:: sh
jrnl last sunday *: Best day of my life.
@@ -65,30 +73,42 @@ If you don't want to add a date (ie. your entry will be dated as now), The follo
Viewing
-------
-::
+
+
+.. code-block:: sh
jrnl -n 10
-will list you the ten latest entries (if you're lazy, ``jrnl -10`` will do the same), ::
+will list you the ten latest entries (if you're lazy, ``jrnl -10`` will do the same),
+
+.. code-block:: sh
jrnl -from "last year" -until march
-everything that happened from the start of last year to the start of last march. To only see your favourite entries, use ::
+everything that happened from the start of last year to the start of last march. To only see your favourite entries, use
+
+.. code-block:: sh
jrnl -starred
Using Tags
----------
-Keep track of people, projects or locations, by tagging them with an ``@`` in your entries ::
+Keep track of people, projects or locations, by tagging them with an ``@`` in your entries
+
+.. code-block:: sh
jrnl Had a wonderful day on the @beach with @Tom and @Anna.
-You can filter your journal entries just like this: ::
+You can filter your journal entries just like this:
+
+.. code-block:: sh
jrnl @pinkie @WorldDomination
-Will print all entries in which either ``@pinkie`` or ``@WorldDomination`` occurred. ::
+Will print all entries in which either ``@pinkie`` or ``@WorldDomination`` occurred.
+
+.. code-block:: sh
jrnl -n 5 -and @pineapple @lubricant
@@ -101,7 +121,9 @@ the last five entries containing both ``@pineapple`` **and** ``@lubricant``. You
Editing older entries
---------------------
-You can edit selected entries after you wrote them. This is particularly useful when your journal file is encrypted or if you're using a DayOne journal. To use this feature, you need to have an editor configured in your journal configuration file (see :doc:`advanced usage `)::
+You can edit selected entries after you wrote them. This is particularly useful when your journal file is encrypted or if you're using a DayOne journal. To use this feature, you need to have an editor configured in your journal configuration file (see :doc:`advanced usage `)
+
+.. code-block:: sh
jrnl -until 1950 @texas -and @history --edit
@@ -109,7 +131,9 @@ Will open your editor with all entries tagged with ``@texas`` and ``@history`` b
Of course, if you are using multiple journals, you can also edit e.g. the latest entry of your work journal with ``jrnl work -n 1 --edit``. In any case, this will bring up your editor and save (and, if applicable, encrypt) your edited journal after you save and exit the editor.
-You can also use this feature for deleting entries from your journal::
+You can also use this feature for deleting entries from your journal
+
+.. code-block:: sh
jrnl @girlfriend -until 'june 2012' --edit
diff --git a/features/data/configs/multiple_without_default.json b/features/data/configs/multiple_without_default.json
new file mode 100644
index 00000000..042e843a
--- /dev/null
+++ b/features/data/configs/multiple_without_default.json
@@ -0,0 +1,16 @@
+{
+ "default_hour": 9,
+ "timeformat": "%Y-%m-%d %H:%M",
+ "linewrap": 80,
+ "encrypt": false,
+ "editor": "",
+ "default_minute": 0,
+ "highlight": true,
+ "password": "",
+ "journals": {
+ "simple": "features/journals/simple.journal",
+ "work": "features/journals/work.journal",
+ "ideas": "features/journals/nothing.journal"
+ },
+ "tagsymbols": "@"
+}
diff --git a/features/multiple_journals.feature b/features/multiple_journals.feature
index fb26eef8..4ac465ab 100644
--- a/features/multiple_journals.feature
+++ b/features/multiple_journals.feature
@@ -35,7 +35,14 @@ Feature: Multiple journals
When we run "jrnl ideas 23 july 2012: sell my junk on ebay and make lots of money"
Then journal "ideas" should have 1 entry
+<<<<<<< HEAD
Scenario: Don't crash if no default journal is specified
Given we use the config "bug343.yaml"
When we run "jrnl a long day in the office"
Then we should see the message "No default journal configured"
+=======
+ Scenario: Gracefully handle a config without a default journal
+ Given we use the config "multiple_without_default.json"
+ When we run "jrnl fork this repo and fix something"
+ Then we should see the message "You have not specified a journal. Either provide a default journal in your config file, or specify one of your journals on the command line."
+>>>>>>> master
diff --git a/features/regression.feature b/features/regression.feature
index 727f7c27..01e7ed08 100644
--- a/features/regression.feature
+++ b/features/regression.feature
@@ -70,6 +70,7 @@ Feature: Zapped bugs should stay dead.
| I'm feeling sore because I forgot to stretch.
"""
+<<<<<<< HEAD
Scenario: DayOne tag searching should work with tags containing a mixture of upper and lower case.
# https://github.com/maebert/jrnl/issues/354
Given we use the config "dayone.yaml"
@@ -78,3 +79,11 @@ Feature: Zapped bugs should stay dead.
"""
2013-05-17 11:39 This entry has tags!
"""
+=======
+ Scenario: Writing an entry at the prompt with non-ascii characters
+ # https://github.com/maebert/jrnl/issues/295
+ Given we use the config "basic.json"
+ When we run "jrnl" and enter "Crème brûlée & Mötorhead"
+ Then we should get no error
+ and the journal should contain "Crème brûlée & Mötorhead"
+>>>>>>> master
diff --git a/features/steps/core.py b/features/steps/core.py
index 4813b252..8d71794d 100644
--- a/features/steps/core.py
+++ b/features/steps/core.py
@@ -7,6 +7,7 @@ from jrnl import __version__
from dateutil import parser as date_parser
from collections import defaultdict
import os
+<<<<<<< HEAD
import json
import yaml
import keyring
@@ -31,6 +32,13 @@ class TestKeyring(keyring.backend.KeyringBackend):
keyring.set_keyring(TestKeyring())
+=======
+import codecs
+import json
+import keyring
+import keyrings
+keyring.set_keyring(keyrings.alt.file.PlaintextKeyring())
+>>>>>>> master
try:
from io import StringIO
except ImportError:
@@ -47,8 +55,14 @@ def ushlex(command):
def read_journal(journal_name="default"):
+<<<<<<< HEAD
config = util.load_config(install.CONFIG_FILE_PATH)
with open(config['journals'][journal_name]) as journal_file:
+=======
+ with open(cli.CONFIG_PATH) as config_file:
+ config = json.load(config_file)
+ with codecs.open(config['journals'][journal_name], 'r', 'utf-8') as journal_file:
+>>>>>>> master
journal = journal_file.read()
return journal
@@ -81,7 +95,11 @@ def run_with_input(context, command, inputs=None):
buffer = StringIO(text.strip())
util.STDIN = buffer
try:
+<<<<<<< HEAD
cli.run(args or [])
+=======
+ cli.run(args)
+>>>>>>> master
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code
@@ -91,7 +109,7 @@ def run_with_input(context, command, inputs=None):
def run(context, command):
args = ushlex(command)[1:]
try:
- cli.run(args or None)
+ cli.run(args)
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code
@@ -180,9 +198,14 @@ def check_output(context, text=None):
def check_output_time_inline(context, text):
out = context.stdout_capture.getvalue()
local_tz = tzlocal.get_localzone()
+<<<<<<< HEAD
utc_time = date_parser.parse(text)
local_date = utc_time.astimezone(local_tz).strftime("%Y-%m-%d %H:%M")
assert local_date in out, local_date
+=======
+ local_time = date_parser.parse(text).astimezone(local_tz).strftime("%Y-%m-%d %H:%M")
+ assert local_time in out, local_time
+>>>>>>> master
@then('the output should contain')
@@ -250,7 +273,11 @@ def config_var(context, key, value, journal=None):
@then('the journal should have {number:d} entry')
@then('journal "{journal_name}" should have {number:d} entries')
@then('journal "{journal_name}" should have {number:d} entry')
+<<<<<<< HEAD
def check_journal_entries(context, number, journal_name="default"):
+=======
+def check_num_entries(context, number, journal_name="default"):
+>>>>>>> master
journal = open_journal(journal_name)
assert len(journal.entries) == number
diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py
index 9e988f78..ec91925f 100644
--- a/jrnl/DayOneJournal.py
+++ b/jrnl/DayOneJournal.py
@@ -36,6 +36,8 @@ class DayOne(Journal.Journal):
filenames.append(os.path.join(root, filename))
self.entries = []
for filename in filenames:
+ if os.path.isdir(filename):
+ continue
with open(filename, 'rb') as plist_entry:
try:
dict_entry = plistlib.readPlist(plist_entry)
@@ -64,9 +66,17 @@ class DayOne(Journal.Journal):
if not hasattr(entry, "uuid"):
entry.uuid = uuid.uuid1().hex
+<<<<<<< HEAD
filename = os.path.join(self.config['journal'], "entries", entry.uuid.upper() + ".doentry")
+=======
+ utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple()))
+ # make sure to upper() the uuid since uuid.uuid1 returns a lowercase string by default
+ # while dayone uses uppercase by default. On fully case preserving filesystems (e.g.
+ # linux) this results in duplicated entries when we save the file
+ filename = os.path.join(self.config['journal'], "entries", entry.uuid.upper() + ".doentry")
+>>>>>>> master
entry_plist = {
'Creation Date': utc_time,
'Starred': entry.starred if hasattr(entry, 'starred') else False,
diff --git a/jrnl/Entry.py b/jrnl/Entry.py
index 1306cef5..21fd362a 100755
--- a/jrnl/Entry.py
+++ b/jrnl/Entry.py
@@ -122,3 +122,58 @@ class Entry:
def __ne__(self, other):
return not self.__eq__(other)
+<<<<<<< HEAD
+=======
+
+ def to_dict(self):
+ return {
+ 'title': self.title,
+ 'body': self.body,
+ 'date': self.date.strftime("%Y-%m-%d"),
+ 'time': self.date.strftime("%H:%M"),
+ 'starred': self.starred
+ }
+
+ def to_md(self):
+ date_str = self.date.strftime(self.journal.config['timeformat'])
+ body_wrapper = "\n\n" if self.body else ""
+ body = body_wrapper + self.body
+ space = "\n"
+ md_head = "###"
+
+ return "{md} {date}, {title} {body} {space}".format(
+ md=md_head,
+ date=date_str,
+ title=self.title,
+ body=body,
+ space=space
+ )
+
+ def to_html(self):
+ html = "\n"
+ html += "\n"
+ html += "\t\n"
+ html += "\t\t\n"
+ html += "\t\n\n"
+ html += "\t\n"
+ html += "\t\t\n"
+ html += "\t\t\tJournal
\n"
+ html += "\t\t\t
\n"
+ # date time title body
+ html +="\t\t\t\t" + self.date.strftime(self.journal.config['timeformat']) + "\t" + str(self.title) + "
\n"
+ html +="\t\t\t\t" + str(self.body) + "
\n\t\t\t
\n\t\t\t
\n"
+ html +="\t\t\n"
+ html +="\t\n"
+ html +=""
+ return html
+>>>>>>> master
diff --git a/jrnl/__init__.py b/jrnl/__init__.py
index 796c58c4..05688acf 100644
--- a/jrnl/__init__.py
+++ b/jrnl/__init__.py
@@ -8,7 +8,11 @@ jrnl is a simple journal application for your command line.
from __future__ import absolute_import
__title__ = 'jrnl'
+<<<<<<< HEAD
__version__ = '2.0.0-rc1'
+=======
+__version__ = '1.9.8'
+>>>>>>> master
__author__ = 'Manuel Ebert'
__license__ = 'MIT License'
__copyright__ = 'Copyright 2013 - 2015 Manuel Ebert'
diff --git a/jrnl/cli.py b/jrnl/cli.py
index 72eff470..09c5edfd 100644
--- a/jrnl/cli.py
+++ b/jrnl/cli.py
@@ -11,6 +11,11 @@ from __future__ import unicode_literals
from __future__ import absolute_import
from . import Journal
from . import util
+<<<<<<< HEAD
+=======
+from . import importers
+from . import exporters
+>>>>>>> master
from . import install
from . import plugins
from .util import ERROR_COLOR, RESET_COLOR
@@ -19,8 +24,15 @@ import argparse
import sys
import logging
+<<<<<<< HEAD
log = logging.getLogger(__name__)
logging.getLogger("keyring.backend").setLevel(logging.ERROR)
+=======
+xdg_config = os.environ.get('XDG_CONFIG_HOME')
+CONFIG_PATH = os.path.join(xdg_config, "jrnl") if xdg_config else os.path.expanduser('~/.jrnl_config')
+PYCRYPTO = install.module_exists("Crypto")
+log = logging.getLogger(__name__)
+>>>>>>> master
def parse_args(args=None):
@@ -43,7 +55,12 @@ def parse_args(args=None):
exporting = parser.add_argument_group('Export / Import', 'Options for transmogrifying your journal')
exporting.add_argument('-s', '--short', dest='short', action="store_true", help='Show only titles or line containing the search tags')
exporting.add_argument('--tags', dest='tags', action="store_true", help='Returns a list of all tags and number of occurences')
+<<<<<<< HEAD
exporting.add_argument('--export', metavar='TYPE', dest='export', choices=plugins.EXPORT_FORMATS, help='Export your journal. TYPE can be {}.'.format(plugins.util.oxford_list(plugins.EXPORT_FORMATS)), default=False, const=None)
+=======
+ exporting.add_argument('--import-json', metavar='FILEPATH', dest='import_json', help='Import a journal from a JSON file.', default=False, const=None)
+ exporting.add_argument('--export', metavar='TYPE', dest='export', choices=['text', 'txt', 'markdown', 'md', 'json', 'html'], help='Export your journal. TYPE can be json, markdown, html, or text.', default=False, const=None)
+>>>>>>> master
exporting.add_argument('-o', metavar='OUTPUT', dest='output', help='Optionally specifies output file when using --export. If OUTPUT is a directory, exports each entry into an individual file instead.', default=False, const=None)
exporting.add_argument('--import', metavar='TYPE', dest='import_', choices=plugins.IMPORT_FORMATS, help='Import entries into your journal. TYPE can be {}, and it defaults to jrnl if nothing else is specified.'.format(plugins.util.oxford_list(plugins.IMPORT_FORMATS)), default=False, const='jrnl', nargs='?')
exporting.add_argument('-i', metavar='INPUT', dest='input', help='Optionally specifies input file when using --import.', default=False, const=None)
@@ -58,12 +75,16 @@ def guess_mode(args, config):
"""Guesses the mode (compose, read or export) from the given arguments"""
compose = True
export = False
+<<<<<<< HEAD
import_ = False
if args.import_ is not False:
compose = False
export = False
import_ = True
elif args.decrypt is not False or args.encrypt is not False or args.export is not False or any((args.short, args.tags, args.edit)):
+=======
+ if args.decrypt is not False or args.encrypt is not False or args.import_json is not False or args.export is not False or any((args.short, args.tags, args.edit)):
+>>>>>>> master
compose = False
export = True
elif any((args.start_date, args.end_date, args.on_date, args.limit, args.strict, args.starred)):
@@ -98,10 +119,19 @@ def decrypt(journal, filename=None):
journal.config['encrypt'] = False
journal.config['password'] = ""
+<<<<<<< HEAD
new_journal = Journal.PlainJournal(filename, **journal.config)
new_journal.entries = journal.entries
new_journal.write(filename)
util.prompt("Journal decrypted to {0}.".format(filename or new_journal.config['journal']))
+=======
+def touch_journal(filename):
+ """If filename does not exist, touch the file"""
+ if not os.path.exists(filename):
+ log.debug('Creating journal file %s', filename)
+ util.prompt("[Journal created at {0}]".format(filename))
+ open(filename, 'a').close()
+>>>>>>> master
def list_journals(config):
@@ -127,11 +157,17 @@ def update_config(config, new_config, scope, force_local=False):
def configure_logger(debug=False):
+<<<<<<< HEAD
logging.basicConfig(
level=logging.DEBUG if debug else logging.INFO,
format='%(levelname)-8s %(name)-12s %(message)s'
)
logging.getLogger('parsedatetime').setLevel(logging.INFO) # disable parsedatetime debug logging
+=======
+ logging.basicConfig(level=logging.DEBUG if debug else logging.INFO,
+ format='%(levelname)-8s %(name)-12s %(message)s')
+ logging.getLogger('parsedatetime').setLevel(logging.INFO) # disable parsedatetime debug logging
+>>>>>>> master
def run(manual_args=None):
@@ -143,7 +179,18 @@ def run(manual_args=None):
print(util.py2encode(version_str))
sys.exit(0)
+<<<<<<< HEAD
config = install.load_or_install_jrnl()
+=======
+ if not os.path.exists(CONFIG_PATH):
+ log.debug('Configuration file not found, installing jrnl...')
+ config = install.install_jrnl(CONFIG_PATH)
+ else:
+ log.debug('Reading configuration from file %s', CONFIG_PATH)
+ config = util.load_and_fix_json(CONFIG_PATH, "configuration")
+ install.upgrade_config(config, config_path=CONFIG_PATH)
+
+>>>>>>> master
if args.ls:
util.prnt(list_journals(config))
sys.exit(0)
@@ -173,7 +220,36 @@ def run(manual_args=None):
pass
log.debug('Using journal "%s"', journal_name)
+<<<<<<< HEAD
mode_compose, mode_export, mode_import = guess_mode(args, config)
+=======
+ journal_conf = config['journals'].get(journal_name)
+ if type(journal_conf) is dict: # We can override the default config on a by-journal basis
+ log.debug('Updating configuration with specific jourlnal overrides %s', journal_conf)
+ config.update(journal_conf)
+ else: # But also just give them a string to point to the journal file
+ config['journal'] = journal_conf
+
+ if config['journal'] is None:
+ util.prompt("You have not specified a journal. Either provide a default journal in your config file, or specify one of your journals on the command line.")
+ sys.exit(1)
+
+ config['journal'] = os.path.expanduser(os.path.expandvars(config['journal']))
+ touch_journal(config['journal'])
+ log.debug('Using journal path %(journal)s', config)
+ mode_compose, mode_export = guess_mode(args, config)
+>>>>>>> master
+
+ # open journal file or folder
+ if os.path.isdir(config['journal']):
+ if config['journal'].strip("/").endswith(".dayone") or \
+ "entries" in os.listdir(config['journal']):
+ journal = DayOneJournal.DayOne(**config)
+ else:
+ util.prompt("[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
+ sys.exit(1)
+ else:
+ journal = Journal.Journal(journal_name, **config)
# How to quit writing?
if "win32" in sys.platform:
@@ -205,6 +281,7 @@ def run(manual_args=None):
else:
mode_compose = False
+<<<<<<< HEAD
# This is where we finally open the journal!
try:
journal = Journal.open_journal(journal_name, config)
@@ -216,6 +293,8 @@ def run(manual_args=None):
if mode_import:
plugins.get_importer(args.import_).import_(journal, args.input)
+=======
+>>>>>>> master
# Writing mode
elif mode_compose:
raw = " ".join(args.text).strip()
@@ -248,6 +327,9 @@ def run(manual_args=None):
elif args.tags:
print(util.py2encode(plugins.get_exporter("tags").export(journal)))
+ elif args.import_json is not False:
+ importers.import_json(args.import_json, config)
+
elif args.export is not False:
exporter = plugins.get_exporter(args.export)
print(exporter.export(journal, args.output))
@@ -255,14 +337,14 @@ def run(manual_args=None):
elif args.encrypt is not False:
encrypt(journal, filename=args.encrypt)
# Not encrypting to a separate file: update config!
- if not args.encrypt:
+ if not args.encrypt or args.encrypt == config['journal']:
update_config(original_config, {"encrypt": True}, journal_name, force_local=True)
install.save_config(original_config)
elif args.decrypt is not False:
decrypt(journal, filename=args.decrypt)
# Not decrypting to a separate file: update config!
- if not args.decrypt:
+ if not args.decrypt or args.decrypt == config['journal']:
update_config(original_config, {"encrypt": False}, journal_name, force_local=True)
install.save_config(original_config)
@@ -281,7 +363,7 @@ def run(manual_args=None):
if num_deleted:
prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
if num_edited:
- prompts.append("{0} {1} modified".format(num_edited, "entry" if num_deleted == 1 else "entries"))
+ prompts.append("{0} {1} modified".format(num_edited, "entry" if num_edited == 1 else "entries"))
if prompts:
util.prompt("[{0}]".format(", ".join(prompts).capitalize()))
journal.entries += other_entries
diff --git a/jrnl/exporters.py b/jrnl/exporters.py
new file mode 100644
index 00000000..881f3781
--- /dev/null
+++ b/jrnl/exporters.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from __future__ import absolute_import, unicode_literals
+import os
+import json
+from .util import u, slugify
+import codecs
+
+
+def get_tags_count(journal):
+ """Returns a set of tuples (count, tag) for all tags present in the journal."""
+ # Astute reader: should the following line leave you as puzzled as me the first time
+ # I came across this construction, worry not and embrace the ensuing moment of enlightment.
+ tags = [tag
+ for entry in journal.entries
+ for tag in set(entry.tags)]
+ # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag]
+ tag_counts = set([(tags.count(tag), tag) for tag in tags])
+ return tag_counts
+
+
+def to_tag_list(journal):
+ """Prints a list of all tags and the number of occurrences."""
+ tag_counts = get_tags_count(journal)
+ result = ""
+ if not tag_counts:
+ return '[No tags found in journal.]'
+ elif min(tag_counts)[0] == 0:
+ tag_counts = filter(lambda x: x[0] > 1, tag_counts)
+ result += '[Removed tags that appear only once.]\n'
+ result += "\n".join("{0:20} : {1}".format(tag, n) for n, tag in sorted(tag_counts, reverse=True))
+ return result
+
+
+def to_json(journal):
+ """Returns a JSON representation of the Journal."""
+ tags = get_tags_count(journal)
+ result = {
+ "tags": dict((tag, count) for count, tag in tags),
+ "entries": [e.to_dict() for e in journal.entries]
+ }
+ return json.dumps(result, indent=2)
+
+
+def to_md(journal):
+ """Returns a markdown representation of the Journal"""
+ out = []
+ year, month = -1, -1
+ for e in journal.entries:
+ if not e.date.year == year:
+ year = e.date.year
+ out.append(str(year))
+ out.append("=" * len(str(year)) + "\n")
+ if not e.date.month == month:
+ month = e.date.month
+ out.append(e.date.strftime("%B"))
+ out.append('-' * len(e.date.strftime("%B")) + "\n")
+ out.append(e.to_md())
+ result = "\n".join(out)
+ return result
+
+
+def to_txt(journal):
+ """Returns the complete text of the Journal."""
+ return journal.pprint()
+
+
+def to_html(journal):
+ html = "\n"
+ html += "\n"
+ html += "\t\n"
+ html += "\t\t\n"
+ html += "\t\n\n"
+ html += "\t\n"
+ html += "\t\t\n"
+ html += "\t\t\tJournal
\n"
+ html += "\t\t\t
\n"
+ for element in journal.entries:
+ # date time title body
+ html += "\t\t\t\t" + str(element.date) + "\t" + element.title + "
\n"
+ html += "\t\t\t\t" + element.body + "
\n\t\t\t
\n\t\t\t
\n"
+ html += "\t\t\n"
+ html += "\t\n"
+ html += ""
+ return html
+
+def export(journal, format, output=None):
+ """Exports the journal to various formats.
+ format should be one of json, txt, text, md, markdown, html.
+ If output is None, returns a unicode representation of the output.
+ If output is a directory, exports entries into individual files.
+ Otherwise, exports to the given output file.
+ """
+ maps = {
+ "json": to_json,
+ "txt": to_txt,
+ "text": to_txt,
+ "md": to_md,
+ "markdown": to_md,
+ "html": to_html
+ }
+ if format not in maps:
+ return "[ERROR: can't export to '{0}'. Valid options are 'md', 'txt', 'html', and 'json']".format(format)
+ if output and os.path.isdir(output): # multiple files
+ return write_files(journal, output, format)
+ else:
+ content = maps[format](journal)
+ if output:
+ try:
+ with codecs.open(output, "w", "utf-8") as f:
+ f.write(content)
+ return "[Journal exported to {0}]".format(output)
+ except IOError as e:
+ return "[ERROR: {0} {1}]".format(e.filename, e.strerror)
+ else:
+ return content
+
+
+def write_files(journal, path, format):
+ """Turns your journal into separate files for each entry.
+ Format should be either json, md or txt."""
+ make_filename = lambda entry: e.date.strftime("%Y-%m-%d_{0}.{1}".format(slugify(u(e.title)), format))
+ for e in journal.entries:
+ full_path = os.path.join(path, make_filename(e))
+ if format == 'json':
+ content = json.dumps(e.to_dict(), indent=2) + "\n"
+ elif format in ('md', 'markdown'):
+ content = e.to_md()
+ elif format in ('txt', 'text'):
+ content = e.__unicode__()
+ elif format == 'html':
+ content = e.to_html()
+ with codecs.open(full_path, "w", "utf-8") as f:
+ f.write(content)
+ return "[Journal exported individual files in {0}]".format(path)
diff --git a/jrnl/importers.py b/jrnl/importers.py
new file mode 100644
index 00000000..e522d0f5
--- /dev/null
+++ b/jrnl/importers.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+import os
+import json
+import sys
+from . import util
+
+def import_json(path, jrnl_config):
+ json_data = util.load_and_fix_json(path, "JSON")
+
+ new_jrnl = open(jrnl_config["journal"], "w")
+ for element in json_data["entries"]:
+ new_jrnl.write(
+ element["date"] +
+ " " +
+ element["time"] +
+ " " +
+ element["title"] +
+ "\n" +
+ element["body"]
+ )
+ new_jrnl.close()
diff --git a/jrnl/install.py b/jrnl/install.py
index 7cc8f880..a1859f20 100644
--- a/jrnl/install.py
+++ b/jrnl/install.py
@@ -114,6 +114,7 @@ def install():
except OSError:
pass
+<<<<<<< HEAD
# Encrypt it?
password = getpass.getpass("Enter password for journal (leave blank for no encryption): ")
if password:
@@ -126,6 +127,10 @@ def install():
print("Journal will be encrypted.")
else:
PlainJournal._create(default_config['journals']['default'])
+=======
+ if not os.path.isdir(path): # if it's a directory and exists (e.g. a DayOne journal, let it be)
+ open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there
+>>>>>>> master
config = default_config
save_config(config)
diff --git a/jrnl/time.py b/jrnl/time.py
index 531293de..378d4c92 100644
--- a/jrnl/time.py
+++ b/jrnl/time.py
@@ -48,7 +48,10 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None):
return None
if flag is 1: # Date found, but no time. Use the default time.
- date = datetime(*date[:3], hour=default_hour or 0, minute=default_minute or 0)
+ date = datetime(*date[:3],
+ hour=23 if inclusive else default_hour or 0,
+ minute=59 if inclusive else default_minute or 0,
+ second=59 if inclusive else 0)
else:
date = datetime(*date[:6])
diff --git a/jrnl/util.py b/jrnl/util.py
index f6a7735f..a0a5e74a 100644
--- a/jrnl/util.py
+++ b/jrnl/util.py
@@ -16,11 +16,15 @@ import tempfile
import subprocess
import codecs
import unicodedata
+<<<<<<< HEAD
import shlex
import logging
log = logging.getLogger(__name__)
+=======
+import logging
+>>>>>>> master
PY3 = sys.version_info[0] == 3
PY2 = sys.version_info[0] == 2
@@ -30,6 +34,7 @@ STDOUT = sys.stdout
TEST = False
__cached_tz = None
+<<<<<<< HEAD
WARNING_COLOR = "\033[33m"
ERROR_COLOR = "\033[31m"
RESET_COLOR = "\033[0m"
@@ -45,6 +50,9 @@ SENTENCE_SPLITTER = re.compile(r"""
| # Otherwise,
\n # a sentence also terminates newlines.
)""", re.UNICODE | re.VERBOSE)
+=======
+log = logging.getLogger(__name__)
+>>>>>>> master
def getpass(prompt="Password: "):
@@ -116,6 +124,8 @@ def prnt(s):
def prompt(msg):
"""Prints a message to the std err stream defined in util."""
+ if not msg:
+ return
if not msg.endswith("\n"):
msg += "\n"
STDERR.write(u(msg))
@@ -123,12 +133,21 @@ def prompt(msg):
def py23_input(msg=""):
prompt(msg)
+<<<<<<< HEAD
return STDIN.readline().strip()
+=======
+ return u(STDIN.readline()).strip()
+>>>>>>> master
def py23_read(msg=""):
+<<<<<<< HEAD
print(msg)
return STDIN.read()
+=======
+ prompt(msg)
+ return u(STDIN.read())
+>>>>>>> master
def yesno(prompt, default=True):
@@ -136,6 +155,7 @@ def yesno(prompt, default=True):
raw = py23_input(prompt)
return {'y': True, 'n': False}.get(raw.lower(), default)
+<<<<<<< HEAD
def load_config(config_path):
"""Tries to load a config file from YAML.
@@ -160,6 +180,40 @@ def scope_config(config, journal_name):
def get_text_from_editor(config, template=""):
filehandle, tmpfile = tempfile.mkstemp(prefix="jrnl", text=True, suffix=".txt")
+=======
+def load_and_fix_json(json_path, target_file):
+ """Tries to load a json object from a file.
+ If that fails, tries to fix common errors (no or extra , at end of the line).
+ """
+ with open(json_path) as f:
+ json_str = f.read()
+ log.debug('%s file %s read correctly', target_file, json_path)
+ data = None
+ try:
+ return json.loads(json_str)
+ except ValueError as e:
+ log.debug('Could not parse %s %s: %s', target_file, json_str, e, exc_info=True)
+ # Attempt to fix extra ,
+ json_str = re.sub(r",[ \n]*}", "}", json_str)
+ # Attempt to fix missing ,
+ json_str = re.sub(r"([^{,]) *\n *(\")", r"\1,\n \2", json_str)
+ try:
+ log.debug('Attempting to reload automatically fixed %s file %s', target_file, json_str)
+ data = json.loads(json_str)
+ with open(json_path, 'w') as f:
+ json.dump(data, f, indent=2)
+ log.debug('Fixed %s saved in file %s', target_file, json_path)
+ prompt("[Some errors in your {0} file have been fixed for you.]".format(target_file))
+ return data
+ except ValueError as e:
+ log.debug('Could not load fixed %s: %s', target_file, e, exc_info=True)
+ prompt("[There seems to be something wrong with your {0} file at {1}: {2}]".format(target_file, json_path, e.message))
+ prompt("[Journal was NOT modified]")
+ sys.exit(1)
+
+def get_text_from_editor(config, template=""):
+ _, tmpfile = tempfile.mkstemp(prefix="jrnl", text=True, suffix=".txt")
+>>>>>>> master
with codecs.open(tmpfile, 'w', "utf-8") as f:
if template:
f.write(template)
diff --git a/setup.py b/setup.py
index f6097064..d6112f11 100644
--- a/setup.py
+++ b/setup.py
@@ -51,13 +51,8 @@ except ImportError:
readline_available = False
-if sys.argv[-1] == 'publish':
- os.system("python setup.py sdist upload")
- sys.exit()
-
base_dir = os.path.dirname(os.path.abspath(__file__))
-
def get_version(filename="jrnl/__init__.py"):
with open(os.path.join(base_dir, filename)) as initfile:
for line in initfile.readlines():
@@ -65,6 +60,71 @@ def get_version(filename="jrnl/__init__.py"):
if m:
return m.group(1)
+
+def get_changelog(filename="CHANGELOG.md"):
+ changelog = {}
+ current_version = None
+ with open(os.path.join(base_dir, filename)) as changelog_file:
+ for line in changelog_file.readlines():
+ if line.startswith("* __"):
+ parts = line.strip("* ").split(" ", 1)
+ if len(parts) == 2:
+ current_version, changes = parts[0].strip("_\n"), parts[1]
+ changelog[current_version] = [changes.strip()]
+ else:
+ current_version = parts[0].strip("_\n")
+ changelog[current_version] = []
+ elif line.strip() and current_version and not line.startswith("#"):
+ changelog[current_version].append(line.strip(" *\n"))
+ return changelog
+
+def dist_pypi():
+ os.system("python setup.py sdist upload")
+ sys.exit()
+
+def dist_github():
+ """Creates a release on the maebert/jrnl repository on github"""
+ import requests
+ import keyring
+ import getpass
+ version = get_version()
+ version_tuple = version.split(".")
+ changes_since_last_version = ["* __{}__: {}".format(key, "\n".join(changes)) for key, changes in get_changelog().items() if key.startswith("{}.{}".format(*version_tuple))]
+ changes_since_last_version = "\n".join(sorted(changes_since_last_version, reverse=True))
+ payload = {
+ "tag_name": version,
+ "target_commitish": "master",
+ "name": version,
+ "body": "Changes in Version {}.{}: \n\n{}".format(version_tuple[0], version_tuple[1], changes_since_last_version)
+ }
+ print("Preparing release {}...".format(version))
+ username = keyring.get_password("github", "__default_user") or raw_input("Github username: ")
+ password = keyring.get_password("github", username) or getpass.getpass()
+ otp = raw_input("One Time Token: ")
+ response = requests.post("https://api.github.com/repos/maebert/jrnl/releases", headers={"X-GitHub-OTP": otp}, json=payload, auth=(username, password))
+ if response.status_code in (403, 404):
+ print("Authentication error.")
+ else:
+ keyring.set_password("github", "__default_user", username)
+ keyring.set_password("github", username, password)
+ if response.status_code > 299:
+ if "message" in response.json():
+ print("Error: {}".format(response.json()['message']))
+ for error_dict in response.json().get('errors', []):
+ print("*", error_dict)
+ else:
+ print("Unkown error")
+ print(response.text)
+ else:
+ print("Release created.")
+ sys.exit()
+
+if sys.argv[-1] == 'publish':
+ dist_pypi()
+
+if sys.argv[-1] == 'github_release':
+ dist_github()
+
conditional_dependencies = {
"pyreadline>=2.0": not readline_available and "win32" in sys.platform,
"readline>=6.2": not readline_available and "win32" not in sys.platform,
@@ -75,6 +135,7 @@ conditional_dependencies = {
setup(
+<<<<<<< HEAD
name="jrnl",
version=get_version(),
description="A command line journal application that stores your journal in a plain text file",
@@ -90,27 +151,46 @@ setup(
"passlib>=1.6.2",
"pyxdg>=0.25",
"asteval>=0.9.8",
+=======
+ name = "jrnl",
+ version = get_version(),
+ description = "A command line journal application that stores your journal in a plain text file",
+ packages = ['jrnl'],
+ install_requires = [
+ "parsedatetime>=1.2",
+ "pytz>=2013b",
+ "six>=1.6.1",
+ "tzlocal>=1.1",
+ "keyring>=3.3",
+ "keyrings.alt>=1.3",
+>>>>>>> master
] + [p for p, cond in conditional_dependencies.items() if cond],
long_description=__doc__,
entry_points={
+<<<<<<< HEAD
'console_scripts': [
'jrnl = jrnl.cli:run',
],
+=======
+ "console_scripts": [
+ "jrnl = jrnl:run",
+ ]
+>>>>>>> master
},
classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Console',
- 'Intended Audience :: End Users/Desktop',
- 'License :: OSI Approved :: MIT License',
- 'Natural Language :: English',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Topic :: Office/Business :: News/Diary',
- 'Topic :: Text Processing'
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Console",
+ "Intended Audience :: End Users/Desktop",
+ "License :: OSI Approved :: MIT License",
+ "Natural Language :: English",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: 3.4",
+ "Topic :: Office/Business :: News/Diary",
+ "Topic :: Text Processing"
],
# metadata for upload to PyPI
author="Manuel Ebert",