pandoc_rb 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []