thefox-wallet 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +11 -2
- data/.travis.yml +1 -1
- data/Makefile +10 -1
- data/README.md +123 -3
- data/bin/wallet +19 -15
- data/lib/wallet/command.rb +11 -6
- data/lib/wallet/command_add.rb +21 -7
- data/lib/wallet/command_categories.rb +1 -0
- data/lib/wallet/command_clear.rb +1 -0
- data/lib/wallet/command_csv.rb +4 -0
- data/lib/wallet/command_html.rb +3 -0
- data/lib/wallet/command_list.rb +15 -2
- data/lib/wallet/entry.rb +6 -5
- data/lib/wallet/version.rb +2 -2
- data/lib/wallet/wallet.rb +79 -30
- metadata +3 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f1d2d7b98050da769b3220c1b93ae33fa2d66732
         | 
| 4 | 
            +
              data.tar.gz: 901db16b1cc71053ccbe69b11a71b03f27e460a0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0102a8e412d49f20f5b2123c45c4834546b82037cdbecd564f44fb696734f174bae911e802fb6d297c7b379b7fc9ff091b4ac1d706a5eef4f1fb37f3461f50c0
         | 
| 7 | 
            +
              data.tar.gz: bbf235d00f3b8aad963226867ee385c713ffa02ee77c6e49fbbf58e9a5186e1b09e75ef82637e45bf3c39b8e0e3c658aaa8fe6395a4923104707b8b792ecf592
         | 
    
        data/.gitlab-ci.yml
    CHANGED
    
    | @@ -35,8 +35,17 @@ test_23: | |
| 35 35 | 
             
                script:
         | 
| 36 36 | 
             
                    - make test
         | 
| 37 37 |  | 
| 38 | 
            -
             | 
| 39 | 
            -
                image: ruby:2. | 
| 38 | 
            +
            test_24:
         | 
| 39 | 
            +
                image: ruby:2.4
         | 
| 40 | 
            +
                stage: test
         | 
| 41 | 
            +
                environment: test
         | 
| 42 | 
            +
                only:
         | 
| 43 | 
            +
                    - tags
         | 
| 44 | 
            +
                script:
         | 
| 45 | 
            +
                    - make test
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            release:
         | 
| 48 | 
            +
                image: ruby:2.4
         | 
| 40 49 | 
             
                stage: release
         | 
| 41 50 | 
             
                environment: gem
         | 
| 42 51 | 
             
                only:
         | 
    
        data/.travis.yml
    CHANGED
    
    
    
        data/Makefile
    CHANGED
    
    | @@ -5,7 +5,7 @@ include Makefile.common | |
| 5 5 |  | 
| 6 6 | 
             
            .PHONY: dev
         | 
| 7 7 | 
             
            dev:
         | 
| 8 | 
            -
            	$(BUNDLER) exec ./bin/wallet -w wallet_test2 add -t ' | 
| 8 | 
            +
            	$(BUNDLER) exec ./bin/wallet -w wallet_test2 add -t 'Taxes Q1' -e 5 -c Company
         | 
| 9 9 |  | 
| 10 10 | 
             
            .PHONY: test
         | 
| 11 11 | 
             
            test:
         | 
| @@ -18,3 +18,12 @@ cov: | |
| 18 18 | 
             
            .PHONY: cov_local
         | 
| 19 19 | 
             
            cov_local:
         | 
| 20 20 | 
             
            	RUBYOPT=-w TZ=Europe/Vienna SIMPLECOV_PHPUNIT_LOAD_PATH=../simplecov-phpunit COVERAGE=1 $(BUNDLER) exec ./test/suite_all.rb -v
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            README.html: README.md
         | 
| 23 | 
            +
            	mkreadme
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            watch_readme:
         | 
| 26 | 
            +
            	while true; do \
         | 
| 27 | 
            +
            		$(MAKE) README.html; \
         | 
| 28 | 
            +
            		sleep 1; \
         | 
