panache 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +75 -0
- data/Rakefile +1 -0
- data/bin/panache +36 -0
- data/lib/panache/version.rb +3 -0
- data/lib/panache.rb +157 -0
- data/panache.gemspec +26 -0
- data/post-commit +4 -0
- data/test/test_file.rb +17 -0
- metadata +75 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
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
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
|