From 49063691a38d9aed1277d60bb1af5fc696c46481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Thu, 2 Aug 2018 09:22:42 +0200 Subject: [PATCH] Detect pandoc functionality based on usage and output formats Pandoc introduced --wrap=preserve only in version 1.16 (https://pandoc.org/releases.html#pandoc-1.16-02-jan-2016). We can check by usage whether gfm mode and wrap is supported to be compatible with the largest range of versions. --- .../formats/markdown/pandoc_wrapper.rb | 110 ++++++++++++++++++ .../formats/markdown/textile_converter.rb | 22 ++-- .../markdown/pandoc_wrapper_spec.rb | 99 ++++++++++++++++ 3 files changed, 218 insertions(+), 13 deletions(-) create mode 100644 lib/open_project/text_formatting/formats/markdown/pandoc_wrapper.rb create mode 100644 spec/lib/open_project/text_formatting/markdown/pandoc_wrapper_spec.rb diff --git a/lib/open_project/text_formatting/formats/markdown/pandoc_wrapper.rb b/lib/open_project/text_formatting/formats/markdown/pandoc_wrapper.rb new file mode 100644 index 0000000000..599974dc43 --- /dev/null +++ b/lib/open_project/text_formatting/formats/markdown/pandoc_wrapper.rb @@ -0,0 +1,110 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'open3' + +module OpenProject::TextFormatting::Formats + module Markdown + class PandocWrapper + def initialize; end + + def execute!(stdin) + run_pandoc! pandoc_arguments, stdin_data: stdin + end + + def pandoc_arguments + [ + wrap_mode, + '--atx-headers', + '-f', + 'textile', + '-t', + output_format + ] + end + + ## + # Detect available output markdown format + # pandoc recommends format 'gfm' but that isnt available in current LTS + # markdown_github, which is deprecated, is however available. + def output_format + if gfm_supported? + 'gfm' + else + 'markdown_github' + end + end + + ## + # Detect available wrap mode + # --wrap=preserve will keep the wrapping the same, however is only available in versions 1.16+ + # In older versions we try to use the deprecated --no-wrap instead + # --atx-headers will lead to headers like `### Some header` and '## Another header' + def wrap_mode + usage = read_usage_string + + # Detect wrap usage + if usage.include? '--wrap=' + '--wrap=preserve' + elsif usage.include? '--no-wrap' + '--no-wrap' + else + raise 'Your pandoc version has neither --no-wrap nor --wrap=preserve. Please install a recent version of pandoc.' + end + end + + ## + # Detect whether an output format for gfm exists + # so we don't have to use the legacy github_markdown format. + def gfm_supported? + read_output_formats.match? /^gfm$/ + end + + private + + ## + # Run pandoc through open3 and raise if an exception occurred + def run_pandoc!(args, **options) + output, stderr_str, status = Open3.capture3('pandoc', *args, **options) + raise stderr_str unless status.success? + + output + end + + def read_usage_string + run_pandoc! %w[--help] + end + + def read_output_formats + run_pandoc! %w[--list-output-formats] + end + end + end +end diff --git a/lib/open_project/text_formatting/formats/markdown/textile_converter.rb b/lib/open_project/text_formatting/formats/markdown/textile_converter.rb index 9556b9599e..06a22cc81f 100644 --- a/lib/open_project/text_formatting/formats/markdown/textile_converter.rb +++ b/lib/open_project/text_formatting/formats/markdown/textile_converter.rb @@ -52,7 +52,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -require 'open3' +require_relative 'pandoc_wrapper' module OpenProject::TextFormatting::Formats module Markdown @@ -186,22 +186,18 @@ module OpenProject::TextFormatting::Formats cleanup_before_pandoc(textile) - # TODO pandoc recommends format 'gfm' but that isnt available in current LTS - # markdown_github, which is deprecated, is however available. - # --wrap=preserve will keep the wrapping the same - # --atx-headers will lead to headers like `### Some header` and '## Another header' - command = %w(pandoc --wrap=preserve --atx-headers -f textile -t markdown_github) - markdown, stderr_str, status = begin - Open3.capture3(*command, stdin_data: textile) - rescue StandardError - raise "Pandoc failed with unspecific error" - end - - raise "Pandoc failed: #{stderr_str}" unless status.success? + markdown = execute_pandoc_with_stdin! textile cleanup_after_pandoc(markdown) end + def execute_pandoc_with_stdin!(textile) + wrapper = PandocWrapper.new + wrapper.execute! textile + rescue StandardError => e + raise "Execution of pandoc failed: #{e}" + end + def models_to_convert { ::Announcement => [:text], diff --git a/spec/lib/open_project/text_formatting/markdown/pandoc_wrapper_spec.rb b/spec/lib/open_project/text_formatting/markdown/pandoc_wrapper_spec.rb new file mode 100644 index 0000000000..6e9e68e107 --- /dev/null +++ b/spec/lib/open_project/text_formatting/markdown/pandoc_wrapper_spec.rb @@ -0,0 +1,99 @@ +#-- encoding: UTF-8 +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe OpenProject::TextFormatting::Formats::Markdown::PandocWrapper do + let(:subject) { described_class.new } + let(:output_formats) { "gfm\n foo\n" } + + before do + allow(subject).to receive(:read_usage_string).and_return(usage_string) + allow(subject).to receive(:read_output_formats).and_return(output_formats) + end + + describe 'gfm mode' do + let(:usage_string) { 'does not matter' } + context 'when gfm exists' do + it 'uses gfm format' do + expect(subject.output_format).to eq 'gfm' + end + end + + context 'when gfm does not exist' do + let(:output_formats) { "bar\n foo\n" } + + it 'uses the legacy format' do + expect(subject.output_format).to eq 'markdown_github' + end + end + end + + describe 'wrap mode' do + context 'when wrap=preserve exists' do + let(:usage_string) do + <<~EOS + --list-output-formats + --list-highlight-languages + --list-highlight-styles + --wrap=auto|none|preserve + -v --version + -h --help + EOS + end + + it do + expect(subject.wrap_mode).to eq('--wrap=preserve') + end + end + + context 'when only no-wrap exists' do + let(:usage_string) do + <<~EOS + --list-output-formats + --list-highlight-languages + --list-highlight-styles + --no-wrap + -v --version + -h --help + EOS + end + it do + expect(subject.wrap_mode).to eq('--no-wrap') + end + end + + context 'when neither exists' do + let(:usage_string) { 'wat?' } + it do + expect { subject.wrap_mode }.to raise_error 'Your pandoc version has neither --no-wrap nor --wrap=preserve. Please install a recent version of pandoc.' + end + end + end +end