Skip to content

Instantly share code, notes, and snippets.

@alem0lars
Created December 26, 2017 20:54
Show Gist options
  • Save alem0lars/dbc4f92fa7af250b72db579473ab2928 to your computer and use it in GitHub Desktop.
Save alem0lars/dbc4f92fa7af250b72db579473ab2928 to your computer and use it in GitHub Desktop.
Trello two-way sync (WIP)
#!/usr/bin/env ruby
require "net/http"
require "json"
require "pathname"
require "date"
module TRSync
module Model
class Board
include Enumerable
def initialize
@lists = []
end
def <<(list)
if list.is_a? List
@lists << list
else
@lists << List.parse(list)
end
end
def [](list_name)
@lists.find { |list| list.name == list_name }
end
def each
@lists.each { |list| yield list }
end
def last_list
@lists.last
end
def to_pretty_s(indent = 1)
indent_str = "\n#{"\t" * indent}"
lists = @lists.empty? ? "<none>" : "#{indent_str}#{@lists.map { |l| l.to_pretty_s(indent + 1)}.join(indent_str)}"
"Board{lists=#{lists}}"
end
end
class List
include Enumerable
attr_accessor :id, :name
def initialize(id, name)
@id = id
@name = name
@cards = []
end
def <<(card)
if card.is_a? Card
@cards << card
else
@cards << Card.parse(card)
end
end
def [](card_name)
@cards.find { |card| card.name == card_name }
end
def each
@cards.each { |card| yield card }
end
def last_card
@cards.last
end
def to_pretty_s(indent = 1)
indent_str = "\n#{"\t" * indent}"
cards = @cards.empty? ? "<none>" : "#{indent_str}#{@cards.map { |c| c.to_pretty_s(indent + 1)}.join(indent_str)}"
"List{id=#{id}, name=#{name}, cards=#{cards}}"
end
def self.parse(data)
List.new(data["id"], data["name"])
end
end
class Card
attr_accessor :id, :name, :desc, :closed, :date, :short_link, :url
def initialize(id, name, desc, closed, date, short_link, url)
@id = id
@name = name
@desc = desc
@closed = closed
@date = date
@short_link = short_link
@url = url
@attachments = []
@checklists = []
end
def add_attachment(attachment)
if attachment.is_a? Attachment
@attachments << attachment
else
@attachments << Attachment.parse(attachment)
end
end
def attachments
@attachments.to_enum(:each)
end
def last_attachment
@attachments.last
end
def add_checklist(checklist)
if checklist.is_a? Attachment
@checklists << checklist
else
@checklists << Checklist.parse(checklist)
end
end
def checklists
@checklists.to_enum(:each)
end
def last_checklist
@checklists.last
end
def to_pretty_s(indent = 1)
indent_str = "\n#{"\t" * indent}"
attachments = @attachments.empty? ? "<none>" : "#{indent_str}#{@attachments.map { |a| a.to_pretty_s(indent + 1)}.join(indent_str)}"
checklists = @checklists.empty? ? "<none>" : "#{indent_str}#{@checklists.map { |c| c.to_pretty_s(indent + 1)}.join(indent_str)}"
"Card{#{indent_str}id=#{id}, name=#{name}, desc=#{desc.empty? ? "<none>" : "..."},#{indent_str}closed=#{closed}, date=#{date}, short_link=#{short_link},#{indent_str}url=#{url}, attachments=#{attachments}, checklists=#{checklists}}"
end
def self.parse(data)
Card.new(
data["id"],
data["name"],
data["desc"],
data["closed"],
DateTime.parse(data["dateLastActivity"]),
data["shortLink"],
data["shortUrl"]
)
end
end
class Attachment
attr_accessor :id, :name, :url, :date
def initialize(id, name, url, date)
@id = id
@name = name
@url = url
@date = date
end
def to_pretty_s(indent = 1)
"Attachment{id=#{id}, name=#{name}, url=#{url}, date=#{date}}"
end
def self.parse(data)
Attachment.new(
data["id"],
data["name"],
data["url"],
DateTime.parse(data["date"])
)
end
end
class Checklist
include Enumerable
attr_accessor :id, :name
def initialize(id, name)
@id = id
@name = name
@items = []
end
def <<(item)
@items << item
end
def each
@items.each { |item| yield item }
end
def last_item
@items.last
end
def to_pretty_s(indent = 1)
indent_str = "\n#{"\t" * indent}"
items = @items.empty? ? "<none>" : "#{indent_str}#{@items.map(&:to_s).join(indent_str)}"
"Checklist{id=#{id}, name=#{name}, items=#{items}}"
end
def self.parse(data)
cl = Checklist.new(data["id"], data["name"])
data["checkItems"].each do |item|
cl << { id: item["id"], name: item["name"], state: item["state"] }
end
cl
end
end
end
module Request
class Base
attr_reader :params, :path, :method, :use_ssl
def initialize(path: [], params: {}, method: :get, use_ssl: true,
key: nil, token: nil)
@base_url = "https://api.trello.com/1"
@params = { key: key, token: token }.merge(params).delete_if do |k, v|
v.nil? || v.empty?
end
def @params.to_s
self.map{ |k, v| "#{k}=#{v}" }.join("&")
end
@path = path
def @path.to_s
self.join("/")
end
@method = method
@use_ssl = use_ssl
end
def url
"#@base_url/#{path}"
end
def perform
uri, req = case method
when :get
uri = URI("#{url}?#{params}")
req = Net::HTTP::Get.new(uri, "Content-Type" => "application/json")
[uri, req]
when :post
uri = URI(url)
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.body = params.to_json
[uri, req]
end
puts ">>> Performing request: #{uri}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
http.request(req)
end
JSON.parse(res.body)
rescue StandardError => exc
puts "!!! Got error while performing request: #{exc}"
end
end
class Board < Base
def initialize(board_id, **kwargs)
super(**kwargs.merge({
path: ["boards", board_id],
params: { fields: (kwargs[:fields] || []).join(","), lists: "all" }
}))
end
end
class Cards < Base
def initialize(board_id, **kwargs)
super(**kwargs.merge({
path: ["boards", board_id, "cards"],
params: { fields: (kwargs[:fields] || []).join(",") }
}))
end
end
class Attachments < Base
def initialize(card_id, **kwargs)
super(**kwargs.merge({
path: ["cards", card_id, "attachments"],
params: { fields: (kwargs[:fields] || []).join(",") }
}))
end
end
class Checklists < Base
def initialize(card_id, **kwargs)
super(**kwargs.merge({
path: ["cards", card_id, "checklists"],
params: { fields: (kwargs[:fields] || []).join(",") }
}))
end
end
end
module Retriever
module Local
class Board
def initialize(root_dir)
@root_dir = root_dir
end
def retrieve
Pathname.glob(@root_dir.join("*")).select { |list_dir| list_dir.directory? }.collect_concat do |list_dir|
list = {}
list[:name] = list_dir.basename
list
end
end
end
end
module Remote
class Base
def initialize(trello_key, trello_token)
@trello_key = trello_key
@trello_token = trello_token
end
end
class Board < Base
def initialize(board_id, trello_key, trello_token)
super(trello_key, trello_token)
@board_id = board_id
end
def retrieve
board_data = TRSync::Request::Board.new(@board_id, key: @trello_key, token: @trello_token).perform
cards_data = TRSync::Request::Cards.new(@board_id, key: @trello_key, token: @trello_token).perform
board = TRSync::Model::Board.new
board_data["lists"].each do |list_data|
board << list_data
cards_data.select { |c| c["idList"] == board.last_list.id }
.each do |card_data|
board.last_list << card_data
attachments = TRSync::Request::Attachments.new(board.last_list.last_card.id, key: @trello_key, token: @trello_token).perform
attachments.each do |attachment_data|
board.last_list.last_card.add_attachment(attachment_data)
end
checklists = TRSync::Request::Checklists.new(board.last_list.last_card.id, key: @trello_key, token: @trello_token).perform
checklists.each do |checklist_data|
board.last_list.last_card.add_checklist(checklist_data)
end
end
end
board
end
end
end
end
class Synchronizer
def initialize(root_dir, board_id, trello_key, trello_token, check_delay)
@remote_board_retriever = TRSync::Retriever::Remote::Board.new(board_id, trello_key, trello_token)
@local_board_retriever = TRSync::Retriever::Local::Board.new(root_dir)
@check_delay = check_delay
end
def start
threads = []
# Perform sync
loop do
perform_sync
sleep @check_delay
end
threads.map(&:join)
end
private def perform_sync
puts ">>> Performing sync.."
remote_board = @remote_board_retriever.retrieve
local_board = @local_board_retriever.retrieve
# remote_lists.each do |remote_list|
# name = remote_list[:name]
# local_list = local_lists.find { |local_list| local_list[:name] == name }
# if local_list
# update_local_list(local_list, remote_list)
# else
# create_local_list(remote_list)
# end
# remote_list[:cards].each do |card|
# # TODO
# end
# end
end
#
# Update an existing local list
#
private def update_local_list(local_list, remote_list)
# TODO
end
#
# Create a new local list
#
private def create_local_list(remote_list)
# TODO
end
end
end
# Entry-Point
begin
ENV["ROOT_DIR"] = "."
ENV["BOARD_ID"] = "A16esx8M"
ENV["TRELLO_API_KEY"] = "52dcbfb53f378bf37b3296e0e2ece4a3"
ENV["TRELLO_TOKEN"] = "4dcf3ff950b2a80c48e1e708aa9cc82cfa8507658b54234781c847d70339fb47"
synchronizer = TRSync::Synchronizer.new(ENV["ROOT_DIR"], ENV["BOARD_ID"], ENV["TRELLO_API_KEY"], ENV["TRELLO_TOKEN"], 1)
synchronizer.start
rescue SystemExit, Interrupt
puts "!!! Shutting down !!!"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment