diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2cbb17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/target +/lib +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +.lein-deps-sum +.lein-failures +.lein-plugins +.lein-repl-history + +.nrepl-port diff --git a/README.md b/README.md new file mode 100644 index 0000000..48bb140 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# abc + +A Clojure library designed to ... well, that part is up to you. + +## Usage + +FIXME + +## License + +Copyright © 2014 FIXME + +Distributed under the Eclipse Public License, the same as Clojure. diff --git a/doc/intro.md b/doc/intro.md new file mode 100644 index 0000000..0edf1a7 --- /dev/null +++ b/doc/intro.md @@ -0,0 +1,3 @@ +# Introduction to abc + +TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..407bf16 --- /dev/null +++ b/project.clj @@ -0,0 +1,7 @@ +(defproject abc "0.1.0-SNAPSHOT" + :description "FIXME: write description" + :url "http://example.com/FIXME" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies [[org.clojure/clojure "1.5.1"] + [org.jruby/jrubyparser "0.5.3"]]) diff --git a/src/abc/core.clj b/src/abc/core.clj new file mode 100644 index 0000000..b85c071 --- /dev/null +++ b/src/abc/core.clj @@ -0,0 +1,11 @@ +(ns abc.core) + +(defn metric-result [rs] + (fn [metric] + (let [metric-result (get-in rs [:metrics metric] 0)] + (* metric-result metric-result)))) + +(defn abc [rs] + (Math/sqrt (apply + + (map (metric-result rs) + [:branches :assignments :conditionals])))) diff --git a/src/abc/extractors/ruby.clj b/src/abc/extractors/ruby.clj new file mode 100644 index 0000000..9942abb --- /dev/null +++ b/src/abc/extractors/ruby.clj @@ -0,0 +1,69 @@ +(ns abc.extractors.ruby + (:import (org.jrubyparser Parser CompatVersion) + (org.jrubyparser.parser ParserConfiguration) + (java.io StringReader))) + +(defn parse-ruby [rb] + (let [parser (Parser.) + config (ParserConfiguration. 0 CompatVersion/RUBY1_9)] + (.parse parser "" (StringReader. rb) config))) + +(defn- children? [_] true) +(defn- children [n] + (.childNodes n)) +(defn- make-tree [root] + (tree-seq children? children root)) + +(defn- defn-node? [n] + (instance? org.jrubyparser.ast.DefnNode n)) +(defn- assignment-node? [n] + (instance? org.jrubyparser.ast.LocalAsgnNode n)) +(defn- conditional-node? [n] + (instance? org.jrubyparser.ast.IfNode n)) +(defn- branch-node? [n] + (instance? org.jrubyparser.ast.FCallNode n)) + +(defn- filter-nodes [root f] + (loop [tree root] + (cond (empty? tree) '() + (f (first tree)) (cons (first tree) (filter-nodes (rest tree) f)) + (seq? (first tree)) (concat (filter-nodes (first tree) f) + (filter-nodes (rest tree) f)) + :else (recur (rest tree))))) + +(defn- line-number [n] + (inc (.. n getPosition getEndLine))) + +(defn- line-range [n] + (let [source-position (.getPosition n)] + [(.getStartOffset source-position) + (.getEndOffset source-position)])) + +(defn- extract [f] + (fn [root] + (let [nodes (filter-nodes (make-tree root) f)] + (when-not (empty? nodes) + (map (fn [n] + {:line (line-number n) :range (line-range n)}) + nodes))))) + +(defn- ruby-source [rb [start end]] + (apply str (take (- end start) (drop start rb)))) + +(defn- with-source [rb & fks] + (fn [root] + (reduce (fn [e [f k]] + (assoc e k + (map (fn [env] + (assoc env :source (ruby-source rb (:range env)))) + ((extract f) root)))) + {} + fks))) + +(defn parse [rb] + (let [root (parse-ruby rb) + defn-nodes (filter-nodes (make-tree root) defn-node?)] + (map (with-source rb [assignment-node? :assignments] + [conditional-node? :conditionals] + [branch-node? :branches]) + defn-nodes))) diff --git a/test/abc/core_test.clj b/test/abc/core_test.clj new file mode 100644 index 0000000..0731f0f --- /dev/null +++ b/test/abc/core_test.clj @@ -0,0 +1,10 @@ +(ns abc.core-test + (:require [clojure.test :refer :all] + [abc.core :refer :all])) + +(deftest abc-test + (testing "metric" + (is (= (abc {:method "foo" :metrics {:conditionals 2 + :branches 5 + :assignments 10}})) + 11))) diff --git a/test/abc/extractors/ruby_test.clj b/test/abc/extractors/ruby_test.clj new file mode 100644 index 0000000..ceb184d --- /dev/null +++ b/test/abc/extractors/ruby_test.clj @@ -0,0 +1,23 @@ +(ns abc.extractors.ruby-test + (:require [clojure.test :refer :all] + [abc.extractors.ruby :refer :all])) + +(def ruby +"def foo + bar = 3 + wat if condition + method_call() +end") + +(deftest ruby-extractor-test + (testing "assignment" + (is (= [{:line 2 :range [10 17] :source "bar = 3"}] + (:assignments (first (parse ruby)))))) + + (testing "conditional" + (is (= [{:line 3 :range [20 36] :source "wat if condition"}] + (:conditionals (first (parse ruby)))))) + + (testing "branching" + (is (= [{:line 4 :range [39 52] :source "method_call()"}] + (:branches (first (parse ruby)))))))