diff --git a/Gemfile b/Gemfile index 3c46657..9bcf8d8 100644 --- a/Gemfile +++ b/Gemfile @@ -6,3 +6,4 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rest-client" gem "pry" +gem "naught" diff --git a/Gemfile.lock b/Gemfile.lock index d37ad52..bd9ac08 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,6 +10,7 @@ GEM mime-types (3.2.2) mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) + naught (1.1.0) netrc (0.11.0) pry (0.11.3) coderay (~> 1.1.0) @@ -26,8 +27,9 @@ PLATFORMS ruby DEPENDENCIES + naught pry rest-client BUNDLED WITH - 1.16.2 + 1.17.2 diff --git a/gitea_api.rb b/gitea_api.rb new file mode 100644 index 0000000..66ca198 --- /dev/null +++ b/gitea_api.rb @@ -0,0 +1,196 @@ +require 'rest-client' +require 'json' +require 'naught' +require 'pry' + +module Gitea + + class API + attr_accessor :api_key + attr_accessor :base_url + attr_accessor :http_headers + attr_accessor :rest_client + + def initialize(api_key:, base_url: "https://gitea.evolix.org", rest_client: RestClient) + @api_key = api_key + @base_url = base_url + @http_headers = { + "Authorization" => "token #{api_key}", + "Content-Type" => "application/json", + "Accept" => "application/json", + } + @rest_client = rest_client + end + + def create_issue(issue:, project_name:, user_name:) + puts "Gitea::API.create_issue(project_name: #{project_name})" + + response = rest_client.post( + "#{base_url}/api/v1/repos/#{project_name}/issues", + JSON.generate(issue), + http_headers.merge("Sudo" => user_name) + ) { |response, request, result, &block| + case response.code + when 201 + if response.body.empty? + {} + else + JSON.parse(response.body) + end + else + binding.pry + end + } + + rescue => ex + binding.pry + end + + def create_comment(comment:, project_name:, issue_number:, user_name:) + puts "Gitea::API.create_comment(project_name: #{project_name}, issue_number: #{issue_number})" + + response = rest_client.post( + "#{base_url}/api/v1/repos/#{project_name}/issues/#{issue_number}/comments", + JSON.generate(comment), + http_headers.merge("Sudo" => user_name) + ) { |response, request, result, &block| + + case response.code + when 201 + if response.body.empty? + {} + else + JSON.parse(response.body) + end + else + binding.pry + end + } + rescue => ex + binding.pry + end + + def get_repo(org_name:, repo_name:) + puts "Gitea::API.get_repository(org_name: #{org_name}, repo_name: #{repo_name})" + + rest_client.get( + "#{base_url}/api/v1/repos/#{org_name}/#{repo_name}", + http_headers + ) { |response, request, result, &block| + case response.code + when 200 + raw_repository = JSON.parse(response.body) + Repository.new(raw_repository) + else + NullRepository.new + end + } + rescue => ex + binding.pry + end + + def create_user_repository(repository:) + puts "Gitea::API.create_user_repository()" + + response = rest_client.post( + "#{base_url}/api/v1/user/repos", + JSON.generate(repository), + http_headers + ) { |response, request, result, &block| + case response.code + when 201 + if response.body.empty? + {} + else + JSON.parse(response.body) + end + else + binding.pry + end + } + + rescue => ex + binding.pry + end + + def create_org_repository(org_name:, repository:) + puts "Gitea::API.create_org_repository(org_name: #{org_name})" + + response = rest_client.post( + "#{base_url}/api/v1/orgs/#{org_name}/repos", + JSON.generate(repository), + http_headers + ) { |response, request, result, &block| + case response.code + when 201 + if response.body.empty? + {} + else + JSON.parse(response.body) + end + else + binding.pry + end + } + + rescue => ex + binding.pry + end + + def delete_repository(org_name:, repo_name:) + puts "Gitea::API.delete_repository(org_name: #{org_name}, repo_name: #{repo_name})" + + response = rest_client.delete( + "#{base_url}/api/v1/repos/#{org_name}/#{repo_name}", + http_headers + ) { |response, request, result, &block| + case response.code + when 201 + if response.body.empty? + {} + else + JSON.parse(response.body) + end + else + binding.pry + end + } + rescue => ex + binding.pry + end + + def add_collaborator(org_name:, repo_name:, collaborator_name:, permissions: {}) + puts "Gitea::API.add_collaborator(org_name: #{org_name}, repo_name: #{repo_name}, collaborator_name: #{collaborator_name})" + + rest_client.put( + "#{base_url}/api/v1/repos/#{org_name}/#{repo_name}/collaborators/#{collaborator_name}", + JSON.generate(permissions), + http_headers + ) { |response, request, result, &block| + case response.code + when 204 + nil + else + binding.pry + end + } + rescue => ex + binding.pry + end + end + + class Repository + attr_reader :id + + def initialize(repository) + @repository = repository + end + + def id + @repository.fetch("id") + end + end + + NullRepository = Naught.build + +end diff --git a/main.rb b/main.rb index da8ab75..5d7c40c 100644 --- a/main.rb +++ b/main.rb @@ -1,26 +1,28 @@ require_relative "redmine-to-gitea" +require_relative 'textile-to-markdown' +fake_user_names_proc = Proc.new { |x| "redmine_importer" } user_names_proc = Proc.new { |x| - # case x - # when 488 then "abenmiloud" - # when 40 then "benpro" - # when 443 then "btatu" - # when 406 then "emorino" - # when 3 then "gcolpart" - # when 408 then "jcougnoux" - # when 400 then "jdubois" - # when 391 then "jlecour" - # when 430 then "lpoujol" - # when 398 then "pdiogoantunes" - # when 7 then "romain" - # when 240 then "drustan" - # when 344 then "vlaborie" - # when 275 then "jmartinez" - # when 284 then "agobin" - # when 527 then "sbencherif" - # else + case x + when 488 then "abenmiloud" + when 40 then "benpro" + when 443 then "btatu" + when 406 then "emorino" + when 3 then "gcolpart" + when 408 then "jcougnoux" + when 400 then "jdubois" + when 391 then "jlecour" + when 430 then "lpoujol" + when 398 then "pdiogoantunes" + when 7 then "romain" + when 240 then "drustan" + when 344 then "vlaborie" + when 275 then "jmartinez" + when 284 then "agobin" + when 527 then "sbencherif" + else "redmine_importer" - # end + end } statuses_proc = Proc.new { |x| @@ -60,20 +62,40 @@ priorities_proc = Proc.new { |x| } mapping = RedmineToGitea::Mapping.new( - user_names_proc: user_names_proc, + user_names_proc: fake_user_names_proc, statuses_proc: statuses_proc, closed_statuses_proc: closed_statuses_proc, priorities_proc: priorities_proc ) - -migrator = RedmineToGitea::Migrator.new( - redmine_api: Redmine::API.new(api_key: ENV.fetch('REDMINE_API_KEY')), - gitea_api: Gitea::API.new(api_key: ENV.fetch('GITEA_API_KEY')), - mapping: mapping, - issue_formatter: IssueFormatter.new(mapping: mapping, date_format: "%Y-%m-%d %H:%M:%S") +settings = RedmineToGitea::Settings.new( + mapping: mapping, + date_format: "%Y-%m-%d %H:%M:%S", + textile_to_markdown: TextileToMarkdown, ) +migrator = RedmineToGitea::Migrator.new( + redmine_api: Redmine::API.new(api_key: ENV.fetch('REDMINE_API_KEY')), + gitea_api: Gitea::API.new(api_key: ENV.fetch('GITEA_API_KEY')), + settings: settings +) + + +repository = { + "name": "tests-redmine-to-gitea", + "private": true, +} +migrator.reset_project( + org_name: "jlecour", + repository: repository, + collaborators: [ + "redmine_importer" + ] +) + +# evolix-private: 231 +# ansible-roles: 226 +# evogestion: 226 migrator.issues( - redmine_project: 226, + redmine_project: 209, gitea_project: "jlecour/tests-redmine-to-gitea" ) diff --git a/redmine-to-gitea.rb b/redmine-to-gitea.rb index bbfef9e..0d58f33 100644 --- a/redmine-to-gitea.rb +++ b/redmine-to-gitea.rb @@ -1,224 +1,5 @@ -require 'rest-client' -require 'json' -require 'pry' - -require_relative 'textile-to-markdown' - -module Gitea - - class API - attr_accessor :api_key - attr_accessor :base_url - attr_accessor :http_headers - attr_accessor :rest_client - - def initialize(api_key:, base_url: "https://gitea.evolix.org", rest_client: RestClient) - @api_key = api_key - @base_url = base_url - @http_headers = { - "Authorization" => "token #{api_key}", - "Content-Type" => "application/json", - "Accept" => "application/json", - } - @rest_client = rest_client - end - - def post_issue(issue:, project_name:, user_name:) - puts "Gitea::API.post_issue(project_name: #{project_name})" - - response = rest_client.post( - "#{base_url}/api/v1/repos/#{project_name}/issues", - JSON.generate(issue), - http_headers.merge("Sudo" => user_name) - ) - - result = JSON.parse(response.body) - rescue => ex - binding.pry - end - - def post_comment(comment:, project_name:, issue_number:, user_name:) - puts "Gitea::API.post_comment(project_name: #{project_name}, issue_number: #{issue_number})" - - response = rest_client.post( - "#{base_url}/api/v1/repos/#{project_name}/issues/#{issue_number}/comments", - JSON.generate(comment), - http_headers.merge("Sudo" => user_name) - ) - - result = JSON.parse(response.body) - rescue => ex - binding.pry - end - end - -end - -module Redmine - - class API - attr_accessor :api_key - attr_accessor :base_url - attr_accessor :http_headers - attr_accessor :rest_client - - def initialize(api_key:, base_url: "https://forge.evolix.org", rest_client: RestClient) - @api_key = api_key - @base_url = base_url - @http_headers = { - "X-Redmine-API-Key" => api_key, - "Content-Type" => "application/json", - "Accept" => "application/json" - } - @rest_client = rest_client - end - - def get_issues(project_id:, offset: 0, limit: 25) - puts "Redmine::API.get_issues(project_id: #{project_id}, offset: #{offset}, limit: #{limit})" - - response = rest_client.get( - "#{base_url}/issues.json?project_id=#{project_id}&offset=#{offset}&limit=#{limit}", - http_headers - ) - result = JSON.parse(response.body) - - ResultSet.new( - results: result.fetch("issues").map { |issue| Issue.new(issue) }, - total: result.fetch("total_count"), - offset: result.fetch("offset"), - limit: result.fetch("limit") - ) - rescue => ex - binding.pry - end - - def get_issue(id:) - puts "Redmine::API.get_issue(id: #{id})" - - response = rest_client.get( - "#{base_url}/issues/#{id}.json?include=journals", - http_headers - ) - result = JSON.parse(response.body) - - Issue.new(result.fetch("issue")) - rescue => ex - binding.pry - end - end - - class ResultSet - attr_reader :results - attr_reader :total - attr_reader :offset - attr_reader :limit - - def initialize(results:, total:, offset:, limit:) - @results = results - @total = total - @offset = offset - @limit = limit - end - - def remaining_results? - (offset + limit) < total - end - end - - class Issue - attr_reader :issue - - def initialize(issue) - @issue = issue - end - - def id - issue.fetch("id") - end - def project_name - issue.fetch("project", {}).fetch("name") - end - def tracker_name - issue.fetch("tracker", {}).fetch("name") - end - def status_id - issue.fetch("status", {}).fetch("id") - end - def status_name - issue.fetch("status", {}).fetch("name") - end - def category_name - if issue.key?("category") - issue.fetch("category", {}).fetch("name") - end - end - def author_id - issue.fetch("author", {}).fetch("id") - end - def author_name - issue.fetch("author", {}).fetch("name") - end - def assigned_to - if issue.key?("assigned_to") - issue.fetch("assigned_to").fetch("id") - end - end - def start_date - issue.fetch("start_date") - end - def done_ratio - issue.fetch("done_ratio") - end - def created_on - issue.fetch("created_on") - end - def updated_on - issue.fetch("updated_on") - end - def subject - issue.fetch("subject") - end - def description - issue.fetch("description") - end - def journals - if issue.key?("journals") - issue.fetch("journals", {}).map { |journal| - Journal.new(journal) - } - else - [] - end - end - end - - class Journal - attr_reader :journal - - def initialize(journal) - @journal = journal - end - def user_id - journal.fetch("user", {}).fetch("id") - end - def user_name - journal.fetch("user", {}).fetch("name") - end - def created_on - journal.fetch("created_on") - end - def details - journal.fetch("details", []) - end - def notes - journal.fetch("notes", "") - end - def private_notes? - journal.fetch("private_notes", false) - end - end - -end +require_relative 'redmine_api' +require_relative 'gitea_api' module RedmineToGitea @@ -238,8 +19,7 @@ module RedmineToGitea def user_name(redmine_user_id) return nil if redmine_user_id.nil? - # user_names_proc.call(redmine_user_id) - "redmine_importer" + user_names_proc.call(redmine_user_id) end def status(redmine_status_id) @@ -255,17 +35,51 @@ module RedmineToGitea end end + class Settings + attr_accessor :mapping + attr_accessor :date_format + attr_accessor :textile_to_markdown + + def initialize(mapping:, date_format:, textile_to_markdown: Proc.new { |x| x } ) + @mapping = mapping + @date_format = date_format + @textile_to_markdown = textile_to_markdown + end + end + class Migrator attr_accessor :redmine_api attr_accessor :gitea_api + attr_accessor :settings attr_accessor :issue_formatter - attr_accessor :mapping - def initialize(redmine_api:, gitea_api:, mapping: Mapping.new, issue_formatter: IssueFormatter.new) - @redmine_api = redmine_api - @gitea_api = gitea_api - @mapping = mapping - @issue_formatter = issue_formatter + def initialize(redmine_api:, gitea_api:, settings: Settings.new) + @redmine_api = redmine_api + @gitea_api = gitea_api + @settings = settings + @issue_formatter = IssueFormatter.new( + mapping: mapping, + date_format: settings.date_format, + textile_to_markdown: settings.textile_to_markdown + ) + end + + def reset_project(org_name:, repository:, collaborators: []) + repo_name = repository.fetch(:name) + old_repository = gitea_api.get_repo(org_name: org_name, repo_name: repo_name) + + if old_repository.id + gitea_api.delete_repository(org_name: org_name, repo_name: repo_name) + end + + # gitea_api.create_org_repository(org_name: org_name, repository: repository) + gitea_api.create_user_repository(repository: repository) + + collaborators.each do |collaborator| + gitea_api.add_collaborator(org_name: org_name, repo_name: repo_name, collaborator_name: collaborator) + end + rescue => ex + binding.pry end def issues(redmine_project:, gitea_project:) @@ -294,6 +108,10 @@ module RedmineToGitea private + def mapping + settings.mapping + end + def import_issue(redmine_issue, gitea_project) gitea_issue = { "title" => redmine_issue.subject, @@ -309,7 +127,7 @@ module RedmineToGitea user = mapping.user_name(redmine_issue.author_id) - created_gitea_issue = gitea_api.post_issue( + created_gitea_issue = gitea_api.create_issue( issue: gitea_issue, project_name: gitea_project, user_name: user @@ -322,7 +140,7 @@ module RedmineToGitea } user_name = mapping.user_name(journal.user_id) - gitea_api.post_comment( + gitea_api.create_comment( comment: gitea_comment, project_name: gitea_project, issue_number: gitea_issue_number, @@ -334,111 +152,111 @@ module RedmineToGitea binding.pry end -end + class IssueFormatter + attr_accessor :mapping + attr_accessor :date_format + attr_accessor :textile_to_markdown -class IssueFormatter - attr_accessor :mapping - attr_accessor :date_format - attr_accessor :textile_to_markdown - - def initialize(mapping:, date_format: "%c", textile_to_markdown: TextileToMarkdown) - @mapping = mapping - @date_format = date_format - @textile_to_markdown = textile_to_markdown - end - - def description(issue) - issue_body = "> **Issue imported from forge.evolix.org on #{Time.now.strftime(date_format)}**" - issue_body += "\n> * Project: #{issue.project_name}" - issue_body += "\n> * Issue: #{issue.id}" - issue_body += "\n> * Tracker: #{issue.tracker_name}" - issue_body += "\n> * Status: #{issue.status_name}" - issue_body += "\n> * Author: #{issue.author_name}" - issue_body += "\n> * Category: #{issue.category_name}" - issue_body += "\n> * Started at: #{DateTime.parse(issue.start_date).strftime(date_format)}" - issue_body += "\n> * Done ratio: #{issue.done_ratio}" - issue_body += "\n> * Created at: #{DateTime.parse(issue.created_on).strftime(date_format)}" - issue_body += "\n> * Updated at: #{DateTime.parse(issue.updated_on).strftime(date_format)}" - issue_body += "\n\n" - issue_body += textile_to_markdown.convert(issue.description) - - issue_body - rescue => ex - binding.pry - end - - def comment(journal) - comment_body = "> **Comment imported from forge.evolix.org on #{Time.now.strftime(date_format)}**" - comment_body += "\n> * Author: #{journal.user_name}" if journal.user_name - comment_body += "\n> * Created at: #{DateTime.parse(journal.created_on).strftime(date_format)}" if journal.created_on - - comment_details(journal.details).each do |detail| - comment_body += "\n> * #{detail}" + def initialize(mapping:, date_format: "%c", textile_to_markdown: ) + @mapping = mapping + @date_format = date_format + @textile_to_markdown = textile_to_markdown end - unless journal.notes.empty? - comment_body += "\n\n" - if journal.private_notes? - comment_body += "_This note is private its content has not been imported_" - else - comment_body += textile_to_markdown.convert(journal.notes) + def description(issue) + issue_body = "> **Issue imported from forge.evolix.org on #{Time.now.strftime(date_format)}**" + issue_body += "\n> * Project: #{issue.project_name}" + issue_body += "\n> * Issue: #{issue.id}" + issue_body += "\n> * Tracker: #{issue.tracker_name}" + issue_body += "\n> * Status: #{issue.status_name}" + issue_body += "\n> * Author: #{issue.author_name}" + issue_body += "\n> * Category: #{issue.category_name}" + issue_body += "\n> * Started at: #{DateTime.parse(issue.start_date).strftime(date_format)}" + issue_body += "\n> * Done ratio: #{issue.done_ratio}" + issue_body += "\n> * Created at: #{DateTime.parse(issue.created_on).strftime(date_format)}" + issue_body += "\n> * Updated at: #{DateTime.parse(issue.updated_on).strftime(date_format)}" + issue_body += "\n\n" + issue_body += textile_to_markdown.call(issue.description) + + issue_body + rescue => ex + binding.pry + end + + def comment(journal) + comment_body = "> **Comment imported from forge.evolix.org on #{Time.now.strftime(date_format)}**" + comment_body += "\n> * Author: #{journal.user_name}" if journal.user_name + comment_body += "\n> * Created at: #{DateTime.parse(journal.created_on).strftime(date_format)}" if journal.created_on + + comment_details(journal.details).each do |detail| + comment_body += "\n> * #{detail}" end - end - comment_body - rescue => ex - binding.pry - end - - private - - def comment_details(details) - results = [] - details.each do |detail| - if detail['property'] == "attr" - case detail['name'] - when "status_id" then - old = mapping.status(detail['old_value']) - new = mapping.status(detail['new_value']) - results.push "Status: #{old} → #{new}" - when "priority_id" then - old = mapping.priority(detail['old_value']) - new = mapping.priority(detail['new_value']) - results.push "Priority: #{old} → #{new}" - when "done_ratio" then - old = detail['old_value'] - new = detail['new_value'] - results.push "Done ratio: #{old}% → #{new}%" - when "project_id" then - old = detail['old_value'] || "ø" - new = detail['new_value'] || "ø" - results.push "Project: #{old} → #{new}" - # when "tracker_id" then - # old = detail['old_value'] || "ø" - # new = detail['new_value'] || "ø" - # results.push "Tracker: #{old} → #{new}" - when "assigned_to_id" then - old = mapping.user_name(detail['old_value']) || "ø" - new = mapping.user_name(detail['new_value']) || "ø" - results.push "Assigned to: #{old} → #{new}" - when "fixed_version_id" then - old = detail['old_value'] || "ø" - new = detail['new_value'] || "ø" - results.push "Version: #{old} → #{new}" - when "subject", "description" - next + unless journal.notes.empty? + comment_body += "\n\n" + if journal.private_notes? + comment_body += "_This note is private its content has not been imported_" else - old = detail['old_value'] || "ø" - new = detail['new_value'] || "ø" - results.push "#{detail['name']}: #{old} → #{new}" + comment_body += textile_to_markdown.call(journal.notes) end - elsif detail['property'] == "relation" - results.push "Related to: #{detail['new_value']}" - else - next end + + comment_body + rescue => ex + binding.pry end - results + private + + def comment_details(details) + results = [] + details.each do |detail| + if detail['property'] == "attr" + case detail['name'] + when "status_id" then + old = mapping.status(detail['old_value']) + new = mapping.status(detail['new_value']) + results.push "Status: #{old} → #{new}" + when "priority_id" then + old = mapping.priority(detail['old_value']) + new = mapping.priority(detail['new_value']) + results.push "Priority: #{old} → #{new}" + when "done_ratio" then + old = detail['old_value'] + new = detail['new_value'] + results.push "Done ratio: #{old}% → #{new}%" + when "project_id" then + old = detail['old_value'] || "ø" + new = detail['new_value'] || "ø" + results.push "Project: #{old} → #{new}" + # when "tracker_id" then + # old = detail['old_value'] || "ø" + # new = detail['new_value'] || "ø" + # results.push "Tracker: #{old} → #{new}" + when "assigned_to_id" then + old = mapping.user_name(detail['old_value']) || "ø" + new = mapping.user_name(detail['new_value']) || "ø" + results.push "Assigned to: #{old} → #{new}" + when "fixed_version_id" then + old = detail['old_value'] || "ø" + new = detail['new_value'] || "ø" + results.push "Version: #{old} → #{new}" + when "subject", "description" + next + else + old = detail['old_value'] || "ø" + new = detail['new_value'] || "ø" + results.push "#{detail['name']}: #{old} → #{new}" + end + elsif detail['property'] == "relation" + results.push "Related to: #{detail['new_value']}" + else + next + end + end + + results + end end + end diff --git a/redmine_api.rb b/redmine_api.rb new file mode 100644 index 0000000..b387602 --- /dev/null +++ b/redmine_api.rb @@ -0,0 +1,169 @@ +require 'rest-client' +require 'json' +require 'pry' + +module Redmine + + class API + attr_accessor :api_key + attr_accessor :base_url + attr_accessor :http_headers + attr_accessor :rest_client + + def initialize(api_key:, base_url: "https://forge.evolix.org", rest_client: RestClient) + @api_key = api_key + @base_url = base_url + @http_headers = { + "X-Redmine-API-Key" => api_key, + "Content-Type" => "application/json", + "Accept" => "application/json" + } + @rest_client = rest_client + end + + def get_issues(project_id:, offset: 0, limit: 25) + puts "Redmine::API.get_issues(project_id: #{project_id}, offset: #{offset}, limit: #{limit})" + + response = rest_client.get( + "#{base_url}/issues.json?project_id=#{project_id}&offset=#{offset}&limit=#{limit}", + http_headers + ) + result = JSON.parse(response.body) + + ResultSet.new( + results: result.fetch("issues").map { |issue| Issue.new(issue) }, + total: result.fetch("total_count"), + offset: result.fetch("offset"), + limit: result.fetch("limit") + ) + rescue => ex + binding.pry + end + + def get_issue(id:) + puts "Redmine::API.get_issue(id: #{id})" + + response = rest_client.get( + "#{base_url}/issues/#{id}.json?include=journals", + http_headers + ) + result = JSON.parse(response.body) + + Issue.new(result.fetch("issue")) + rescue => ex + binding.pry + end + end + + class ResultSet + attr_reader :results + attr_reader :total + attr_reader :offset + attr_reader :limit + + def initialize(results:, total:, offset:, limit:) + @results = results + @total = total + @offset = offset + @limit = limit + end + + def remaining_results? + (offset + limit) < total + end + end + + class Issue + attr_reader :issue + + def initialize(issue) + @issue = issue + end + + def id + issue.fetch("id") + end + def project_name + issue.fetch("project", {}).fetch("name") + end + def tracker_name + issue.fetch("tracker", {}).fetch("name") + end + def status_id + issue.fetch("status", {}).fetch("id") + end + def status_name + issue.fetch("status", {}).fetch("name") + end + def category_name + if issue.key?("category") + issue.fetch("category", {}).fetch("name") + end + end + def author_id + issue.fetch("author", {}).fetch("id") + end + def author_name + issue.fetch("author", {}).fetch("name") + end + def assigned_to + if issue.key?("assigned_to") + issue.fetch("assigned_to").fetch("id") + end + end + def start_date + issue.fetch("start_date") + end + def done_ratio + issue.fetch("done_ratio") + end + def created_on + issue.fetch("created_on") + end + def updated_on + issue.fetch("updated_on") + end + def subject + issue.fetch("subject") + end + def description + issue.fetch("description") + end + def journals + if issue.key?("journals") + issue.fetch("journals", {}).map { |journal| + Journal.new(journal) + } + else + [] + end + end + end + + class Journal + attr_reader :journal + + def initialize(journal) + @journal = journal + end + def user_id + journal.fetch("user", {}).fetch("id") + end + def user_name + journal.fetch("user", {}).fetch("name") + end + def created_on + journal.fetch("created_on") + end + def details + journal.fetch("details", []) + end + def notes + journal.fetch("notes", "") + end + def private_notes? + journal.fetch("private_notes", false) + end + end + +end diff --git a/textile-to-markdown.rb b/textile-to-markdown.rb index 4e09732..dfd108a 100644 --- a/textile-to-markdown.rb +++ b/textile-to-markdown.rb @@ -2,7 +2,7 @@ require 'tempfile' class TextileToMarkdown # https://github.com/Ecodev/redmine_convert_textile_to_markown - def self.convert(textile) + def self.call(textile) # Redmine support @ inside inline code marked with @ (such as "@git@github.com@"), but not pandoc. # So we inject a placeholder that will be replaced later on with a real backtick. tag_code = 'pandoc-unescaped-single-backtick'