outlaw 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.
@@ -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: []