From b85a93adb2f01d8ade9a5c28039322618a767cb2 Mon Sep 17 00:00:00 2001 From: "ryuji.ito" Date: Fri, 20 Oct 2017 16:26:21 +0900 Subject: [PATCH 1/2] rm all --- .travis.yml | 9 - LICENSE.txt | 21 -- README.md | 44 --- build.gradle | 76 ----- gradle/wrapper/gradle-wrapper.jar | Bin 51018 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 6 - gradlew | 164 ----------- gradlew.bat | 90 ------ lib/embulk/output/google_spreadsheets.rb | 3 - .../GoogleSpreadsheetsOutputPlugin.java | 276 ------------------ .../TestGoogleSpreadsheetsOutputPlugin.java | 5 - 11 files changed, 694 deletions(-) delete mode 100644 .travis.yml delete mode 100755 LICENSE.txt delete mode 100644 README.md delete mode 100644 build.gradle delete mode 100755 gradle/wrapper/gradle-wrapper.jar delete mode 100755 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100755 gradlew.bat delete mode 100644 lib/embulk/output/google_spreadsheets.rb delete mode 100644 src/main/java/org/embulk/output/google_spreadsheets/GoogleSpreadsheetsOutputPlugin.java delete mode 100644 src/test/java/org/embulk/output/google_spreadsheets/TestGoogleSpreadsheetsOutputPlugin.java diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3222364..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: java -jdk: - - openjdk7 - - oraclejdk7 - - oraclejdk8 -sudo: false -script: - - ./gradlew --info check - - ./gradlew --info test diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100755 index 43acdd5..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index 4e2b663..0000000 --- a/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Google Spreadsheets output plugin for Embulk [![Build Status](https://travis-ci.org/kataring/embulk-output-google_spreadsheets.svg?branch=master)](https://travis-ci.org/kataring/embulk-output-google_spreadsheets) [![Gem Version](https://badge.fury.io/rb/embulk-output-google_spreadsheets.svg)](http://badge.fury.io/rb/embulk-output-google_spreadsheets) - -Embulk output plugin to load into Google Spreadsheets. - -## Overview - -* **Plugin type**: output -* **Load all or nothing**: no -* **Resume supported**: no -* **Cleanup supported**: yes - -## Usage - -### Install plugin - -``` -embulk gem install embulk-output-google_spreadsheets -``` - - -## Configuration - -- **service_account_email**: Your Google service account email (string, required) -- **p12_keyfile**: Fullpath of private key in P12(PKCS12) format (string, required) -- **spreadsheet_id**: Your spreadsheet id (string, required) -- **sheet_index**: sheet index (int, optional default: 0) -- **application_name**: Anything you like (string, optional defaulf: "Embulk-GoogleSpreadsheets-OutputPlugin") - -## Example - -```yaml -out: - type: google_spreadsheets - service_account_email: 'XXXXXXXXXXXXXXXXXXXXXXXX@developer.gserviceaccount.com' - p12_keyfile: '/tmp/embulk.p12' - spreadsheet_id: '1RPXaB85DXM7sGlpFYIcpoD2GWFpktgh0jBHlF4m1a0A' -``` - - -## Build - -``` -$ ./gradlew gem -``` diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 41d4791..0000000 --- a/build.gradle +++ /dev/null @@ -1,76 +0,0 @@ -plugins { - id "com.jfrog.bintray" version "1.1" - id "com.github.jruby-gradle.base" version "0.1.5" - id "java" -} -import com.github.jrubygradle.JRubyExec -repositories { - mavenCentral() - jcenter() -} -configurations { - provided -} - -version = "0.2.0" - -dependencies { - compile "org.embulk:embulk-core:0.7.4" - provided "org.embulk:embulk-core:0.7.4" - testCompile "junit:junit:4.+" - - compile 'com.google.api-client:google-api-client:1.19.1' - compile 'com.google.gdata:core:1.47.1' - compile 'com.google.apis:google-api-services-drive:v2-rev184-1.20.0' -} - -task classpath(type: Copy, dependsOn: ["jar"]) { - doFirst { file("classpath").deleteDir() } - from (configurations.runtime - configurations.provided + files(jar.archivePath)) - into "classpath" -} -clean { delete "classpath" } - -task gem(type: JRubyExec, dependsOn: ["gemspec", "classpath"]) { - jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "build" - script "${project.name}.gemspec" - doLast { ant.move(file: "${project.name}-${project.version}.gem", todir: "pkg") } -} - -task gemPush(type: JRubyExec, dependsOn: ["gem"]) { - jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "push" - script "pkg/${project.name}-${project.version}.gem" -} - -task "package"(dependsOn: ["gemspec", "classpath"]) << { - println "> Build succeeded." - println "> You can run embulk with '-L ${file(".").absolutePath}' argument." -} - -task gemspec { - ext.gemspecFile = file("${project.name}.gemspec") - inputs.file "build.gradle" - outputs.file gemspecFile - doLast { gemspecFile.write($/ -Gem::Specification.new do |spec| - spec.name = "${project.name}" - spec.version = "${project.version}" - spec.authors = ["Noriaki Katayama"] - spec.summary = %[Google Spreadsheets output plugin for Embulk] - spec.description = %[Dumps records to Google Spreadsheets.] - spec.email = ["kataring@gmail.com"] - spec.licenses = ["MIT"] - spec.homepage = "https://github.com/kataring/embulk-output-google_spreadsheets" - - spec.files = `git ls-files`.split("\n") + Dir["classpath/*.jar"] - spec.test_files = spec.files.grep(%r"^(test|spec)/") - spec.require_paths = ["lib"] - - #spec.add_dependency 'YOUR_GEM_DEPENDENCY', ['~> YOUR_GEM_DEPENDENCY_VERSION'] - spec.add_development_dependency 'bundler', ['~> 1.0'] - spec.add_development_dependency 'rake', ['>= 10.0'] -end -/$) - } -} -clean { delete "${project.name}.gemspec" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100755 index c97a8bdb9088d370da7e88784a7a093b971aa23a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51018 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100755 index aec9973..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/lib/embulk/output/google_spreadsheets.rb b/lib/embulk/output/google_spreadsheets.rb deleted file mode 100644 index 65b1055..0000000 --- a/lib/embulk/output/google_spreadsheets.rb +++ /dev/null @@ -1,3 +0,0 @@ -Embulk::JavaPlugin.register_output( - "google_spreadsheets", "org.embulk.output.google_spreadsheets.GoogleSpreadsheetsOutputPlugin", - File.expand_path('../../../../classpath', __FILE__)) diff --git a/src/main/java/org/embulk/output/google_spreadsheets/GoogleSpreadsheetsOutputPlugin.java b/src/main/java/org/embulk/output/google_spreadsheets/GoogleSpreadsheetsOutputPlugin.java deleted file mode 100644 index 3a758fa..0000000 --- a/src/main/java/org/embulk/output/google_spreadsheets/GoogleSpreadsheetsOutputPlugin.java +++ /dev/null @@ -1,276 +0,0 @@ -package org.embulk.output.google_spreadsheets; - -import org.embulk.config.TaskReport; -import org.embulk.config.Config; -import org.embulk.config.ConfigDefault; -import org.embulk.config.ConfigDiff; -import org.embulk.config.ConfigSource; -import org.embulk.config.Task; -import org.embulk.config.TaskSource; -import org.embulk.spi.Exec; -import org.embulk.spi.OutputPlugin; -import org.embulk.spi.Schema; -import org.embulk.spi.TransactionalPageOutput; -import org.embulk.spi.Page; -import org.embulk.spi.Column; -import org.embulk.spi.ColumnVisitor; -import org.embulk.spi.PageReader; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; -import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; -import com.google.api.client.json.jackson2.JacksonFactory; -import com.google.gdata.client.spreadsheet.SpreadsheetService; -import com.google.gdata.data.spreadsheet.*; -import com.google.gdata.util.ServiceException; -import com.google.api.services.drive.DriveScopes; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.http.HttpTransport; - -import java.io.IOException; -import java.io.File; -import java.net.URL; -import java.util.List; -import java.util.Arrays; -import org.embulk.spi.time.Timestamp; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import org.slf4j.Logger; -import java.security.GeneralSecurityException; - -public class GoogleSpreadsheetsOutputPlugin - implements OutputPlugin -{ - public interface PluginTask - extends Task - { - @Config("service_account_email") - public String getServiceAccountEmail(); - - @Config("spreadsheet_id") - public String getSpreadsheetId(); - - @Config("p12_keyfile") - public String getP12Keyfile(); - - @Config("sheet_index") - @ConfigDefault("0") - public int getSheetIndex(); - - @Config("application_name") - @ConfigDefault("\"Embulk-GoogleSpreadsheets-OutputPlugin\"") - public String getApplicationName(); - - @Config("default_timezone") - @ConfigDefault("\"UTC\"") - public String getDefaultTimezone(); - } - - private final Logger log; - - public GoogleSpreadsheetsOutputPlugin() - { - log = Exec.getLogger(getClass()); - } - - @Override - public ConfigDiff transaction(ConfigSource config, - Schema schema, int taskCount, - OutputPlugin.Control control) - { - PluginTask task = config.loadConfig(PluginTask.class); - - // retryable (idempotent) output: - // return resume(task.dump(), schema, taskCount, control); - - // non-retryable (non-idempotent) output: - control.run(task.dump()); - return Exec.newConfigDiff(); - } - - @Override - public ConfigDiff resume(TaskSource taskSource, - Schema schema, int taskCount, - OutputPlugin.Control control) - { - throw new UnsupportedOperationException("google_spreadsheets output plugin does not support resuming"); - } - - @Override - public void cleanup(TaskSource taskSource, - Schema schema, int taskCount, - List successTaskReports) - { - } - - private GoogleCredential getServiceAccountCredential(PluginTask task) - throws IOException, GeneralSecurityException - { - HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); - JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); - List scopes = Arrays.asList(DriveScopes.DRIVE, "https://spreadsheets.google.com/feeds"); - - return new GoogleCredential.Builder().setTransport(httpTransport) - .setJsonFactory(jsonFactory) - .setServiceAccountId(task.getServiceAccountEmail()) - .setServiceAccountPrivateKeyFromP12File(new File(task.getP12Keyfile())) - .setServiceAccountScopes(scopes) - .build(); - } - - @Override - public TransactionalPageOutput open(TaskSource taskSource, Schema schema, int taskIndex) - { - PluginTask task = taskSource.loadTask(PluginTask.class); - GoogleSpreadsheetsPageOutput pageOutput = new GoogleSpreadsheetsPageOutput(task); - pageOutput.open(schema); - return pageOutput; - } - - public class GoogleSpreadsheetsPageOutput implements TransactionalPageOutput - { - private PageReader pageReader; - private SpreadsheetService service; - private WorksheetEntry worksheet; - private ListEntry row; - private DateTimeFormatter formatter; - - public GoogleSpreadsheetsPageOutput(PluginTask task) { - try { - GoogleCredential credentials = getServiceAccountCredential(task); - service = new SpreadsheetService(task.getApplicationName()); - service.setProtocolVersion(SpreadsheetService.Versions.V3); - service.setOAuth2Credentials(credentials); - - URL entryUrl = new URL("https://spreadsheets.google.com/feeds/spreadsheets/" + task.getSpreadsheetId()); - SpreadsheetEntry spreadsheet = service.getEntry(entryUrl, SpreadsheetEntry.class); - - WorksheetFeed worksheetFeed = service.getFeed(spreadsheet.getWorksheetFeedUrl(), WorksheetFeed.class); - List worksheets = worksheetFeed.getEntries(); - worksheet = worksheets.get(task.getSheetIndex()); - - DateTimeZone zone = DateTimeZone.forID(task.getDefaultTimezone()); - formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZone(zone); - } - catch (ServiceException e) { - throw new RuntimeException(e); - } - catch (IOException e) { - throw new RuntimeException(e); - } - catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - } - - void open(final Schema schema) - { - pageReader = new PageReader(schema); - } - - @Override - public void add(Page page) - { - log.debug("add: start"); - pageReader.setPage(page); - URL listFeedUrl = worksheet.getListFeedUrl(); - - while (pageReader.nextRecord()) { - row = new ListEntry(); - pageReader.getSchema().visitColumns(new ColumnVisitor() { - @Override - public void booleanColumn(Column column) { - if (pageReader.isNull(column)) { - log.debug("booleanColumn: null"); - row.getCustomElements().setValueLocal(column.getName(), ""); - } else { - log.debug("booleanColumn: " + pageReader.getBoolean(column)); - row.getCustomElements().setValueLocal(column.getName(), (pageReader.getBoolean(column) ? "true" : "false")); - } - } - - @Override - public void longColumn(Column column) { - if (pageReader.isNull(column)) { - log.debug("longColumn: null"); - row.getCustomElements().setValueLocal(column.getName(), ""); - } else { - log.debug("longColumn: " + pageReader.getLong(column)); - row.getCustomElements().setValueLocal(column.getName(), Long.toString(pageReader.getLong(column))); - } - } - - @Override - public void doubleColumn(Column column) { - if (pageReader.isNull(column)) { - log.debug("doubleColumn: null"); - row.getCustomElements().setValueLocal(column.getName(), ""); - } else { - log.debug("doubleColumn: " + pageReader.getDouble(column)); - row.getCustomElements().setValueLocal(column.getName(), Double.toString(pageReader.getDouble(column))); - } - } - - @Override - public void stringColumn(Column column) { - if (pageReader.isNull(column)) { - log.debug("stringColumn: null"); - row.getCustomElements().setValueLocal(column.getName(), ""); - } else { - log.debug("stringColumn: " + pageReader.getString(column)); - row.getCustomElements().setValueLocal(column.getName(), pageReader.getString(column)); - } - } - - @Override - public void timestampColumn(Column column) { - if (pageReader.isNull(column)) { - log.debug("timestampColumn: null"); - row.getCustomElements().setValueLocal(column.getName(), ""); - } else { - Timestamp timestamp = pageReader.getTimestamp(column); - String strTimestamp = formatter.print(timestamp.toEpochMilli()); - log.debug("timestampColumn: " + strTimestamp); - row.getCustomElements().setValueLocal(column.getName(), strTimestamp); - } - } - }); - - try { - row = service.insert(listFeedUrl, row); - } - catch (Exception e){ - log.warn("can not insert:" + e.toString()); - } - } - log.debug("add: end"); - } - - @Override - public void finish() - { - try { - // TODO - } finally { - close(); - } - } - - @Override - public void close() - { - // TODO do nothing - } - - @Override - public void abort() - { - // TODO do nothing - } - - @Override - public TaskReport commit() - { - return Exec.newTaskReport(); - } - } -} diff --git a/src/test/java/org/embulk/output/google_spreadsheets/TestGoogleSpreadsheetsOutputPlugin.java b/src/test/java/org/embulk/output/google_spreadsheets/TestGoogleSpreadsheetsOutputPlugin.java deleted file mode 100644 index 7ebfd0a..0000000 --- a/src/test/java/org/embulk/output/google_spreadsheets/TestGoogleSpreadsheetsOutputPlugin.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.embulk.output.google_spreadsheets; - -public class TestGoogleSpreadsheetsOutputPlugin -{ -} From e257bbd91dd48b5518b826d09e27c7c11d7ed302 Mon Sep 17 00:00:00 2001 From: "ryuji.ito" Date: Mon, 6 Nov 2017 12:31:18 +0900 Subject: [PATCH 2/2] rewrite plugin --- .gitignore | 9 +- .ruby-version | 1 + Gemfile | 2 + LICENSE.txt | 21 ++ README.md | 66 +++++++ Rakefile | 3 + embulk-output-google_spreadsheets.gemspec | 20 ++ lib/embulk/output/google_spreadsheets.rb | 149 +++++++++++++++ .../spreadsheets_client.rb | 180 ++++++++++++++++++ .../google_spreadsheets/write_buffer.rb | 50 +++++ 10 files changed, 495 insertions(+), 6 deletions(-) create mode 100644 .ruby-version create mode 100644 Gemfile create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 Rakefile create mode 100644 embulk-output-google_spreadsheets.gemspec create mode 100644 lib/embulk/output/google_spreadsheets.rb create mode 100644 lib/embulk/output/google_spreadsheets/spreadsheets_client.rb create mode 100644 lib/embulk/output/google_spreadsheets/write_buffer.rb diff --git a/.gitignore b/.gitignore index 7148592..c0b9bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ *~ -*.iml -.idea -build/ -/classpath/ -/.gradle /pkg/ -*.gemspec +/tmp/ +/.bundle/ +/Gemfile.lock diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..7f9ec44 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +jruby-9.1.13.0 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..9784aee --- /dev/null +++ b/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org/' +gemspec diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..43acdd5 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf32067 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Google Spreadsheets output plugin for Embulk + +## Overview + +* **Plugin type**: output +* **Load all or nothing**: no +* **Resume supported**: no +* **Cleanup supported**: no + +## Configuration + +- auth_method (string, default: 'authorized_user'): 'authorized_user' or 'service_account' +- json_keyfile (string, required): credential file path or `content` string +- spreadsheets_url (string, required): your spreadsheet's url +- worksheet_title (string, required): worksheet's title +- mode (string, default: append): writing record method, available mode are `append` and `replace` +- header_line (bool, default: false): if true, write header to first record +- start_row (integer, default: 1): specific the start row +- start_column (integer, default: 1): specific the start column +- null_string (string, default: ''): replace null to `null_string` +- default_timezone (string, default: '+00:00'): time zone offset of timestamp columns +- default_timestamp_format (string, default: '%Y-%m-%d %H:%M:%S.%6N %z'): output format of timestamp columns + +**json_keyfile** + +specific the credential file which the Google Developer Console provides for authorization. +https://console.developers.google.com/apis/credentials +if use oauth, should be included the `refresh_token` field in credential json file. + +``` +{ + "client_id": "******************************************************", + "client_secret": "***************************", + "refresh_token": "***************************" +} +``` + +## Example + +``` +out: + type: google_spreadsheets + json_keyfile: + content: | + { + "client_id": "******************************************************", + "client_secret": "***************************", + "refresh_token": "***************************" + } + # json_keyfile: './keyfile.json' + spreadsheets_url: 'https://docs.google.com/spreadsheets/d/16RSM_xj5ZB4rz0WBlnIbD1KHO46KASnAY04e_oYUSEE/edit' + worksheet_title: 'シート1' + start_row: 1 + start_column: 1 + header_line: true + null_string : '(NULL)' + default_timezone: '+09:00' + default_timestamp_format: "%Y-%m-%d %H:%M:%S %z" +``` + + +## Build + +``` +$ rake +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..8bfbf16 --- /dev/null +++ b/Rakefile @@ -0,0 +1,3 @@ +require "bundler/gem_tasks" + +task default: :build diff --git a/embulk-output-google_spreadsheets.gemspec b/embulk-output-google_spreadsheets.gemspec new file mode 100644 index 0000000..90c51b7 --- /dev/null +++ b/embulk-output-google_spreadsheets.gemspec @@ -0,0 +1,20 @@ +Gem::Specification.new do |spec| + spec.name = "embulk-output-google_spreadsheets" + spec.version = "0.1.0" + spec.authors = ["potato2003"] + spec.summary = "Google Spreadsheets output plugin for Embulk" + spec.description = "Dumps records to Google Spreadsheets." + spec.email = ["potato2003@gmail.com"] + spec.licenses = ["MIT"] + # TODO set this: spec.homepage = "https://github.com/ryuji.ito/embulk-output-google_spreadsheets" + + spec.files = `git ls-files`.split("\n") + Dir["classpath/*.jar"] + spec.test_files = spec.files.grep(%r{^(test|spec)/}) + spec.require_paths = ["lib"] + + #spec.add_dependency 'YOUR_GEM_DEPENDENCY', ['~> YOUR_GEM_DEPENDENCY_VERSION'] + spec.add_development_dependency 'embulk', ['>= 0.8.35'] + spec.add_development_dependency 'bundler', ['>= 1.10.6'] + spec.add_development_dependency 'rake', ['>= 10.0'] + spec.add_dependency 'google-api-client' +end diff --git a/lib/embulk/output/google_spreadsheets.rb b/lib/embulk/output/google_spreadsheets.rb new file mode 100644 index 0000000..f21dbe3 --- /dev/null +++ b/lib/embulk/output/google_spreadsheets.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'googleauth' +require 'google/apis/sheets_v4' + +require_relative 'google_spreadsheets/write_buffer' +require_relative 'google_spreadsheets/spreadsheets_client' + +module Embulk + module Output + + class GoogleSpreadsheets < OutputPlugin + Plugin.register_output("google_spreadsheets", self) + + # support config by file path or content which supported by org.embulk.spi.unit.LocalFile + # json_keyfile: + # content: | + class LocalFile + # return JSON string + def self.load(v) + if v.is_a?(String) + File.read(v) + elsif v.is_a?(Hash) + v['content'] + end + end + end + + def self.transaction(config, schema, count, &control) + # configuration code: + task = { + # required + "json_keyfile" => config.param("json_keyfile", LocalFile, nil), + "spreadsheets_url" => config.param("spreadsheets_url", :string), + "worksheet_title" => config.param("worksheet_title", :string), + + # optional + "auth_method" => config.param("auth_method", :string, default: "authorized_user"), # 'auth_method' or 'service_account' + "mode" => config.param("mode", :string, default: "append"), # `replace` or `append` + "header_line" => config.param("header_line", :bool, default: false), + "start_column" => config.param("start_column", :integer, default: 1), + "start_row" => config.param("start_row", :integer, default: 1), + "null_string" => config.param("null_string", :string, default: ""), + "default_timezone" => config.param("default_timezone", :string, default: "+00:00"), + "default_timestamp_format" => config.param("default_timestamp_format", :string, default: "%Y-%m-%d %H:%M:%S.%6N %z"), + } + + # + # prepare to write records + # + + # support 'UTC' specially + task["default_timezone"] = "+00:00" if task["default_timezone"] == 'UTC' + + client = SpreadsheetsClient.new(task, schema) + + mode = task["mode"].to_sym + raise "unsupported mode: #{mode.inspect}" unless [:append, :replace].include? mode + + if mode == :replace + client.clear_records + end + + if task['header_line'] + client.write_header_line + end + + Embulk.logger.info("set task: #{task.inspect}") + + task_reports = yield(task) + next_config_diff = {} + return next_config_diff + end + + def init + @mode = task["mode"].to_sym + @null_string = task["null_string"] + + @client = SpreadsheetsClient.new(task, schema) + init_cursor @client + @buffer = WriteBuffer.new + end + + def close + end + + def add(page) + num_records = 0 + + page.each do |values| + record = schema.names.zip(schema.types, values) + + f = record.map do |(name, type, value)| + format(type, value) + end + + @buffer.write_record(f) + num_records += 1 + end + + Embulk.logger.info("buffering #{num_records} records") + end + + def finish + total = 0 + + @buffer.each_slice(1000) do |chunked_records| + Embulk.logger.debug { "flush buffer: write %d records to spreadsheet" % chunked_records.length } + total += chunked_records.length + + @client.write_records(chunked_records) + end + + Embulk.logger.info("finish to write total %d records" % total) + + @buffer.close + end + + def abort + @buffer.close + end + + def commit + task_report = {} + return task_report + end + + def init_cursor(client) + client.set_cursor_to_last_row + end + + def format(type, v) + return @null_string if v.nil? + + case type + when :timestamp + zone_offset = task['default_timezone'] + format = task['default_timestamp_format'] + + v.dup.localtime(zone_offset).strftime(format) + when :json + v.to_json + else + v + end + end + end + end +end diff --git a/lib/embulk/output/google_spreadsheets/spreadsheets_client.rb b/lib/embulk/output/google_spreadsheets/spreadsheets_client.rb new file mode 100644 index 0000000..c622d4d --- /dev/null +++ b/lib/embulk/output/google_spreadsheets/spreadsheets_client.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +module Embulk + module Output + class GoogleSpreadsheets < OutputPlugin + + class SpreadsheetsClient + + DEFAULT_SCOPE = [ + Google::Apis::SheetsV4::AUTH_SPREADSHEETS + ] + + attr_reader :service, :spreadsheets_url, :spreadsheets_id, :worksheet_title, :schema, :task, :cursor + + def initialize(task, schema) + @task = task + @schema = schema + + @spreadsheets_url = task['spreadsheets_url'] + @spreadsheets_id = spreadsheets_id_by_url(@spreadsheets_url) + if @spreadsheets_id.nil? + raise ArgumentError, "failed to extract spreadsheet's id from url, maybe given spreadsheets_url is invalid." + end + + @worksheet_title = task['worksheet_title'] + + @service = Google::Apis::SheetsV4::SheetsService.new + @service.client_options.application_name = 'embulk-output-google_spreadsheets' + @service.authorization = make_credentials(task) + + @cursor = 0 + end + + def replace_mode? + task['mode'] == 'replace' + end + + def append_mode? + task['mode'] == 'append' + end + + def write_header_line + vr = ensure_value_range(headers) + service.update_spreadsheet_value spreadsheets_id, range, vr, value_input_option: 'RAW' + end + + def write_records(records) + vr = ensure_value_range(records) + service.append_spreadsheet_value spreadsheets_id, next_range(records), vr, value_input_option: 'RAW' + end + + def append(records) + vr = ensure_value_range(records) + service.append_spreadsheet_value spreadsheets_id, all_records_range, vr, value_input_option: 'RAW' + end + + def set_cursor_to_last_row + res = service.get_spreadsheet_values spreadsheets_id, all_records_range + @cursor = res.values&.size || 0 + + Embulk.logger.info { "fetched existing #{@cursor} records (including header line), set next records position at #{@cursor + task['start_row']}" } + end + + def clear_records + service.clear_values spreadsheets_id, all_records_range + end + + def headers + schema.names + end + + private + def make_credentials(task) + key = StringIO.new(JSON.parse(task['json_keyfile']).to_json) + + case task['auth_method'] + when 'authorized_user' + Google::Auth::UserRefreshCredentials.make_creds(json_key_io: key, scope: DEFAULT_SCOPE) + when 'service_account' + Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: key, scope: DEFAULT_SCOPE) + else + raise ConfigError.new("Unknown auth method: #{task['auth_method']}") + end + end + + def spreadsheets_id_by_url(url) + base_url = 'https://docs.google.com/spreadsheets/d/' + url.scan(%r{#{base_url}([^/]+)}).dig(0, 0) # return spreadsheets id if matche succeed, nil otherwise + end + + def range + c = col_num_to_a1 task["start_column"] + r = task["start_row"] + + "#{safe_worksheet_title}!#{c}#{r}" + end + + def next_range(records) + c = col_num_to_a1 task["start_column"] + r = task["start_row"] + cursor + @cursor += records.length + + "#{safe_worksheet_title}!#{c}#{r}" + end + + def all_records_range + end_col = col_num_to_a1(task["start_column"] + headers.length - 1) + "#{range}:#{end_col}" + end + + # retrun a worksheet title safe for spreadsheet api + def safe_worksheet_title + @safe_worksheet_title ||= begin + title_safe = worksheet_title.gsub("'", "''") # escape signle quote + "'#{title_safe}'" # surround title with single quotes + end + end + + # ensure given data type as value range + # see: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values#ValueRange + # + # { + # major_dimension: String // DIMENSION_UNSPECIFIED, ROWS or COLUMNS + # values: Array[Array[Object]] + # } + # + # ValueRange.values is Array>, + # outer array representing list of rows and + # iner array representing list of columns in row. + def ensure_value_range(arr_or_obj) + { + major_dimension: "ROWS", + values: ensure_2dimension_array(arr_or_obj) + } + end + + def ensure_2dimension_array(arr_or_obj) + arr = ensure_array(arr_or_obj) + return [arr] unless arr[0].is_a? Array + + arr + end + + def ensure_array(arr_or_obj) + return [arr_or_obj] unless arr_or_obj.is_a? Array + + arr_or_obj + end + + # convert col number to a1 annotation + # + def col_num_to_a1(col_num) + offset = 'A'.ord + radix = 26 # number of letters in alphabet + + a1 = [] + + while 1 <= col_num do + # adjust col number. + # A1 annotation is base-26 (A-Z) but 0 does not exists. + # + # for example, + # (NA) == 0 base-10 + # A base-26 == 1 base-10 + # Z base-26 == 26 base-10 + # AA base-26 == 27 base-10 (if A is 0, AA also become 0) + # + col_num -= 1 + a1 << (col_num % radix + offset).chr + col_num /= radix + end + + return 'A' if a1.empty? + + a1.reverse.join + end + end + end + end +end diff --git a/lib/embulk/output/google_spreadsheets/write_buffer.rb b/lib/embulk/output/google_spreadsheets/write_buffer.rb new file mode 100644 index 0000000..c019255 --- /dev/null +++ b/lib/embulk/output/google_spreadsheets/write_buffer.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'forwardable' +require 'tempfile' + +module Embulk + module Output + class GoogleSpreadsheets < OutputPlugin + + class WriteBuffer + extend Forwardable + include Enumerable + + attr_reader :file + + def initialize + @file = Tempfile.new(safe_class_name) + end + + def close + return if file.nil? + + file.close + file.unlink + file = nil + end + + def write_record(r) + write(Marshal.dump(r) << "\n") + end + + def each + file.rewind + file.each do |serialized_record| + yield Marshal.restore(serialized_record) + end + end + + def_delegators :@file, :write + + private + # retrun a class name safe for filesystem + def safe_class_name() + self.class.name.downcase.gsub(/[^\d\w]/, '_') + end + + end + end + end +end