| 29 | 
            +
            	done
         | 
    
        data/README.md
    CHANGED
    
    | @@ -13,7 +13,7 @@ While conventionally programs like Microsoft Excel or [LibreOffice](https://www. | |
| 13 13 | 
             
            - Import CSV files.
         | 
| 14 14 | 
             
            - Export data to CSV.
         | 
| 15 15 | 
             
            - Generate HTML summary.
         | 
| 16 | 
            -
            -  | 
| 16 | 
            +
            - Categories
         | 
| 17 17 |  | 
| 18 18 | 
             
            ## Install
         | 
| 19 19 |  | 
| @@ -24,16 +24,136 @@ The preferred method of installation is via RubyGems.org: | |
| 24 24 |  | 
| 25 25 | 
             
            or via `Gemfile`:
         | 
| 26 26 |  | 
| 27 | 
            -
            	gem 'thefox-wallet', '~>0. | 
| 27 | 
            +
            	gem 'thefox-wallet', '~>0.16'
         | 
| 28 28 |  | 
| 29 29 | 
             
            Use it in your sources:
         | 
| 30 30 |  | 
| 31 31 | 
             
            	require 'thefox-wallet'
         | 
| 32 32 |  | 
| 33 | 
            +
            ## Options
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            - `-w`, `--wallet <path>`  
         | 
| 36 | 
            +
            	Base path to a wallet directory. Each wallet has its own directory storing all informations. This option can be used for all comamnds.
         | 
| 37 | 
            +
            - `--id <id>`  
         | 
| 38 | 
            +
            	ID used for a new entry. If an ID is provided no new entry will be added if an entry with the same ID already exists. Use `--force` to overwrite this.
         | 
| 39 | 
            +
            - `-t`, `--title <title>`  
         | 
| 40 | 
            +
            	Title used for a new entry.
         | 
| 41 | 
            +
            - `-d`, `--date <date>`  
         | 
| 42 | 
            +
            	Date used for a new entry.
         | 
| 43 | 
            +
            - `--start <date>`  
         | 
| 44 | 
            +
            	Start-date used for a range.
         | 
| 45 | 
            +
            - `--end <date>`  
         | 
| 46 | 
            +
            	End-date used for a range.
         | 
| 47 | 
            +
            - `-r`, `--revenue <revenue>`  
         | 
| 48 | 
            +
            	Revenue used for a new entry.
         | 
| 49 | 
            +
            - `-e`, `--expense <expense>`  
         | 
| 50 | 
            +
            	Expense used for a new entry.
         | 
| 51 | 
            +
            - `-c`, `--category <category>`  
         | 
| 52 | 
            +
            	Category used for a new entry.
         | 
| 53 | 
            +
            - `-o`, `--comment <comment>`  
         | 
| 54 | 
            +
            	Comment used for a new entry.
         | 
| 55 | 
            +
            - `--import`, `--export`  
         | 
| 56 | 
            +
            	Import/Export CSV
         | 
| 57 | 
            +
            - `-p`, `--path <path>`  
         | 
| 58 | 
            +
            	Path used for `csv` import/export and `html` directory path.
         | 
| 59 | 
            +
            - `-i`, `--interactive`  
         | 
| 60 | 
            +
            	Use some commands interactively.
         | 
| 61 | 
            +
            - `-f`, `--force`, `--no-force`  
         | 
| 62 | 
            +
            	Force (or no force) `add` command. See `--id` option.
         | 
| 63 | 
            +
            - `-v`, `--verbose`  
         | 
| 64 | 
            +
            	Log on debug level.
         | 
| 65 | 
            +
            - `-V`, `--version`  
         | 
| 66 | 
            +
            	Show version.
         | 
| 67 | 
            +
            - `-h`, `--help`  
         | 
| 68 | 
            +
            	Show help page.
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            ## Commands
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            ### Add Command
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            Add a new entry.
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            	$ wallet add [-w <path>] [--id <id>] [-r <revenue>] [-e <expense>] [-c <category>] [-o <comment>] [-i] [-f|--no-force] -t|--title <title>
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            When `--interactive` (`-i`) option is used, parse `%d` with `printf`. Separate multiple `%`-variables with `,`. This feature can be used on template scripts that run the `wallet add` command with pre-defined texts.
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            For example. To use the following in a template script
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            	$ wallet add --title 'Income tax %d/Q%d' --interactive
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            and set the values on interactive input when the command is running.
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            ```bash
         | 
| 87 | 
            +
            $ wallet add --title 'Income tax %d/Q%d' --interactive
         | 
| 88 | 
            +
            title: [Income tax %d/Q%d] 2017,1
         | 
| 89 | 
            +
            ```
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            This would set the title to `Income tax 2017/Q1`. So the input will not be set as value rather than replaced by the variables in the template text. Acting like `printf`.
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            Expenses are always converted to minus.
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            Calculations will be `eval`ed as Ruby code. For example:
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            	$ wallet add --title Test --expense 14+7
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            The expense will be `-21`. Expenses are always minus.
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            In the following example the expense will be `-3`:
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            	$ wallet add --title Test --expense 10-7
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            The same applies to revenue.
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            See `AddCommand::revenue` and `AddCommand::expense` functions.
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            ### Categories Command
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            List all used categories.
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            	$ wallet categories [-w <path>]
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            Each entry can have one category. It's planned to implement [Multiple Categories](https://github.com/TheFox/wallet/issues/3) for entries.
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            You can define the categories yourself. The `list` command has a filter option `-c` to list all entries of a certain category. The `html` command (for generating a HTML output) will also sum all entries for each category. If the category is not set on `add` command the category will be set to `default`. The `default` category will not be shown in list views.
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            ### Clear Command
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            Clear temp and cache files.
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            	$ wallet clear [-w <path>]
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            If the html directory path (`-p`) provided to the `html` command is outside of the wallet base path (`-w`) this directory will **NOT** be deleted by the `clear` command. If the default html directory (`wallet/html`) is used this directory will be removed. This command does **NOT** delete any entries stored at `wallet/data`.
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            ### CSV Command
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            Import or export to/from CSV file format.
         | 
| 130 | 
            +
             | 
| 131 | 
            +
            	$ wallet csv [-w <path>] [--import|--export] -p <path>
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            ### HTML Command
         | 
| 134 | 
            +
             | 
| 135 | 
            +
            Exports a wallet as HTML. List all years in an index HTML file and all months for each year. Generates a HTML file for each month based on entries.
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            	$ wallet html [-w <path>] [--start <date>] [--end <date>] [-c <category,...>] [-p <path>] [-v]
         | 
| 138 | 
            +
             | 
| 139 | 
            +
            Option `-c` can take multiple categories separated by `,`.
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            ### List Command
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            List entries. Per default this command lists all entries of today's date.
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            	$ wallet list [-w <path>] [-d <YYYY>[-<MM>[-<DD>]]] [-c <category>]
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            You can either provide a year `YYYY`, a month `YYYY-MM` or a day `YYYY-MM-DD`.
         | 
| 148 | 
            +
             | 
| 149 | 
            +
            ## Dates
         | 
| 150 | 
            +
             | 
| 151 | 
            +
            Dates (`<date>` or `YYYY-MM-DD`) used in this documentation are not limited to `YYYY-MM-DD` format. You can also use `MM/DD/YYYY`, `DD.MM.YYYY`, `YYYYMMDD`, etc. Dates are parsed by [`Date::parse`](https://ruby-doc.org/stdlib-1.9.3/libdoc/date/rdoc/DateTime.html#method-c-parse).
         | 
| 152 | 
            +
             | 
| 33 153 | 
             
            ## Project Links
         | 
| 34 154 |  | 
| 35 155 | 
             
            - [Blog Post about Wallet](http://blog.fox21.at/2015/07/09/wallet.html)
         | 
| 36 | 
            -
            - [Gem](https://rubygems.org/gems/thefox-wallet)
         | 
| 156 | 
            +
            - [Wallet Gem](https://rubygems.org/gems/thefox-wallet)
         | 
| 37 157 | 
             
            - [Travis CI Repository](https://travis-ci.org/TheFox/wallet)
         | 
| 38 158 |  | 
| 39 159 | 
             
            ## License
         | 
    
        data/bin/wallet
    CHANGED
    
    | @@ -20,56 +20,59 @@ opts = OptionParser.new do |o| | |
| 20 20 | 
             
            	o.separator('    categories   List categories')
         | 
| 21 21 | 
             
            	o.separator('    clear        Clear temp and cache files')
         | 
| 22 22 | 
             
            	o.separator('    csv          Import/Export CSV file')
         | 
| 23 | 
            -
            	o.separator('    html          | 
| 23 | 
            +
            	o.separator('    html         Exports a wallet as HTML')
         | 
| 24 24 | 
             
            	o.separator('    list         List entries')
         | 
| 25 | 
            -
            	o.separator('    vi           Import per VIM editor')
         | 
| 26 25 | 
             
            	o.separator('')
         | 
| 27 26 |  | 
| 28 27 | 
             
            	o.on('-w', '--wallet <path>', 'Path to wallet directory.') do |path|
         | 
| 29 28 | 
             
            		@options[:wallet_path] = Pathname.new(path).expand_path
         | 
| 30 29 | 
             
            	end
         | 
| 31 30 |  | 
| 32 | 
            -
            	o.on('--id <id>', 'ID used for  | 
| 31 | 
            +
            	o.on('--id <id>', 'ID used for a new entry.') do |id|
         | 
| 33 32 | 
             
            		@options[:entry_id] = id
         | 
| 34 33 | 
             
            	end
         | 
| 35 34 |  | 
| 36 | 
            -
            	o.on('-t', '--title <title>', 'Title used for  | 
| 35 | 
            +
            	o.on('-t', '--title <title>', 'Title used for a new entry.') do |title|
         | 
| 37 36 | 
             
            		@options[:entry_title] = title
         | 
| 38 37 | 
             
            	end
         | 
| 39 38 |  | 
| 40 | 
            -
            	o.on('-d', '--date < | 
| 39 | 
            +
            	o.on('-d', '--date <YYYY-MM-DD>', 'Date used for a new entry.') do |date|
         | 
| 41 40 | 
             
            		@options[:entry_date] = date
         | 
| 42 41 | 
             
            	end
         | 
| 43 42 |  | 
| 44 | 
            -
            	o.on('--start < | 
| 43 | 
            +
            	o.on('--start <YYYY-MM-DD>', 'Start-date used for a range.') do |date|
         | 
| 45 44 | 
             
            		@options[:entry_date_start] = Date.parse(date)
         | 
| 46 45 | 
             
            	end
         | 
| 47 46 |  | 
| 48 | 
            -
            	o.on('--end < | 
| 47 | 
            +
            	o.on('--end <YYYY-MM-DD>', 'End-date used for a range.') do |date|
         | 
| 49 48 | 
             
            		@options[:entry_date_end] = Date.parse(date)
         | 
| 50 49 | 
             
            	end
         | 
| 51 50 |  | 
| 52 | 
            -
            	o.on('-r', '--revenue <revenue>', 'Revenue used for  | 
| 53 | 
            -
            		@ | 
| 51 | 
            +
            	o.on('-r', '--revenue <revenue>', 'Revenue used for a new entry.') do |revenue|
         | 
| 52 | 
            +
            		# @TODO replace with Wumber.
         | 
| 53 | 
            +
            		# @options[:entry_revenue] = revenue.to_s.sub(/,/, '.').to_f.abs
         | 
| 54 | 
            +
            		@options[:entry_revenue] = eval(revenue.to_s.to_s.gsub(/,/, '.')).to_f.round(TheFox::Wallet::NUMBER_ROUND).abs
         | 
| 54 55 | 
             
            	end
         | 
| 55 56 |  | 
| 56 | 
            -
            	o.on('-e', '--expense <expense>', 'Expense used for  | 
| 57 | 
            -
            		@ | 
| 57 | 
            +
            	o.on('-e', '--expense <expense>', 'Expense used for a new entry.') do |expense|
         | 
| 58 | 
            +
            		# @TODO replace with Wumber.
         | 
| 59 | 
            +
            		# @options[:entry_expense] = -expense.to_s.sub(/,/, '.').to_f.abs
         | 
| 60 | 
            +
            		@options[:entry_expense] = -eval(expense.to_s.gsub(/,/, '.')).to_f.round(TheFox::Wallet::NUMBER_ROUND).abs
         | 
| 58 61 | 
             
            	end
         | 
| 59 62 |  | 
| 60 | 
            -
            	o.on('-c', '--category <category>', 'Category used for  | 
| 63 | 
            +
            	o.on('-c', '--category <category>', 'Category used for a new entry.') do |category|
         | 
| 61 64 | 
             
            		@options[:entry_category] = category
         | 
| 62 65 | 
             
            	end
         | 
| 63 66 |  | 
| 64 | 
            -
            	o.on('-o', '--comment <comment>', 'Comment used for  | 
| 67 | 
            +
            	o.on('-o', '--comment <comment>', 'Comment used for a new entry.') do |comment|
         | 
| 65 68 | 
             
            		@options[:entry_comment] = comment
         | 
| 66 69 | 
             
            	end
         | 
| 67 70 |  | 
| 68 | 
            -
            	o.on('--import', 'Import') do
         | 
| 71 | 
            +
            	o.on('--import', 'Import CSV') do
         | 
| 69 72 | 
             
            		@options[:is_import] = true
         | 
| 70 73 | 
             
            	end
         | 
| 71 74 |  | 
| 72 | 
            -
            	o.on('--export', 'Export') do
         | 
| 75 | 
            +
            	o.on('--export', 'Export CSV') do
         | 
| 73 76 | 
             
            		@options[:is_export] = true
         | 
| 74 77 | 
             
            	end
         | 
| 75 78 |  | 
| @@ -118,5 +121,6 @@ if command_name | |
| 118 121 | 
             
            		exit 1
         | 
| 119 122 | 
             
            	end
         | 
| 120 123 | 
             
            else
         | 
| 124 | 
            +
            	# Show help page when no argument was given.
         | 
| 121 125 | 
             
            	opts.parse(['-h'])
         | 
| 122 126 | 
             
            end
         | 
    
        data/lib/wallet/command.rb
    CHANGED
    
    | @@ -1,4 +1,7 @@ | |
| 1 1 |  | 
| 2 | 
            +
            # Basic Command
         | 
| 3 | 
            +
            # All other commands subclass from this class.
         | 
| 4 | 
            +
             | 
| 2 5 | 
             
            require 'date'
         | 
| 3 6 | 
             
            require 'pathname'
         | 
| 4 7 |  | 
| @@ -9,9 +12,9 @@ module TheFox::Wallet | |
| 9 12 | 
             
            		NAME = 'default'
         | 
| 10 13 |  | 
| 11 14 | 
             
            		def initialize(options = Hash.new)
         | 
| 12 | 
            -
            			 | 
| 13 | 
            -
            			@options | 
| 14 | 
            -
            			
         | 
| 15 | 
            +
            			# Initialize all options.
         | 
| 16 | 
            +
            			@options = options
         | 
| 17 | 
            +
            			@options[:logger] ||= Logger.new(IO::NULL)
         | 
| 15 18 | 
             
            			@options[:wallet_path] ||= Pathname.new('wallet')
         | 
| 16 19 | 
             
            			@options[:entry_id] ||= nil
         | 
| 17 20 | 
             
            			@options[:entry_title] ||= nil
         | 
| @@ -32,7 +35,7 @@ module TheFox::Wallet | |
| 32 35 | 
             
            		def run
         | 
| 33 36 | 
             
            		end
         | 
| 34 37 |  | 
| 35 | 
            -
            		def self.create_by_name(name, options =  | 
| 38 | 
            +
            		def self.create_by_name(name, options = Hash.new)
         | 
| 36 39 | 
             
            			classes = [
         | 
| 37 40 | 
             
            				AddCommand,
         | 
| 38 41 | 
             
            				CategoriesCommand,
         | 
| @@ -42,10 +45,12 @@ module TheFox::Wallet | |
| 42 45 | 
             
            				ListCommand,
         | 
| 43 46 | 
             
            			]
         | 
| 44 47 |  | 
| 48 | 
            +
            			# Search class by name.
         | 
| 45 49 | 
             
            			classes.each do |cclass|
         | 
| 46 | 
            -
            				#puts "class: '#{cclass::NAME}' '#{cclass.is_matching_class(name)}'"
         | 
| 47 50 | 
             
            				if cclass.is_matching_class(name)
         | 
| 48 | 
            -
            					 | 
| 51 | 
            +
            					# Create a new Object from the found Class.
         | 
| 52 | 
            +
            					cmd = cclass.new(options)
         | 
| 53 | 
            +
            					return cmd
         | 
| 49 54 | 
             
            				end
         | 
| 50 55 | 
             
            			end
         | 
| 51 56 |  | 
    
        data/lib/wallet/command_add.rb
    CHANGED
    
    | @@ -3,6 +3,7 @@ require 'date' | |
| 3 3 |  | 
| 4 4 | 
             
            module TheFox::Wallet
         | 
| 5 5 |  | 
| 6 | 
            +
            	# Add a new Entry.
         | 
| 6 7 | 
             
            	class AddCommand < Command
         | 
| 7 8 |  | 
| 8 9 | 
             
            		NAME = 'add'
         | 
| @@ -13,15 +14,18 @@ module TheFox::Wallet | |
| 13 14 | 
             
            			end
         | 
| 14 15 |  | 
| 15 16 | 
             
            			if @options[:is_interactively]
         | 
| 17 | 
            +
            				# Interactive User Input
         | 
| 18 | 
            +
            				
         | 
| 16 19 | 
             
            				print "title: [#{@options[:entry_title]}] "
         | 
| 17 20 | 
             
            				title_t = STDIN.gets.strip
         | 
| 18 21 | 
             
            				if title_t.length > 0
         | 
| 22 | 
            +
            					# Search for '%d' in title.
         | 
| 19 23 | 
             
            					if @options[:entry_title] =~ /%d/
         | 
| 24 | 
            +
            						# Like sprintf.
         | 
| 20 25 | 
             
            						@options[:entry_title] = @options[:entry_title] % title_t.split(',').map{ |s| s.strip }
         | 
| 21 26 | 
             
            					else
         | 
| 22 27 | 
             
            						@options[:entry_title] = title_t
         | 
| 23 28 | 
             
            					end
         | 
| 24 | 
            -
            					puts "title: #{@options[:entry_title]}"
         | 
| 25 29 | 
             
            				end
         | 
| 26 30 |  | 
| 27 31 | 
             
            				print "date: [#{@options[:entry_date]}] "
         | 
| @@ -61,6 +65,8 @@ module TheFox::Wallet | |
| 61 65 | 
             
            				raise "Option --title is required for command '#{NAME}'"
         | 
| 62 66 | 
             
            			end
         | 
| 63 67 |  | 
| 68 | 
            +
            			# --force option
         | 
| 69 | 
            +
            			# --id    option
         | 
| 64 70 | 
             
            			is_unique = !@options[:force] && @options[:entry_id]
         | 
| 65 71 |  | 
| 66 72 | 
             
            			puts "id:       '#{@options[:entry_id]}'"
         | 
| @@ -74,8 +80,13 @@ module TheFox::Wallet | |
| 74 80 | 
             
            			puts "force:    #{@options[:force] ? 'YES' : 'NO'}"
         | 
| 75 81 | 
             
            			puts "unique:   #{is_unique ? 'YES' : 'NO'} (#{is_unique})"
         | 
| 76 82 |  | 
| 83 | 
            +
            			# Create new Entry.
         | 
| 77 84 | 
             
            			entry = Entry.new(@options[:entry_id], @options[:entry_title], @options[:entry_date], @options[:entry_revenue], @options[:entry_expense], @options[:entry_category], @options[:entry_comment])
         | 
| 85 | 
            +
            			
         | 
| 86 | 
            +
            			# Initialize Wallet.
         | 
| 78 87 | 
             
            			wallet = Wallet.new(@options[:wallet_path])
         | 
| 88 | 
            +
            			
         | 
| 89 | 
            +
            			# Add Entry to Wallet.
         | 
| 79 90 | 
             
            			added = wallet.add(entry, is_unique)
         | 
| 80 91 |  | 
| 81 92 | 
             
            			puts "added:    #{added ? 'YES' : 'NO'}"
         | 
| @@ -83,20 +94,23 @@ module TheFox::Wallet | |
| 83 94 | 
             
            			added
         | 
| 84 95 | 
             
            		end
         | 
| 85 96 |  | 
| 97 | 
            +
            		# @TODO replace with Wumber.
         | 
| 86 98 | 
             
            		def revenue(revenue_s)
         | 
| 87 | 
            -
            			rv = nil
         | 
| 88 99 | 
             
            			if !revenue_s.nil? && revenue_s.length > 0
         | 
| 89 | 
            -
            				 | 
| 100 | 
            +
            				# Replace , with . in numbers. '1,23' means '1.23'.
         | 
| 101 | 
            +
            				# eval the revenue so calculations are solved.
         | 
| 102 | 
            +
            				eval(revenue_s.to_s.gsub(/,/, '.')).to_f.round(NUMBER_ROUND).abs
         | 
| 90 103 | 
             
            			end
         | 
| 91 | 
            -
            			rv
         | 
| 92 104 | 
             
            		end
         | 
| 93 105 |  | 
| 106 | 
            +
            		# @TODO replace with Wumber.
         | 
| 94 107 | 
             
            		def expense(expense_s)
         | 
| 95 | 
            -
            			rv = nil
         | 
| 96 108 | 
             
            			if !expense_s.nil? && expense_s.length > 0
         | 
| 97 | 
            -
            				 | 
| 109 | 
            +
            				# Replace , with . in numbers. '1,23' means '1.23'.
         | 
| 110 | 
            +
            				# eval the revenue so calculations are solved.
         | 
| 111 | 
            +
            				# Expenses are always minus.
         | 
| 112 | 
            +
            				-eval(expense_s.to_s.gsub(/,/, '.')).to_f.round(NUMBER_ROUND).abs
         | 
| 98 113 | 
             
            			end
         | 
| 99 | 
            -
            			rv
         | 
| 100 114 | 
             
            		end
         | 
| 101 115 |  | 
| 102 116 | 
             
            	end
         | 
    
        data/lib/wallet/command_clear.rb
    CHANGED
    
    
    
        data/lib/wallet/command_csv.rb
    CHANGED
    
    | @@ -1,6 +1,7 @@ | |
| 1 1 |  | 
| 2 2 | 
             
            module TheFox::Wallet
         | 
| 3 3 |  | 
| 4 | 
            +
            	# Import or export to/from CSV file format.
         | 
| 4 5 | 
             
            	class CsvCommand < Command
         | 
| 5 6 |  | 
| 6 7 | 
             
            		NAME = 'csv'
         | 
| @@ -13,11 +14,14 @@ module TheFox::Wallet | |
| 13 14 | 
             
            			wallet = Wallet.new(@options[:wallet_path])
         | 
| 14 15 | 
             
            			wallet.logger = @options[:logger]
         | 
| 15 16 |  | 
| 17 | 
            +
            			# Import by default.
         | 
| 16 18 | 
             
            			if @options[:is_import] || !@options[:is_export]
         | 
| 19 | 
            +
            				# Import
         | 
| 17 20 | 
             
            				@options[:logger].info("import csv #{@options[:path]} ...") if @options[:logger]
         | 
| 18 21 | 
             
            				wallet.import_csv_file(@options[:path])
         | 
| 19 22 | 
             
            				@options[:logger].info("import csv #{@options[:path]} done") if @options[:logger]
         | 
| 20 23 | 
             
            			elsif @options[:is_export]
         | 
| 24 | 
            +
            				# Export
         | 
| 21 25 | 
             
            				@options[:logger].info("export csv #{@options[:path]} ...") if @options[:logger]
         | 
| 22 26 | 
             
            				wallet.export_csv_file(@options[:path])
         | 
| 23 27 | 
             
            				@options[:logger].info("export csv #{@options[:path]} done") if @options[:logger]
         | 
    
        data/lib/wallet/command_html.rb
    CHANGED
    
    
    
        data/lib/wallet/command_list.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 1 1 |  | 
| 2 2 | 
             
            module TheFox::Wallet
         | 
| 3 3 |  | 
| 4 | 
            +
            	# List entries. Per default this command lists all entries of today.
         | 
| 5 | 
            +
            	# @TODO Use terminal-table for output? https://github.com/tj/terminal-table
         | 
| 4 6 | 
             
            	class ListCommand < Command
         | 
| 5 7 |  | 
| 6 8 | 
             
            		NAME = 'list'
         | 
| @@ -10,8 +12,8 @@ module TheFox::Wallet | |
| 10 12 |  | 
| 11 13 | 
             
            			wallet = Wallet.new(@options[:wallet_path])
         | 
| 12 14 | 
             
            			entries = wallet.entries(@options[:entry_date], @options[:entry_category].to_s)
         | 
| 13 | 
            -
            			#entries = wallet.entries(@options[:entry_date], @options[:entry_end_date], @options[:entry_category].to_s)
         | 
| 14 15 |  | 
| 16 | 
            +
            			# Get max length of all columns.
         | 
| 15 17 | 
             
            			entries_l = entries
         | 
| 16 18 | 
             
            				.map{ |day_name, day_items| day_items.count }
         | 
| 17 19 | 
             
            				.inject{ |sum, n| sum + n }
         | 
| @@ -55,6 +57,7 @@ module TheFox::Wallet | |
| 55 57 | 
             
            				.select{ |i| i != '' }
         | 
| 56 58 | 
             
            				.count > 0
         | 
| 57 59 |  | 
| 60 | 
            +
            			# Limit some columns to a maximum length.
         | 
| 58 61 | 
             
            			if title_l < 6
         | 
| 59 62 | 
             
            				title_l = 6
         | 
| 60 63 | 
             
            			end
         | 
| @@ -78,6 +81,7 @@ module TheFox::Wallet | |
| 78 81 | 
             
            				comment_l = 25
         | 
| 79 82 | 
             
            			end
         | 
| 80 83 |  | 
| 84 | 
            +
            			# Format String for each column.
         | 
| 81 85 | 
             
            			entries_f = "%#{entries_l}s"
         | 
| 82 86 | 
             
            			title_f = "%-#{title_l}s"
         | 
| 83 87 | 
             
            			revenue_f = "%#{revenue_l}s"
         | 
| @@ -86,6 +90,7 @@ module TheFox::Wallet | |
| 86 90 | 
             
            			category_f = "%-#{category_l}s"
         | 
| 87 91 | 
             
            			comment_f = "%-#{comment_l}s"
         | 
| 88 92 |  | 
| 93 | 
            +
            			# Create a table header.
         | 
| 89 94 | 
             
            			header = ''
         | 
| 90 95 | 
             
            			header << '#' * entries_l << '  '
         | 
| 91 96 | 
             
            			header << 'Date ' << ' ' * 7
         | 
| @@ -102,15 +107,24 @@ module TheFox::Wallet | |
| 102 107 |  | 
| 103 108 | 
             
            			header_l = header.length
         | 
| 104 109 | 
             
            			header.sub!(/ +$/, '')
         | 
| 110 | 
            +
            			
         | 
| 111 | 
            +
            			# Print table header.
         | 
| 105 112 | 
             
            			puts header
         | 
| 106 113 | 
             
            			puts '-' * header_l
         | 
| 107 114 |  | 
| 115 | 
            +
            			# Sums
         | 
| 108 116 | 
             
            			revenue_total = 0.0
         | 
| 109 117 | 
             
            			expense_total = 0.0
         | 
| 110 118 | 
             
            			balance_total = 0.0
         | 
| 119 | 
            +
            			
         | 
| 120 | 
            +
            			# Do not repeat the same date over and over again.
         | 
| 111 121 | 
             
            			previous_date = ''
         | 
| 122 | 
            +
            			
         | 
| 112 123 | 
             
            			entry_no = 0
         | 
| 124 | 
            +
            			
         | 
| 125 | 
            +
            			# Iterate all days.
         | 
| 113 126 | 
             
            			entries.sort.each do |day_name, day_items|
         | 
| 127 | 
            +
            				# Iterate all entries of a day.
         | 
| 114 128 | 
             
            				day_items.each do |entry|
         | 
| 115 129 | 
             
            					entry_no += 1
         | 
| 116 130 |  | 
| @@ -124,7 +138,6 @@ module TheFox::Wallet | |
| 124 138 | 
             
            					balance_total += entry['balance']
         | 
| 125 139 |  | 
| 126 140 | 
             
            					category = entry['category'] == 'default' ? '' : entry['category']
         | 
| 127 | 
            -
            					# has_category = category != ''
         | 
| 128 141 |  | 
| 129 142 | 
             
            					comment = entry['comment']
         | 
| 130 143 | 
             
            					if comment.length >= 25
         | 
    
        data/lib/wallet/entry.rb
    CHANGED
    
    | @@ -37,6 +37,7 @@ module TheFox | |
| 37 37 | 
             
            				revenue_t = revenue.to_f
         | 
| 38 38 | 
             
            				expense_t = expense.to_f
         | 
| 39 39 | 
             
            				if revenue_t < 0 && expense_t == 0
         | 
| 40 | 
            +
            					# Revenue is minus and no expense was provided.
         | 
| 40 41 | 
             
            					self.revenue = 0.0
         | 
| 41 42 | 
             
            					self.expense = revenue_t
         | 
| 42 43 | 
             
            				else
         | 
| @@ -57,19 +58,17 @@ module TheFox | |
| 57 58 | 
             
            			end
         | 
| 58 59 |  | 
| 59 60 | 
             
            			def date=(date)
         | 
| 60 | 
            -
            				# puts "class: #{date.class}"
         | 
| 61 61 | 
             
            				case date
         | 
| 62 62 | 
             
            				when String
         | 
| 63 | 
            -
            					#  | 
| 63 | 
            +
            					# String
         | 
| 64 64 | 
             
            					@date = Date.parse(date)
         | 
| 65 65 | 
             
            				when Fixnum
         | 
| 66 | 
            -
            					#  | 
| 66 | 
            +
            					# Fixnum
         | 
| 67 67 | 
             
            					@date = Time.at(date).to_date
         | 
| 68 68 | 
             
            				when Date
         | 
| 69 | 
            -
            					#  | 
| 69 | 
            +
            					# Date
         | 
| 70 70 | 
             
            					@date = date
         | 
| 71 71 | 
             
            				else
         | 
| 72 | 
            -
            					# puts 'RAISE'
         | 
| 73 72 | 
             
            					raise ArgumentError, "Wrong class: #{date.class}"
         | 
| 74 73 | 
             
            				end
         | 
| 75 74 | 
             
            			end
         | 
| @@ -104,6 +103,7 @@ module TheFox | |
| 104 103 | 
             
            				@comment = comment.nil? ? '' : comment.to_s
         | 
| 105 104 | 
             
            			end
         | 
| 106 105 |  | 
| 106 | 
            +
            			# Convert Entry to a Hash.
         | 
| 107 107 | 
             
            			def to_h
         | 
| 108 108 | 
             
            				{
         | 
| 109 109 | 
             
            					'id' => @id,
         | 
| @@ -117,6 +117,7 @@ module TheFox | |
| 117 117 | 
             
            				}
         | 
| 118 118 | 
             
            			end
         | 
| 119 119 |  | 
| 120 | 
            +
            			# Restore a Entry from a Hash.
         | 
| 120 121 | 
             
            			def self.from_h(h)
         | 
| 121 122 | 
             
            				id = h['id']
         | 
| 122 123 | 
             
            				title = h['title']
         | 
    
        data/lib/wallet/version.rb
    CHANGED
    
    
    
        data/lib/wallet/wallet.rb
    CHANGED
    
    | @@ -1,5 +1,5 @@ | |
| 1 1 |  | 
| 2 | 
            -
            #  | 
| 2 | 
            +
            # Wallet Main Class
         | 
| 3 3 |  | 
| 4 4 | 
             
            require 'logger'
         | 
| 5 5 | 
             
            require 'yaml'
         | 
| @@ -7,8 +7,9 @@ require 'yaml/store' | |
| 7 7 | 
             
            require 'csv'
         | 
| 8 8 | 
             
            require 'pathname'
         | 
| 9 9 | 
             
            require 'fileutils'
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            #  | 
| 10 | 
            +
             | 
| 11 | 
            +
            # OpenStruct use to generate HTML.
         | 
| 12 | 
            +
            require 'ostruct'
         | 
| 12 13 |  | 
| 13 14 | 
             
            module TheFox
         | 
| 14 15 | 
             
            	module Wallet
         | 
| @@ -20,12 +21,14 @@ module TheFox | |
| 20 21 |  | 
| 21 22 | 
             
            			def initialize(dir_path = nil)
         | 
| 22 23 | 
             
            				@exit = false
         | 
| 23 | 
            -
            				@logger =  | 
| 24 | 
            +
            				@logger = Logger.new(IO::NULL)
         | 
| 24 25 | 
             
            				@dir_path = dir_path || Pathname.new('wallet').expand_path
         | 
| 25 26 | 
             
            				@dir_path_basename = @dir_path.basename
         | 
| 26 27 | 
             
            				@dir_path_basename_s = @dir_path_basename.to_s
         | 
| 27 28 | 
             
            				@data_path = Pathname.new('data').expand_path(@dir_path)
         | 
| 28 29 | 
             
            				@tmp_path = Pathname.new('tmp').expand_path(@dir_path)
         | 
| 30 | 
            +
            				
         | 
| 31 | 
            +
            				# Internal path. Not the same as provided by --path option.
         | 
| 29 32 | 
             
            				@html_path = Pathname.new('html').expand_path(@dir_path)
         | 
| 30 33 |  | 
| 31 34 | 
             
            				@has_transaction = false
         | 
| @@ -37,11 +40,13 @@ module TheFox | |
| 37 40 | 
             
            				@entries_index_is_loaded = false
         | 
| 38 41 |  | 
| 39 42 | 
             
            				Signal.trap('SIGINT') do
         | 
| 40 | 
            -
            					#@logger.warn('received SIGINT. break ...') | 
| 43 | 
            +
            					#@logger.warn('received SIGINT. break ...')
         | 
| 41 44 | 
             
            					@exit = true
         | 
| 42 45 | 
             
            				end
         | 
| 43 46 | 
             
            			end
         | 
| 44 47 |  | 
| 48 | 
            +
            			# Add an Entry to the wallet.
         | 
| 49 | 
            +
            			# Used by Add Command.
         | 
| 45 50 | 
             
            			def add(entry, is_unique = false)
         | 
| 46 51 | 
             
            				if !entry.is_a?(Entry)
         | 
| 47 52 | 
             
            					raise ArgumentError, 'variable must be a Entry instance'
         | 
| @@ -166,6 +171,7 @@ module TheFox | |
| 166 171 | 
             
            				@transaction_files = Hash.new
         | 
| 167 172 | 
             
            			end
         | 
| 168 173 |  | 
| 174 | 
            +
            			# Sums a year, a month, a day or a certain category.
         | 
| 169 175 | 
             
            			def sum(year = nil, month = nil, day = nil, category = nil)
         | 
| 170 176 | 
             
            				year_s = year.to_i.to_s
         | 
| 171 177 | 
             
            				month_f = '%02d' % month.to_i
         | 
| @@ -225,8 +231,25 @@ module TheFox | |
| 225 231 | 
             
            				sum(nil, nil, nil, category)
         | 
| 226 232 | 
             
            			end
         | 
| 227 233 |  | 
| 234 | 
            +
            			# Get all entries.
         | 
| 235 | 
            +
            			# Used by List Command.
         | 
| 228 236 | 
             
            			def entries(begin_date, category = nil)
         | 
| 229 | 
            -
            				begin_year, begin_month, begin_day = begin_date.split('-') | 
| 237 | 
            +
            				begin_year, begin_month, begin_day = begin_date.split('-') #.map{ |n| n.to_i }
         | 
| 238 | 
            +
            				
         | 
| 239 | 
            +
            				if begin_year.length > 4
         | 
| 240 | 
            +
            					# When begin_date got not splitted by '-'.
         | 
| 241 | 
            +
            					# YYYYM[MD[D]]
         | 
| 242 | 
            +
            					
         | 
| 243 | 
            +
            					begin_month = begin_year[4..-1]
         | 
| 244 | 
            +
            					begin_year = begin_year[0..3]
         | 
| 245 | 
            +
            					
         | 
| 246 | 
            +
            					if begin_month.length > 2
         | 
| 247 | 
            +
            						# YYYYMMD[D]
         | 
| 248 | 
            +
            						
         | 
| 249 | 
            +
            						begin_day = begin_month[2..-1]
         | 
| 250 | 
            +
            						begin_month = begin_month[0..1]
         | 
| 251 | 
            +
            					end
         | 
| 252 | 
            +
            				end
         | 
| 230 253 |  | 
| 231 254 | 
             
            				begin_year_s = begin_year.to_i.to_s
         | 
| 232 255 | 
             
            				begin_month_f = '%02d' % begin_month.to_i
         | 
| @@ -243,7 +266,7 @@ module TheFox | |
| 243 266 |  | 
| 244 267 | 
             
            				category = category.to_s.downcase
         | 
| 245 268 |  | 
| 246 | 
            -
            				 | 
| 269 | 
            +
            				entries_h = Hash.new
         | 
| 247 270 | 
             
            				Dir[glob].each do |file_path|
         | 
| 248 271 |  | 
| 249 272 | 
             
            					data = YAML.load_file(file_path)
         | 
| @@ -251,21 +274,21 @@ module TheFox | |
| 251 274 | 
             
            						if begin_day
         | 
| 252 275 | 
             
            							day_key = "#{begin_year_s}-#{begin_month_f}-#{begin_day_f}"
         | 
| 253 276 | 
             
            							if data['days'].has_key?(day_key)
         | 
| 254 | 
            -
            								 | 
| 277 | 
            +
            								entries_h[day_key] = data['days'][day_key]
         | 
| 255 278 | 
             
            							end
         | 
| 256 279 | 
             
            						else
         | 
| 257 | 
            -
            							 | 
| 280 | 
            +
            							entries_h.merge!(data['days'])
         | 
| 258 281 | 
             
            						end
         | 
| 259 282 | 
             
            					else
         | 
| 260 283 | 
             
            						if begin_day
         | 
| 261 284 | 
             
            							day_key = "#{begin_year_s}-#{begin_month_f}-#{begin_day_f}"
         | 
| 262 285 | 
             
            							if data['days'].has_key?(day_key)
         | 
| 263 | 
            -
            								 | 
| 286 | 
            +
            								entries_h[day_key] = data['days'][day_key].keep_if{ |day_item|
         | 
| 264 287 | 
             
            									day_item['category'].downcase == category
         | 
| 265 288 | 
             
            								}
         | 
| 266 289 | 
             
            							end
         | 
| 267 290 | 
             
            						else
         | 
| 268 | 
            -
            							 | 
| 291 | 
            +
            							entries_h.merge!(data['days'].map{ |day_name, day_items|
         | 
| 269 292 | 
             
            								day_items.keep_if{ |day_item|
         | 
| 270 293 | 
             
            									day_item['category'].downcase == category
         | 
| 271 294 | 
             
            								}
         | 
| @@ -277,9 +300,11 @@ module TheFox | |
| 277 300 | 
             
            					end
         | 
| 278 301 |  | 
| 279 302 | 
             
            				end
         | 
| 280 | 
            -
            				 | 
| 303 | 
            +
            				entries_h
         | 
| 281 304 | 
             
            			end
         | 
| 282 305 |  | 
| 306 | 
            +
            			# Get all used categories.
         | 
| 307 | 
            +
            			# Used by Categories Command.
         | 
| 283 308 | 
             
            			def categories
         | 
| 284 309 | 
             
            				categories_h = Hash.new
         | 
| 285 310 | 
             
            				Dir[Pathname.new('month_*.yml').expand_path(@data_path)].each do |file_path|
         | 
| @@ -304,14 +329,14 @@ module TheFox | |
| 304 329 | 
             
            				categories_a
         | 
| 305 330 | 
             
            			end
         | 
| 306 331 |  | 
| 307 | 
            -
            			 | 
| 332 | 
            +
            			# Used by HTML Command.
         | 
| 308 333 | 
             
            			# Generate HTML files from date_start to date_end.
         | 
| 309 334 | 
             
            			def generate_html(html_path = nil, date_start = nil, date_end = nil, category = nil)
         | 
| 310 335 | 
             
            				# @FIXME use @exit on all loops in this function
         | 
| 311 336 |  | 
| 312 337 | 
             
            				html_path ||= @html_path
         | 
| 313 338 |  | 
| 314 | 
            -
            				@logger.info("generate html to #{html_path} ...") | 
| 339 | 
            +
            				@logger.info("generate html to #{html_path} ...")
         | 
| 315 340 |  | 
| 316 341 | 
             
            				create_dirs
         | 
| 317 342 |  | 
| @@ -352,6 +377,7 @@ module TheFox | |
| 352 377 | 
             
            				gitignore_file.write('*')
         | 
| 353 378 | 
             
            				gitignore_file.close
         | 
| 354 379 |  | 
| 380 | 
            +
            				# Write CSS file.
         | 
| 355 381 | 
             
            				css_file_path = Pathname.new('style.css').expand_path(html_path)
         | 
| 356 382 | 
             
            				css_file = File.open(css_file_path, 'w')
         | 
| 357 383 | 
             
            				css_file.write('
         | 
| @@ -396,7 +422,7 @@ module TheFox | |
| 396 422 | 
             
            								<link rel="stylesheet" href="style.css" type="text/css" />
         | 
| 397 423 | 
             
            							</head>
         | 
| 398 424 | 
             
            							<body>
         | 
| 399 | 
            -
            								<h1><a href=" | 
| 425 | 
            +
            								<h1><a href=".">' << @dir_path_basename_s << '</a></h1>
         | 
| 400 426 | 
             
            								<p>Generated @ ' << DateTime.now.strftime('%Y-%m-%d %H:%M:%S') << ' by <a href="' << HOMEPAGE << '">' << NAME << '</a> v' << VERSION << '</p>
         | 
| 401 427 |  | 
| 402 428 | 
             
            								<h2>Year: ' << year_s << '</h2>
         | 
| @@ -423,7 +449,7 @@ module TheFox | |
| 423 449 | 
             
            					categories_available.map{ |item| categories_year_balance[item] = 0.0 }
         | 
| 424 450 | 
             
            					year_total = Hash.new
         | 
| 425 451 |  | 
| 426 | 
            -
            					@logger.info("generate year #{year}") | 
| 452 | 
            +
            					@logger.info("generate year #{year}")
         | 
| 427 453 | 
             
            					@data_path.each_child do |file_path|
         | 
| 428 454 | 
             
            						file_name_p = file_path.basename
         | 
| 429 455 | 
             
            						file_name_s = file_name_p.to_s
         | 
| @@ -476,7 +502,7 @@ module TheFox | |
| 476 502 | 
             
            						end
         | 
| 477 503 |  | 
| 478 504 | 
             
            						if write_html
         | 
| 479 | 
            -
            							@logger.debug("file: #{month_file_name_s} (from #{file_name_s})") | 
| 505 | 
            +
            							@logger.debug("file: #{month_file_name_s} (from #{file_name_s})")
         | 
| 480 506 |  | 
| 481 507 | 
             
            							month_file = File.open(month_file_path, 'w')
         | 
| 482 508 | 
             
            							month_file.write('
         | 
| @@ -487,7 +513,7 @@ module TheFox | |
| 487 513 | 
             
            										<link rel="stylesheet" href="style.css" type="text/css" />
         | 
| 488 514 | 
             
            									</head>
         | 
| 489 515 | 
             
            									<body>
         | 
| 490 | 
            -
            										<h1><a href=" | 
| 516 | 
            +
            										<h1><a href=".">' << @dir_path_basename_s << '</a></h1>
         | 
| 491 517 | 
             
            										<p>Generated @ ' << DateTime.now.strftime('%Y-%m-%d %H:%M:%S') << ' by  <a href="' << HOMEPAGE << '">' << NAME << '</a> v' << VERSION << ' from <code>' << file_name_s << '</code></p>
         | 
| 492 518 |  | 
| 493 519 | 
             
            										<h2>Month: ' << month_s << ' <a href="' << year_file_name_s << '">' << year_s << '</a></h2>
         | 
| @@ -619,8 +645,10 @@ module TheFox | |
| 619 645 | 
             
            					yeardat_file_path = Pathname.new("year_#{year_s}.dat").expand_path(@tmp_path)
         | 
| 620 646 | 
             
            					yeardat_file = File.new(yeardat_file_path, 'w')
         | 
| 621 647 | 
             
            					yeardat_file.write(year_total
         | 
| 648 | 
            +
            						.sort{ |a, b| a[0] <=> b[0] }
         | 
| 622 649 | 
             
            						.map{ |k, m| "#{year_s}-#{m.month_s} #{m.revenue} #{m.expense} #{m.balance} #{m.balance_total} #{m.balance_total}" }
         | 
| 623 650 | 
             
            						.join("\n"))
         | 
| 651 | 
            +
            					yeardat_file.write("\n")
         | 
| 624 652 | 
             
            					yeardat_file.close
         | 
| 625 653 |  | 
| 626 654 | 
             
            					gnuplot_file_path = Pathname.new("year_#{year_s}.gp").expand_path(@tmp_path)
         | 
| @@ -727,10 +755,15 @@ module TheFox | |
| 727 755 | 
             
            					store['changes'] = html_options['changes']
         | 
| 728 756 | 
             
            				end
         | 
| 729 757 |  | 
| 758 | 
            +
            				# Convert Years Totals to DAT file rows.
         | 
| 730 759 | 
             
            				totaldat_file_c = years_total.map{ |k, y| "#{y.year} #{y.revenue} #{y.expense} #{y.balance} #{y.balance_total}" }
         | 
| 760 | 
            +
            				
         | 
| 761 | 
            +
            				# Print maximal 10 years on GNUPlot.
         | 
| 731 762 | 
             
            				if totaldat_file_c.count > 10
         | 
| 732 763 | 
             
            					totaldat_file_c = totaldat_file_c.slice(-10, 10)
         | 
| 733 764 | 
             
            				end
         | 
| 765 | 
            +
            				
         | 
| 766 | 
            +
            				# Convert DAT file rows to one String.
         | 
| 734 767 | 
             
            				totaldat_file_c = totaldat_file_c.join("\n")
         | 
| 735 768 |  | 
| 736 769 | 
             
            				# DAT file for GNUPlot.
         | 
| @@ -768,9 +801,10 @@ module TheFox | |
| 768 801 |  | 
| 769 802 | 
             
            				system("gnuplot #{gnuplot_file_path} &> /dev/null")
         | 
| 770 803 |  | 
| 771 | 
            -
            				@logger.info('generate html done') | 
| 804 | 
            +
            				@logger.info('generate html done')
         | 
| 772 805 | 
             
            			end
         | 
| 773 806 |  | 
| 807 | 
            +
            			# Used by CSV Command.
         | 
| 774 808 | 
             
            			def import_csv_file(file_path)
         | 
| 775 809 | 
             
            				transaction_start
         | 
| 776 810 |  | 
| @@ -800,14 +834,15 @@ module TheFox | |
| 800 834 |  | 
| 801 835 | 
             
            					added = add(Entry.new(id, title, date, revenue, expense, category, comment), true)
         | 
| 802 836 |  | 
| 803 | 
            -
            					@logger.debug("import row '#{id}' -- #{added ? 'YES' : 'NO'}") | 
| 837 | 
            +
            					@logger.debug("import row '#{id}' -- #{added ? 'YES' : 'NO'}")
         | 
| 804 838 | 
             
            				end
         | 
| 805 839 |  | 
| 806 | 
            -
            				@logger.info('save data ...') | 
| 840 | 
            +
            				@logger.info('save data ...')
         | 
| 807 841 |  | 
| 808 842 | 
             
            				transaction_end
         | 
| 809 843 | 
             
            			end
         | 
| 810 844 |  | 
| 845 | 
            +
            			# Used by CSV Command.
         | 
| 811 846 | 
             
            			def export_csv_file(file_path)
         | 
| 812 847 | 
             
            				csv_options = {
         | 
| 813 848 | 
             
            					:col_sep => ',',
         | 
| @@ -820,7 +855,7 @@ module TheFox | |
| 820 855 | 
             
            				}
         | 
| 821 856 | 
             
            				CSV.open(file_path, 'wb', csv_options) do |csv|
         | 
| 822 857 | 
             
            					Dir[Pathname.new('month_*.yml').expand_path(@data_path)].each do |yaml_file_path|
         | 
| 823 | 
            -
            						@logger.info("export #{File.basename(yaml_file_path)}") | 
| 858 | 
            +
            						@logger.info("export #{File.basename(yaml_file_path)}")
         | 
| 824 859 |  | 
| 825 860 | 
             
            						data = YAML.load_file(yaml_file_path)
         | 
| 826 861 |  | 
| @@ -853,9 +888,11 @@ module TheFox | |
| 853 888 | 
             
            				@entries_index.include?(entry.id)
         | 
| 854 889 | 
             
            			end
         | 
| 855 890 |  | 
| 891 | 
            +
            			# Build an entry-by-id Hash.
         | 
| 892 | 
            +
            			# ID => Entry
         | 
| 856 893 | 
             
            			def build_entry_by_id_index(force = false)
         | 
| 857 894 | 
             
            				if @entries_by_ids.nil? || force
         | 
| 858 | 
            -
            					@logger.debug('build entry-by-id index') | 
| 895 | 
            +
            					@logger.debug('build entry-by-id index')
         | 
| 859 896 |  | 
| 860 897 | 
             
            					glob = Pathname.new('month_*.yml').expand_path(@data_path)
         | 
| 861 898 |  | 
| @@ -872,12 +909,14 @@ module TheFox | |
| 872 909 | 
             
            				end
         | 
| 873 910 | 
             
            			end
         | 
| 874 911 |  | 
| 912 | 
            +
            			# Find an Entry by a given ID.
         | 
| 875 913 | 
             
            			def find_entry_by_id(id)
         | 
| 876 914 | 
             
            				build_entry_by_id_index
         | 
| 877 915 |  | 
| 878 916 | 
             
            				@entries_by_ids[id]
         | 
| 879 917 | 
             
            			end
         | 
| 880 918 |  | 
| 919 | 
            +
            			# Used by Clear Command.
         | 
| 881 920 | 
             
            			def clear
         | 
| 882 921 | 
             
            				c = 0
         | 
| 883 922 |  | 
| @@ -907,6 +946,7 @@ module TheFox | |
| 907 946 |  | 
| 908 947 | 
             
            			private
         | 
| 909 948 |  | 
| 949 | 
            +
            			# Create all needed subdirectories for this wallet.
         | 
| 910 950 | 
             
            			def create_dirs
         | 
| 911 951 | 
             
            				unless @dir_path.exist?
         | 
| 912 952 | 
             
            					@dir_path.mkpath
         | 
| @@ -920,6 +960,7 @@ module TheFox | |
| 920 960 | 
             
            					@tmp_path.mkpath
         | 
| 921 961 | 
             
            				end
         | 
| 922 962 |  | 
| 963 | 
            +
            				# Ignore all files in wallet/tmp.
         | 
| 923 964 | 
             
            				tmp_gitignore_path = Pathname.new('.gitignore').expand_path(@tmp_path)
         | 
| 924 965 | 
             
            				unless tmp_gitignore_path.exist?
         | 
| 925 966 | 
             
            					gitignore_file = File.open(tmp_gitignore_path, 'w')
         | 
| @@ -930,12 +971,14 @@ module TheFox | |
| 930 971 | 
             
            				if @entries_index_file_path.exist?
         | 
| 931 972 | 
             
            					load_entries_index_file
         | 
| 932 973 | 
             
            				else
         | 
| 974 | 
            +
            					# When the entries index file does not exist from an older Wallet version.
         | 
| 933 975 | 
             
            					build_entry_by_id_index(true)
         | 
| 934 976 | 
             
            					@entries_index = @entries_by_ids.keys
         | 
| 935 977 | 
             
            					save_entries_index_file
         | 
| 936 978 | 
             
            				end
         | 
| 937 979 | 
             
            			end
         | 
| 938 980 |  | 
| 981 | 
            +
            			# Get the sums for a given day.
         | 
| 939 982 | 
             
            			def calc_day(day, category = nil)
         | 
| 940 983 | 
             
            				revenue = 0
         | 
| 941 984 | 
             
            				expense = 0
         | 
| @@ -965,8 +1008,9 @@ module TheFox | |
| 965 1008 | 
             
            				}
         | 
| 966 1009 | 
             
            			end
         | 
| 967 1010 |  | 
| 1011 | 
            +
            			# Get all used years.
         | 
| 1012 | 
            +
            			# Range is optional.
         | 
| 968 1013 | 
             
            			def years(date_start = nil, date_end = nil)
         | 
| 969 | 
            -
            				
         | 
| 970 1014 | 
             
            				files = Array.new
         | 
| 971 1015 | 
             
            				@data_path.each_child(false) do |file|
         | 
| 972 1016 | 
             
            					if file.extname == '.yml' && /^month_/.match(file.to_s)
         | 
| @@ -984,18 +1028,23 @@ module TheFox | |
| 984 1028 | 
             
            					.map{ |file| file.to_s[6, 4].to_i }
         | 
| 985 1029 | 
             
            					.uniq
         | 
| 986 1030 | 
             
            					.keep_if{ |year| year >= date_start_year && year <= date_end_year }
         | 
| 1031 | 
            +
            					.sort
         | 
| 987 1032 | 
             
            			end
         | 
| 988 1033 |  | 
| 1034 | 
            +
            			# Load the entries index file only once.
         | 
| 989 1035 | 
             
            			def load_entries_index_file
         | 
| 990 | 
            -
            				 | 
| 991 | 
            -
            					 | 
| 992 | 
            -
             | 
| 993 | 
            -
             | 
| 994 | 
            -
             | 
| 995 | 
            -
             | 
| 1036 | 
            +
            				if @entries_index_is_loaded
         | 
| 1037 | 
            +
            					return
         | 
| 1038 | 
            +
            				end
         | 
| 1039 | 
            +
            				
         | 
| 1040 | 
            +
            				@entries_index_is_loaded = true
         | 
| 1041 | 
            +
            				if @entries_index_file_path.exist?
         | 
| 1042 | 
            +
            					data = YAML.load_file(@entries_index_file_path.to_s)
         | 
| 1043 | 
            +
            					@entries_index = data['index']
         | 
| 996 1044 | 
             
            				end
         | 
| 997 1045 | 
             
            			end
         | 
| 998 1046 |  | 
| 1047 | 
            +
            			# Save the entries index file.
         | 
| 999 1048 | 
             
            			def save_entries_index_file
         | 
| 1000 1049 | 
             
            				store = YAML::Store.new(@entries_index_file_path.to_s)
         | 
| 1001 1050 | 
             
            				store.transaction do
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: thefox-wallet
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.17.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Christian Mayer
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2017- | 
| 11 | 
            +
            date: 2017-08-12 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: pry
         | 
| @@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 131 131 | 
             
                  version: '0'
         | 
| 132 132 | 
             
            requirements: []
         | 
| 133 133 | 
             
            rubyforge_project: 
         | 
| 134 | 
            -
            rubygems_version: 2.6. | 
| 134 | 
            +
            rubygems_version: 2.6.12
         | 
| 135 135 | 
             
            signing_key: 
         | 
| 136 136 | 
             
            specification_version: 4
         | 
| 137 137 | 
             
            summary: Finances Tracking
         |