watnow 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Etienne Lemay
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # Watnow
2
+
3
+ <b>Watnow</b> finds and lists your project TODOs and FIXMEs.<br>
4
+ It basically does what the Rails’ `rake notes` does, but in a more generic way.
5
+
6
+ ## Installation
7
+ ```sh
8
+ $ gem install watnow
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```sh
14
+ $ watnow
15
+ ```
16
+
17
+ ### Help
18
+ ```sh
19
+ $ watnow --help
20
+
21
+ Usage: watnow [options]
22
+
23
+ -h, --help show this message
24
+ -v, --version display version
25
+ -d, --directory DIR directory DIR to scan (defaults: ./)
26
+
27
+ watnow commands:
28
+ open <ID> open annotation ID in your editor
29
+ remove <ID> remove annotation ID
30
+ ```
31
+
32
+ ## Commands
33
+ ### open
34
+ Opens an annotation in your $EDITOR focused on the annotation line.
35
+ ```sh
36
+ $ watnow open 13
37
+ ```
38
+
39
+ ### remove
40
+ Removes the annotation line from its file.<br>
41
+ (Make sure there is nothing else on that line)
42
+ ```sh
43
+ $ watnow remove 13
44
+ ```
45
+
46
+ ## Features
47
+ ### Mentions
48
+ ```rb
49
+ # TODO @rafBM: Update user form
50
+ # TODO Update user controller @rafBM
51
+ ```
52
+ ```sh
53
+ [ 2 ] TODO: Update user form [ @rafBM ]
54
+ [ 1 ] TODO: Update user controller [ @rafBM ]
55
+ ```
56
+
57
+ ### Priority
58
+ Exclamation mark (!) preceded by a whitespace. (`/\s(!+)\s?/`)
59
+
60
+ ```rb
61
+ # TODO !!!: This is level 3 urgent
62
+ # TODO @rafBM: Just do it !
63
+ # TODO: This is not a priority!!
64
+ # TODO !!!!!!!!!!!!! @EtienneLem: This is a nicolas-cage-level urgent task
65
+ ```
66
+ ```sh
67
+ [ 3 ] TODO: This is not a priority!!
68
+ [ 2 ] TODO: Just do it [ @rafBM - ! ]
69
+ [ 1 ] TODO: This is level 3 urgent [ !!! ]
70
+ [ 4 ] TODO: This is a nicolas-cage-level urgent task [ @EtienneLem - !!!!!!!!!!!!! ]
71
+ ```
72
+
73
+ ### Super color-friendly
74
+ (Seriously, I’m open to color suggestions…)
75
+ ![color-friendly](https://s3.amazonaws.com/watnow/colors.png)
76
+
77
+ ## Watnow config
78
+ Override defaults in `~/.watnowconfig`. Supported options are:
79
+ ```
80
+ username (String) | A username so that you can be mentioned in TODOs | Default: ''
81
+ color (Boolean) | Enable/disable colored output | Default: true
82
+ patterns (Array) | An array of string/regex that you want to monitor | Default: []
83
+ ```
84
+
85
+ Use YAML syntax:
86
+ ```
87
+ username: EtienneLem
88
+ color: false
89
+ patterns: [potato, ba(na)+]
90
+ ```
91
+
92
+ ## Contribution
93
+ My Ruby skills are far from exemplary, please teach me.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/watnow ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'watnow'
7
+ Watnow::Extractor.new
@@ -0,0 +1,41 @@
1
+ module Watnow
2
+ # An Annotation is basically a group of AnnotationLine
3
+ class Annotation
4
+
5
+ @@id = 0
6
+ @@instances = []
7
+
8
+ attr_accessor :id, :file, :lines, :priority
9
+
10
+ # An ORM-like method that returns all Annotation instances
11
+ def self.all
12
+ @@instances
13
+ end
14
+
15
+ def initialize(opts)
16
+ @priority = 0
17
+ @file = opts[:file]
18
+ @lines = set_lines(opts[:lines])
19
+
20
+ # Auto increment instances id
21
+ # Push instance into a class array
22
+ @id = @@id += 1
23
+ @@instances << self
24
+ end
25
+
26
+ private
27
+
28
+ # Loop through lines data and create AnnotationLine instance
29
+ # Send self so that an AnnotationLine knows its Annotation and vice versa
30
+ def set_lines(lines)
31
+ results = []
32
+
33
+ lines.each do |line|
34
+ results << AnnotationLine.new(line, self)
35
+ end
36
+
37
+ results
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,54 @@
1
+ module Watnow
2
+ class AnnotationLine
3
+
4
+ @@id = 0
5
+ @@instances = []
6
+ @@tag_length = 0
7
+
8
+ attr_accessor :id, :lineno, :tag, :priority, :mention, :message, :annotation
9
+
10
+ # An ORM-like find method that returns an AnnotationLine instance
11
+ def self.find(id)
12
+ @@instances.each do |instance|
13
+ return instance if instance.id == id
14
+ end
15
+
16
+ nil
17
+ end
18
+
19
+ # Returns longest tag length
20
+ # Used to format output
21
+ def self.tag_length
22
+ @@tag_length
23
+ end
24
+
25
+ def initialize(opts, annotation)
26
+ @lineno = opts[:lineno]
27
+ @tag = opts[:tag]
28
+ @priority = opts[:priority] || 0
29
+ @mention = opts[:mention] || ''
30
+ @message = opts[:message]
31
+ @annotation = annotation
32
+
33
+ @annotation.priority = @priority if @priority > @annotation.priority
34
+
35
+ # Auto increment instances id
36
+ # Push instance into a class array
37
+ @id = @@id += 1
38
+ @@instances << self
39
+
40
+ # Update class’ tag_length to the longest tag
41
+ @@tag_length = @tag.length if @tag.length > @@tag_length
42
+ end
43
+
44
+ # Returns an array of "meta data"
45
+ # Namely: The AnnotationLine mention and priority
46
+ def meta_data
47
+ data = []
48
+ data << "@#{@mention}" unless @mention.empty?
49
+ data << Array.new(@priority + 1).join('!') if @priority > 0
50
+ data
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,51 @@
1
+ require 'yaml'
2
+
3
+ module Watnow::Config
4
+
5
+ # Default constants
6
+ FOLDER_IGNORE = %w(tmp node_modules db public log)
7
+ FILE_EXTENSION_IGNORE = %w(tmproj markdown md txt)
8
+ PATTERNS = %w(TODO FIXME)
9
+
10
+ def self.included(base)
11
+ defaults = {
12
+ 'color' => true,
13
+ 'folder_ignore' => [],
14
+ 'file_extension_ignore' => [],
15
+ 'patterns' => [],
16
+ 'username' => ''
17
+ }
18
+
19
+ # Parse YAML config file (~/.watnowconfig)
20
+ parser = YamlParser.new
21
+ custom_options = parser.parse
22
+
23
+ # Merge defaults with custom options
24
+ # Add default constants
25
+ options = defaults.merge(custom_options)
26
+ options['folder_ignore'].concat(FOLDER_IGNORE)
27
+ options['file_extension_ignore'].concat(FILE_EXTENSION_IGNORE)
28
+ options['patterns'].concat(PATTERNS)
29
+
30
+ # Generate singleton methods with options keys for quick access
31
+ # i.e. self.color, self.patterns, self.username, etc
32
+ options.each do |option|
33
+ define_singleton_method option[0] do
34
+ option[1]
35
+ end
36
+ end
37
+ end
38
+
39
+ # Simple YAML parser
40
+ # Returns empty object if no file found
41
+ class YamlParser
42
+ def parse
43
+ begin
44
+ YAML.load_file("#{ENV['HOME']}/.watnowconfig")
45
+ rescue
46
+ {}
47
+ end
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,53 @@
1
+ require 'optparse'
2
+
3
+ module Watnow
4
+ # Home made Option parser built on top of Ruby’s optparse
5
+ class OptParser
6
+
7
+ def self.parse(args)
8
+ # Defaults
9
+ options = {}
10
+ options[:directory] = '.'
11
+
12
+ opts = OptionParser.new('', 24, ' ') do |opts|
13
+ opts.separator ' Usage: watnow [options]'
14
+ opts.separator ''
15
+
16
+ # Help
17
+ opts.on('-h', '--help', 'show this message') do
18
+ puts opts
19
+ exit
20
+ end
21
+
22
+ # Version
23
+ opts.on('-v', '--version', 'display version') do
24
+ puts VERSION
25
+ exit
26
+ end
27
+
28
+ # Directory
29
+ opts.on('-d', '--directory DIR', 'directory DIR to scan (defaults: ./)') do |d|
30
+ options[:directory] = d
31
+ end
32
+
33
+ # Commands
34
+ opts.separator ''
35
+ opts.separator ' watnow commands:'
36
+ opts.separator ' open <ID> open annotation ID in your editor'
37
+ opts.separator ' remove <ID> remove annotation ID'
38
+
39
+ options[:open] = get_option_value('open', args)
40
+ options[:remove] = get_option_value('remove', args)
41
+
42
+ end
43
+
44
+ opts.parse!(args)
45
+ options
46
+ end
47
+
48
+ def self.get_option_value(option, args)
49
+ args.include?(option) ? args[args.index(option) + 1] : nil
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Watnow
2
+ VERSION = "0.0.1"
3
+ end
data/lib/watnow.rb ADDED
@@ -0,0 +1,171 @@
1
+ require 'watnow/version'
2
+ require 'watnow/config'
3
+ require 'watnow/option_parser'
4
+ require 'watnow/annotation/annotation'
5
+ require 'watnow/annotation/annotation_line'
6
+ require 'colored'
7
+
8
+ module Watnow
9
+ class Extractor
10
+ include Watnow::Config
11
+
12
+ def initialize
13
+ # Parse options and scan given directory
14
+ options = OptParser.parse(ARGV)
15
+ scan(options[:directory])
16
+
17
+ # If open command is parsed (`watnow open 13`)
18
+ if options[:open]
19
+ id = Integer(options[:open])
20
+ annotation_line = AnnotationLine.find(id)
21
+ open_file_at_line(annotation_line.annotation.file, annotation_line.lineno)
22
+
23
+ # If remove command is parsed (`watnow remove 13`)
24
+ elsif options[:remove]
25
+ id = Integer(options[:remove])
26
+ annotation_line = AnnotationLine.find(id)
27
+ remove_line_of_file(annotation_line.annotation.file, annotation_line.lineno)
28
+
29
+ # No specific command parsed. Output the annotations list
30
+ else
31
+ output(Annotation.all)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Recursively scan given directory
38
+ # Ignore folders and files from Config
39
+ # Create a new Annotation when data si found
40
+ def scan(dir, annotations=[])
41
+ Dir.glob("#{dir}/*") do |path|
42
+ next if File.basename(path) =~ /(#{Config.folder_ignore.join('|')})$/
43
+
44
+ if File.directory? path
45
+ scan(path, annotations)
46
+ else
47
+ begin
48
+ next if File.extname(path) =~ /(#{Config.file_extension_ignore.join('|')})$/
49
+ content = read_file(path, /(#{Config.patterns.join('|')})/i)
50
+ Annotation.new(content) if content
51
+ rescue
52
+ nil
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Read a file line by line and look for given patterns
59
+ def read_file(file, patterns)
60
+ lineno = 0
61
+
62
+ result = File.readlines(file).inject([]) do |list, line|
63
+ lineno += 1
64
+ next list unless line =~ patterns
65
+
66
+ # Pattern is found, save the tag
67
+ tag = line.slice!($1)
68
+
69
+ # Look for priority
70
+ # Exclamation mark (!) preceded by a whitespace
71
+ priority = line.slice!(/\s(!+)\s?/)
72
+ priority = $1 ? $1.length : 0
73
+
74
+ # Look for mention
75
+ # Any word character preceded by @
76
+ mention = line.slice!(/\s@(\w+)\s*/)
77
+ mention = $1
78
+
79
+ # We assume the message starts with any word character
80
+ message = line.slice!(/(\w.*)/)
81
+ message = $1
82
+
83
+ # Push data into result
84
+ # Remove closing comment characters that we didn’t extract from regexs
85
+ list << {
86
+ :lineno => lineno,
87
+ :tag => tag,
88
+ :priority => priority,
89
+ :mention => mention,
90
+ :message => message.gsub(/\s*(\*\/|-->|%>)$/, '')
91
+ }
92
+ end
93
+
94
+ # Returns content that is sent to Annotation
95
+ result.empty? ? nil : { :file => file, :lines => result }
96
+ end
97
+
98
+ # Open a file with the user’s $EDITOR
99
+ # Automatically focus the annotation line number
100
+ # Supported editors are TextMate, Vim & Sublime Text
101
+ def open_file_at_line(filename, line)
102
+ command = filename
103
+
104
+ if ENV['EDITOR'] =~ /mate/
105
+ command = "#{filename} --line #{line}"
106
+ elsif ENV['EDITOR'] =~ /vi|vim/
107
+ command = "+#{line} #{filename}"
108
+ elsif ENV['EDITOR'] =~ /subl/
109
+ command = "#{filename}:#{line}"
110
+ end
111
+
112
+ Kernel.system("$EDITOR #{command}")
113
+ end
114
+
115
+ # Remove line from given file
116
+ def remove_line_of_file(filename, line)
117
+ file = File.readlines(filename)
118
+ file.delete_at(line - 1)
119
+ File.open(filename, 'w+') {|f| f.write file.join() }
120
+ end
121
+
122
+ def output(annotations)
123
+ # Sort annotations (file) by priority then by id
124
+ annotations.sort_by! { |a| [a.priority, -a.id] }
125
+
126
+ annotations.each do |annotation|
127
+ # Remove './' prefix in filenames
128
+ filename = annotation.file.gsub(/^\.\//, '')
129
+
130
+ # Display the filename
131
+ display_line "\n#{filename}", 'magenta'
132
+
133
+ # Sort annotation lines by priority DESC
134
+ # Higher priority is lower in the list
135
+ annotation.lines.sort! { |a,b| a.priority <=> b.priority }
136
+ annotation.lines.each do |annotation_line|
137
+ # Make sure all tags use the same (white)space
138
+ tag_spaces_count = AnnotationLine.tag_length - annotation_line.tag.length
139
+ tag_spaces = Array.new(tag_spaces_count + 1).join(' ')
140
+
141
+ # Boolean. True if current user is being mentioned in the annotation line
142
+ is_mentioned = (annotation_line.mention.downcase == Config.username.downcase)
143
+
144
+ # The actual outputting
145
+ display_text '[ ', 'green', is_mentioned
146
+ display_text annotation_line.id, 'cyan'
147
+ display_text " ] #{' ' if annotation_line.id < 10}", 'green', is_mentioned
148
+ display_text "#{annotation_line.tag}: #{tag_spaces}#{annotation_line.message}", 'green', is_mentioned
149
+ if annotation_line.meta_data.size > 0
150
+ display_text ' [ ', 'green', is_mentioned
151
+ display_text annotation_line.meta_data.join(' - '), 'cyan'
152
+ display_text ' ]', 'green', is_mentioned
153
+ end
154
+ display_line ""
155
+ end
156
+ end
157
+ end
158
+
159
+ # display_text followed by a newline
160
+ def display_line(msg, color=nil, color_condition=true)
161
+ display_text "#{msg}\n", color, color_condition
162
+ end
163
+
164
+ # Output helper. Print text in color if color_condition is truthy
165
+ def display_text(msg, color=nil, color_condition=true)
166
+ output = color && color_condition && Config.color ? "#{msg}".send(color) : msg
167
+ STDOUT.write "#{output}"
168
+ end
169
+
170
+ end
171
+ end
data/watnow.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/watnow/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Etienne Lemay"]
6
+ gem.email = ["etienne@heliom.ca"]
7
+ gem.homepage = "https://github.com/etiennelem/watnow"
8
+
9
+ gem.description = "Watnow finds and lists your project todo and fixme"
10
+ gem.summary = gem.description
11
+ gem.version = Watnow::VERSION
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.name = "watnow"
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_dependency("colored", "~> 1.2")
20
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: watnow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Etienne Lemay
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: colored
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
30
+ description: Watnow finds and lists your project todo and fixme
31
+ email:
32
+ - etienne@heliom.ca
33
+ executables:
34
+ - watnow
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - LICENSE
41
+ - README.md
42
+ - Rakefile
43
+ - bin/watnow
44
+ - lib/watnow.rb
45
+ - lib/watnow/annotation/annotation.rb
46
+ - lib/watnow/annotation/annotation_line.rb
47
+ - lib/watnow/config.rb
48
+ - lib/watnow/option_parser.rb
49
+ - lib/watnow/version.rb
50
+ - watnow.gemspec
51
+ homepage: https://github.com/etiennelem/watnow
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.23
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Watnow finds and lists your project todo and fixme
75
+ test_files: []