Commit 697dcc0

mokha <mokha@cisco.com>
2019-05-10 17:02:33
start to build parser for scim filter queries
1 parent 7eb9bc3
Changed files (4)
lib/scim/kit/v2/parser.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+require 'parslet'
+
+module Scim
+  module Kit
+    module V2
+=begin
+FILTER    = attrExp / logExp / valuePath / *1"not" "(" FILTER ")"
+
+valuePath = attrPath "[" valFilter "]"
+           ; FILTER uses sub-attributes of a parent attrPath
+
+valFilter = attrExp / logExp / *1"not" "(" valFilter ")"
+
+attrExp   = (attrPath SP "pr") /
+           (attrPath SP compareOp SP compValue)
+
+logExp    = FILTER SP ("and" / "or") SP FILTER
+
+compValue = false / null / true / number / string
+           ; rules from JSON (RFC 7159)
+
+compareOp = "eq" / "ne" / "co" /
+                  "sw" / "ew" /
+                  "gt" / "lt" /
+                  "ge" / "le"
+
+attrPath  = [URI ":"] ATTRNAME *1subAttr
+           ; SCIM attribute name
+           ; URI is SCIM "schema" URI
+
+ATTRNAME  = ALPHA *(nameChar)
+
+nameChar  = "-" / "_" / DIGIT / ALPHA
+
+subAttr   = "." ATTRNAME
+           ; a sub-attribute of a complex attribute
+=end
+      class Parser < Parslet::Parser
+        rule(:lparen) { str('(') >> space? }
+        rule(:rparen) { str(')') >> space? }
+        rule(:digit) { match(/\d/) }
+        rule(:quote) { str('"') }
+        rule(:space) { match('\s').repeat(1) }
+        rule(:space?) { space.maybe }
+
+        rule(:attribute) { match['a-zA-Z\.'].repeat(1) }
+
+        rule(:operator) { equal | not_equal | contains | starts_with | ends_with | greater_than | less_than | less_than_equals | greater_than_equals }
+        rule(:equal) { str("eq") }
+        rule(:not_equal) { str("ne") }
+        rule(:contains) { str("co") }
+        rule(:starts_with) { str("sw") }
+        rule(:ends_with) { str("ew") }
+        rule(:greater_than) { str("gt") }
+        rule(:less_than) { str("lt") }
+        rule(:greater_than_equals) { str("ge") }
+        rule(:less_than_equals) { str("le") }
+
+        rule(:quoted_value) { quote >> value.as(:right) >> quote }
+        rule(:value) { match(/[a-zA-Z0-9:\-]/).repeat(1) }
+
+        rule(:filter) { attribute.as(:left) >> space >> operator.as(:operator) >> space >> quoted_value }
+        root :filter
+
+        def pretty_parse(*args)
+          parse(*args)
+        rescue Parslet::ParseFailed => error
+          puts error.parse_failure_cause.ascii_tree
+        end
+      end
+    end
+  end
+end
lib/scim/kit/v2.rb
@@ -11,6 +11,7 @@ require 'scim/kit/v2/meta'
 require 'scim/kit/v2/mutability'
 require 'scim/kit/v2/resource'
 require 'scim/kit/v2/error'
+require 'scim/kit/v2/parser'
 require 'scim/kit/v2/resource_type'
 require 'scim/kit/v2/returned'
 require 'scim/kit/v2/schema'
spec/scim/kit/v2/parser_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+RSpec.describe Scim::Kit::V2::Parser do
+  subject { described_class.new }
+
+  context "simple expression" do
+    let(:expression) { %Q(userName eq "bjensen") }
+    let(:result) { subject.pretty_parse(expression) }
+
+    specify { expect(result).to be_present }
+    specify { expect(result[:left].to_s).to eql('userName') }
+    specify { expect(result[:operator].to_s).to eql('eq') }
+    specify { expect(result[:right].to_s).to eql('bjensen') }
+  end
+end
scim-kit.gemspec
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
 
   spec.add_dependency 'activemodel', '>= 5.2.0'
   spec.add_dependency 'net-hippie', '~> 0.2'
+  spec.add_dependency 'parslet', '~> 1.8'
   spec.add_dependency 'tilt', '~> 2.0'
   spec.add_dependency 'tilt-jbuilder', '~> 0.7'
   spec.add_development_dependency 'bundler', '~> 1.17'