pandoc_rb 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "pandoc_rb/version"
6
+
7
+ $gem_spec = Gem::Specification.new do |spec|
8
+ spec.name = "pandoc_rb"
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.version = PandocRb::VERSION
11
+ spec.authors = ["Michael Klein"]
12
+ spec.email = ['lambdamichael@gmail.com']
13
+
14
+ spec.summary = "FFI bindings to Pandoc"
15
+ spec.description = "Fast bindings to Pandoc through Ruby's C FFI gem and Haskell's C FFI"
16
+ spec.homepage = 'http://rubygems.org/gems/pandoc_rb'
17
+ spec.license = 'BSD-3-Clause'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features)/}) || f.match(/\.(so|o)$/)
21
+ end
22
+ spec.bindir = "bin"
23
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ spec.extensions = ['ext/pandoc_rb/extconf.rb']
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.15.1"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ spec.add_development_dependency "pry"
31
+ spec.add_development_dependency "pry-byebug"
32
+ spec.add_development_dependency "pandoc-ruby"
33
+ spec.add_development_dependency "rake-compiler"
34
+ spec.add_development_dependency "benchmark-ips"
35
+ spec.add_development_dependency "kalibera"
36
+ end
37
+
@@ -0,0 +1,217 @@
1
+ {-# LANGUAGE BangPatterns #-}
2
+ {-# LANGUAGE DeriveGeneric #-}
3
+ {-# LANGUAGE FlexibleContexts #-}
4
+ {-# LANGUAGE ForeignFunctionInterface #-}
5
+ {-# LANGUAGE RankNTypes #-}
6
+ {-# LANGUAGE StandaloneDeriving #-}
7
+ {-# LANGUAGE TupleSections #-}
8
+ {-# LANGUAGE ScopedTypeVariables #-}
9
+
10
+ module Text.Pandoc.C where
11
+
12
+ import Control.Monad.Trans.Class (lift)
13
+ import Control.Monad.Trans.Either (EitherT(..))
14
+ import Data.Aeson (ToJSON(..), genericToEncoding, defaultOptions, genericToJSON, encode)
15
+ import Data.Bifunctor (bimap, second)
16
+ import Data.ByteString.Lazy (toStrict, fromStrict)
17
+ import Data.ByteString (useAsCStringLen)
18
+ import Data.ByteString.Unsafe (unsafePackMallocCStringLen, unsafeFinalize)
19
+ import Data.Monoid ((<>))
20
+ import Foreign.C.String (CStringLen, peekCStringLen, newCStringLen)
21
+ import Foreign.C.Types (CChar, CLong, CInt(..))
22
+ import Foreign.Marshal.Alloc (malloc)
23
+ import Foreign.Ptr (Ptr)
24
+ import Foreign.Storable (peek, poke)
25
+ import Foreign.Storable.Tuple ()
26
+ import GHC.Generics (Generic)
27
+ import Prelude hiding (log)
28
+ import Text.Pandoc (ReaderOptions, Pandoc, WriterOptions(..), PandocError(..), Reader(..), Writer(..), HTMLMathMethod(..), writerMediaBag, getReader, getWriter, def)
29
+ import Text.Pandoc.MediaBag (MediaBag, extractMediaBag)
30
+ import Text.Parsec.Error (ParseError, Message(..), errorPos, errorMessages)
31
+ import Text.Parsec.Pos (SourcePos, SourceName, Line, Column, sourceName, sourceLine, sourceColumn)
32
+ import System.Timeout (timeout)
33
+ import Control.Concurrent
34
+
35
+
36
+
37
+
38
+ -- | The `Reader` type modified for foreign C exports
39
+ type CReader = ReaderOptions -> CStringLen -> EitherT CStringLen IO (Pandoc, MediaBag)
40
+
41
+ -- | The `Writer` type modified for foreign C exports
42
+ type CWriter = WriterOptions -> Pandoc -> EitherT CStringLen IO CStringLen
43
+
44
+
45
+ -- | Apply a monadic action to the left of an `EitherT`
46
+ mapML :: Monad m => (a -> m b) -> EitherT a m c -> EitherT b m c
47
+ mapML f (EitherT m) = EitherT $ do
48
+ m' <- m
49
+ case m' of
50
+ ( Left x) -> Left <$> f x
51
+ ~(Right y) -> return $ Right y
52
+
53
+ -- | Apply a monadic action to the right of an `EitherT`
54
+ mapMR :: Monad m => (b -> m c) -> EitherT a m b -> EitherT a m c
55
+ mapMR f (EitherT m) = EitherT $ do
56
+ m' <- m
57
+ case m' of
58
+ ( Left x) -> return $ Left x
59
+ ~(Right y) -> Right <$> f y
60
+
61
+
62
+ -- | This function forks a thread to both catch exceptions and better kill infinite loops that can't be caught with `timeout` alone.
63
+ -- For example, @timeout 10 (forever $ return ())@ will never time out, but @timeoutEitherT 10 (lift $ forever $ return ())@ will.
64
+ --
65
+ -- timeoutEitherT is strict within the created thread
66
+ timeoutEitherT :: Int -> EitherT CStringLen IO b -> EitherT CStringLen IO b
67
+ timeoutEitherT us (EitherT io) = EitherT $ do
68
+ mvar <- newEmptyMVar
69
+ tid <- io `forkFinally` (putMVar mvar $!)
70
+ result <- timeout us (takeMVar mvar)
71
+ case result of
72
+ ( Nothing ) -> killThread tid >> Left <$> newCStringLen ("Pandoc timed out after " ++ show us ++ "microseconds")
73
+ ~(Just noTimeout) -> case noTimeout of
74
+ ( Right successful) -> return successful
75
+ ~(Left err ) -> Left <$> newCStringLen ("Pandoc internal error: " ++ show err)
76
+
77
+
78
+
79
+ -- | A clone of `SourcePos`, to allow deriving `Generic`
80
+ data SourcePos' = SourcePos' SourceName !Line !Column deriving (Eq, Ord, Show, Generic)
81
+
82
+ instance ToJSON SourcePos' where
83
+ toEncoding = genericToEncoding defaultOptions
84
+
85
+ deriving instance Generic Message
86
+
87
+ instance ToJSON Message where
88
+ toEncoding = genericToEncoding defaultOptions
89
+
90
+ -- | A clone of `ParseError`, to allow deriving `Generic`
91
+ data ParseError' = ParseError' !SourcePos' [Message] deriving (Generic)
92
+
93
+ -- | Convert to our clone of `SourcePos`
94
+ sourcePos' :: SourcePos -> SourcePos'
95
+ sourcePos' !sp = SourcePos' (sourceName sp) (sourceLine sp) (sourceColumn sp)
96
+
97
+ -- | Convert to our clone of `ParseError`
98
+ parseError' :: ParseError -> ParseError'
99
+ parseError' !pe = ParseError' (sourcePos' (errorPos pe)) (errorMessages pe)
100
+
101
+
102
+ instance ToJSON ParseError where
103
+ toJSON = genericToJSON defaultOptions . parseError'
104
+ toEncoding = genericToEncoding defaultOptions . parseError'
105
+
106
+ instance ToJSON PandocError where
107
+ toEncoding = genericToEncoding defaultOptions
108
+
109
+ -- | Convert a `PandocError` to JSON
110
+ showPandocError :: PandocError -> IO CStringLen
111
+ showPandocError = flip useAsCStringLen return . toStrict . encode
112
+
113
+
114
+ -- | Convert a `Reader` to a `CReader`
115
+ mkCReader :: Reader -> CReader
116
+ mkCReader (StringReader reader) opts cstr = mapML showPandocError . EitherT $ do
117
+ str <- peekCStringLen cstr
118
+ readerResult <- reader opts str
119
+ return $ second (, mempty) $! readerResult
120
+ mkCReader ~(ByteStringReader reader) opts str = mapML showPandocError . EitherT $ do
121
+ bs <- unsafePackMallocCStringLen str
122
+ readerResult <- reader opts $! fromStrict bs
123
+ readerResult `seq` unsafeFinalize bs
124
+ return readerResult
125
+
126
+
127
+ -- | Convert a `Writer` to a `CWriter`
128
+ mkCWriter :: Writer -> CWriter
129
+ mkCWriter (PureStringWriter writer) opts pandoc = lift $ newCStringLen $ writer opts pandoc
130
+ mkCWriter (IOStringWriter writer) opts pandoc = lift $ writer opts pandoc >>= newCStringLen
131
+ mkCWriter ~(IOByteStringWriter writer) opts pandoc = lift $ writer opts pandoc >>= flip useAsCStringLen return . toStrict
132
+
133
+ -- | `getReader` for foreign C exports. Retrieve reader based on formatSpec (format+extensions).
134
+ getCReader :: CStringLen -> EitherT CStringLen IO CReader
135
+ getCReader cstr = do
136
+ str <- lift . peekCStringLen $ cstr
137
+ EitherT $ case getReader str of
138
+ ( Left err ) -> Left <$> newCStringLen err
139
+ ~(Right reader) -> return . Right $ mkCReader reader
140
+
141
+ -- | `getWriter` for foreign C exports. Retrieve writer based on formatSpec (format+extensions).
142
+ getCWriter :: CStringLen -> EitherT CStringLen IO CWriter
143
+ getCWriter cstr = do
144
+ str <- lift . peekCStringLen $ cstr
145
+ EitherT $ case getWriter str of
146
+ ( Left err ) -> Left <$> newCStringLen err
147
+ ~(Right writer) -> return . Right $ mkCWriter writer
148
+
149
+ extractCMediabag :: Bool -> CStringLen -> MediaBag -> EitherT CStringLen IO ()
150
+ extractCMediabag verbose cPath mediaBag = do
151
+ path <- lift . peekCStringLen $ cPath
152
+ case path of
153
+ [] -> return ()
154
+ nonEmptyPath -> lift $ extractMediaBag verbose nonEmptyPath mediaBag
155
+
156
+ -- | The main conversion function.
157
+ -- Takes `ReaderOptions`, `WriterOptions`, reader's formatSpec,
158
+ -- writer's formatSpec, input and returns either an error string
159
+ -- or the output
160
+ convert :: ReaderOptions
161
+ -> WriterOptions
162
+ -> CStringLen -- ^ Reader specification
163
+ -> CStringLen -- ^ Writer specification
164
+ -> CStringLen -- ^ Input
165
+ -> CStringLen -- ^ Extract media dir
166
+ -> EitherT CStringLen IO CStringLen -- ^ Either an error string or the output
167
+ convert readerOpts writerOpts readerStr writerStr input mediaBagStr = do
168
+ reader <- getCReader readerStr
169
+ (pandoc, mediaBag) <- reader readerOpts input
170
+ extractCMediabag False mediaBagStr mediaBag
171
+ writer <- getCWriter writerStr
172
+ let writerOpts' = writerOpts { writerMediaBag = writerMediaBag writerOpts <> mediaBag }
173
+ writer writerOpts' pandoc
174
+
175
+ -- | The `ReaderOptions` wired into the foreign export
176
+ readerOptions :: ReaderOptions
177
+ readerOptions = def
178
+
179
+ -- | The `WriterOptions` wired into the foreign export
180
+ writerOptions :: WriterOptions
181
+ writerOptions = def { writerHTMLMathMethod = MathJax "https://fail.cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML" }
182
+
183
+ -- | `CStringLen` for use by Ruby
184
+ type RStringLen = (Ptr CChar, CLong)
185
+
186
+ -- -- | Free the resulting type of `convert_hs`
187
+ -- freeResult :: Ptr () -> IO ()
188
+ -- freeResult !addr = do
189
+ -- (_, strAddr, _) <- peek (castPtr addr :: Ptr (CInt, Ptr CChar, CLong))
190
+ -- free strAddr
191
+ -- free addr
192
+
193
+ -- foreign export ccall freeResult :: Ptr () -> IO ()
194
+
195
+ -- | Takes: reader, writer, input and returns (isSuccess, output pointer, output length)
196
+ convert_hs :: Ptr RStringLen -> Ptr RStringLen -> Ptr RStringLen -> Ptr RStringLen -> IO (Ptr (CInt, Ptr CChar, CLong))
197
+ convert_hs readerStr writerStr input mediaBagStr = do
198
+ readerStr' <- second fromEnum <$> peek readerStr
199
+ writerStr' <- second fromEnum <$> peek writerStr
200
+ input' <- second fromEnum <$> peek input
201
+ mediaBagStr' <- second fromEnum <$> peek mediaBagStr
202
+ let converted = convert readerOptions writerOptions readerStr' writerStr' input' mediaBagStr'
203
+ let EitherT safeConverted = timeoutEitherT (10 * {- minutes -} 60000000) converted
204
+ result <- bimap (second toEnum) (second toEnum) <$> safeConverted
205
+ let (success, (rstrPtr, rstrLen)) = either (0,) (1,) result
206
+ addr <- malloc
207
+ addr `poke` (success, rstrPtr, rstrLen)
208
+ return addr
209
+
210
+ foreign export ccall convert_hs :: Ptr RStringLen -> Ptr RStringLen -> Ptr RStringLen -> Ptr RStringLen -> IO (Ptr (CInt, Ptr CChar, CLong))
211
+
212
+ -- -- | Dummy main to dissuade the compiler from warning a lack of @_main@
213
+ -- main :: IO CInt
214
+ -- main = print "hi there! I'm main. you probably shouldn't see me." >> return 0
215
+
216
+ -- foreign export ccall main :: IO CInt
217
+
@@ -0,0 +1,9 @@
1
+ #include "HsFFI.h"
2
+ #ifdef __cplusplus
3
+ extern "C" {
4
+ #endif
5
+ extern HsPtr convert_hs(HsPtr a1, HsPtr a2, HsPtr a3, HsPtr a4);
6
+ #ifdef __cplusplus
7
+ }
8
+ #endif
9
+
metadata ADDED
@@ -0,0 +1,206 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pandoc_rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Michael Klein
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.15.1
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.15.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pandoc-ruby
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake-compiler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: benchmark-ips
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: kalibera
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: Fast bindings to Pandoc through Ruby's C FFI gem and Haskell's C FFI
140
+ email:
141
+ - lambdamichael@gmail.com
142
+ executables:
143
+ - console
144
+ - setup
145
+ extensions:
146
+ - ext/pandoc_rb/extconf.rb
147
+ extra_rdoc_files: []
148
+ files:
149
+ - ".gitignore"
150
+ - ".ruby-gemset"
151
+ - ".ruby-version"
152
+ - ".travis.yml"
153
+ - Gemfile
154
+ - Gemfile.lock
155
+ - LICENSE
156
+ - LICENSE.txt
157
+ - README.md
158
+ - Rakefile
159
+ - Setup.hs
160
+ - bench/bench.rb
161
+ - bin/console
162
+ - bin/setup
163
+ - ext/pandoc_rb/extconf.rb
164
+ - ext/pandoc_rb/pandoc-rb.cabal
165
+ - ext/pandoc_rb/pandoc_rb.c
166
+ - ext/pandoc_rb/src/PandocRb.hs
167
+ - ext/pandoc_rb/src/Text/Pandoc/C.hs
168
+ - ext/pandoc_rb/src/Text/Pandoc/C/Types.hs
169
+ - ext/pandoc_rb/src/Text/Pandoc/C/Utils.hs
170
+ - ext/pandoc_rb/stack.yaml
171
+ - ext/pandoc_rb/test/DetailedSpec.hs
172
+ - lib/pandoc_rb.rb
173
+ - lib/pandoc_rb/error.rb
174
+ - lib/pandoc_rb/parse_failure.rb
175
+ - lib/pandoc_rb/parsec_error.rb
176
+ - lib/pandoc_rb/readers.rb
177
+ - lib/pandoc_rb/version.rb
178
+ - lib/pandoc_rb/writers.rb
179
+ - pandoc_rb.gemspec
180
+ - src/Text/Pandoc/C.hs
181
+ - src/Text/Pandoc/C_stub.h
182
+ homepage: http://rubygems.org/gems/pandoc_rb
183
+ licenses:
184
+ - BSD-3-Clause
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 2.6.12
203
+ signing_key:
204
+ specification_version: 4
205
+ summary: FFI bindings to Pandoc
206
+ test_files: []