panache 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vanilla_gem.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ Panache
2
+ =======
3
+
4
+ Panache is a simple way to create style checkers for various languages. It does simple parsing of source
5
+ files and then applies user-specified rules to detect style violations.
6
+
7
+ Code parsing is *not* full lexing; it is an intentional design goal to limit code introspection in order to
8
+ avoid overlap with tasks best left to language-specific linters and static analysis tools. To that end, the
9
+ code parsing is done with Textpow, which uses TextMate syntax files and annotates source with tags (so that we
10
+ can detect things like comments, string literals, etc). Then rules are applied based on Textpow tags.
11
+
12
+ Usage
13
+ -----
14
+
15
+ ```bash
16
+ $ gem install panache
17
+ $ panache [-s panache_styles] code_path [code_path ...]
18
+ ```
19
+
20
+ `panache_styles` is the directory where your style files are stored and each `code_path` is a file
21
+ or directory to check.
22
+
23
+ One of the most useful ways to use Panache is as a pre-commit or post-commit hook in your repository.
24
+ For example, to check each file that was modified after you commit your git post-commit hook could look
25
+ like:
26
+
27
+ ```bash
28
+ #!/bin/sh
29
+
30
+ panache -s ~/repos/panache_styles $(git show --pretty="format:" --name-only)
31
+ ```
32
+
33
+ Creating new styles
34
+ -------------------
35
+
36
+ To create a new style, you will need a Textmate syntax file and a rules file written in the Panache DSL.
37
+
38
+ The easiest way to get Textmate syntax files is to copy them out of a Textmate installation on your computer.
39
+ They should have the path
40
+
41
+ /Applications/TextMate.app/Contents/SharedSupport/Bundles/<your-language>.tmbundle/Syntaxes/<your-language>.plist
42
+
43
+ This is a binary plist file, and Panache requires it to be in the text XML format; you can convert it like this:
44
+
45
+ $ plutil -convert xml1 <your-language>.plist
46
+
47
+ (Of course, you'll want to copy the plist file into the project directory to avoid corrupting your TextMate
48
+ installation). You can download other, more exotic TextMate syntax files from the [TextMate subversion
49
+ repository](http://svn.textmate.org/trunk/Bundles/).
50
+
51
+ Next, you need to write your rules file.
52
+
53
+ TODO(caleb): Update steps below.
54
+
55
+ 1. Make a file called `<language>_style.rb`, where `<language>` is the type of file you want to check.
56
+ 2. Use the DSL implemented in this file to write your style.
57
+ * The regex you give to Panache::Style#create matches the filenames you wish to check.
58
+ * Each rule consists of a regex and a message to indicate what the violation was.
59
+ * The regex may contain a match. The offset of the beginning of this match will be used to indicate
60
+ exactly where a style violation occurred.
61
+
62
+ Dumb example:
63
+
64
+ Panache::Style.create(/\.txt$/) do
65
+ rule /(q)/, "The letter 'q' is not allowed in text files."
66
+ end
67
+
68
+ Output formats
69
+ --------------
70
+
71
+ TODO(caleb) Implement these. Ideas:
72
+
73
+ * Colorized console output
74
+ * Summary output suitable for consumption by other tools (# of tests / # of failures)
75
+ * Markdown? (For automatic CR bot)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/panache ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "panache"
4
+
5
+ # TODO(caleb): multiple output options.
6
+
7
+ def usage
8
+ <<-EOF
9
+ Usage:
10
+ panache [-s styles_directory] code_path [code_path ...]
11
+
12
+ The styles_directory may also be specified with the PANACHE_STYLES environment variable.
13
+ EOF
14
+ end
15
+
16
+ style_directory = nil
17
+ code_paths = []
18
+ ARGV.each_with_index do |option, index|
19
+ next if ARGV[index - 1] == "-s"
20
+ if option == "-s"
21
+ style_directory = ARGV[index + 1]
22
+ next
23
+ end
24
+ code_paths << option
25
+ end
26
+ style_directory ||= ENV["PANACHE_STYLES"]
27
+
28
+ if style_directory.nil? || code_paths.empty?
29
+ puts usage
30
+ exit 1
31
+ end
32
+
33
+ Panache.load_styles(style_directory)
34
+ results = {}
35
+ code_paths.each { |path| results.merge! Panache.check_path(path) }
36
+ Panache::Printer.new(results).print_results
@@ -0,0 +1,3 @@
1
+ module Panache
2
+ VERSION = "0.1.0"
3
+ end
data/lib/panache.rb ADDED
@@ -0,0 +1,157 @@
1
+ require "find"
2
+ require "colorize"
3
+
4
+ module Panache
5
+ @@styles = []
6
+
7
+ DEFAULT_TAB_SPACES = 2
8
+ DEFAULT_STYLE_PATH = File.expand_path("~/repos/panache/panache_styles")
9
+ Violation = Struct.new :line_number, :line, :offset, :style, :rule
10
+
11
+ class Rule
12
+ attr_reader :message
13
+ def initialize(regexes, message)
14
+ @regexes = regexes
15
+ @message = message
16
+ end
17
+
18
+ def check(line)
19
+ offset = 0
20
+ violations = []
21
+ @regexes.each do |regex|
22
+ while (match = regex.match(line, offset))
23
+ # If the match size is 1, then the user did not provide any captures in their regex and we only know
24
+ # that there is >= 1 violation of the rule in this line.
25
+ if match.size == 1
26
+ return [Violation.new(nil, line, nil, nil, self)]
27
+ end
28
+ offset = match.offset(1)[0]
29
+ violations << Violation.new(nil, line, offset, nil, self)
30
+ offset += 1
31
+ end
32
+ end
33
+ violations
34
+ end
35
+ end
36
+
37
+ class Style
38
+ def initialize(file_regex)
39
+ @tab_spaces = DEFAULT_TAB_SPACES
40
+ @file_regex = file_regex
41
+ @rules = []
42
+ end
43
+
44
+ def self.create(file_regex, &block)
45
+ style = Style.new(file_regex)
46
+ style.instance_eval &block
47
+ Panache.add_style style
48
+ end
49
+
50
+ def spaces_per_tab(n)
51
+ @tab_spaces = n
52
+ end
53
+
54
+ def rule(regex, message)
55
+ if regex.is_a? Array
56
+ @rules << Rule.new(regex, message)
57
+ else
58
+ @rules << Rule.new([regex], message)
59
+ end
60
+ end
61
+
62
+ def checks?(file_name)
63
+ file_name =~ @file_regex
64
+ end
65
+
66
+ def check(line)
67
+ # Replace tabs with spaces according to the specified conversion.
68
+ line.gsub!("\t", " " * @tab_spaces)
69
+ @rules.flat_map do |rule|
70
+ violations = rule.check(line)
71
+ violations.each { |v| v.style = self }
72
+ end
73
+ end
74
+ end
75
+
76
+ def self.add_style(style)
77
+ @@styles << style
78
+ end
79
+
80
+ # Load all Panache styles (files matching *_style.rb) from a given directory.
81
+ def self.load_styles(directory = DEFAULT_STYLE_PATH)
82
+ raise "Need a directory of Panache styles" unless File.directory?(directory)
83
+ style_files = Dir.glob(File.join(directory, "*_style.rb"))
84
+ raise "No Panache styles found in #{directory}" if style_files.empty?
85
+ style_files.each do |style|
86
+ puts "Loading #{style}...".green
87
+ load style
88
+ end
89
+ puts "Loaded #{style_files.size} style#{"s" if style_files.size != 1}.".green
90
+ end
91
+
92
+ # Check a given file against loaded styles.
93
+ def self.check_file(path)
94
+ styles = @@styles.select { |style| style.checks? path }
95
+ line_number = 0
96
+ File.open(path).each_with_index.flat_map do |line, line_number|
97
+ line_number += 1
98
+ styles.flat_map { |style| style.check(line) }.each { |violation| violation.line_number = line_number }
99
+ end
100
+ end
101
+
102
+ # Check a file or directory
103
+ def self.check_path(path)
104
+ if File.directory? path
105
+ files = Find.find(path).select { |sub_path| File.file? sub_path }
106
+ elsif File.file? path
107
+ files = [path]
108
+ else
109
+ raise "Bad path: #{path}."
110
+ end
111
+ puts "Checking #{path}".green
112
+ results = {}
113
+ files.each { |file| results[file] = Panache.check_file file }
114
+ results
115
+ end
116
+
117
+ class Printer
118
+ def initialize(results, output_type = :normal)
119
+ @results = results
120
+ @output_type = output_type
121
+ end
122
+
123
+ def print_results
124
+ case @output_type
125
+ when :normal then print_normal
126
+ else print normal
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def print_normal
133
+ violations_found = 0
134
+ bad_files = 0
135
+ @results.each do |file, result|
136
+ next if result.empty?
137
+ puts "Style violations in #{file}:".red
138
+ bad_files += 1
139
+ result.each do |violation|
140
+ violations_found += 1
141
+ puts "Line #{violation.line_number}: #{violation.rule.message}".blue
142
+ puts violation.line
143
+ puts " " * violation.offset + "^".blue if violation.offset
144
+ end
145
+ end
146
+ if violations_found.zero?
147
+ puts "No violations found.".green
148
+ else
149
+ puts "Found #{plural(violations_found, "violation")} in #{plural(bad_files, "bad file")}.".red
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ def plural(amount, thing)
156
+ "#{amount} #{thing}#{"s" unless amount == 1}"
157
+ end
data/panache.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "panache/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "panache"
7
+ s.version = Panache::VERSION
8
+ s.authors = ["Caleb Spare", "Daniel MacDougall"]
9
+ s.email = ["caleb@ooyala.com", "dmac@ooyala.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Create style checkers for various languages}
12
+ s.description = <<-EOF
13
+ Panache is a simple way to create style checkers for various languages.
14
+ It does simple parsing of source files and then applies user-specified rules to detect style violations.
15
+ EOF
16
+
17
+ s.rubyforge_project = "panache"
18
+ s.required_ruby_version = ">= 1.9.2"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+
25
+ s.add_development_dependency "colorize"
26
+ end
data/post-commit ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+ export RBENV_VERSION="1.9.2-p290"
4
+ panache -s ~/repos/panache_styles $(git show --pretty="format:" --name-only)
data/test/test_file.rb ADDED
@@ -0,0 +1,17 @@
1
+ # This is a simple test file.
2
+ #It has issues.
3
+
4
+ def fooBar
5
+ puts "I'm just going to return an extremely long string here...maybe nobody will call me out on this blatant style violation!";
6
+ hello;
7
+ end
8
+
9
+ [1,2,3,4,5].each do |i| puts i end
10
+ (0..10).times {|i| puts i }
11
+ asDf = 3
12
+
13
+ a =<<-EOS
14
+ a
15
+ b
16
+ c
17
+ EOS
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: panache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Caleb Spare
9
+ - Daniel MacDougall
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-11-10 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: colorize
17
+ requirement: &2164368800 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *2164368800
26
+ description: ! 'Panache is a simple way to create style checkers for various languages.
27
+
28
+ It does simple parsing of source files and then applies user-specified rules to
29
+ detect style violations.
30
+
31
+ '
32
+ email:
33
+ - caleb@ooyala.com
34
+ - dmac@ooyala.com
35
+ executables:
36
+ - panache
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - .gitignore
41
+ - Gemfile
42
+ - README.md
43
+ - Rakefile
44
+ - bin/panache
45
+ - lib/panache.rb
46
+ - lib/panache/version.rb
47
+ - panache.gemspec
48
+ - post-commit
49
+ - test/test_file.rb
50
+ homepage: ''
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.9.2
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project: panache
70
+ rubygems_version: 1.8.7
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Create style checkers for various languages
74
+ test_files:
75
+ - test/test_file.rb