code-cleaner 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +64 -0
- data/bin/code-cleaner +141 -0
- data/code-cleaner.gemspec +25 -0
- data/support/pre-commit +37 -0
- data/tasks/code-cleaner.nake +21 -0
- data/tasks/code-cleaner.rake +16 -0
- data/tasks/code-cleaner.thor +20 -0
- metadata +62 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 – 2010 Jakub Šťastný aka Botanicus
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
h1. About
|
2
|
+
|
3
|
+
Remove trailing whitespace, append missing \n and replace tabs by two spaces.
|
4
|
+
|
5
|
+
h1. Usage
|
6
|
+
|
7
|
+
<pre>
|
8
|
+
# edit script.rb and all Ruby scripts in lib directory
|
9
|
+
code-cleaner script.rb lib
|
10
|
+
|
11
|
+
# normalize standard input and print output to standard output
|
12
|
+
cat script.rb | code-cleaner
|
13
|
+
|
14
|
+
# add encoding declaration if missing
|
15
|
+
code-cleaner --encoding=utf-8
|
16
|
+
</pre>
|
17
|
+
|
18
|
+
h2. Blacklisting & Whitelisting
|
19
|
+
|
20
|
+
Of course you don't want to remove spaces from other files like PDF documents, images etc. You don't even want to remove spaces from all Ruby scripts, you probably want to ignore vendored files and locally installed gems. And this is where blacklisting and whitelisting comes handy.
|
21
|
+
|
22
|
+
Default behaviour is to ignore everything from vendor and gems directories and from other directories just files ending with .rb, .rake, .task and .thor are normalized.
|
23
|
+
|
24
|
+
You can change this behaviour by setting environment variables @BLACKLIST@ and @WHITELIST@. What you pass to these variables will be treated as a regular expression. The default behaviour respond to @BLACKLIST='/(vendor|gems)/'@ and @WHITELIST='\.(rb|rake|task|thor)$'@. Here's an example how you can use it:
|
25
|
+
|
26
|
+
<pre>
|
27
|
+
WHITELIST=''\.(rb|rake|nake|thor|task)$'' BLACKLIST='/(vendor|gems)/' code-cleaner .
|
28
|
+
</pre>
|
29
|
+
|
30
|
+
Note that these rules aren't applied for explicit arguments, so if you run @code-cleaner README.textile@, your readme actually will be normalized. It's because blacklisting and whitelisting rules are applied just on directories, so if you run @code-cleaner .@, your readme will stay untouched. Because of this reason @code-cleaner .@ and @code-cleaner *@ will be different.
|
31
|
+
|
32
|
+
If you want to force blacklisting resp. whitelisting even for explicitly specified files, use @--apply-rules@ switch. If you want just try it, use @--try-apply-rules@ which will tell you which files will be skipped and exit without any editing. If some files left in ARGV, the exit status will be 0, otherwise it will be 1.
|
33
|
+
|
34
|
+
In your Ruby projects, you might add all files in your @bin@ and @script@ directories into whitelist: @WHITELIST='(bin/[^/]+|.+\.(rb|rake|nake|thor|task))$'@
|
35
|
+
|
36
|
+
h2. Exit statuses
|
37
|
+
|
38
|
+
If code-cleaner does any changes, the exit status will be 0, otherwise it will be 10. It means you can use it in commands like:
|
39
|
+
|
40
|
+
<pre>
|
41
|
+
if code-cleaner "$file" --apply-rules; then # code-cleaner exits with 0 if some changes were made
|
42
|
+
git add "$file" # so the changes will be committed immediately
|
43
|
+
fi
|
44
|
+
</pre>
|
45
|
+
|
46
|
+
Or, better and more safe way is to directly check exit status of last command via @$?@ as is used in the default @pre-commit@ hook:
|
47
|
+
|
48
|
+
<pre>
|
49
|
+
code-cleaner .
|
50
|
+
|
51
|
+
if [ $? -eq 10 ]; then
|
52
|
+
echo Any modifications were made
|
53
|
+
else
|
54
|
+
echo File was changed
|
55
|
+
fi
|
56
|
+
</pre>
|
57
|
+
|
58
|
+
h2. Pre-commit hook
|
59
|
+
|
60
|
+
The best way how you can ensure your sources are clean is to use pre-commit hook, if your SCM provides this function. Plus, if you are using git, just use @load "code-cleaner.rake"@ in your Rakefile and then run @rake hooks:whitespace:install@. There are also alternatives for "Nake":http://github.com/botanicus/nake (@code-cleaner.nake@) and "Thor":http://github.com/wycats/thor (@code-cleaner.thor@). Second way is to use @rake --rakefile /path/to/code-cleaner/tasks/code-cleaner.rake hooks:whitespace:install@ resp. @nake /path/to/code-cleaner/tasks/code-cleaner.nake hooks:whitespace:install@ directly.
|
61
|
+
|
62
|
+
It might force you to remove your @.git/hooks/pre-commit@ if this file exist. If you haven't done any editation in this file, it's safe to do so and if you done some, then you will need to merge your changes manually.
|
63
|
+
|
64
|
+
If you project run in bundled environment, make sure you have your @bin@ or @script@ directory in your @$PATH@ or change the pre-commit hook to point to the right path to the @code-cleaner@.
|
data/bin/code-cleaner
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
#!/usr/bin/env ruby -i
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# This is black magic!
|
5
|
+
# If you like Perl, you will probably enjoy it
|
6
|
+
# However it's easy to write and it works, so why not.
|
7
|
+
|
8
|
+
# Also be warned this script is evil – it might eat your
|
9
|
+
# cat or burn you house and I'm not responsible for it.
|
10
|
+
|
11
|
+
# == Normalize your source code == #
|
12
|
+
# 1) replace tabs by two space
|
13
|
+
# 2) remove trailing whitespace
|
14
|
+
# 3) add \n at the end of file unless it's already there
|
15
|
+
|
16
|
+
# == Usage == #
|
17
|
+
# code-cleaner app/controller/application_controller.rb
|
18
|
+
# cat app/controller/application_controller.rb | code-cleaner
|
19
|
+
#
|
20
|
+
# == Implementation == #
|
21
|
+
# The point is that ruby with -i will edit files
|
22
|
+
|
23
|
+
require "find"
|
24
|
+
|
25
|
+
$-i = "" # Windows compatibility
|
26
|
+
|
27
|
+
WHITELIST = Regexp.new(ENV["WHITELIST"] || '\.(rb|rake|nake|thor|task)$')
|
28
|
+
BLACKLIST = Regexp.new(ENV["BLACKLIST"] || '/(vendor|gems)/')
|
29
|
+
|
30
|
+
def permitted?(file)
|
31
|
+
WHITELIST.match(file) && ! BLACKLIST.match(file)
|
32
|
+
end
|
33
|
+
|
34
|
+
if ARGF.nil?
|
35
|
+
abort "Hey mate, what about some arguments or piped data? How the fuck you think I work?!"
|
36
|
+
end
|
37
|
+
|
38
|
+
# help
|
39
|
+
if ARGV.include?("-h") or ARGV.include?("--help")
|
40
|
+
abort "[\e[32mUsage\e[0m]\n"\
|
41
|
+
"code-cleaner script.rb # normalize script.rb\n"\
|
42
|
+
"cat script.rb | code-cleaner # normalize standard input and print output to standard output\n"\
|
43
|
+
"\n"\
|
44
|
+
"[\e[32mOptions\e[0m]\n"\
|
45
|
+
"--encoding=utf-8 # add encoding declaration if missing\n"\
|
46
|
+
"--apply-rules # apply blacklisting and whitelisting even on explicitly specified files\n"\
|
47
|
+
"--try-apply-rules # apply rules and exit successfuly if some files left in ARGV, fail otherwise\n"\
|
48
|
+
"\n"\
|
49
|
+
"[\e[32mEnvironment Variables\e[0m]\n"\
|
50
|
+
"BLACKLIST # patterns which will be ignored, defaults to /(vendor|gems)/\n"\
|
51
|
+
"WHITELIST # patterns which won't be ignored, defaults to \.(rb|rake|nake|thor|task)$\n"\
|
52
|
+
"\n"\
|
53
|
+
"[\e[32mTricks\e[0m]\n"\
|
54
|
+
"If you want to add files from your bin or script directory into \n"\
|
55
|
+
"whitelist, use WHITELIST='(bin/[^/]+|.+\.(rb|rake|nake|thor|task))$'"
|
56
|
+
"\n"\
|
57
|
+
"[\e[32mExamples\e[0m]\n"\
|
58
|
+
"WHITELIST='(bin/[^/]+|.+\.(rb|rake|nake|thor|task))$'"
|
59
|
+
end
|
60
|
+
|
61
|
+
def apply_rules
|
62
|
+
original = ARGV.dup
|
63
|
+
ARGV.delete_if { |file| File.file?(file) && ! permitted?(file) }
|
64
|
+
puts "~ Skipping #{original - ARGV}" unless ARGV == original
|
65
|
+
end
|
66
|
+
|
67
|
+
if ARGV.delete("--try-apply-rules")
|
68
|
+
apply_rules
|
69
|
+
ARGV.empty? ? exit(1) : exit
|
70
|
+
end
|
71
|
+
|
72
|
+
if ARGV.delete("--apply-rules")
|
73
|
+
apply_rules
|
74
|
+
end
|
75
|
+
|
76
|
+
unless ARGV.grep(/^--encoding=(.+)$/).empty?
|
77
|
+
argument = ARGV.grep(/^--encoding=(.+)$/).first
|
78
|
+
ARGV.delete(argument)
|
79
|
+
argument.match(/^--encoding=(.+)$/)
|
80
|
+
encoding = $1.dup
|
81
|
+
else
|
82
|
+
encoding = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# expand all directories to list of files
|
86
|
+
if ARGV.any? { |file| File.directory?(file) }
|
87
|
+
args = Array.new
|
88
|
+
ARGV.each do |item|
|
89
|
+
if File.directory?(item)
|
90
|
+
Find.find(item) do |file|
|
91
|
+
if File.file?(file) && permitted?(file)
|
92
|
+
args.push(file)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
else
|
96
|
+
args.push(item)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
ARGV.clear.push(*args)
|
100
|
+
puts "~ Expanding args into #{ARGV.inspect}"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Ruby 1.9 will warn that you can't do in place edit for stdio,
|
104
|
+
# so we will disable in place mode. Yes, in Ruby 1.9 you can finally
|
105
|
+
# turn in place mode on or off from API in better way than just $-i = false.
|
106
|
+
if ARGF.respond_to?(:inplace_mode) && ARGV.empty?
|
107
|
+
ARGF.inplace_mode = false
|
108
|
+
end
|
109
|
+
|
110
|
+
# default exit status is 10 which signalize that we actually haven't changed anything
|
111
|
+
status = 10
|
112
|
+
|
113
|
+
# This is the only way how I was able to inspect if STDIN is empty. Magic, right?
|
114
|
+
# NOTE: if this shows as a bad idea, another way might be to put "/dev/null" resp. "NUL" on Windows to the ARGV
|
115
|
+
begin
|
116
|
+
STDIN.read_nonblock(1)
|
117
|
+
rescue Errno::EAGAIN
|
118
|
+
# The point of inspecting if STDIN and ARGV are empty is that we might
|
119
|
+
# have an empty ARGF if anything passed and if so, it will wait for an interactive input which we don't want
|
120
|
+
unless ARGV.empty?
|
121
|
+
while line = ARGF.gets
|
122
|
+
# I was trying to use ARGF.lineno.eql?(1), but it doesn't work. First problem
|
123
|
+
# was it didn't work with ARGF#each_line because IO#lineno count how many times
|
124
|
+
# IO#gets was called rather than how many \n the stream have. It isn't any
|
125
|
+
# serious problem, however when we have multiple files in ARGV, then the
|
126
|
+
# lineno isn't cleaned after the file in ARGF is switched.
|
127
|
+
# The trick with line.length == ARGF.pos is simple, IO#pos returns position
|
128
|
+
# of last character which was readed by gets or similar method, so if
|
129
|
+
# the position is same as is the line, then it has to be the first line.
|
130
|
+
if line.length == ARGF.pos && encoding && ! line.match(/^#.*coding/)
|
131
|
+
puts "# encoding: #{encoding}"
|
132
|
+
end
|
133
|
+
# You might be wondering WTF is going on, I'm editing files, so I have to use File.open, do the modification on the original content and then save it. The point is we used -i switch which is good for in place editing
|
134
|
+
# flag that we actually changed the file
|
135
|
+
status = 0 if line.match(/^\s*\t\s*/) || ! line.match(/\n/)
|
136
|
+
puts line.gsub(/\t/, " ").rstrip
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
exit status
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env gem build
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "code-cleaner"
|
6
|
+
s.version = "0.1"
|
7
|
+
s.authors = ["Jakub Šťastný aka Botanicus"]
|
8
|
+
s.homepage = "http://github.com/botanicus/code-cleaner"
|
9
|
+
s.summary = "Remove trailing whitespace, append missing \\n and replace tabs by two spaces"
|
10
|
+
s.description = "" # TODO: long description
|
11
|
+
s.cert_chain = nil
|
12
|
+
s.email = ["knava.bestvinensis", "gmail.com"].join("@")
|
13
|
+
s.has_rdoc = false
|
14
|
+
|
15
|
+
# so you can use load "code-cleaner.rake" in you Rakefile
|
16
|
+
s.require_paths = ["tasks"]
|
17
|
+
|
18
|
+
# files
|
19
|
+
s.files = Dir["**/*"]
|
20
|
+
s.executables = ["code-cleaner"]
|
21
|
+
s.default_executable = "code-cleaner"
|
22
|
+
|
23
|
+
# RubyForge
|
24
|
+
s.rubyforge_project = "code-cleaner"
|
25
|
+
end
|
data/support/pre-commit
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
echo "Entering pre-commit hook ..."
|
4
|
+
|
5
|
+
# If you haven't done any commit yet,
|
6
|
+
# git diff will fail with following message:
|
7
|
+
# fatal: No HEAD commit to compare with (yet)
|
8
|
+
|
9
|
+
# TODO: how to get list of files which will be
|
10
|
+
# committed if there isn't any commit in the index yet?
|
11
|
+
# For now we just use normalize everything in current
|
12
|
+
# directory, but it isn't very good solution.
|
13
|
+
files=$(git diff --staged --name-only 2> /dev/null || echo ".")
|
14
|
+
|
15
|
+
# NOTE: we are using printf rather than echo because printf should be
|
16
|
+
# more portable, each echo implementation has quite different behaviour
|
17
|
+
if which code-cleaner &> /dev/null; then
|
18
|
+
for file in $files; do
|
19
|
+
if code-cleaner "$file" --try-apply-rules &> /dev/null ; then
|
20
|
+
printf "Normalizing \"\e[36m$file\e[0m\" "
|
21
|
+
code-cleaner "$file" --apply-rules &> /dev/null
|
22
|
+
# NOTE: we have to check the exit status directly,
|
23
|
+
# so we can be sure that the command just wasn't found
|
24
|
+
if [ $? -eq 10 ]; then # code-cleaner exits with 0 if some changes were made
|
25
|
+
printf "[\e[32mCLEAN\e[0m]\n"
|
26
|
+
else
|
27
|
+
printf "[\e[33mDONE\e[0m]\n"
|
28
|
+
git add "$file" # so the changes will be committed immediately
|
29
|
+
fi
|
30
|
+
else
|
31
|
+
printf "Skipping \"\e[36m$file\e[0m\"\n"
|
32
|
+
fi
|
33
|
+
done
|
34
|
+
else
|
35
|
+
printf "[\e[31mERROR\e[0m] You have to install code-cleaner first!\n"
|
36
|
+
exit 1
|
37
|
+
fi
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "nake/task"
|
5
|
+
|
6
|
+
Nake::Task.new("hooks:whitespace:install") do |task|
|
7
|
+
task.description = "Install hook for automatically removing trailing whitespace"
|
8
|
+
task.define do
|
9
|
+
if File.exist?(".git/hooks/pre-commit")
|
10
|
+
abort "You must remove .git/hooks/pre-commit first!"
|
11
|
+
else
|
12
|
+
begin
|
13
|
+
puts "Installing .git/hooks/pre-commit ..."
|
14
|
+
source = File.join(File.dirname(__FILE__), "..", "support", "pre-commit")
|
15
|
+
FileUtils.install source, ".git/hooks/pre-commit", :mode => 0755
|
16
|
+
rescue Errno::ENOENT
|
17
|
+
abort "You have to run git init first!"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
desc "Install hook for automatically removing trailing whitespace"
|
4
|
+
task "hooks:whitespace:install" do
|
5
|
+
if File.exist?(".git/hooks/pre-commit")
|
6
|
+
abort "You must remove .git/hooks/pre-commit first!"
|
7
|
+
else
|
8
|
+
begin
|
9
|
+
puts "Installing .git/hooks/pre-commit ..."
|
10
|
+
source = File.join(File.dirname(__FILE__), "..", "support", "pre-commit")
|
11
|
+
install source, ".git/hooks/pre-commit", :mode => 0755
|
12
|
+
rescue Errno::ENOENT
|
13
|
+
abort "You have to run git init first!"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Hooks
|
6
|
+
class Whitespace < Thor
|
7
|
+
desc "install", "Install hook for automatically removing trailing whitespace"
|
8
|
+
def install
|
9
|
+
if File.exist?(".git/hooks/pre-commit")
|
10
|
+
abort "You must remove .git/hooks/pre-commit first!"
|
11
|
+
else
|
12
|
+
puts "Installing .git/hooks/pre-commit ..."
|
13
|
+
source = File.join(File.dirname(__FILE__), "..", "support", "pre-commit")
|
14
|
+
FileUtils.install source, ".git/hooks/pre-commit", :mode => 0755
|
15
|
+
end
|
16
|
+
rescue Errno::ENOENT
|
17
|
+
abort "You have to run git init first!"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: code-cleaner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Jakub \xC5\xA0\xC5\xA5astn\xC3\xBD aka Botanicus"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
date: 2009-12-28 00:00:00 +01:00
|
12
|
+
default_executable: code-cleaner
|
13
|
+
dependencies: []
|
14
|
+
|
15
|
+
description: ""
|
16
|
+
email: knava.bestvinensis@gmail.com
|
17
|
+
executables:
|
18
|
+
- code-cleaner
|
19
|
+
extensions: []
|
20
|
+
|
21
|
+
extra_rdoc_files: []
|
22
|
+
|
23
|
+
files:
|
24
|
+
- bin/code-cleaner
|
25
|
+
- code-cleaner-0.1.gem
|
26
|
+
- code-cleaner.gemspec
|
27
|
+
- LICENSE
|
28
|
+
- README.textile
|
29
|
+
- support/pre-commit
|
30
|
+
- tasks/code-cleaner.nake
|
31
|
+
- tasks/code-cleaner.rake
|
32
|
+
- tasks/code-cleaner.thor
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/botanicus/code-cleaner
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- tasks
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project: code-cleaner
|
57
|
+
rubygems_version: 1.3.5
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Remove trailing whitespace, append missing \n and replace tabs by two spaces
|
61
|
+
test_files: []
|
62
|
+
|