panache 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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