summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorYves Fischer <yvesf-git@xapek.org>2016-01-26 00:59:23 +0100
committerYves Fischer <yvesf-git@xapek.org>2016-01-26 00:59:23 +0100
commit7b83533249d6cbb20e5491a3f11f0305c645ec20 (patch)
treece498921b74553e64aff254fa6202b681d9cfb4b /src/main
parent08b882f4cf284bed3f8f80f6e47d37037753a22b (diff)
downloadglocals-classifieds-7b83533249d6cbb20e5491a3f11f0305c645ec20.tar.gz
glocals-classifieds-7b83533249d6cbb20e5491a3f11f0305c645ec20.zip
cleanup
Diffstat (limited to 'src/main')
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/ClassifiedParserMain.groovy87
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy6
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/Main.groovy65
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy84
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy31
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.groovy14
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.java19
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/util/ParserStaticMethods.groovy43
-rw-r--r--src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy34
9 files changed, 199 insertions, 184 deletions
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/ClassifiedParserMain.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/ClassifiedParserMain.groovy
deleted file mode 100644
index 499a37a..0000000
--- a/src/main/groovy/org/xapek/yvesf/classifieds/ClassifiedParserMain.groovy
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.xapek.yvesf.classifieds
-
-import groovy.json.JsonSlurper
-import groovyx.net.http.HTTPBuilder
-import org.apache.http.HttpResponse
-import org.xapek.yvesf.classifieds.util.Fail
-import org.xapek.yvesf.classifieds.util.Failable
-import org.xapek.yvesf.classifieds.util.Success
-
-class ClassifiedParserMain {
- static final url = 'http://www.glocals.com/classifieds/housing-and-real-estate/&get_classified_flats'
-
- static void main(String[] args) {
- handleData(args.length == 1 ? readJsonFile(args.first()) : readNetwork()).with {
- if (it instanceof Fail) {
- println "Failed: ${it.error}"
- System.exit(1)
- } else {
- Dumper.dump(it.value, new PrintWriter(System.out))
- System.exit(0)
- }
- }
- }
-
- static Object readJsonFile(String f) {
- final jsonParser = new JsonSlurper()
- return jsonParser.parse(new File(f))
- }
-
- static Object readNetwork() {
- final http = new HTTPBuilder(url)
- final jsonParser = new JsonSlurper()
- http.post(
- body: ['start': '20', 'limit': '20', 'form[bl_city_network]': '-1'],
- headers: ['User-Agent': 'Mozilla/5.0 Firefox/3.0.4',
- 'Accept' : 'application/json']) { HttpResponse resp ->
- assert resp.statusLine.statusCode < 300 && resp.statusLine.statusCode >= 200:
- "HTTP Request failed with status code ${resp.statusLine.statusCode}"
- return jsonParser.parse(resp.entity.content)
- } as Object
- }
-
- private static Failable<Model.ClassifiedList> handleData(Object p) {
- expectMap(p) << { Map m ->
- getOrFail(m, 'totalCount', Number) << { Number totalCount ->
- getOrFail(m, 'classifieds', List) << { List rawClassifieds ->
- all(rawClassifieds.collect { handleClassified(it) }) << { List<Model.Classified> classifieds ->
- final Model.ClassifiedList classifiedList = new Model.ClassifiedList(totalCount: totalCount)
- classifiedList.addAll(classifieds)
- return new Success(classifiedList)
- }
- }
- }
- }
- }
-
- private static Failable<Model.Classified> handleClassified(Object p) {
- expectMap(p) << { Map map ->
- return new Success(new Model.Classified(map)) // here I cheat a bit
- }
- }
-
- static <T> Failable<List<T>> all(Collection<Failable<T>> values) {
- if (values.every { it.success }) {
- return new Success<List<T>>(values.collect { it.value })
- } else {
- return new Fail("Not all values are successful, first error: ${values.find { !it.success }.error}")
- }
- }
-
- static Failable<Map> expectMap(Object data) {
- if (data instanceof Map) return new Success(data)
- else return new Fail('object is not instanceof Map')
- }
-
- static <T> Failable<T> getOrFail(Map map, String key, Class<T> clazz = Object) {
- if (!map.containsKey(key)) return new Fail('key not in map')
- else {
- final x = map.get(key)
- if (clazz.isAssignableFrom(x.getClass())) {
- return new Success<T>(x as T)
- } else {
- return new Fail("Key ${key} found but not of type ${clazz}")
- }
- }
- }
-}
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy
index 884166d..5fb0568 100644
--- a/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy
+++ b/src/main/groovy/org/xapek/yvesf/classifieds/Dumper.groovy
@@ -1,11 +1,11 @@
package org.xapek.yvesf.classifieds
import groovy.xml.MarkupBuilder
-import org.xapek.yvesf.classifieds.Model.ClassifiedList
+import org.xapek.yvesf.classifieds.Model.ClassifiedsList
class Dumper {
- static dump(ClassifiedList classifiedList, PrintWriter writer) {
+ static dump(ClassifiedsList classifiedList, PrintWriter writer) {
new MarkupBuilder(writer).root {
rss(version: '2.0') {
channel {
@@ -15,7 +15,7 @@ class Dumper {
item {
title("${classified.type} - ${classified.title} - ${classified.composedLocation}")
guid(classified.id)
- author(classified.memberName)
+ author(classified.mem_name)
description(classified.description)
}
}
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/Main.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/Main.groovy
new file mode 100644
index 0000000..23fb434
--- /dev/null
+++ b/src/main/groovy/org/xapek/yvesf/classifieds/Main.groovy
@@ -0,0 +1,65 @@
+package org.xapek.yvesf.classifieds
+
+import groovy.json.JsonSlurper
+import groovy.transform.CompileStatic
+import groovyx.net.http.HTTPBuilder
+import org.apache.http.HttpResponse
+import org.xapek.yvesf.classifieds.util.Failable
+
+import static org.xapek.yvesf.classifieds.util.ParserStaticMethods.*
+
+@CompileStatic
+final class Main {
+ private final static JsonSlurper jsonParser = new JsonSlurper()
+
+ private final static url = 'http://www.glocals.com/classifieds/housing-and-real-estate/&get_classified_flats'
+
+ static void main(String[] args) {
+ final data = handleData(
+ args.length == 1 ?
+ readInputStream(new FileInputStream(args[0])) : readNetwork());
+ if (data.isSuccess()) {
+ Dumper.dump(data.getValue(), new PrintWriter(System.out));
+ System.exit(0);
+ } else {
+ System.out.println("Failed: " + data.errorTrace.join(' -> '))
+ System.exit(1);
+ }
+ }
+
+ static Object readInputStream(InputStream is) {
+ return jsonParser.parse(is)
+ }
+
+ static Object readNetwork() {
+ final http = new HTTPBuilder(url)
+ http.post(
+ body: ['start': '20', 'limit': '20', 'form[bl_city_network]': '-1'],
+ headers: ['User-Agent': 'Mozilla/5.0 Firefox/3.0.4',
+ 'Accept' : 'application/json']) { HttpResponse resp ->
+ assert resp.statusLine.statusCode < 300 && resp.statusLine.statusCode >= 200:
+ "HTTP Request failed with status code ${resp.statusLine.statusCode}"
+ return jsonParser.parse(resp.entity.content)
+ } as Object
+ }
+
+ static Failable<Model.ClassifiedsList> handleData(Object object) {
+ ifType(object, Map) then { Map m ->
+ ifField(m, 'totalCount', Number) then { Number totalCount ->
+ ifField(m, 'classifieds', List) then { List rawClassifieds ->
+ all(rawClassifieds.collect { handleClassified(it) }) then { List<Model.Classified> classifieds ->
+ final classifiedList = new Model.ClassifiedsList(totalCount: totalCount)
+ classifiedList.addAll(classifieds)
+ success(classifiedList)
+ }
+ }
+ }
+ }
+ }
+
+ private static Failable<Model.Classified> handleClassified(Object p) {
+ ifType(p, Map) then { Map map ->
+ success(new Model.Classified(map))
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy
index ee0b11d..cd62c25 100644
--- a/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy
+++ b/src/main/groovy/org/xapek/yvesf/classifieds/Model.groovy
@@ -1,5 +1,8 @@
package org.xapek.yvesf.classifieds
+import groovy.transform.CompileStatic
+
+@CompileStatic
class Model {
static class Classified {
//0 = available" -> "Flexible"
@@ -32,67 +35,38 @@ class Model {
//27 ="title_js" -> "Fully furnished house of 270 sqm and huge terrace of 90 sqm"
//28 ="type" -> "Apts / Housing for rent"
//29 ="views" -> "8"
- private Map map
-
- Classified(Map m) {
- this.@map = m
- }
-
- Map getMap() {
- return map
+ String available
+ String location
+ String city
+ String type
+ String price
+ String currency
+ String views
+ String title
+ String id
+ String mem_name
+ String description
+
+ Classified(Map map) {
+ metaClass.properties.each { MetaProperty property ->
+ if (map.containsKey(property.name)) {
+ final value = map.get(property.name)
+ if (property.type.isAssignableFrom(value.getClass())) {
+ property.setProperty(this, value)
+ } else {
+ throw new IllegalArgumentException("Cannot set field ${property.name} with object of type ${value.getClass()}")
+ }
+ }
+ }
}
String getComposedLocation() {
- return "${map.get('location')}, ${map.get('city')}"
- }
-
- String getType() {
- return map.get('type') as String
- }
-
- String getAvailable() {
- return map.get('available') as String
- }
-
- String getCity() {
- return map.get('city')
- }
-
- String getPrice() {
- return map.get('price') as String
- }
-
- String getCurrency() {
- return map.get('currency') as String
- }
-
- String getViews() {
- return map.get('views') as String
- }
-
- String getTitle() {
- return map.get('title') as String
- }
-
- String getId() {
- return map.get('id') as String
- }
-
- String getMemberName() {
- return map.get('mem_name') as String
- }
-
- String getDescription() {
- return map.get('description') as String
+ return "${location}, ${city}"
}
}
- static class ClassifiedList extends ArrayList<Classified> {
- private long totalCount
-
- void setTotalCount(long totalCount) {
- this.@totalCount = totalCount
- }
+ static class ClassifiedsList extends ArrayList<Classified> {
+ long totalCount
@Override
String toString() {
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy
index c58d6ef..b514385 100644
--- a/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy
+++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/Fail.groovy
@@ -3,19 +3,15 @@ package org.xapek.yvesf.classifieds.util
import groovy.transform.CompileStatic
import groovy.transform.ToString
-
@CompileStatic
-@Newify
@ToString(includeFields = true, includeNames = true)
-class Fail<T> implements Failable<T> {
- private String error
-
- Fail() {
- this.@error = null
- }
+class Fail<T> implements Failable.FailableImpl<T> {
+ private List<String> errorTrace = new ArrayList<>()
- Fail(String error) {
- this.@error = error;
+ protected Fail(String step) {
+ if (step) {
+ prependTrace(step)
+ }
}
@Override
@@ -24,17 +20,22 @@ class Fail<T> implements Failable<T> {
}
@Override
- Object getValue() {
+ T getValue() {
return null
}
@Override
- String getError() {
- return error
+ public <T2> Failable<T2> then(Closure<Failable<T2>> closure) {
+ return this
}
@Override
- def <T2> Failable<T2> leftShift(Closure<Failable<T2>> closure) {
- return null
+ List<String> getErrorTrace() {
+ return errorTrace
+ }
+
+ @Override
+ void prependTrace(String p) {
+ errorTrace.add(0, p)
}
}
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.groovy
deleted file mode 100644
index 4ad5e90..0000000
--- a/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.groovy
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.xapek.yvesf.classifieds.util
-
-import groovy.transform.CompileStatic
-
-@CompileStatic
-interface Failable<T> {
- boolean isSuccess()
-
- T getValue()
-
- String getError()
-
- public <T2> Failable<T2> leftShift(Closure<Failable<T2>> closure);
-} \ No newline at end of file
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.java b/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.java
new file mode 100644
index 0000000..2e1b780
--- /dev/null
+++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/Failable.java
@@ -0,0 +1,19 @@
+package org.xapek.yvesf.classifieds.util;
+
+import groovy.lang.Closure;
+
+import java.util.List;
+
+public interface Failable<T> {
+ boolean isSuccess();
+
+ T getValue();
+
+ <T2> Failable<T2> then(Closure<Failable<T2>> closure);
+
+ List<String> getErrorTrace();
+
+ interface FailableImpl<T> extends Failable<T> {
+ void prependTrace(String p);
+ }
+}
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/ParserStaticMethods.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/ParserStaticMethods.groovy
new file mode 100644
index 0000000..15524c8
--- /dev/null
+++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/ParserStaticMethods.groovy
@@ -0,0 +1,43 @@
+package org.xapek.yvesf.classifieds.util
+
+import groovy.transform.CompileStatic
+import org.xapek.yvesf.classifieds.util.Fail
+import org.xapek.yvesf.classifieds.util.Failable
+import org.xapek.yvesf.classifieds.util.Success
+
+@CompileStatic
+class ParserStaticMethods {
+ static <T> Failable<List<T>> all(Collection<Failable<T>> values) {
+ if (values.every { it.success }) {
+ return success(values.collect { it.value }, "all[size=${values.size()}]")
+ } else {
+ return fail("all[firstIndex=${values.findIndexOf { Failable<T> it -> !it.success }}," +
+ "firstTrace=${values.find { !it.success }.errorTrace.join(' -> ')}]")
+ }
+ }
+
+ static <T> Failable<T> ifType(Object data, Class<T> type) {
+ if (type.isAssignableFrom(data.getClass())) success(data as T, "iftype[${type.name}]")
+ else fail("iftype[${type.name}]")
+ }
+
+ static <T> Failable<T> ifField(Map map, String key, Class<T> clazz = Object) {
+ if (!map.containsKey(key)) return new Fail("ifField[notfound ${key}]")
+ else {
+ final x = map.get(key)
+ if (clazz.isAssignableFrom(x.getClass())) {
+ success(x as T, "ifField[${key}<=>${clazz}]")
+ } else {
+ fail("ifField[${key}<=>${clazz}]")
+ }
+ }
+ }
+
+ static <T> Success<T> success(T value, String trace = null) {
+ return new Success<T>(value, trace)
+ }
+
+ static <T> Fail<T> fail(String trace = null) {
+ return new Fail<T>(trace)
+ }
+}
diff --git a/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy b/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy
index 224909d..9e39527 100644
--- a/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy
+++ b/src/main/groovy/org/xapek/yvesf/classifieds/util/Success.groovy
@@ -3,15 +3,15 @@ package org.xapek.yvesf.classifieds.util
import groovy.transform.CompileStatic
import groovy.transform.ToString
-
@CompileStatic
-@Newify
@ToString(includeFields = true, includeNames = true)
-class Success<T> implements Failable<T> {
- private T value
+class Success<T> implements Failable.FailableImpl<T> {
+ final private T value
+ final private String step
- public Success(T value) {
- this.value = value
+ protected Success(T value, String step) {
+ this.@value = value
+ this.@step = step
}
@Override
@@ -25,12 +25,26 @@ class Success<T> implements Failable<T> {
}
@Override
- String getError() {
- return null
+ public <T2> Failable<T2> then(Closure<Failable<T2>> closure) {
+ try {
+ final failable = closure.call(value)
+ if (!failable.success) {
+ (failable as Failable.FailableImpl<T>).prependTrace(step)
+ }
+ return failable
+ } catch (Exception e) {
+ final failable = new Fail("exception[${e.getClass().name}]")
+ failable.prependTrace(step)
+ return failable
+ }
+ }
+
+ @Override
+ List<String> getErrorTrace() {
+ return []
}
@Override
- def <T2> Failable<T2> leftShift(Closure<Failable<T2>> closure) {
- return closure.call(value)
+ void prependTrace(String p) {
}
} \ No newline at end of file