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 +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
|