outlaw 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ .outlawed
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Brian Glusman
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.
@@ -0,0 +1,35 @@
1
+ #Outlaw
2
+
3
+ ##Keep bad code out of your projects. Your idea of bad code, no one elses.
4
+
5
+ ### Part of MendicantUniversity.org S10 class, personal project.
6
+ From the included outlawed.rb example file for custom rule definition:
7
+
8
+ module Outlaw
9
+ outlaw "@@", "Class variables are evil"
10
+ outlaw "protected", "use private or public, protected is silly in ruby"
11
+ outlaw "module :token end", "nest modules to avoid empty module declarations"
12
+ outlaw "eval", "never eval, rarely class_eval or instance_eval, but never eval"
13
+ end
14
+
15
+ ### Proposed syntax for DSL:
16
+
17
+ A defined collection exists for core classes, such that
18
+
19
+ outlaw "class :symbol < :core_class",
20
+ "core classes implemented in c, can cause bad mojo"
21
+
22
+ will outlaw subclassing from any core class
23
+
24
+
25
+ Users can extend new defined collections and by creating new constants
26
+ defined as arrays of variable names to match in example code the same
27
+ way :core_class is used above
28
+
29
+ Disjoin code segments can be provided as a single example (assuming they
30
+ occur in the same file) by inserting a :disjoint_code_seperator token in
31
+ the outlawed sample definition. I don't have a good use case for this
32
+ at present though, and since it will be difficult to handle disjoint
33
+ code across different files this may not be worthwhile... but similar
34
+ special case symbol meanings may be useful, and suggestions or examples
35
+ are welcome.
@@ -0,0 +1,4 @@
1
+ task :default => 'test'
2
+ task :test do
3
+ sh "ruby test/outlaw_test.rb"
4
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "outlaw"
4
+ load ".outlawed"
5
+
6
+ puts Outlaw.enforce(ARGV[0] ? ARGV[0] : ".")
@@ -0,0 +1,103 @@
1
+ ARGF
2
+ ArgumentError
3
+ Array
4
+ BasicObject
5
+ Bignum
6
+ Binding
7
+ Class
8
+ Comparable
9
+ Complex
10
+ Continuation
11
+ Data
12
+ Dir
13
+ ENV
14
+ EOFError
15
+ Encoding
16
+ Encoding::CompatibilityError
17
+ Encoding::Converter
18
+ Encoding::ConverterNotFoundError
19
+ Encoding::InvalidByteSequenceError
20
+ Encoding::UndefinedConversionError
21
+ EncodingError
22
+ Enumerable
23
+ Enumerator
24
+ Enumerator::Generator
25
+ Enumerator::Yielder
26
+ Errno
27
+ Exception
28
+ FalseClass
29
+ Fiber
30
+ FiberError
31
+ File
32
+ File::Constants
33
+ File::Stat
34
+ FileTest
35
+ Fixnum
36
+ Float
37
+ FloatDomainError
38
+ GC
39
+ GC::Profiler
40
+ Hash
41
+ IO
42
+ IO::WaitReadable
43
+ IO::WaitWritable
44
+ IOError
45
+ IndexError
46
+ Integer
47
+ Interrupt
48
+ Kernel
49
+ KeyError
50
+ LoadError
51
+ LocalJumpError
52
+ Marshal
53
+ MatchData
54
+ Math
55
+ Math::DomainError
56
+ Method
57
+ Module
58
+ Mutex
59
+ NameError
60
+ NilClass
61
+ NoMemoryError
62
+ NoMethodError
63
+ NotImplementedError
64
+ Numeric
65
+ Object
66
+ ObjectSpace
67
+ Proc
68
+ Process
69
+ Process::GID
70
+ Process::Status
71
+ Process::Sys
72
+ Process::UID
73
+ Random
74
+ Range
75
+ RangeError
76
+ Rational
77
+ Regexp
78
+ RegexpError
79
+ RubyVM
80
+ RubyVM::Env
81
+ RubyVM::InstructionSequence
82
+ RuntimeError
83
+ ScriptError
84
+ SecurityError
85
+ Signal
86
+ SignalException
87
+ StandardError
88
+ StopIteration
89
+ String
90
+ Struct
91
+ Symbol
92
+ SyntaxError
93
+ SystemCallError
94
+ SystemExit
95
+ SystemStackError
96
+ Thread
97
+ ThreadError
98
+ ThreadGroup
99
+ Time
100
+ TrueClass
101
+ TypeError
102
+ UnboundMethod
103
+ ZeroDivisionError
@@ -0,0 +1,5 @@
1
+ outlaw "@@", "class variables are evil"
2
+ outlaw "protected", "use private or public, protected is silly in ruby"
3
+ outlaw "module :token end", "nest modules to avoid empty module declarations"
4
+ outlaw "eval", "never eval, rarely class_eval or instance_eval, but never eval"
5
+ outlaw "class :symbol < :core_class", "core classes implemented in c, can cause bad mojo"
@@ -0,0 +1,20 @@
1
+ require 'ripper'
2
+ require_relative 'outlaw/law_dsl'
3
+ require_relative 'outlaw/enforcement'
4
+ require_relative 'outlaw/rule'
5
+
6
+ def outlaw(restriction, message)
7
+ law = Outlaw::LawDSL.parse(restriction, message)
8
+ Outlaw::Enforcement.add(law)
9
+ end
10
+
11
+ module Outlaw
12
+ def self.enforce(dir=".")
13
+ Outlaw::Enforcement.process_directory(dir)
14
+ end
15
+ PARAM_TYPES = [:on_const, :on_ident, :on_ivar, :on_cvar]
16
+ IGNORE_TYPES = [:on_sp, :on_nl, :on_ignored_nl, :on_rparen, :on_lparen]
17
+ SPECIAL_CASES = [:disjoint_code_seperator] #need to work on naming here
18
+ CORE_CLASSES_FILE = File.expand_path("../../data/core_classes.txt", __FILE__)
19
+ CORE_CLASS = File.readlines(CORE_CLASSES_FILE).map &:chomp
20
+ end
@@ -0,0 +1,34 @@
1
+ module Outlaw
2
+ class Enforcement
3
+ class << self
4
+ attr_reader :laws
5
+ def add(law)
6
+ @laws ||= []
7
+ @laws << law
8
+ end
9
+
10
+ def process_directory(path)
11
+ Dir.foreach(path) do |entry|
12
+ next if entry == '.' or entry == '..'
13
+ if File.directory?("#{path}/#{entry}")
14
+ process_directory("#{path}/#{entry}")
15
+ else
16
+ handle("#{path}/#{entry}")
17
+ end
18
+ end
19
+ end
20
+
21
+ def handle(file)
22
+ if file.match(/.rb$/)
23
+ text = File.open(file) {|f| f.read}
24
+ laws.each do |law|
25
+ if law.call(text)
26
+ puts "Outlaw Violation in file: #{file}\nRestriction:" +
27
+ "#{law.restriction}\n\n#{law.message}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,69 @@
1
+ module Outlaw
2
+ module LawDSL
3
+ class << self
4
+ def parse(restriction, message="")
5
+ tokens = restriction.split
6
+ parsed_restriction = []
7
+ tokens.each do |token|
8
+ case
9
+ when special_case?(token)
10
+ next #TODO - handle AST branches/disjoint code/meta-symbols to be defined
11
+ when multipart?(token) #this handles multi-token literals, Const.new etc
12
+ parsed_restriction += Ripper.lex(token)
13
+ .reduce([]){|array, tkn|
14
+ array << token_type_regex(tkn) }
15
+ when defined_collection?(token)
16
+ parsed_restriction << Outlaw.const_get(string_to_sym(token.upcase))
17
+ when parameter?(token)
18
+ parsed_restriction << string_to_sym(token)
19
+ else
20
+ parsed_restriction += build_regex(token)
21
+ end
22
+ end
23
+ Rule.new(message, restriction, &build_block(parsed_restriction))
24
+ end
25
+
26
+ private
27
+
28
+ def token_type_regex(token)
29
+ /#{token[2]}/
30
+ end
31
+
32
+ def parameter?(token)
33
+ token[0].chr == ':'
34
+ end
35
+
36
+ def special_case?(token)
37
+ SPECIAL_CASES.include? token
38
+ end
39
+
40
+ def defined_collection?(token)
41
+ parameter?(token) && Outlaw.const_defined?(string_to_sym(token.upcase))
42
+ end
43
+
44
+ def string_to_sym(str)
45
+ str[1..-1].to_sym
46
+ end
47
+
48
+ def build_regex(token)
49
+ #fully expect this hack to come back & haunt me, but passes curr. examples
50
+ [/\A#{token}/]
51
+ end
52
+
53
+ def multipart?(token)
54
+ !parameter?(token) && Ripper.lex(token).count > 1
55
+ end
56
+
57
+ def build_block(pattern)
58
+ lambda do |file|
59
+ program = Ripper.tokenize(file)
60
+ program.each_with_index do |token, index|
61
+ next unless token.match(pattern.first)
62
+ return true if Rule.test(program, index, pattern)
63
+ end
64
+ return false
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,85 @@
1
+ module Outlaw
2
+ class Rule
3
+ NoDetectionBlockProvided = Class.new(StandardError)
4
+ attr_reader :message, :restriction
5
+ def initialize(message, restriction, &detection_block)
6
+ raise NoDetectionBlockProvided unless detection_block
7
+ @message = message
8
+ @restriction = restriction
9
+ @detection_block = detection_block
10
+ end
11
+
12
+ def call(code)
13
+ @detection_block.call(code)
14
+ end
15
+
16
+ class << self
17
+ def test(program, start_index, pattern)
18
+ pattern_index = 0
19
+ params = params_count_hash(pattern)
20
+ start_index.upto(program.length) do |index|
21
+ code = program[index]
22
+ part = pattern[pattern_index]
23
+
24
+ next if IGNORE_TYPES.include? token_type(code)
25
+ return false unless match_token?(code, part, params[part])
26
+ pattern_index +=1
27
+ return true if pattern_index >= pattern.length
28
+ end
29
+
30
+ return false
31
+ # got to end of program without completing pattern
32
+ end
33
+
34
+ private
35
+
36
+ def match_token?(code, part, parameter)
37
+ case part
38
+ when Array
39
+ match = true if part.include?(code)
40
+ when Regexp
41
+ match = true if code.match(part)
42
+ when Symbol
43
+ return false unless param_type_equal(token_type(code), part)
44
+ #check count on first and count down subseq matches
45
+ if parameter.first.nil? #history of parameter match if any
46
+ parameter[0] = code
47
+ parameter[1] -= 1 #first occurrence of parameter
48
+ match = true
49
+ else
50
+ if parameter.first == code
51
+ parameter[1] -= 1
52
+ match = true
53
+ else
54
+ match = false
55
+ end
56
+ end
57
+ else
58
+ match = false
59
+ end
60
+
61
+ match
62
+ end
63
+
64
+
65
+ def param_type_equal(lex, param)
66
+ #for now just check if it's a variable type, not kw, ws or other token
67
+ PARAM_TYPES.include? lex
68
+ end
69
+
70
+ def token_type(code)
71
+ Ripper.lex(code).flatten(1)[1] #Ripper's name for token type is returned in array
72
+ end
73
+
74
+ def params_count_hash(pattern)
75
+ params = Hash.new(0)
76
+ pattern.each do |element|
77
+ params[element] += 1 if element.respond_to?(:to_sym)
78
+ end
79
+ output = {} #nil is placeholder for matched lex code
80
+ params.keys.each {|key| output[key] = [nil, params[key]]}
81
+ output
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ module Outlaw
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+ require "outlaw/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "outlaw"
8
+ s.version = Outlaw::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Brian Glusman"]
11
+ s.email = ["brian@neomindlabs.com"]
12
+ s.homepage = "https://github.com/bglusman/Outlaw"
13
+ s.summary = "Outlaw helps you enforce your opinions to keep bad code out your projects."
14
+ s.rubyforge_project = "outlaw"
15
+
16
+ s.description = <<-DESC
17
+ Keep bad code out of your projects. Your idea of bad code, no one elses.
18
+
19
+ Outlaw defines an example based DSL for demonstrating anti-patterns and
20
+ builds a rule for each anti-pattern that it alerts the user to violations
21
+ when encountered in a project's codebase during scanning.
22
+
23
+ Outlaw is a work in progress and contributions, suggestions and forks are welcome.
24
+ Outlaw was a personal project for Mendicant University, Session 10 in Jan '12
25
+ DESC
26
+
27
+
28
+ s.files = `git ls-files`.split("\n")
29
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
30
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
31
+ s.require_paths = ["lib"]
32
+
33
+ s.add_dependency "ripper-plus", "~> 1.3.0"
34
+
35
+ s.add_development_dependency "rake", "~> 0.9.0"
36
+ s.add_development_dependency "minitest"
37
+ end
@@ -0,0 +1,181 @@
1
+ require_relative 'test_helper'
2
+
3
+ module Outlaw
4
+ describe LawDSL do
5
+ it "returns a Rule which is called on code and returns true or false" do
6
+ end
7
+
8
+ before do
9
+ @okay_file = <<CODE
10
+ class Whatever < Set
11
+ def okaything(here)
12
+ @here = here
13
+ end
14
+ end
15
+
16
+ module WithContent
17
+ def sumthin
18
+ class_eval('1 + 1')
19
+ end
20
+ end
21
+ CODE
22
+
23
+ end
24
+
25
+
26
+
27
+ it "correctly builds rule for class var" do
28
+ code1 = "@@"
29
+ rule1 = LawDSL.parse(code1)
30
+
31
+ class_var_file = <<CODE
32
+ def badthing(here)
33
+ @@here = here
34
+ end
35
+ CODE
36
+
37
+ class_result = rule1.call(class_var_file)
38
+ result1a = rule1.call(@okay_file)
39
+ class_result .must_equal true
40
+ end
41
+
42
+ it "correctly builds rule for protected" do
43
+ code2 = "protected"
44
+ rule2 = LawDSL.parse(code2)
45
+
46
+ protected_file = <<CODE
47
+ class Whatever
48
+ protected
49
+ def not_really
50
+ :false
51
+ end
52
+ end
53
+ CODE
54
+
55
+ protected_result= rule2.call(protected_file)
56
+ result2a = rule2.call(@okay_file)
57
+
58
+ protected_result.must_equal true
59
+ result2a .must_equal false
60
+ end
61
+
62
+
63
+ it "correctly builds rule for eval" do
64
+ code3 = "eval"
65
+ rule3 = LawDSL.parse(code3)
66
+
67
+ eval_file = <<CODE
68
+ def not_really
69
+ eval('1 + 1')
70
+ end
71
+ CODE
72
+
73
+ eval_result = rule3.call(eval_file)
74
+ result3a = rule3.call(@okay_file)
75
+
76
+ eval_result.must_equal true
77
+ result3a.must_equal false
78
+ end
79
+
80
+
81
+ it "correctly builds rule for module" do
82
+ code4 = "module :name end"
83
+ rule4 = LawDSL.parse(code4)
84
+
85
+ module_file = <<CODE
86
+ module Thing
87
+ end
88
+ CODE
89
+
90
+ module_result = rule4.call(module_file)
91
+ result4a = rule4.call(@okay_file)
92
+
93
+ module_result.must_equal true
94
+ result4a.must_equal false
95
+ end
96
+
97
+ it "correctly builds rule for core" do
98
+ code5 = "class :symbol < :core_class"
99
+ rule5 = LawDSL.parse(code5)
100
+
101
+ core_file = <<CODE
102
+ class Whatever < String
103
+ def badthing(here)
104
+ @here = here
105
+ end
106
+ end
107
+ CODE
108
+ core_result = rule5.call(core_file)
109
+ result5a = rule5.call(@okay_file)
110
+ core_result .must_equal true
111
+ result5a.must_equal false
112
+ end
113
+
114
+
115
+ # it "correctly builds rule for unless else" do
116
+ # code6 = "unless :symbols
117
+ # :disjoint_code_seperator
118
+ # else :more_symbols"
119
+ # rule6 = LawDSL.parse(code6)
120
+
121
+ # core_file = <<CODE
122
+ # class Whatever < String
123
+ # def badthing(here)
124
+ # @here = here
125
+ # end
126
+ # end
127
+ # CODE
128
+
129
+ it "correctly builds rule for rescue nil" do
130
+ code7 = "rescue nil"
131
+ rule7 = LawDSL.parse(code7)
132
+
133
+ nil_file = <<CODE
134
+ begin
135
+ "hi"
136
+ rescue nil
137
+ "bye"
138
+ end
139
+ CODE
140
+ nil_result = rule7.call(nil_file)
141
+ result7a = rule7.call(@okay_file)
142
+ nil_result .must_equal true
143
+ result7a.must_equal false
144
+ end
145
+
146
+ it "correctly builds rule for inherit struct.new" do
147
+ code8 = "class :symbol < Struct.new"
148
+ rule8 = LawDSL.parse(code8)
149
+
150
+ struct_file = <<CODE
151
+ class MyClass < Struct.new("Customer", :name, :address)
152
+ CODE
153
+
154
+ struct_result = rule8.call(struct_file)
155
+ result8a = rule8.call(@okay_file)
156
+
157
+ struct_result.must_equal true
158
+ result8a.must_equal false
159
+ end
160
+
161
+
162
+ # end
163
+
164
+ it "returns a hash with key counts and nil placeholders" do
165
+ params = Rule.send(:params_count_hash, [/module/, :token1, :token2, :token1, /class/, :token3, /end/])
166
+ params.keys.size.must_equal 3
167
+ params[:token1].last.must_equal 2
168
+ end
169
+
170
+ it "returns a hash with key counts and nil placeholders" do
171
+ params = Rule.send(:params_count_hash, [/module/, :token1, :token2, :token1, /class/, :token3, /end/])
172
+ params.keys.size.must_equal 3
173
+ params[:token1].last.must_equal 2
174
+ end
175
+
176
+ it "returns a block from build_block method" do
177
+ block = LawDSL.send(:build_block,"@@")
178
+ assert_kind_of Proc, block
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,3 @@
1
+ require_relative '../lib/outlaw'
2
+ require 'minitest/spec'
3
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: outlaw
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Glusman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ripper-plus
16
+ requirement: &70327141430060 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70327141430060
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70327141429400 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70327141429400
36
+ - !ruby/object:Gem::Dependency
37
+ name: minitest
38
+ requirement: &70327141427520 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70327141427520
47
+ description: ! " Keep bad code out of your projects. Your idea of bad code, no
48
+ one elses.\n\n Outlaw defines an example based DSL for demonstrating anti-patterns
49
+ and\n builds a rule for each anti-pattern that it alerts the user to violations\n
50
+ \ when encountered in a project's codebase during scanning.\n\n Outlaw
51
+ is a work in progress and contributions, suggestions and forks are welcome.\n Outlaw
52
+ was a personal project for Mendicant University, Session 10 in Jan '12\n"
53
+ email:
54
+ - brian@neomindlabs.com
55
+ executables:
56
+ - outlaw
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - .gitignore
61
+ - Gemfile
62
+ - LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - bin/outlaw
66
+ - data/core_classes.txt
67
+ - examples/.outlawed.example
68
+ - lib/outlaw.rb
69
+ - lib/outlaw/enforcement.rb
70
+ - lib/outlaw/law_dsl.rb
71
+ - lib/outlaw/rule.rb
72
+ - lib/outlaw/version.rb
73
+ - outlaw.gemspec
74
+ - test/outlaw_test.rb
75
+ - test/test_helper.rb
76
+ homepage: https://github.com/bglusman/Outlaw
77
+ licenses: []
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project: outlaw
96
+ rubygems_version: 1.8.11
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Outlaw helps you enforce your opinions to keep bad code out your projects.
100
+ test_files: []